Compare commits

...

7 Commits
main ... v1.4.0

Author SHA1 Message Date
github-actions[bot] 2e0b738321
Chore: get version from env (#541)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 7e5d474c5d)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 18:27:05 +08:00
github-actions[bot] 8dfeae2cc6
Fix: get the region from status (#539)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit d4c42ed147)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 17:45:15 +08:00
github-actions[bot] 171c490d7c
Fix: optimize the permission of the definition management page (#537)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit aab99402b7)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-31 15:30:23 +08:00
github-actions[bot] 328a212da8
Feat: default show the overview tab on the application status page (#534)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 2ea7749295)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-30 14:48:45 +08:00
github-actions[bot] c9006c5faf
Feat: change the title to Balloon (#532)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit a77ea37867)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-29 12:46:44 +08:00
github-actions[bot] 44facaa92a
Feat: Optimize the kubernetes object editing component (#530)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit 0036d125dd)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-28 20:50:51 +08:00
github-actions[bot] 0275634270
Fix: fail to set the policy value (#528)
Signed-off-by: barnettZQG <barnett.zqg@gmail.com>
(cherry picked from commit f707d87cb2)

Co-authored-by: barnettZQG <barnett.zqg@gmail.com>
2022-05-27 20:12:54 +08:00
26 changed files with 540 additions and 340 deletions

View File

@ -40,6 +40,7 @@ jobs:
push: true push: true
build-args: | build-args: |
GITVERSION=git-${{ steps.vars.outputs.git_revision }} GITVERSION=git-${{ steps.vars.outputs.git_revision }}
VERSION=${{ steps.get_version.outputs.VERSION }}
tags: |- tags: |-
acr.kubevela.net/oamdev/velaux:${{ steps.get_version.outputs.VERSION }} acr.kubevela.net/oamdev/velaux:${{ steps.get_version.outputs.VERSION }}
oamdev/velaux:${{ steps.get_version.outputs.VERSION }} oamdev/velaux:${{ steps.get_version.outputs.VERSION }}

View File

@ -1,6 +1,8 @@
FROM node:16-alpine as builder FROM node:16-alpine as builder
ARG VERSION
WORKDIR /app/velaux WORKDIR /app/velaux
ADD . . ADD . .
ENV VERSION=${VERSION}
RUN apk add --no-cache git && yarn install && yarn build RUN apk add --no-cache git && yarn install && yarn build
RUN rm -rf /app/velaux/build/mock RUN rm -rf /app/velaux/build/mock

View File

@ -89,6 +89,9 @@ function getClientEnvironment(publicUrl) {
// which is why it's disabled by default. // which is why it's disabled by default.
// It is defined here so it is available in the webpackHotDevClient. // It is defined here so it is available in the webpackHotDevClient.
FAST_REFRESH: process.env.FAST_REFRESH !== 'false', FAST_REFRESH: process.env.FAST_REFRESH !== 'false',
// The release version
VERSION: process.env.VERSION,
}, },
); );
// Stringify all values so we can feed into webpack DefinePlugin // Stringify all values so we can feed into webpack DefinePlugin

View File

@ -1,6 +1,6 @@
{ {
"name": "valaux", "name": "valaux",
"version": "1.4.0-beta.2", "version": "1.4.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node scripts/start.js", "start": "node scripts/start.js",

View File

@ -116,6 +116,11 @@ export function getPolicyDetail(params: { appName: string; policyName: string })
return get(gurl, params).then((res) => res); return get(gurl, params).then((res) => res);
} }
export function deletePolicy(params: { appName: string; policyName: string }) {
const gurl = `${application}/${params.appName}/policies/${params.policyName}`;
return rdelete(gurl, params).then((res) => res);
}
export function createApplicationTemplate(params: any) { export function createApplicationTemplate(params: any) {
const gurl = isMock const gurl = isMock
? `${createApplicationTemplate_mock}` ? `${createApplicationTemplate_mock}`

View File

@ -15,7 +15,6 @@ const Permission = (props: Props) => {
if (!props.userInfo) { if (!props.userInfo) {
return null; return null;
} }
console.log(props.request);
if (!checkPermission(props.request, props.project, props.userInfo)) { if (!checkPermission(props.request, props.project, props.userInfo)) {
return null; return null;
} }

View File

@ -127,4 +127,9 @@
} }
} }
} }
.line {
line-height: 16px;
padding: 0;
margin: 0;
}
} }

View File

