diff --git a/dashboard/config/config.js b/dashboard/config/config.js
index 44335c32c..e89bb9571 100644
--- a/dashboard/config/config.js
+++ b/dashboard/config/config.js
@@ -68,6 +68,18 @@ export default defineConfig({
path: '/ApplicationList/CreateApplication',
component: './ApplicationList/CreateApplication',
},
+ {
+ name: 'ApplicationList.Components',
+ hideInMenu: true,
+ path: '/ApplicationList/:appName/Components',
+ component: './ApplicationList/Components',
+ },
+ {
+ name: 'ApplicationList.Components.createComponent',
+ hideInMenu: true,
+ path: '/ApplicationList/:appName/createComponent',
+ component: './ApplicationList/CreateComponent',
+ },
{
name: 'Workload',
icon: 'table',
diff --git a/dashboard/config/proxy.js b/dashboard/config/proxy.js
index a21e760c9..f12014d4c 100644
--- a/dashboard/config/proxy.js
+++ b/dashboard/config/proxy.js
@@ -8,7 +8,7 @@
export default {
dev: {
'/api': {
- target: 'http://30.11.171.17:38081/',
+ target: 'http://30.11.171.29:38081/',
changeOrigin: true,
},
},
diff --git a/dashboard/src/layouts/BasicLayout.jsx b/dashboard/src/layouts/BasicLayout.jsx
index fdafe8ad3..07994195f 100644
--- a/dashboard/src/layouts/BasicLayout.jsx
+++ b/dashboard/src/layouts/BasicLayout.jsx
@@ -4,7 +4,7 @@
* https://github.com/ant-design/ant-design-pro-layout
*/
import ProLayout from '@ant-design/pro-layout';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
import { Link, useIntl, connect, history } from 'umi';
import RightContent from '@/components/GlobalHeader/RightContent';
import {
@@ -44,6 +44,7 @@ const AddIcon = (menuData) => {
const BasicLayout = (props) => {
const { settings, dispatch, menus } = props;
const [currentSelectKeys, setCurrentSelectedKeys] = useState('');
+ const timerRef = useRef();
const getCurrentSelectKeys = () => {
const pathnameCur = props.history.location.pathname;
if (pathnameCur) {
@@ -70,9 +71,14 @@ const BasicLayout = (props) => {
type: 'menus/getMenuData',
});
}
- props.history.listen((route) => {
+ timerRef.current = props.history.listen((route) => {
getCurrentSelectKeys(route.pathname);
});
+ return () => {
+ if (timerRef.current) {
+ timerRef.current = null;
+ }
+ };
// setCurrentSelectedKeys('applist')
}, []);
diff --git a/dashboard/src/pages/ApplicationList/ApplicationListDetail/index.jsx b/dashboard/src/pages/ApplicationList/ApplicationListDetail/index.jsx
index 0d7c6860e..d66240bd3 100644
--- a/dashboard/src/pages/ApplicationList/ApplicationListDetail/index.jsx
+++ b/dashboard/src/pages/ApplicationList/ApplicationListDetail/index.jsx
@@ -38,18 +38,26 @@ class TableList extends React.Component {
}
componentDidMount() {
- this.getInitialData(1);
+ this.getInitialData();
}
- getInitialData = async (times) => {
- const appName = _.get(this.props, 'location.state.appName', '');
- const envName = _.get(this.props, 'location.state.envName', '');
- const traitType = _.get(this.props, 'location.state.traitType', '');
+ getInitialData = async () => {
+ let appName = '';
+ let envName = '';
+ if (this.props.location.state) {
+ appName = _.get(this.props, 'location.state.appName', '');
+ envName = _.get(this.props, 'location.state.envName', '');
+ sessionStorage.setItem('appName', appName);
+ sessionStorage.setItem('envName', envName);
+ } else {
+ appName = sessionStorage.getItem('appName');
+ envName = sessionStorage.getItem('envName');
+ }
+ this.setState({
+ appName,
+ envName,
+ });
if (appName && envName) {
- this.setState({
- envName,
- appName,
- });
const res = await this.props.dispatch({
type: 'applist/getAppDetail',
payload: {
@@ -76,12 +84,6 @@ class TableList extends React.Component {
} else if (workloadType && workloadType === 'Deployment') {
this.getAcceptTrait('deployment');
}
- if (traitType && times === 1) {
- await this.setState({
- visible: true,
- });
- this.child.setDefaultValue(traitType);
- }
}
};
diff --git a/dashboard/src/pages/ApplicationList/ComponentDetail/Topology.jsx b/dashboard/src/pages/ApplicationList/ComponentDetail/Topology.jsx
new file mode 100644
index 000000000..aff61e680
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/ComponentDetail/Topology.jsx
@@ -0,0 +1,108 @@
+import React, { useEffect } from 'react';
+import G6 from '@antv/g6';
+
+const Topology = () => {
+ let graph = null;
+ const data = {
+ nodes: [
+ {
+ id: 'node1',
+ x: 150,
+ y: 50,
+ label: 'node1',
+ },
+ {
+ id: 'node2',
+ x: 250,
+ y: 200,
+ label: 'node2',
+ },
+ {
+ id: 'node3',
+ x: 100,
+ y: 350,
+ label: 'node3',
+ },
+ ],
+ edges: [
+ {
+ source: 'node1',
+ target: 'node2',
+ label: 'edge 1',
+ },
+ {
+ source: 'node2',
+ target: 'node3',
+ label: 'edge 2',
+ },
+ {
+ source: 'node3',
+ target: 'node1',
+ label: 'edge 3',
+ },
+ ],
+ };
+
+ const width = 1000;
+ const height = 400;
+
+ useEffect(() => {
+ if (!graph) {
+ graph = new G6.Graph({
+ container: 'container',
+ width,
+ height,
+ // translate the graph to align the canvas's center, support by v3.5.1
+ fitCenter: true,
+ defaultNode: {
+ type: 'circle',
+ size: [40],
+ color: '#5B8FF9',
+ style: {
+ fill: '#9EC9FF',
+ lineWidth: 3,
+ },
+ labelCfg: {
+ style: {
+ fill: '#1890ff',
+ fontSize: 14,
+ },
+ position: 'bottom',
+ },
+ },
+ defaultEdge: {
+ labelCfg: {
+ autoRotate: true,
+ style: {
+ background: {
+ fill: '#ffffff',
+ stroke: '#9EC9FF',
+ padding: [2, 2, 2, 2],
+ radius: 2,
+ },
+ },
+ },
+ },
+ modes: {
+ default: ['drag-canvas', 'drag-node'],
+ },
+ nodeStateStyles: {
+ // style configurations for hover state
+ hover: {
+ fillOpacity: 0.8,
+ },
+ // style configurations for selected state
+ selected: {
+ lineWidth: 5,
+ },
+ },
+ });
+ }
+ graph.data(data);
+ graph.render();
+ }, []);
+
+ return
;
+};
+
+export default Topology;
diff --git a/dashboard/src/pages/ApplicationList/ComponentDetail/index.jsx b/dashboard/src/pages/ApplicationList/ComponentDetail/index.jsx
new file mode 100644
index 000000000..45bf2ecd1
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/ComponentDetail/index.jsx
@@ -0,0 +1,462 @@
+import React, { Fragment } from 'react';
+import './index.less';
+import { Button, Row, Col, Tabs, Popconfirm, message, Tooltip, Modal, Spin } from 'antd';
+import { connect } from 'dva';
+import _ from 'lodash';
+import CreateTraitItem from '../../../components/AttachOneTrait/index.jsx';
+
+const { TabPane } = Tabs;
+
+@connect(({ loading, globalData }) => ({
+ loadingAll: loading.models.applist,
+ currentEnv: globalData.currentEnv,
+}))
+class TableList extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ appDetailData: {},
+ visible: false,
+ traitList: [],
+ availableTraitList: [],
+ envName: '',
+ appName: '',
+ };
+ }
+
+ componentDidMount() {
+ this.getInitialData();
+ }
+
+ UNSAFE_componentWillReceiveProps(nextProps) {
+ if (this.props.appName !== nextProps.appName) {
+ this.getInitialData(nextProps.appName);
+ }
+ }
+
+ getInitialData = async (nextAppName) => {
+ let appName = _.get(this.props, 'appName', '');
+ const envName = _.get(this.props, 'envName', '');
+ appName = nextAppName || appName;
+ if (appName && envName) {
+ this.setState({
+ envName,
+ appName,
+ });
+ const res = await this.props.dispatch({
+ type: 'applist/getAppDetail',
+ payload: {
+ envName,
+ appName,
+ },
+ });
+ if (res) {
+ this.setState({
+ appDetailData: res,
+ });
+ }
+ const traits = await this.props.dispatch({
+ type: 'trait/getTraits',
+ });
+ if (traits) {
+ this.setState({
+ traitList: traits,
+ });
+ }
+ const workloadType = _.get(res, 'Workload.workload.kind', '');
+ if (workloadType && workloadType === 'ContainerizedWorkload') {
+ this.getAcceptTrait('containerized');
+ } else if (workloadType && workloadType === 'Deployment') {
+ this.getAcceptTrait('deployment');
+ }
+ }
+ };
+
+ getAcceptTrait = (workloadType) => {
+ const res = this.state.traitList.filter((item) => {
+ if (item.appliesTo.indexOf(workloadType) !== -1) {
+ return true;
+ }
+ return false;
+ });
+ this.setState(() => ({
+ availableTraitList: res,
+ }));
+ };
+
+ deleteApp = async (e) => {
+ e.stopPropagation();
+ const { envName } = this.state;
+ const { appDetailData } = this.state;
+ const appName = _.get(appDetailData, 'Workload.workload.metadata.name', '');
+ if (appName && envName) {
+ const res = await this.props.dispatch({
+ type: 'applist/deleteApp',
+ payload: {
+ appName,
+ envName,
+ },
+ });
+ if (res) {
+ message.success(res);
+ this.props.history.push({ pathname: '/ApplicationList' });
+ }
+ }
+ };
+
+ deleteTrait = async (e, item) => {
+ e.stopPropagation();
+ const { appName, envName } = this.state;
+ const traitNameObj = _.get(item, 'trait.metadata.annotations', '');
+ const traitName = traitNameObj['vela.oam.dev/traitDef'] || traitNameObj['trait.oam.dev/name'];
+ if (traitName && appName && envName) {
+ const res = await this.props.dispatch({
+ type: 'trait/deleteOneTrait',
+ payload: {
+ envName,
+ appName,
+ traitName,
+ },
+ });
+ if (res) {
+ message.success(res);
+ this.getInitialData(2);
+ }
+ }
+ };
+
+ cancel = (e) => {
+ e.stopPropagation();
+ };
+
+ createTrait = async () => {
+ await this.setState({
+ visible: true,
+ });
+ };
+
+ handleOk = async () => {
+ await this.child.validateFields();
+ const submitData = this.child.getSelectValue();
+ if (submitData.name) {
+ const submitObj = {
+ name: submitData.name,
+ flags: [],
+ };
+ Object.keys(submitData).forEach((currentKey) => {
+ if (currentKey !== 'name' && submitData[currentKey]) {
+ submitObj.flags.push({
+ name: currentKey,
+ value: submitData[currentKey].toString(),
+ });
+ }
+ });
+ const { envName, appName } = this.state;
+ if (envName && appName) {
+ const res = await this.props.dispatch({
+ type: 'trait/attachOneTraits',
+ payload: {
+ envName,
+ appName,
+ params: submitObj,
+ },
+ });
+ if (res) {
+ this.setState({
+ visible: false,
+ });
+ message.success(res);
+ this.getInitialData(2);
+ }
+ }
+ } else {
+ message.warning('please select a trait type');
+ }
+ };
+
+ handleCancel = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
+ hrefClick = (e) => {
+ e.stopPropagation();
+ };
+
+ gotoWorkloadDetail = (e) => {
+ e.stopPropagation();
+ };
+
+ gotoTraitDetail = (e) => {
+ e.stopPropagation();
+ };
+
+ render() {
+ const status = _.get(this.state.appDetailData, 'Status', '');
+ const Workload = _.get(this.state.appDetailData, 'Workload.workload', {});
+ const Traits = _.get(this.state.appDetailData, 'Traits', []);
+ let containers = {};
+ containers = _.get(Workload, 'spec.containers[0]', {});
+ let { loadingAll } = this.props;
+ loadingAll = loadingAll || false;
+ const colorObj = {
+ Deployed: '#4CAF51',
+ Staging: '#F44337',
+ UNKNOWN: '#1890ff',
+ };
+ return (
+
+
+
+
{_.get(Workload, 'metadata.name')}
+
+ {Workload.apiVersion}, Kind={Workload.kind}
+
+
+
+
+
+ this.gotoWorkloadDetail(e)}
+ style={{ background: colorObj[status] || '#1890ff' }}
+ >
+
+
+ {Workload.kind}
+ {Workload.apiVersion}
+
+
+
+ ?
+
+
+
+
+ Name:{_.get(Workload, 'metadata.name')}
+
+
Settings:
+
+ {Object.keys(containers).map((currentKey) => {
+ if (currentKey === 'ports') {
+ return (
+
+
+ port
+
+
+ {_.get(containers[currentKey], '[0].containerPort', '')}
+
+
+ );
+ // eslint-disable-next-line no-else-return
+ } else if (currentKey === 'name') {
+ return ;
+ // eslint-disable-next-line no-else-return
+ } else if (currentKey === 'env') {
+ return (
+
+
+ env
+
+
+ {_.get(containers[currentKey], '[0].value', '')}
+
+
+ );
+ }
+ return (
+
+
+ {currentKey}
+
+
+ {containers[currentKey]}
+
+
+ );
+ })}
+
+
+ this.deleteApp(e)}
+ onCancel={this.cancel}
+ okText="Yes"
+ cancelText="No"
+ >
+
+
+
+
+
+ {Traits.length ? (
+ Traits.map((item, index) => {
+ const traitItem = _.get(item, 'trait', {});
+ const annotations = _.get(traitItem, 'metadata.annotations', {});
+ let traitType = 1;
+ const spec = _.get(traitItem, 'spec', {});
+ if (traitItem.kind === 'Ingress') {
+ traitType = 2;
+ }
+ return (
+ this.gotoTraitDetail(e, traitItem)}
+ key={index.toString()}
+ >
+
+
+ {traitItem.kind}
+ {traitItem.apiVersion}
+
+
+ {
+ e.stopPropagation();
+ }}
+ >
+ ?
+
+
+
+
+ {Object.keys(annotations).map((currentKey3) => {
+ return (
+
+
+ {currentKey3}:
+
+
+ {annotations[currentKey3]}
+
+
+ );
+ })}
+
+
Properties:
+
+ {traitType === 2 ? (
+
+
+ domain
+
+
+ {_.get(spec, 'rules[0].host', '')}
+
+
+ service
+
+
+
+ {_.get(
+ spec,
+ 'rules[0].http.paths[0].backend.serviceName',
+ '',
+ )}
+
+
+
+ port
+
+
+
+ {_.get(
+ spec,
+ 'rules[0].http.paths[0].backend.servicePort',
+ '',
+ )}
+
+
+
+ ) : (
+ Object.keys(spec).map((currentKey) => {
+ return (
+
+
+ {currentKey}
+
+
+ {spec[currentKey]}
+
+
+ );
+ })
+ )}
+
+
+
this.deleteTrait(e, item)}
+ onCancel={this.cancel}
+ okText="Yes"
+ cancelText="No"
+ >
+
+
+
+
+ );
+ })
+ ) : (
+
+ )}
+
+
+ +
+
+
+
+
+
+
+ Topology
+
+
+
+
+ Cancel
+ ,
+ ,
+ ]}
+ >
+ {
+ this.child = ref;
+ }}
+ availableTraitList={this.state.availableTraitList}
+ initialValues={{}}
+ />
+
+
+
+ );
+ }
+}
+
+export default TableList;
diff --git a/dashboard/src/pages/ApplicationList/ComponentDetail/index.less b/dashboard/src/pages/ApplicationList/ComponentDetail/index.less
new file mode 100644
index 000000000..3c81de664
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/ComponentDetail/index.less
@@ -0,0 +1,47 @@
+.app-detial {
+ .ant-tabs {
+ min-height: 500px;
+ padding: 10px 20px;
+ background-color: #fff;
+ .ant-tabs-content-holder {
+ background: #fff;
+ }
+ }
+ .summaryBox1,
+ .summaryBox2,
+ .summaryBox {
+ clear: both;
+ margin-bottom: 20px;
+ padding: 0 10px 10px;
+ .title {
+ font-weight: 500;
+ font-size: 16px;
+ line-height: 36px;
+ }
+ p {
+ margin: 0;
+ font-size: 12px;
+ line-height: 20px;
+ }
+ }
+ .summaryBox1 {
+ color: #fff;
+ background: #0097a7;
+ cursor: pointer;
+ }
+ .summaryBox2 {
+ color: #fff;
+ background: #92c47c;
+ }
+ .summaryBox {
+ border: 1px solid black;
+ cursor: pointer;
+ }
+ .hasCursor {
+ font-size: 20px;
+ cursor: pointer;
+ }
+ .floatRight {
+ float: right;
+ }
+}
diff --git a/dashboard/src/pages/ApplicationList/Components/index.jsx b/dashboard/src/pages/ApplicationList/Components/index.jsx
new file mode 100644
index 000000000..3b117fef4
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/Components/index.jsx
@@ -0,0 +1,126 @@
+import React from 'react';
+import { Link } from 'umi';
+import { Breadcrumb, Button, Menu, Spin } from 'antd';
+import { connect } from 'dva';
+import _ from 'lodash';
+import './index.less';
+import ComponentDetail from '../ComponentDetail/index.jsx';
+
+@connect(({ loading }) => ({
+ loadingAll: loading.models.applist,
+}))
+class TableList extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ envName: '',
+ componentName: '',
+ defaultSelectedKeys: 'a1',
+ };
+ }
+
+ UNSAFE_componentWillMount() {
+ let appName = '';
+ let envName = '';
+ if (this.props.location.state) {
+ appName = _.get(this.props, 'location.state.appName', '');
+ envName = _.get(this.props, 'location.state.envName', '');
+ sessionStorage.setItem('appName', appName);
+ sessionStorage.setItem('envName', envName);
+ } else {
+ appName = sessionStorage.getItem('appName');
+ envName = sessionStorage.getItem('envName');
+ }
+ this.setState({
+ envName,
+ componentName: appName,
+ });
+ if (appName === 'test33') {
+ this.setState({
+ defaultSelectedKeys: 'a1',
+ });
+ } else if (appName === 'test01') {
+ this.setState({
+ defaultSelectedKeys: 'c2',
+ });
+ } else {
+ this.setState({
+ defaultSelectedKeys: 'c3',
+ });
+ }
+ }
+
+ changeComponent = ({ key }) => {
+ if (key === 'a1') {
+ this.setState({
+ componentName: 'test33',
+ });
+ } else if (key === 'c2') {
+ this.setState({
+ componentName: 'test01',
+ });
+ } else {
+ this.setState({
+ componentName: 'testoo',
+ });
+ }
+ };
+
+ render() {
+ const { envName, componentName, defaultSelectedKeys } = this.state;
+ const { loadingAll } = this.props;
+ return (
+
+
+
+
+ Home
+
+
+ Applications
+
+ appname
+ Components
+ {componentName}
+
+
+
+
+
+
+
+
+ add a new comp
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default TableList;
diff --git a/dashboard/src/pages/ApplicationList/Components/index.less b/dashboard/src/pages/ApplicationList/Components/index.less
new file mode 100644
index 000000000..e7f4b283e
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/Components/index.less
@@ -0,0 +1,35 @@
+.appComponent {
+ display: flex;
+ height: calc(100% - 46px);
+ .left {
+ width: 200px;
+ background: #fff;
+ .ant-menu-item-group-title {
+ color: #000;
+ font-weight: 500;
+ font-size: 18px;
+ border-top: 1px solid #efefef;
+ border-bottom: 1px solid #efefef;
+ }
+ .ant-menu-item-group-list .ant-menu-item,
+ .ant-menu-item-group-list .ant-menu-submenu-title {
+ padding-left: 16px !important;
+ color: #000;
+ font-size: 16px;
+ }
+ .addComp {
+ padding: 8px 16px;
+ color: blue;
+ font-size: 16px;
+ text-decoration: underline;
+ background-color: #fff;
+ border-top: 1px solid #efefef;
+ border-bottom: 1px solid #efefef;
+ cursor: pointer;
+ }
+ }
+ .right {
+ flex: 1;
+ // clear: both;
+ }
+}
diff --git a/dashboard/src/pages/ApplicationList/CreateApplication/index.jsx b/dashboard/src/pages/ApplicationList/CreateApplication/index.jsx
index 46b02595d..af75a99ea 100644
--- a/dashboard/src/pages/ApplicationList/CreateApplication/index.jsx
+++ b/dashboard/src/pages/ApplicationList/CreateApplication/index.jsx
@@ -52,13 +52,6 @@ class TableList extends React.Component {
};
}
- UNSAFE_componentWillMount() {
- const activeStep = _.get(this.props, 'location.state.activeStep', 0);
- this.setState(() => ({
- current: activeStep,
- }));
- }
-
componentDidMount() {
this.getInitalData();
}
@@ -73,20 +66,6 @@ class TableList extends React.Component {
this.setState({
traitList: traits,
});
- const traitType = _.get(this.props, 'location.state.TraitType', '');
- if (traitType) {
- this.setState({
- availableTraitList: traits,
- traitNum: [
- {
- refname: null,
- initialData: { name: traitType },
- uniq: new Date().valueOf(),
- },
- ],
- });
- }
-
if (Array.isArray(res) && res.length) {
this.setState(
() => ({
@@ -94,7 +73,13 @@ class TableList extends React.Component {
}),
() => {
if (this.state.current === 0) {
- const WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
+ let WorkloadType = '';
+ if (this.props.location.state) {
+ WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
+ sessionStorage.setItem('WorkloadType', WorkloadType);
+ } else {
+ WorkloadType = sessionStorage.getItem('WorkloadType');
+ }
this.formRefStep1.current.setFieldsValue({
workload_type: WorkloadType || this.state.workloadList[0].name,
});
diff --git a/dashboard/src/pages/ApplicationList/CreateComponent/index.jsx b/dashboard/src/pages/ApplicationList/CreateComponent/index.jsx
new file mode 100644
index 000000000..a33fa50f7
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/CreateComponent/index.jsx
@@ -0,0 +1,642 @@
+import React, { Fragment } from 'react';
+import { PageContainer } from '@ant-design/pro-layout';
+import './index.less';
+import { Button, Row, Col, Form, Input, Select, Steps, message, Breadcrumb } from 'antd';
+import { connect } from 'dva';
+import { Link } from 'umi';
+import _ from 'lodash';
+import CreateTraitItem from '../createTrait/index.jsx';
+
+const { Option } = Select;
+const { Step } = Steps;
+
+const layout = {
+ labelCol: {
+ span: 8,
+ },
+ wrapperCol: {
+ span: 16,
+ },
+};
+
+@connect(({ loading, globalData }) => ({
+ loadingAll: loading.models.workload,
+ currentEnv: globalData.currentEnv,
+}))
+class TableList extends React.Component {
+ formRefStep1 = React.createRef();
+
+ formRefStep2All = React.createRef();
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ current: 0,
+ isShowMore: false,
+ traitNum: [
+ {
+ refname: null,
+ initialData: {},
+ uniq: new Date().valueOf(),
+ },
+ ],
+ traitList: [],
+ availableTraitList: [],
+ workloadList: [],
+ workloadSettings: [],
+ step1SubmitObj: {},
+ step1InitialValues: {
+ workload_type: '',
+ },
+ step1Settings: [],
+ appName: '',
+ envName: '',
+ };
+ }
+
+ componentDidMount() {
+ this.getInitalData();
+ }
+
+ getInitalData = async () => {
+ let appName = '';
+ let envName = '';
+ if (this.props.location.state) {
+ appName = _.get(this.props, 'location.state.appName', '');
+ envName = _.get(this.props, 'location.state.envName', '');
+ sessionStorage.setItem('appName', appName);
+ sessionStorage.setItem('envName', envName);
+ } else {
+ appName = sessionStorage.getItem('appName');
+ envName = sessionStorage.getItem('envName');
+ }
+ this.setState({
+ appName,
+ envName,
+ });
+ const res = await this.props.dispatch({
+ type: 'workload/getWorkload',
+ });
+ const traits = await this.props.dispatch({
+ type: 'trait/getTraits',
+ });
+ this.setState({
+ traitList: traits,
+ });
+ if (Array.isArray(res) && res.length) {
+ this.setState(
+ () => ({
+ workloadList: res,
+ }),
+ () => {
+ if (this.state.current === 0) {
+ let WorkloadType = '';
+ if (this.props.location.state) {
+ WorkloadType = _.get(this.props, 'location.state.WorkloadType', '');
+ sessionStorage.setItem('WorkloadType', WorkloadType);
+ } else {
+ WorkloadType = sessionStorage.getItem('WorkloadType');
+ }
+ this.formRefStep1.current.setFieldsValue({
+ workload_type: WorkloadType || this.state.workloadList[0].name,
+ });
+ this.workloadTypeChange(this.state.workloadList[0].name);
+ }
+ },
+ );
+ }
+ };
+
+ onFinishStep1 = (values) => {
+ this.setState({
+ current: 1,
+ step1InitialValues: values,
+ isShowMore: false,
+ });
+ };
+
+ onFinishStep2 = async () => {
+ const asyncValidateArray = [];
+ this.state.traitNum.forEach((item) => {
+ asyncValidateArray.push(item.refname.validateFields());
+ });
+ await Promise.all(asyncValidateArray);
+ const newTraitNum = this.state.traitNum.map((item) => {
+ // eslint-disable-next-line no-param-reassign
+ item.initialData = item.refname.getSelectValue();
+ return item;
+ });
+ // 进行trait数据整理,便于第三步展示
+ this.setState(() => ({
+ traitNum: newTraitNum,
+ current: 2,
+ }));
+ };
+
+ gotoStep2 = () => {
+ this.setState({
+ current: 1,
+ isShowMore: false,
+ });
+ };
+
+ gotoStep1 = () => {
+ this.setState({
+ current: 0,
+ });
+ };
+
+ changeShowMore = () => {
+ this.setState({
+ isShowMore: true,
+ });
+ };
+
+ addMore = (e) => {
+ e.preventDefault();
+ this.setState((prev) => ({
+ traitNum: prev.traitNum.concat([
+ {
+ refname: null,
+ initialData: {},
+ uniq: new Date().valueOf(),
+ },
+ ]),
+ }));
+ };
+
+ createApp = async () => {
+ const { traitNum } = this.state;
+ const { step1SubmitObj } = this.state;
+ if (step1SubmitObj.env_name !== this.props.currentEnv) {
+ step1SubmitObj.env_name = this.props.currentEnv;
+ }
+ const submitObj = _.cloneDeep(step1SubmitObj);
+ const { workload_name: workloadName } = step1SubmitObj;
+ submitObj.flags.push({
+ name: 'name',
+ value: workloadName.toString(),
+ });
+ // 处理数据为提交的格式
+ if (traitNum.length) {
+ const { env_name: envName } = step1SubmitObj;
+ const step2SubmitObj = [];
+ traitNum.forEach(({ initialData }) => {
+ if (initialData.name) {
+ const initialObj = {
+ name: initialData.name,
+ env_name: envName,
+ workload_name: workloadName,
+ flags: [],
+ };
+ Object.keys(initialData).forEach((key) => {
+ if (key !== 'name' && initialData[key]) {
+ initialObj.flags.push({
+ name: key,
+ value: initialData[key].toString(),
+ });
+ }
+ });
+ step2SubmitObj.push(initialObj);
+ }
+ });
+ submitObj.traits = step2SubmitObj;
+ }
+ const res = await this.props.dispatch({
+ type: 'workload/createWorkload',
+ payload: {
+ params: submitObj,
+ },
+ });
+ if (res) {
+ message.success(res);
+ const { appName, envName } = this.state;
+ this.props.history.push({
+ pathname: `/ApplicationList/${appName}/Components`,
+ state: { appName, envName },
+ });
+ }
+ };
+
+ createWorkload = async () => {
+ await this.formRefStep1.current.validateFields();
+ const currentData = this.formRefStep1.current.getFieldsValue();
+ const submitObj = {
+ env_name: this.props.currentEnv,
+ workload_type: currentData.workload_type,
+ workload_name: currentData.workload_name,
+ flags: [],
+ };
+ Object.keys(currentData).forEach((key) => {
+ if (key !== 'workload_name' && key !== 'workload_type' && currentData[key]) {
+ submitObj.flags.push({
+ name: key,
+ value: currentData[key].toString(),
+ });
+ }
+ });
+ this.setState({
+ current: 1,
+ step1InitialValues: currentData,
+ step1Settings: submitObj.flags,
+ step1SubmitObj: submitObj,
+ });
+ this.getAcceptTrait(currentData.workload_type);
+ };
+
+ workloadTypeChange = (value) => {
+ const content = this.formRefStep1.current.getFieldsValue();
+ this.formRefStep1.current.resetFields();
+ const initialObj = {
+ workload_type: content.workload_type,
+ workload_name: content.workload_name,
+ };
+ this.formRefStep1.current.setFieldsValue(initialObj);
+ const currentWorkloadSetting = this.state.workloadList.filter((item) => {
+ return item.name === value;
+ });
+ if (currentWorkloadSetting.length) {
+ this.setState(
+ {
+ workloadSettings: currentWorkloadSetting[0].parameters,
+ },
+ () => {
+ this.state.workloadSettings.forEach((item) => {
+ if (item.default) {
+ initialObj[item.name] = item.default;
+ }
+ });
+ this.formRefStep1.current.setFieldsValue(initialObj);
+ },
+ );
+ }
+ this.setState({
+ traitNum: [
+ {
+ refname: null,
+ initialData: {},
+ uniq: new Date().valueOf(),
+ },
+ ],
+ });
+ };
+
+ getAcceptTrait = (workloadType) => {
+ const res = this.state.traitList.filter((item) => {
+ if (item.appliesTo.indexOf(workloadType) !== -1) {
+ return true;
+ }
+ return false;
+ });
+ this.setState(() => ({
+ availableTraitList: res,
+ }));
+ };
+
+ deleteTraitItem = (uniq) => {
+ // 删除的时候不要依据数组的index删除,要一个唯一性的值
+ this.state.traitNum = this.state.traitNum.filter((item) => {
+ return item.uniq !== uniq;
+ });
+ this.setState((prev) => ({
+ traitNum: prev.traitNum,
+ }));
+ };
+
+ render() {
+ const { appName, envName } = this.state;
+ const { current, step1InitialValues, traitNum, workloadSettings } = this.state;
+ let { workloadList } = this.state;
+ workloadList = Array.isArray(workloadList) ? workloadList : [];
+ let currentDetail;
+ if (current === 0) {
+ currentDetail = (
+
+
+
+
+
?
+ {Array.isArray(workloadSettings) && workloadSettings.length ? (
+ workloadSettings.map((item) => {
+ if (item.name === 'name') {
+ return
;
+ }
+ return item.type === 4 ? (
+
+
+
+ ) : (
+
+
+
+ );
+ })
+ ) : (
+ <>>
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+ } else if (current === 1) {
+ currentDetail = (
+
+
+
+
+ Name:{step1InitialValues.workload_name}
+
+
+
+
{step1InitialValues.workload_type}
+
+ apps/v1
+
+ more...
+
+
+ {this.state.isShowMore ? (
+
+
+ Settings:
+
+
+ {this.state.step1Settings.map((item) => {
+ return (
+
+
+ {item.name}:
+
+
+ {item.value}
+
+
+ );
+ })}
+
+
+ ) : (
+ ''
+ )}
+
+
+ {traitNum.map((item) => {
+ return (
+ {
+ // eslint-disable-next-line no-param-reassign
+ item.refname = ref;
+ }}
+ key={item.uniq.toString()}
+ availableTraitList={this.state.availableTraitList}
+ uniq={item.uniq}
+ initialValues={item.initialData}
+ deleteTraitItem={this.deleteTraitItem}
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+ );
+ } else {
+ currentDetail = (
+
+
+
+ Name:{step1InitialValues.workload_name}
+
+
+
+
+
+
+ {step1InitialValues.workload_type}
+ apps/v1
+
+
+
Settings:
+
+ {this.state.step1Settings.map((item) => {
+ return (
+
+
+ {item.name}:
+
+
+ {item.value}
+
+
+ );
+ })}
+
+
+
+
+
+ {traitNum.map(({ initialData }, index) => {
+ if (initialData.name) {
+ return (
+
+
+
+ {initialData.name}
+ core.oam.dev/v1alpha2
+
+
+
Properties:
+
+ {Object.keys(initialData).map((currentKey) => {
+ if (currentKey !== 'name') {
+ return (
+
+
+ {currentKey}:
+
+
+ {initialData[currentKey]}
+
+
+ );
+ }
+ return ;
+ })}
+
+
+ );
+ }
+ return ;
+ })}
+
+
+
+
+
+
+
+
+ );
+ }
+ return (
+
+
+
+
+ Home
+
+
+ Applications
+
+
+
+ {' '}
+ {appName}
+
+
+ createComponent
+
+
+
+
+
+
+
+
+
+ {currentDetail}
+
+
+
+ );
+ }
+}
+
+export default TableList;
diff --git a/dashboard/src/pages/ApplicationList/CreateComponent/index.less b/dashboard/src/pages/ApplicationList/CreateComponent/index.less
new file mode 100644
index 000000000..9140cc13d
--- /dev/null
+++ b/dashboard/src/pages/ApplicationList/CreateComponent/index.less
@@ -0,0 +1,66 @@
+.create-container {
+ padding: 10px;
+ background: #fff;
+}
+.create-app {
+ .minBox {
+ margin: 20px 10px;
+ padding: 10px;
+ border: 1px solid #eee;
+ .title {
+ font-size: 16px;
+ line-height: 36px;
+ }
+ }
+ .buttonBox {
+ clear: both;
+ height: 42px;
+ margin: 0 10px;
+ padding: 10px;
+ }
+ .floatRight {
+ float: right;
+ }
+ .floatRightGap {
+ float: right;
+ margin-right: 16px;
+ }
+ .relativeBox {
+ position: relative;
+ padding: 0 48px 0 16px;
+ .hasMore {
+ position: absolute;
+ top: 0;
+ right: 16px;
+ padding: 4px;
+ color: rgb(24, 144, 255);
+ font-size: 16px;
+ cursor: pointer;
+ }
+ }
+ .summaryBox1,
+ .summaryBox2,
+ .summaryBox {
+ clear: both;
+ margin-bottom: 20px;
+ padding: 0 10px 10px;
+ p {
+ margin: 0;
+ font-size: 12px;
+ line-height: 20px;
+ }
+ }
+ .summaryBox1 {
+ color: #fff;
+ background: rgb(24, 144, 255);
+ }
+ .summaryBox2 {
+ background: yellow;
+ }
+ .summaryBox {
+ border: 1px solid black;
+ }
+ .hasMargin {
+ padding: 8px 0;
+ }
+}
diff --git a/dashboard/src/pages/ApplicationList/index.jsx b/dashboard/src/pages/ApplicationList/index.jsx
index 60dfabb0a..512fea3c4 100644
--- a/dashboard/src/pages/ApplicationList/index.jsx
+++ b/dashboard/src/pages/ApplicationList/index.jsx
@@ -1,21 +1,34 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { BranchesOutlined, ApartmentOutlined } from '@ant-design/icons';
-import { Button, Card, Row, Col, Form, Spin, Empty, Breadcrumb } from 'antd';
+import { Button, Card, Row, Col, Form, Spin, Empty, Breadcrumb, Modal, Input } from 'antd';
import { connect } from 'dva';
import moment from 'moment';
import './index.less';
import { Link } from 'umi';
+const layout = {
+ labelCol: {
+ span: 6,
+ },
+ wrapperCol: {
+ span: 18,
+ },
+};
+
@connect(({ loading, applist, globalData }) => ({
loadingAll: loading.models.applist,
returnObj: applist.returnObj,
currentEnv: globalData.currentEnv,
}))
class TableList extends React.Component {
+ formRef = React.createRef();
+
constructor(props) {
super(props);
- this.state = {};
+ this.state = {
+ visible: false,
+ };
}
componentDidMount() {
@@ -43,6 +56,23 @@ class TableList extends React.Component {
return true;
}
+ showModal = () => {
+ this.setState({
+ visible: true,
+ });
+ };
+
+ handleOk = async () => {
+ await this.formRef.current.validateFields();
+ // const submitData = await this.formRef.current.validateFields();
+ };
+
+ handleCancel = () => {
+ this.setState({
+ visible: false,
+ });
+ };
+
onFinish = () => {};
handleChange = () => {};
@@ -153,6 +183,40 @@ class TableList extends React.Component {
)}
+
+ Create
+ ,
+ ]}
+ >
+
+
+
+
+
+
+
+
);
diff --git a/dashboard/src/pages/Capability/Detail/index.jsx b/dashboard/src/pages/Capability/Detail/index.jsx
index d3811cba9..fc496a9ff 100644
--- a/dashboard/src/pages/Capability/Detail/index.jsx
+++ b/dashboard/src/pages/Capability/Detail/index.jsx
@@ -1,11 +1,14 @@
import React, { Fragment } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
-import { Space, Button, Row, Col, message, Spin, Breadcrumb } from 'antd';
+import { Space, Button, Row, Col, message, Spin, Breadcrumb, Modal } from 'antd';
import { Link } from 'umi';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
+const { confirm } = Modal;
+
@connect(({ loading, globalData }) => ({
loadingAll: loading.models.capability,
currentEnv: globalData.currentEnv,
@@ -16,6 +19,7 @@ class TableList extends React.PureComponent {
this.state = {
workloadList: [],
traitList: [],
+ capabilityCenterName: '',
};
}
@@ -31,7 +35,16 @@ class TableList extends React.PureComponent {
const workloadList = [];
const traitList = [];
if (Array.isArray(res)) {
- const capabilityCenterName = _.get(this.props, 'location.state.name', '');
+ let capabilityCenterName = '';
+ if (this.props.location.state) {
+ capabilityCenterName = _.get(this.props, 'location.state.name', '');
+ sessionStorage.setItem('capabilityCenterName', capabilityCenterName);
+ } else {
+ capabilityCenterName = sessionStorage.getItem('capabilityCenterName');
+ }
+ this.setState({
+ capabilityCenterName,
+ });
res.forEach((item) => {
if (item.center === capabilityCenterName) {
if (item.type === 'workload') {
@@ -55,7 +68,7 @@ class TableList extends React.PureComponent {
installSignle = async (e, name) => {
e.stopPropagation();
- const capabilityCenterName = _.get(this.props, 'location.state.name', '');
+ const { capabilityCenterName } = this.state;
const res = await this.props.dispatch({
type: 'capability/syncOneCapability',
payload: {
@@ -65,7 +78,10 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
- window.location.reload();
+ this.getInitialData();
+ await this.props.dispatch({
+ type: 'menus/getMenuData',
+ });
}
};
@@ -80,13 +96,16 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
- window.location.reload();
+ this.getInitialData();
+ await this.props.dispatch({
+ type: 'menus/getMenuData',
+ });
}
}
};
syncAllSignle = async () => {
- const capabilityCenterName = _.get(this.props, 'location.state.name', '');
+ const { capabilityCenterName } = this.state;
if (capabilityCenterName) {
const res = await this.props.dispatch({
type: 'capability/syncCapability',
@@ -96,13 +115,51 @@ class TableList extends React.PureComponent {
});
if (res) {
message.success(res);
- window.location.reload();
+ this.getInitialData();
+ await this.props.dispatch({
+ type: 'menus/getMenuData',
+ });
}
}
};
showDeleteConfirm = () => {
- message.info('正在开发中...');
+ // eslint-disable-next-line
+ const _this = this;
+ const { capabilityCenterName } = this.state;
+ if (capabilityCenterName) {
+ confirm({
+ title: `Are you sure delete ${capabilityCenterName}?`,
+ icon: ,
+ width: 500,
+ content: (
+
+
您本次移除 {capabilityCenterName},将会删除的应用列表:
+
+ 确认后,移除 {capabilityCenterName},并且删除相应的应用?
+
+
+ ),
+ okText: 'Yes',
+ okType: 'danger',
+ cancelText: 'No',
+ async onOk() {
+ const res = await _this.props.dispatch({
+ type: 'capability/deleteCapability',
+ payload: {
+ capabilityCenterName,
+ },
+ });
+ if (res) {
+ message.success(res);
+ _this.props.history.push({ pathname: '/Capability' });
+ }
+ },
+ onCancel() {
+ // console.log('Cancel');
+ },
+ });
+ }
};
render() {
diff --git a/dashboard/src/pages/Capability/index.jsx b/dashboard/src/pages/Capability/index.jsx
index 31e1a0001..b5da9ee7e 100644
--- a/dashboard/src/pages/Capability/index.jsx
+++ b/dashboard/src/pages/Capability/index.jsx
@@ -1,13 +1,14 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Table, Space, Modal, Form, Input, message, Spin, Breadcrumb } from 'antd';
-import { CopyOutlined } from '@ant-design/icons';
+import { CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Link } from 'umi';
import './index.less';
import { connect } from 'dva';
import _ from 'lodash';
const { Column } = Table;
+const { confirm } = Modal;
const layout = {
labelCol: {
@@ -31,6 +32,7 @@ class TableList extends React.PureComponent {
this.state = {
visible: false,
capabilityList: [],
+ isCreateLoading: false,
};
}
@@ -55,14 +57,19 @@ class TableList extends React.PureComponent {
}
};
- showModal = () => {
- this.setState({
+ showModal = async () => {
+ await this.setState({
visible: true,
+ isCreateLoading: false,
});
+ this.formRef.current.resetFields();
};
handleOk = async () => {
const submitData = await this.formRef.current.validateFields();
+ this.setState({
+ isCreateLoading: true,
+ });
const res = await this.props.dispatch({
type: 'capability/createCapabilityCenter',
payload: {
@@ -111,7 +118,9 @@ class TableList extends React.PureComponent {
this.setState(() => ({
capabilityList: newList1,
}));
- window.location.reload();
+ await this.props.dispatch({
+ type: 'menus/getMenuData',
+ });
}
};
@@ -126,8 +135,40 @@ class TableList extends React.PureComponent {
message.success('copy success');
};
- showDeleteConfirm = () => {
- message.info('正在开发中...');
+ showDeleteConfirm = (record) => {
+ if (record) {
+ // eslint-disable-next-line
+ const _this = this;
+ confirm({
+ title: `Are you sure delete ${record}?`,
+ icon: ,
+ width: 500,
+ content: (
+
+
您本次移除 {record},将会删除的应用列表:
+
确认后,移除{record},并且删除相应的应用?
+
+ ),
+ okText: 'Yes',
+ okType: 'danger',
+ cancelText: 'No',
+ async onOk() {
+ const res = await _this.props.dispatch({
+ type: 'capability/deleteCapability',
+ payload: {
+ capabilityCenterName: record,
+ },
+ });
+ if (res) {
+ message.success(res);
+ _this.getInitialData();
+ }
+ },
+ onCancel() {
+ // console.log('Cancel');
+ },
+ });
+ }
};
render() {
@@ -135,6 +176,7 @@ class TableList extends React.PureComponent {
let { loadingList } = this.props;
loadingList = loadingList || false;
capabilityList = Array.isArray(capabilityList) ? capabilityList : [];
+ const { isCreateLoading } = this.state;
return (
@@ -160,7 +202,12 @@ class TableList extends React.PureComponent {
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
-