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} + +
+ +
+
+ + + a1(containerized) + c2(deploy) + c3(webserver) + + +
+ + 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={[ - , ]} diff --git a/dashboard/src/pages/System/Env/index.jsx b/dashboard/src/pages/System/Env/index.jsx index 7d4c6d716..47f19d217 100644 --- a/dashboard/src/pages/System/Env/index.jsx +++ b/dashboard/src/pages/System/Env/index.jsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; -import { Button, Table, Space, Modal, Form, Input, Tooltip } from 'antd'; +import { Button, Table, Space, Modal, Form, Input, Tooltip, Breadcrumb } from 'antd'; import { ExclamationCircleOutlined } from '@ant-design/icons'; import './index.less'; import { connect } from 'dva'; +import { Link } from 'umi'; import * as _ from 'lodash'; const { confirm } = Modal; @@ -147,65 +148,76 @@ const TableList = (props) => { }, ]; return ( - -
- - - +
+
+ + + Home + + System + Env +
- - {env && env.envName ? 'Update' : 'Create'} - , - ]} - > -
- - - - - - -
-
- record.envName} columns={columns} dataSource={tableEnvs} /> - + +
+ + + +
+ + {env && env.envName ? 'Update' : 'Create'} + , + ]} + > +
+ + + + + + + +
+
record.envName} columns={columns} dataSource={tableEnvs} /> + + ); }; export default connect((env) => { diff --git a/dashboard/src/pages/Traits/Detail/index.jsx b/dashboard/src/pages/Traits/Detail/index.jsx index 9ccfe9eac..39544fcc3 100644 --- a/dashboard/src/pages/Traits/Detail/index.jsx +++ b/dashboard/src/pages/Traits/Detail/index.jsx @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; import './index.less'; -import { Form, Input, Button, Row, Col, Tabs, Table, Breadcrumb } from 'antd'; +import { Form, Input, Button, Row, Col, Tabs, Table, Breadcrumb, Space } from 'antd'; import { CheckCircleOutlined } from '@ant-design/icons'; import _ from 'lodash'; import { connect } from 'dva'; @@ -61,6 +61,17 @@ const columns = [ dataIndex: 'Age', key: 'Age', }, + { + title: 'Action', + dataIndex: 'name', + key: 'name', + render: () => ( + + + + + ), + }, ]; const data = [ @@ -144,6 +155,7 @@ class TableList extends React.Component { hasShowEdit2: false, traitItem: {}, appName: '', + envName: '', appDetailData: {}, }; } @@ -153,16 +165,27 @@ class TableList extends React.Component { } async getInitialData() { - const traitItem = _.get(this.props, 'location.state.traitItem', {}); + let traitItem = ''; + let appName = ''; + let envName = ''; + if (this.props.location.state) { + traitItem = _.get(this.props, 'location.state.traitItem', ''); + appName = _.get(this.props, 'location.state.appName', ''); + envName = _.get(this.props, 'location.state.envName', ''); + sessionStorage.setItem('traitItem', traitItem); + sessionStorage.setItem('appName', appName); + sessionStorage.setItem('envName', envName); + } else { + traitItem = sessionStorage.getItem('traitItem'); + appName = sessionStorage.getItem('appName'); + envName = sessionStorage.getItem('envName'); + } this.setState({ traitItem, + appName, + envName, }); - const appName = _.get(this.props, 'location.state.appName', ''); - const envName = _.get(this.props, 'location.state.envName', ''); if (appName && envName) { - this.setState({ - appName, - }); const res = await this.props.dispatch({ type: 'applist/getAppDetail', payload: { @@ -203,7 +226,7 @@ class TableList extends React.Component { }; render() { - const { hasShowEdit, hasShowEdit2 } = this.state; + const { hasShowEdit, hasShowEdit2, envName } = this.state; const status = _.get(this.state.appDetailData, 'Status', ''); const { traitItem, appName } = this.state; const metadata = _.get(traitItem, 'metadata', ''); @@ -214,7 +237,6 @@ class TableList extends React.Component { if (traitItem.kind === 'Ingress') { traitType = 2; } - const envName = _.get(this.props, 'location.state.envName', ''); return (
diff --git a/dashboard/src/pages/Workload/Detail/index.jsx b/dashboard/src/pages/Workload/Detail/index.jsx index 114ea0106..d4706c9fd 100644 --- a/dashboard/src/pages/Workload/Detail/index.jsx +++ b/dashboard/src/pages/Workload/Detail/index.jsx @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; import './index.less'; -import { Form, Input, Button, Row, Col, Tabs, Table, Spin, Breadcrumb } from 'antd'; +import { Form, Input, Button, Row, Col, Tabs, Table, Spin, Breadcrumb, Space } from 'antd'; import { CheckCircleOutlined } from '@ant-design/icons'; import { connect } from 'dva'; import { Link } from 'umi'; @@ -61,6 +61,17 @@ const columns = [ dataIndex: 'Age', key: 'Age', }, + { + title: 'Action', + dataIndex: 'name', + key: 'name', + render: () => ( + + + + + ), + }, ]; const data = [ @@ -143,6 +154,8 @@ class TableList extends React.PureComponent { hasShowEdit: false, hasShowEdit2: false, appDetailData: {}, + appName: '', + envName: '', }; } @@ -151,8 +164,21 @@ class TableList extends React.PureComponent { } async getInitialData() { - const appName = _.get(this.props, 'location.state.appName', ''); - const envName = _.get(this.props, 'location.state.envName', ''); + 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) { const res = await this.props.dispatch({ type: 'applist/getAppDetail', @@ -194,7 +220,7 @@ class TableList extends React.PureComponent { }; render() { - const { hasShowEdit, hasShowEdit2 } = this.state; + const { hasShowEdit, hasShowEdit2, appName, envName } = this.state; const status = _.get(this.state.appDetailData, 'Status', ''); const Workload = _.get(this.state.appDetailData, 'Workload.workload', {}); const metadata = _.get(Workload, 'metadata', {}); @@ -202,8 +228,6 @@ class TableList extends React.PureComponent { containers = _.get(Workload, 'spec.containers[0]', {}); let { loadingAll } = this.props; loadingAll = loadingAll || false; - const appName = _.get(this.props, 'location.state.appName', ''); - const envName = _.get(this.props, 'location.state.envName', ''); return (
diff --git a/dashboard/src/services/capability.js b/dashboard/src/services/capability.js index 92531119d..024bc0f17 100644 --- a/dashboard/src/services/capability.js +++ b/dashboard/src/services/capability.js @@ -31,10 +31,10 @@ export async function syncCapability({ capabilityCenterName }) { }); } /* - * Delete /api/capabilities/:capabilityName (删除一个 capability) + * DELETE /capability-centers/:capabilityCenterName/ (Capability Center delete) (删除一个 capabilityCenter) */ -export async function deleteCapability({ capabilityName }) { - return request(`/api/capabilities/${capabilityName}`, { +export async function deleteCapability({ capabilityCenterName }) { + return request(`/api/capability-centers/${capabilityCenterName}`, { method: 'delete', }); }