@ -20,7 +20,7 @@ import pod from '../../assets/resources/pod.svg';
import kubevela from '../../assets/KubeVela-01.svg'; import kubevela from '../../assets/KubeVela-01.svg';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import { Dropdown, Icon, Menu, Tag } from '@b-design/ui'; import { Dropdown, Icon, Menu, Tag, Balloon } from '@b-design/ui';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
@ -60,7 +60,7 @@ export interface TreeNode {
function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
@ -68,7 +68,6 @@ function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode)
'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy', 'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy',
'warning-status': node.resource.healthStatus?.statusCode == 'Progressing', 'warning-status': node.resource.healthStatus?.statusCode == 'Progressing',
})} })}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -99,17 +98,25 @@ function renderResourceNode(props: TreeGraphProps, id: string, node: GraphNode)
</If> </If>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
className={classNames('graph-node', 'graph-node-app')} className={classNames('graph-node', 'graph-node-app')}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -126,12 +133,21 @@ function renderAppNode(props: TreeGraphProps, id: string, node: GraphNode) {
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
const { appName, envName } = props; const { appName, envName } = props;
return ( const graphNode = (
<div <div
key={id} key={id}
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
@ -139,7 +155,6 @@ function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy', 'error-status': node.resource.healthStatus?.statusCode == 'UnHealthy',
'warning-status': node.resource.healthStatus?.statusCode == 'Progressing', 'warning-status': node.resource.healthStatus?.statusCode == 'Progressing',
})} })}
title={describeNode(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -172,16 +187,24 @@ function renderPodNode(props: TreeGraphProps, id: string, node: GraphNode) {
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeNode(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) { function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) {
const fullName = nodeKey(node); const fullName = nodeKey(node);
return ( const graphNode = (
<div <div
onClick={() => props.onNodeClick && props.onNodeClick(fullName)} onClick={() => props.onNodeClick && props.onNodeClick(fullName)}
className={classNames('graph-node', 'graph-node-cluster')} className={classNames('graph-node', 'graph-node-cluster')}
title={describeCluster(node)}
style={{ style={{
left: node.x, left: node.x,
top: node.y, top: node.y,
@ -198,6 +221,15 @@ function renderClusterNode(props: TreeGraphProps, id: string, node: GraphNode) {
</div> </div>
</div> </div>
); );
return (
<Balloon trigger={graphNode}>
<div>
{describeCluster(node).map((line) => {
return <p className="line">{line}</p>;
})}
</div>
</Balloon>
);
} }
function setNode(graph: dagre.graphlib.Graph<GraphNode, GraphEdge>, node: TreeNode) { function setNode(graph: dagre.graphlib.Graph<GraphNode, GraphEdge>, node: TreeNode) {

View File

@ -50,7 +50,7 @@ export function describeNode(node: GraphNode) {
if (node.resource.healthStatus?.reason) { if (node.resource.healthStatus?.reason) {
lines.push(`Reason: ${node.resource.healthStatus?.reason}`); lines.push(`Reason: ${node.resource.healthStatus?.reason}`);
} }
if (node.resource.kind === 'Service') { if (node.resource.kind === 'Service' && node.resource.additionalInfo?.EIP) {
lines.push(`EIP: ${node.resource.additionalInfo?.EIP}`); lines.push(`EIP: ${node.resource.additionalInfo?.EIP}`);
} }
if (node.resource.kind === 'Pod') { if (node.resource.kind === 'Pod') {
@ -58,12 +58,12 @@ export function describeNode(node: GraphNode) {
lines.push(`Ready: ${node.resource.additionalInfo?.Ready}`); lines.push(`Ready: ${node.resource.additionalInfo?.Ready}`);
lines.push(`Restarts: ${node.resource.additionalInfo?.Restarts}`); lines.push(`Restarts: ${node.resource.additionalInfo?.Restarts}`);
} }
return lines.join('\n'); return lines;
} }
export function describeCluster(node: GraphNode) { export function describeCluster(node: GraphNode) {
const lines = [`Cluster: ${node.resource.name}`]; const lines = [`Cluster: ${node.resource.name}`];
return lines.join('\n'); return lines;
} }
export function treeNodeKey(node: TreeNode & { uid?: string }) { export function treeNodeKey(node: TreeNode & { uid?: string }) {

View File

@ -4,7 +4,9 @@ import { Upload, Button, Icon, Message } from '@b-design/ui';
import DefinitionCode from '../../components/DefinitionCode'; import DefinitionCode from '../../components/DefinitionCode';
import Translation from '../../components/Translation'; import Translation from '../../components/Translation';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import { v4 as uuid } from 'uuid';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import type { KubernetesObject } from './objects';
type Props = { type Props = {
value: any; value: any;
@ -15,6 +17,7 @@ type Props = {
type State = { type State = {
message: string; message: string;
containerId: string; containerId: string;
showButton: boolean;
}; };
class K8sObjectsCode extends React.Component<Props, State> { class K8sObjectsCode extends React.Component<Props, State> {
@ -23,7 +26,8 @@ class K8sObjectsCode extends React.Component<Props, State> {
super(props); super(props);
this.state = { this.state = {
message: '', message: '',
containerId: Date.now().toString(), containerId: uuid(),
showButton: false,
}; };
this.form = new Field(this, { this.form = new Field(this, {
onChange: () => { onChange: () => {
@ -37,17 +41,20 @@ class K8sObjectsCode extends React.Component<Props, State> {
const { value } = this.props; const { value } = this.props;
this.setValues(value); this.setValues(value);
}; };
componentWillReceiveProps(nextProps: Props) {
const { value } = nextProps;
if (value !== this.props.value) {
this.setValues(value);
}
}
setValues = (value: any) => { setValues = (value: KubernetesObject[]) => {
if (value) { if (value) {
try { try {
const code = yaml.dump(value); let code = '---\n';
if (value instanceof Array) {
value.map((res) => {
if (res) {
code = code + yaml.dump(res) + '---\n';
}
});
} else {
code = yaml.dump(value) + '---\n';
}
this.form.setValues({ code: code }); this.form.setValues({ code: code });
} catch {} } catch {}
} }
@ -57,10 +64,11 @@ class K8sObjectsCode extends React.Component<Props, State> {
const { onChange, value } = this.props; const { onChange, value } = this.props;
if (onChange) { if (onChange) {
try { try {
let object = yaml.load(v); let object: any = yaml.load(v);
if (!(object instanceof Array)) { if (!(object instanceof Array)) {
object = [object]; object = [object];
} }
object = object.filter((ob: any) => ob != null);
if (yaml.dump(value) != v) { if (yaml.dump(value) != v) {
onChange(object); onChange(object);
} }
@ -68,11 +76,14 @@ class K8sObjectsCode extends React.Component<Props, State> {
} catch (error: any) { } catch (error: any) {
if ((error.message = 'expected a single document in the stream, but found more')) { if ((error.message = 'expected a single document in the stream, but found more')) {
try { try {
const objects = yaml.loadAll(v); let objects = yaml.loadAll(v);
if (yaml.dump(value) != v) { if (yaml.dump(value) != v) {
objects = objects.filter((ob: any) => ob != null);
onChange(objects); onChange(objects);
} }
this.setState({ message: '' }); this.setState({
message: '',
});
} catch (err: any) { } catch (err: any) {
this.setState({ message: err.message }); this.setState({ message: err.message });
} }
@ -96,15 +107,25 @@ class K8sObjectsCode extends React.Component<Props, State> {
}; };
}; };
onConvert2WebService = () => {};
render() { render() {
const { id } = this.props; const { id } = this.props;
const { init } = this.form; const { init } = this.form;
const { message, containerId } = this.state; const { message, containerId, showButton } = this.state;
return ( return (
<div id={id}> <div id={id}>
<If condition={message}> <If condition={message}>
<span style={{ color: 'red' }}>{message}</span> <span style={{ color: 'red' }}>{message}</span>
</If> </If>
<Message type="notice" style={{ marginTop: '16px' }}>
<Translation>
The input data will be automatically formatted. Ensure that the input data is a valid
k8s resource YAML.
</Translation>
</Message>
<Upload request={this.customRequest}> <Upload request={this.customRequest}>
<Button text type="normal" className="padding-left-0"> <Button text type="normal" className="padding-left-0">
<Icon type="cloudupload" /> <Icon type="cloudupload" />
@ -120,12 +141,19 @@ class K8sObjectsCode extends React.Component<Props, State> {
{...init('code')} {...init('code')}
/> />
</div> </div>
<Message type="notice" style={{ marginTop: '16px' }}>
<Translation> <If condition={showButton}>
The input data will be automatically formatted. Ensure that the input data is a valid <div style={{ marginTop: '16px' }}>
k8s resource YAML. <span style={{ fontSize: '14px', color: '#000', marginRight: '16px' }}>
</Translation> <Translation>
</Message> Convert the kubernetes resource component to the webservice component?
</Translation>
</span>
<Button type="secondary" onClick={this.onConvert2WebService}>
Yes
</Button>
</div>
</If>
</div> </div>
); );
} }

View File

@ -0,0 +1,27 @@
import type { ResourceObject } from '../../interface/observation';
export interface KubernetesObject extends ResourceObject {
apiVersion: string;
kind: string;
spec?: Record<string, any>;
}
export function isDeployment(object: KubernetesObject): boolean {
return object.kind === 'Deployment';
}
export function isShowConvertButton(objects: KubernetesObject[]): boolean {
return objects.filter((ob) => isDeployment(ob)).length > 0;
}
export function buildWebServiceBaseDeployment(object: KubernetesObject): Record<string, any> {
return {
image: object.spec?.containers[0].image,
ports: [],
readinessProbe: {},
livenessProbe: {},
env: [],
memory: '',
cpu: '',
};
}

View File

@ -42,9 +42,12 @@ class PolicySelect extends React.Component<Props, State> {
}) })
.then((res) => { .then((res) => {
if (res && res.policies) { if (res && res.policies) {
const policyListData = (res.policies || []).map( const policyListData = (res.policies || []).map((item: ApplicationPolicyBase) => {
(item: ApplicationPolicyBase) => `${item.name}(${item.type})`, return {
); label: `${item.name}(${item.type})`,
value: item.name,
};
});
this.setState({ this.setState({
policySelectDataSource: policyListData, policySelectDataSource: policyListData,
}); });

View File

@ -93,6 +93,8 @@ export interface Metadata {
resourceVersion: string; resourceVersion: string;
selfLink: string; selfLink: string;
uid: string; uid: string;
annotations?: Record<string, string>;
labels?: Record<string, string>;
} }
export interface ManagedFields { export interface ManagedFields {
@ -111,28 +113,22 @@ export interface CloudResource {
status: string; status: string;
} }
export interface Configuration { export interface Configuration extends ResourceObject {
apiVersion: string; apiVersion: string;
kind: string; kind: string;
metadata: {
name: string;
namespace: string;
creationTimestamp: string;
annotations: any;
labels: any;
};
spec: { spec: {
region: string;
providerRef: { providerRef: {
name: string; name: string;
namespace: string; namespace: string;
}; };
region?: string;
}; };
status?: { status?: {
apply?: { apply?: {
outputs?: any; outputs?: any;
state?: string; state?: string;
message?: string; message?: string;
region?: string;
}; };
}; };
} }

View File

@ -50,6 +50,13 @@ class TabsContent extends Component<Props, State> {
payload: { appName: appName }, payload: { appName: appName },
}); });
}; };
loadApplicationPolicies = async () => {
const { appName } = this.props;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
render() { render() {
const { activeKey, applicationDetail, envbinding } = this.props; const { activeKey, applicationDetail, envbinding } = this.props;
const { visibleEnvPlan } = this.state; const { visibleEnvPlan } = this.state;
@ -128,6 +135,7 @@ class TabsContent extends Component<Props, State> {
onOK={() => { onOK={() => {
this.loadEnvbinding(); this.loadEnvbinding();
this.loadApplicationWorkflows(); this.loadApplicationWorkflows();
this.loadApplicationPolicies();
this.setState({ visibleEnvPlan: false }); this.setState({ visibleEnvPlan: false });
}} }}
/> />

View File

@ -65,6 +65,8 @@ const LeftMenu = (props: Props) => {
return null; return null;
}); });
const releaseVersion = process.env.VERSION || version;
return ( return (
<div style={{ position: 'relative', height: '100%' }}> <div style={{ position: 'relative', height: '100%' }}>
<div className="slide-wrapper"> <div className="slide-wrapper">
@ -74,7 +76,7 @@ const LeftMenu = (props: Props) => {
align="t" align="t"
trigger={ trigger={
<div className="nav-container"> <div className="nav-container">
{version}-{__COMMIT_HASH__} {releaseVersion}-{__COMMIT_HASH__}
</div> </div>
} }
> >

View File

@ -75,7 +75,7 @@ export function getLeftSlider(pathname) {
link: '/definitions', link: '/definitions',
iconType: 'database-set', iconType: 'database-set',
navName: 'Definitions', navName: 'Definitions',
permission: { resource: 'definition:*', action: 'update' }, permission: { resource: 'definition:*', action: 'list' },
}, },
], ],
}, },

View File

@ -552,5 +552,6 @@
"Your component type does not support the image field, and the image update cannot be performed": "选择的组件不存在 image 字段,无法进行镜像更新", "Your component type does not support the image field, and the image update cannot be performed": "选择的组件不存在 image 字段,无法进行镜像更新",
"Please select a component": "请选择一个组件", "Please select a component": "请选择一个组件",
"Please select a workflow": "请选择一个工作流", "Please select a workflow": "请选择一个工作流",
"Please select a payloadType": "请选择请求体类型,不同的外部系统请求参数类型具有差异" "Please select a payloadType": "请选择请求体类型,不同的外部系统请求参数类型具有差异",
"Resource Graph": "资源拓扑图"
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Card, Dialog, Grid } from '@b-design/ui'; import { Card, Dialog, Grid, Icon } from '@b-design/ui';
import type { import type {
ApplicationDetail, ApplicationDetail,
ApplicationPolicyBase, ApplicationPolicyBase,
@ -12,6 +12,7 @@ import Empty from '../../../../components/Empty';
import Translation from '../../../../components/Translation'; import Translation from '../../../../components/Translation';
import locale from '../../../../utils/locale'; import locale from '../../../../utils/locale';
import Item from '../../../../components/Item'; import Item from '../../../../components/Item';
import Permission from '../../../../components/Permission';
// import Permission from '../../../../components/Permission'; // import Permission from '../../../../components/Permission';
type Props = { type Props = {
@ -43,13 +44,13 @@ class PolicyList extends Component<Props, State> {
render() { render() {
const { Row, Col } = Grid; const { Row, Col } = Grid;
const { policies, envbinding } = this.props; const { policies, envbinding, applicationDetail } = this.props;
const envNameAlias: any = {}; const envNameAlias: any = {};
envNameAlias[''] = '-'; envNameAlias[''] = '-';
envbinding?.map((item) => { envbinding?.map((item) => {
envNameAlias[item.name] = item.alias; envNameAlias[item.name] = item.alias;
}); });
//const projectName = applicationDetail && applicationDetail.project?.name; const projectName = applicationDetail && applicationDetail.project?.name;
return ( return (
<div className="list-warper"> <div className="list-warper">
<div className="box"> <div className="box">
@ -63,24 +64,24 @@ class PolicyList extends Component<Props, State> {
{item.alias ? `${item.alias}(${item.name})` : item.name} {item.alias ? `${item.alias}(${item.name})` : item.name}
{/* </a> */} {/* </a> */}
</div> </div>
{/* <div className="trigger-list-operation"> <div className="trigger-list-operation">
<Permission <Permission
request={{ request={{
resource: `project:${projectName}/application/policy:${item.name}`, resource: `project:${projectName}/application/policy:${item.name}`,
action: 'delete', action: 'delete',
}}
project={projectName}
>
<Icon
type="ashbin1"
size={14}
className="margin-right-0 cursor-pointer"
onClick={() => {
this.handlePolicyDelete(item.name);
}} }}
/> project={projectName}
</Permission> >
</div> */} <Icon
type="ashbin1"
size={14}
className="margin-right-0 cursor-pointer"
onClick={() => {
this.handlePolicyDelete(item.name);
}}
/>
</Permission>
</div>
</div> </div>
<div className="policy-list-content"> <div className="policy-list-content">
<Row wrap={true}> <Row wrap={true}>

View File

@ -10,6 +10,7 @@ import {
deleteComponent, deleteComponent,
getComponentDefinitions, getComponentDefinitions,
deleteApplicationPlan, deleteApplicationPlan,
deletePolicy,
} from '../../api/application'; } from '../../api/application';
import Translation from '../../components/Translation'; import Translation from '../../components/Translation';
import Title from '../../components/Title'; import Title from '../../components/Title';
@ -370,6 +371,26 @@ class ApplicationConfig extends Component<Props, State> {
}); });
}; };
onDeletePolicy = (policyName: string) => {
const { appName } = this.state;
deletePolicy({ appName: appName, policyName: policyName }).then((re) => {
if (re) {
Message.success('Application policy deleted successfully');
this.loadApplicationPolicies();
}
});
};
loadApplicationPolicies = async () => {
const {
params: { appName },
} = this.props.match;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
render() { render() {
const { applicationDetail, workflows, components, policies, envbinding } = this.props; const { applicationDetail, workflows, components, policies, envbinding } = this.props;
const { const {
@ -555,7 +576,7 @@ class ApplicationConfig extends Component<Props, State> {
policies={policies} policies={policies}
envbinding={envbinding} envbinding={envbinding}
onDeletePolicy={(name: string) => { onDeletePolicy={(name: string) => {
console.log(name); this.onDeletePolicy(name);
}} }}
onShowPolicy={(name: string) => { onShowPolicy={(name: string) => {
console.log(name); console.log(name);

View File

@ -15,7 +15,7 @@ import type {
} from '../../../../interface/application'; } from '../../../../interface/application';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import locale from '../../../../utils/locale'; import locale from '../../../../utils/locale';
import { Link } from 'dva/router'; import { Link, routerRedux } from 'dva/router';
import i18n from 'i18next'; import i18n from 'i18next';
import Permission from '../../../../components/Permission'; import Permission from '../../../../components/Permission';
@ -113,13 +113,14 @@ class Header extends Component<Props, State> {
Dialog.confirm({ Dialog.confirm({
content: i18n.t('Are you sure you want to delete the current environment binding?'), content: i18n.t('Are you sure you want to delete the current environment binding?'),
onOk: () => { onOk: () => {
const { applicationDetail, envName, updateEnvs } = this.props; const { applicationDetail, envName, updateEnvs, dispatch } = this.props;
if (applicationDetail) { if (applicationDetail) {
deleteApplicationEnvbinding({ appName: applicationDetail.name, envName: envName }).then( deleteApplicationEnvbinding({ appName: applicationDetail.name, envName: envName }).then(
(re) => { (re) => {
if (re) { if (re) {
Message.success(i18n.t('Environment binding deleted successfully')); Message.success(i18n.t('Environment binding deleted successfully'));
updateEnvs(); updateEnvs();
dispatch(routerRedux.push(`/applications/${applicationDetail.name}/config`));
} }
}, },
); );

View File

@ -99,7 +99,7 @@ class PodDetail extends React.Component<Props, State> {
this.setState({ showContainerLog: true, containerName: containerName }); this.setState({ showContainerLog: true, containerName: containerName });
}; };
getContainerCloumns = () => { getContainerColumns = () => {
const { observability } = this.state; const { observability } = this.state;
const { clusterName, env, pod, application } = this.props; const { clusterName, env, pod, application } = this.props;
let domain = ''; let domain = '';
@ -244,7 +244,7 @@ class PodDetail extends React.Component<Props, State> {
]; ];
}; };
getEventCloumns = () => { getEventColumns = () => {
return [ return [
{ {
key: 'type', key: 'type',
@ -282,15 +282,15 @@ class PodDetail extends React.Component<Props, State> {
render() { render() {
const { Column } = Table; const { Column } = Table;
const containerColumns = this.getContainerCloumns(); const containerColumns = this.getContainerColumns();
const eventCloumns = this.getEventCloumns(); const eventColumns = this.getEventColumns();
const { events, containers, loading, showContainerLog, containerName } = this.state; const { events, containers, loading, showContainerLog, containerName } = this.state;
const { pod, clusterName } = this.props; const { pod, clusterName } = this.props;
return ( return (
<div className="table-podDetails-list margin-top-20"> <div className="table-podDetails-list margin-top-20">
<Table <Table
className="container-table-wraper margin-top-20" className="container-table-wrapper margin-top-20"
dataSource={containers} dataSource={containers || []}
hasBorder={false} hasBorder={false}
primaryKey="name" primaryKey="name"
loading={loading} loading={loading}
@ -301,15 +301,15 @@ class PodDetail extends React.Component<Props, State> {
</Table> </Table>
<Table <Table
className="event-table-wraper margin-top-20" className="event-table-wrapper margin-top-20"
dataSource={events} dataSource={events || []}
hasBorder={false} hasBorder={false}
loading={loading} loading={loading}
primaryKey="time" primaryKey="time"
locale={locale().Table} locale={locale().Table}
> >
{eventCloumns && {eventColumns &&
eventCloumns.map((col, key) => <Column {...col} key={key} align={'left'} />)} eventColumns.map((col, key) => <Column {...col} key={key} align={'left'} />)}
</Table> </Table>
<If condition={showContainerLog}> <If condition={showContainerLog}>

View File

@ -1,16 +1,16 @@
.podlist-table-wraper { .podlist-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }
} }
.container-table-wraper { .container-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }
} }
.event-table-wraper { .event-table-wrapper {
.next-table-cell.last .next-table-cell-wrapper { .next-table-cell.last .next-table-cell-wrapper {
text-align: left; text-align: left;
} }

View File

@ -184,10 +184,15 @@ class ApplicationInstanceList extends React.Component<Props, State> {
const instances: CloudInstance[] = []; const instances: CloudInstance[] = [];
if (Array.isArray(configurations) && configurations.length > 0) { if (Array.isArray(configurations) && configurations.length > 0) {
configurations.map((configuration) => { configurations.map((configuration) => {
let url = configuration.metadata.annotations['cloud-resource/console-url']; let url = '';
const identifierKey = configuration.metadata.annotations['cloud-resource/identifier']; let identifierKey = '';
if (configuration.metadata.annotations) {
url = configuration.metadata.annotations['cloud-resource/console-url'];
identifierKey = configuration.metadata.annotations['cloud-resource/identifier'];
}
const outputs = configuration.status?.apply?.outputs; const outputs = configuration.status?.apply?.outputs;
let instanceName = ''; let instanceName = '';
const region = configuration.status?.apply?.region || configuration.spec.region || '';
if (outputs) { if (outputs) {
if (outputs[identifierKey]) { if (outputs[identifierKey]) {
instanceName = outputs[identifierKey].value; instanceName = outputs[identifierKey].value;
@ -197,8 +202,8 @@ class ApplicationInstanceList extends React.Component<Props, State> {
if (Array.isArray(params) && params.length > 0) { if (Array.isArray(params) && params.length > 0) {
params.map((param) => { params.map((param) => {
const paramKey = param.replace('{', '').replace('}', ''); const paramKey = param.replace('{', '').replace('}', '');
if (paramKey.toLowerCase() == 'region' && configuration.spec.region) { if (paramKey.toLowerCase() == 'region' && region) {
url = url.replace(param, configuration.spec.region); url = url.replace(param, region);
} }
if (outputs[paramKey]) { if (outputs[paramKey]) {
url = url.replace(param, outputs[paramKey].value); url = url.replace(param, outputs[paramKey].value);
@ -209,12 +214,13 @@ class ApplicationInstanceList extends React.Component<Props, State> {
} }
instances.push({ instances.push({
instanceName: instanceName, instanceName: instanceName,
status: configuration.status?.apply?.state || 'Initing', status: configuration.status?.apply?.state || '-',
url: url, url: url,
createTime: configuration.metadata.creationTimestamp, createTime: configuration.metadata.creationTimestamp || '',
region: configuration.spec.region, region: region,
message: configuration.status?.apply?.message, message: configuration.status?.apply?.message,
type: configuration.metadata.labels['workload.oam.dev/type'], type:
configuration.metadata.labels && configuration.metadata.labels['workload.oam.dev/type'],
}); });
}); });
} }
@ -549,11 +555,11 @@ class ApplicationInstanceList extends React.Component<Props, State> {
<Table <Table
style={{ marginBottom: '32px' }} style={{ marginBottom: '32px' }}
locale={locale().Table} locale={locale().Table}
className="podlist-table-wraper" className="podlist-table-wrapper"
size="medium" size="medium"
primaryKey={'primaryKey'} primaryKey={'primaryKey'}
loading={loading} loading={loading}
dataSource={podList} dataSource={podList || []}
expandedIndexSimulate expandedIndexSimulate
expandedRowRender={expandedRowRender} expandedRowRender={expandedRowRender}
openRowKeys={this.state.openRowKeys} openRowKeys={this.state.openRowKeys}

View File

@ -3,12 +3,17 @@ import React from 'react';
import { If } from 'tsx-control-statements/components'; import { If } from 'tsx-control-statements/components';
import type { TreeNode } from '../../../../components/TreeGraph'; import type { TreeNode } from '../../../../components/TreeGraph';
import { TreeGraph } from '../../../../components/TreeGraph'; import { TreeGraph } from '../../../../components/TreeGraph';
import type { ApplicationDetail, EnvBinding } from '../../../../interface/application'; import type {
ApplicationDetail,
ApplicationStatus,
EnvBinding,
} from '../../../../interface/application';
import type { AppliedResource, ResourceTreeNode } from '../../../../interface/observation'; import type { AppliedResource, ResourceTreeNode } from '../../../../interface/observation';
import { ShowResource } from './resource-show'; import { ShowResource } from './resource-show';
import './index.less'; import './index.less';
type Props = { type Props = {
applicationStatus?: ApplicationStatus;
application?: ApplicationDetail; application?: ApplicationDetail;
env?: EnvBinding; env?: EnvBinding;
resources: AppliedResource[]; resources: AppliedResource[];

View File

@ -11,7 +11,7 @@ import {
Message, Message,
Dialog, Dialog,
Tag, Tag,
Switch, Tab,
} from '@b-design/ui'; } from '@b-design/ui';
import type { import type {
ApplicationComponent, ApplicationComponent,
@ -42,6 +42,7 @@ import { checkPermission } from '../../utils/permission';
import type { LoginUserInfo } from '../../interface/user'; import type { LoginUserInfo } from '../../interface/user';
import './index.less'; import './index.less';
import ApplicationGraph from './components/ApplicationGraph'; import ApplicationGraph from './components/ApplicationGraph';
import i18n from '../../i18n';
type Props = { type Props = {
dispatch: ({}) => {}; dispatch: ({}) => {};
@ -69,7 +70,7 @@ type State = {
resourceLoading: boolean; resourceLoading: boolean;
endpointLoading: boolean; endpointLoading: boolean;
envName: string; envName: string;
mode: 'table' | 'graph'; mode: 'overview' | 'resource-graph' | 'application-graph';
}; };
@connect((store: any) => { @connect((store: any) => {
@ -84,7 +85,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
resourceLoading: false, resourceLoading: false,
endpointLoading: false, endpointLoading: false,
envName: '', envName: '',
mode: 'graph', mode: 'overview',
resources: [], resources: [],
}; };
} }
@ -114,9 +115,11 @@ class ApplicationStatusPage extends React.Component<Props, State> {
this.props.dispatch({ this.props.dispatch({
type: 'application/getApplicationStatus', type: 'application/getApplicationStatus',
payload: { appName: appName, envName: envName }, payload: { appName: appName, envName: envName },
callback: () => { callback: (res: any) => {
this.loadApplicationEndpoints(); if (res.status) {
this.loadApplicationAppliedResources(); this.loadApplicationEndpoints();
this.loadApplicationAppliedResources();
}
}, },
}); });
} }
@ -175,7 +178,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
loadApplicationAppliedResources = async () => { loadApplicationAppliedResources = async () => {
const { mode } = this.state; const { mode } = this.state;
if (mode == 'graph') { if (mode === 'resource-graph') {
await this.loadResourceTree(); await this.loadResourceTree();
return; return;
} }
@ -256,6 +259,16 @@ class ApplicationStatusPage extends React.Component<Props, State> {
}); });
}; };
loadApplicationPolicies = async () => {
const {
params: { appName },
} = this.props.match;
this.props.dispatch({
type: 'application/getApplicationPolicies',
payload: { appName: appName },
});
};
loadApplicationEnvbinding = async () => { loadApplicationEnvbinding = async () => {
const { const {
params: { appName }, params: { appName },
@ -325,14 +338,13 @@ class ApplicationStatusPage extends React.Component<Props, State> {
} }
}; };
onChangeMode = () => { onChangeMode = (mode: string | number) => {
const { mode } = this.state; if (mode == 'overview') {
if (mode == 'graph') { this.setState({ mode: mode }, () => {
this.setState({ mode: 'table' }, () => {
this.loadApplicationAppliedResources(); this.loadApplicationAppliedResources();
}); });
} else { } else if (mode == 'resource-graph') {
this.setState({ mode: 'graph' }, () => { this.setState({ mode: mode }, () => {
this.loadApplicationAppliedResources(); this.loadApplicationAppliedResources();
}); });
} }
@ -351,7 +363,6 @@ class ApplicationStatusPage extends React.Component<Props, State> {
resources, resources,
componentName, componentName,
deployLoading, deployLoading,
mode,
} = this.state; } = this.state;
const gatewayIPs: any = []; const gatewayIPs: any = [];
endpoints?.map((endpointObj) => { endpoints?.map((endpointObj) => {
@ -381,6 +392,7 @@ class ApplicationStatusPage extends React.Component<Props, State> {
updateEnvs={() => { updateEnvs={() => {
this.loadApplicationEnvbinding(); this.loadApplicationEnvbinding();
this.loadApplicationWorkflows(); this.loadApplicationWorkflows();
this.loadApplicationPolicies();
}} }}
updateQuery={(params: { target?: string; component?: string }) => { updateQuery={(params: { target?: string; component?: string }) => {
this.updateQuery(params); this.updateQuery(params);
@ -391,247 +403,282 @@ class ApplicationStatusPage extends React.Component<Props, State> {
dispatch={this.props.dispatch} dispatch={this.props.dispatch}
/> />
</Loading> </Loading>
<If condition={applicationStatus}> <Tab onChange={this.onChangeMode} shape="capsule">
<Switch <Tab.Item title={i18n.t('Overview')} key="overview">
unCheckedChildren="Table" <Loading visible={loading && resourceLoading} style={{ width: '100%' }}>
checkedChildren="Graph" <If condition={applicationStatus}>
defaultChecked={true} <If condition={componentStatus}>
onChange={this.onChangeMode} <Card
/> locale={locale().Card}
</If> style={{ marginTop: '8px', marginBottom: '16px' }}
<Loading visible={loading && resourceLoading} style={{ width: '100%' }}> contentHeight="auto"
<If condition={applicationStatus && mode === 'graph'}> title={<Translation>Component Status</Translation>}
<ApplicationGraph application={applicationDetail} env={env} resources={resources} /> >
</If> <Table
<If condition={applicationStatus && mode === 'table'}> locale={locale().Table}
<Card className="customTable"
locale={locale().Card} dataSource={componentStatus}
contentHeight="200px" >
title={<Translation>Applied Resources</Translation>} <Table.Column
> align="left"
<Table locale={locale().Table} dataSource={resources}> dataIndex="name"
<Table.Column style={{ width: '17%' }}
dataIndex="cluster" title={<Translation>Name</Translation>}
title={<Translation>Cluster</Translation>} />
width="200px" <Table.Column
cell={(v: string) => { dataIndex="cluster"
let clusterName = v; title={<Translation>Cluster</Translation>}
if (!clusterName) { width="200px"
clusterName = 'Local'; cell={(v: string) => {
} let clusterName = v;
if ( if (!clusterName) {
checkPermission( clusterName = 'Local';
{ resource: 'cluster:*', action: 'list' }, }
applicationDetail?.project?.name, if (
userInfo, checkPermission(
) { resource: 'cluster:*', action: 'list' },
) { applicationDetail?.project?.name,
return <Link to="/clusters">{clusterName}</Link>; userInfo,
} )
return <span>{clusterName}</span>; ) {
}} return <Link to="/clusters">{clusterName}</Link>;
/> }
<Table.Column return <span>{clusterName}</span>;
dataIndex="name" }}
width="240px" />
title={<Translation>Namespace/Name</Translation>} <Table.Column
cell={(v: string, i: number, row: AppliedResource) => { align="left"
return `${row.namespace}/${row.name}`; dataIndex="healthy"
}} width="100px"
/> cell={(v: boolean) => {
<Table.Column if (v) {
width="200px" return (
dataIndex="kind"
title={<Translation>Kind</Translation>}
/>
<Table.Column
dataIndex="apiVersion"
title={<Translation>APIVersion</Translation>}
/>
<Table.Column dataIndex="component" title={<Translation>Component</Translation>} />
<Table.Column
dataIndex="deployVersion"
title={<Translation>Revision</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
if (row.latest) {
return (
<span>
<Icon
style={{ color: 'green', marginRight: '8px' }}
type="NEW"
title="latest version resource"
/>
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
</span>
);
}
return (
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
);
}}
/>
</Table>
</Card>
<If condition={componentStatus}>
<Card
locale={locale().Card}
style={{ marginTop: '8px', marginBottom: '16px' }}
contentHeight="auto"
title={<Translation>Component Status</Translation>}
>
<Table locale={locale().Table} className="customTable" dataSource={componentStatus}>
<Table.Column
align="left"
dataIndex="name"
style={{ width: '17%' }}
title={<Translation>Name</Translation>}
/>
<Table.Column
align="left"
dataIndex="healthy"
width="100px"
cell={(v: boolean) => {
if (v) {
return (
<div>
<span className="circle circle-success" />
<span>Healthy</span>
</div>
);
}
return (
<div>
<span className="circle circle-warning" />
<span>UnHealthy</span>
</div>
);
}}
title={<Translation>Healthy</Translation>}
/>
<Table.Column
align="left"
dataIndex="trait"
cell={(v: boolean, i: number, record: ComponentStatus) => {
const { traits } = record;
const Tags = (traits || []).map((item) => {
if (item.healthy) {
return (
<Tag type="normal" size="small">
<div> <div>
<span className="circle circle-success" /> <span className="circle circle-success" />
<span>{item.type}</span> <span>Healthy</span>
</div> </div>
</Tag> );
); }
} else {
return (
<Tag type="normal" size="small">
<div>
<span className="circle circle-failure" />
<span>{item.type}</span>
</div>
</Tag>
);
}
});
return <TagGroup className="tags-content">{Tags}</TagGroup>;
}}
title={<Translation>Traits</Translation>}
/>
<Table.Column
align="center"
dataIndex="message"
style={{ width: '50%' }}
title={<Translation>Message</Translation>}
cell={(v: string, i: number, record: ComponentStatus) => {
const { message = '', traits } = record;
const TraitMessages = (traits || []).map((item) => {
if (item.message) {
return ( return (
<div> <div>
<span>{item.type}: </span> <span className="circle circle-warning" />
<span>{item.message}</span> <span>UnHealthy</span>
</div> </div>
); );
} }}
}); title={<Translation>Healthy</Translation>}
return ( />
<div> <Table.Column
<div>{message}</div> align="left"
{TraitMessages} dataIndex="trait"
</div> cell={(v: boolean, i: number, record: ComponentStatus) => {
); const { traits } = record;
}} const Tags = (traits || []).map((item) => {
/> if (item.healthy) {
</Table> return (
</Card> <Tag type="normal" size="small">
</If> <div>
<If condition={applicationStatus?.conditions}> <span className="circle circle-success" />
<Card <span>{item.type}</span>
locale={locale().Card} </div>
style={{ marginTop: '8px' }} </Tag>
contentHeight="auto" );
title={<Translation>Conditions</Translation>} } else {
> return (
<Table locale={locale().Table} dataSource={applicationStatus?.conditions}> <Tag type="normal" size="small">
<Table.Column <div>
width="150px" <span className="circle circle-failure" />
dataIndex="type" <span>{item.type}</span>
title={<Translation>Type</Translation>} </div>
/> </Tag>
<Table.Column dataIndex="status" title={<Translation>Status</Translation>} /> );
<Table.Column
dataIndex="lastTransitionTime"
title={<Translation>LastTransitionTime</Translation>}
/>
<Table.Column
dataIndex="reason"
title={<Translation>Reason</Translation>}
cell={(v: string, index: number, row: Condition) => {
if (row.message) {
return (
<Balloon
trigger={
<span style={{ color: 'red', cursor: 'pointer' }}>
{v} <Icon size={'xs'} type="question-circle" />
</span>
} }
> });
{row.message} return <TagGroup className="tags-content">{Tags}</TagGroup>;
</Balloon> }}
title={<Translation>Traits</Translation>}
/>
<Table.Column
align="center"
dataIndex="message"
title={<Translation>Message</Translation>}
cell={(v: string, i: number, record: ComponentStatus) => {
const { message = '', traits } = record;
const TraitMessages = (traits || []).map((item) => {
if (item.message) {
return (
<div>
<span>{item.type}: </span>
<span>{item.message}</span>
</div>
);
}
});
return (
<div>
<div>{message}</div>
{TraitMessages}
</div>
);
}}
/>
</Table>
</Card>
</If>
<Card
locale={locale().Card}
contentHeight="200px"
title={<Translation>Applied Resources</Translation>}
>
<Table locale={locale().Table} dataSource={resources}>
<Table.Column
dataIndex="name"
width="240px"
title={<Translation>Namespace/Name</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
return `${row.namespace}/${row.name}`;
}}
/>
<Table.Column
dataIndex="cluster"
title={<Translation>Cluster</Translation>}
width="200px"
cell={(v: string) => {
let clusterName = v;
if (!clusterName) {
clusterName = 'Local';
}
if (
checkPermission(
{ resource: 'cluster:*', action: 'list' },
applicationDetail?.project?.name,
userInfo,
)
) {
return <Link to="/clusters">{clusterName}</Link>;
}
return <span>{clusterName}</span>;
}}
/>
<Table.Column
width="200px"
dataIndex="kind"
title={<Translation>Kind</Translation>}
/>
<Table.Column
dataIndex="apiVersion"
title={<Translation>APIVersion</Translation>}
/>
<Table.Column
dataIndex="component"
title={<Translation>Component</Translation>}
/>
<Table.Column
dataIndex="deployVersion"
title={<Translation>Revision</Translation>}
cell={(v: string, i: number, row: AppliedResource) => {
if (row.latest) {
return (
<span>
<Icon
style={{ color: 'green', marginRight: '8px' }}
type="NEW"
title="latest version resource"
/>
<Link to={`/applications/${applicationDetail?.name}/revisions`}>
{v}
</Link>
</span>
);
}
return (
<Link to={`/applications/${applicationDetail?.name}/revisions`}>{v}</Link>
); );
} }}
return <span>{v}</span>; />
}} </Table>
/> </Card>
</Table>
</Card> <If condition={applicationStatus?.conditions}>
</If> <Card
</If> locale={locale().Card}
<If condition={!applicationStatus}> style={{ marginTop: '8px' }}
<div className="deployNotice"> contentHeight="auto"
<div className="noticeBox"> title={<Translation>Conditions</Translation>}
<h2>
<Translation>Not Deploy</Translation>
</h2>
<div className="desc">
<Translation>The current environment has not been deployed.</Translation>
</div>
<div className="noticeAction">
<Button
loading={deployLoading}
disabled={applicationDetail?.readOnly}
onClick={() => this.onDeploy()}
type="primary"
> >
<Translation>Deploy</Translation> <Table locale={locale().Table} dataSource={applicationStatus?.conditions}>
</Button> <Table.Column
width="150px"
dataIndex="type"
title={<Translation>Type</Translation>}
/>
<Table.Column dataIndex="status" title={<Translation>Status</Translation>} />
<Table.Column
dataIndex="lastTransitionTime"
title={<Translation>LastTransitionTime</Translation>}
/>
<Table.Column
dataIndex="reason"
title={<Translation>Reason</Translation>}
cell={(v: string, index: number, row: Condition) => {
if (row.message) {
return (
<Balloon
trigger={
<span style={{ color: 'red', cursor: 'pointer' }}>
{v} <Icon size={'xs'} type="question-circle" />
</span>
}
>
{row.message}
</Balloon>
);
}
return <span>{v}</span>;
}}
/>
</Table>
</Card>
</If>
</If>
<If condition={!applicationStatus}>
<div className="deployNotice">
<div className="noticeBox">
<h2>
<Translation>Not Deploy</Translation>
</h2>
<div className="desc">
<Translation>The current environment has not been deployed.</Translation>
</div>
<div className="noticeAction">
<Button
loading={deployLoading}
disabled={applicationDetail?.readOnly}
onClick={() => this.onDeploy()}
type="primary"
>
<Translation>Deploy</Translation>
</Button>
</div>
</div>
</div> </div>
</div> </If>
</div> </Loading>
</If> </Tab.Item>
</Loading> <Tab.Item title={i18n.t('Resource Graph')} key="resource-graph">
<Loading visible={loading && resourceLoading} style={{ width: '100%' }}>
<If condition={applicationStatus}>
<ApplicationGraph
applicationStatus={applicationStatus}
application={applicationDetail}
env={env}
resources={resources}
/>
</If>
</Loading>
</Tab.Item>
</Tab>
</div> </div>
); );
} }

View File

@ -12,6 +12,8 @@ import SelectSearch from './components/SelectSearch';
import locale from '../../utils/locale'; import locale from '../../utils/locale';
import { getMatchParamObj } from '../../utils/utils'; import { getMatchParamObj } from '../../utils/utils';
import './index.less'; import './index.less';
import { checkPermission } from '../../utils/permission';
import type { LoginUserInfo } from '../../interface/user';
type Props = { type Props = {
match: { match: {
@ -19,6 +21,7 @@ type Props = {
definitionType: string; definitionType: string;
}; };
}; };
userInfo?: LoginUserInfo;
}; };
type State = { type State = {
@ -30,7 +33,7 @@ type State = {
}; };
@connect((store: any) => { @connect((store: any) => {
return { ...store.definitions }; return { ...store.definitions, ...store.user };
}) })
class Definitions extends Component<Props, State> { class Definitions extends Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
@ -62,10 +65,14 @@ class Definitions extends Component<Props, State> {
} }
lisDefinitions() { lisDefinitions() {
const { userInfo } = this.props;
const { definitionType } = this.state; const { definitionType } = this.state;
if (!definitionType) { if (!definitionType) {
return; return;
} }
if (!checkPermission({ resource: 'definition:*', action: 'list' }, '', userInfo)) {
return;
}
const params = { const params = {
definitionType, definitionType,
queryAll: true, queryAll: true,