diff --git a/dashboard/config/config.js b/dashboard/config/config.js index 69ff35cc6..eb34b8d97 100644 --- a/dashboard/config/config.js +++ b/dashboard/config/config.js @@ -121,6 +121,19 @@ export default defineConfig({ path: '/Capability', component: './Capability', }, + { + path: '/System', + name: 'System', + icon: 'table', + routes: [ + { + name: 'Env', + icon: 'table', + path: '/System/Env', + component: './System/Env', + }, + ], + }, { name: 'Capability.Detail', hideInMenu: true, diff --git a/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.css b/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.css new file mode 100644 index 000000000..fa0c32508 --- /dev/null +++ b/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.css @@ -0,0 +1,96 @@ +.ant-pro-global-header-layout-side { + padding-right: 0; +} +.ant-dropdown-menu-item, +.ant-dropdown-menu-submenu-title { + clear: both; + margin: 0; + padding: 5px 20px; + color: deepskyblue; + font-weight: normal; + font-size: 14px; + line-height: 22px; + white-space: nowrap; + cursor: pointer; + transition: all 0.3s; + border-top: 1px solid #dee4e6; +} + +.ant-dropdown-menu-item:nth-child(1) { + border-top: 1px solid #fff; +} + +.ant-dropdown-menu-item-active { + background: rgb(0, 21, 41) !important; +} + +.ant-dropdown-menu-item-active .box1 { + color: white; +} + +.ant-dropdown-menu-item-active .box2 { + color: white; +} + +.box { + display: flex; + width: 100%; + flex-direction: column; +} + +.box1 { + font-size: 16px; + color: #5095d4; +} + +.box2 { + font-size: 12px; + color: #8d959b; +} + +.ant-dropdown-trigger { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 100%; +} + +.drop-box { + display: flex; + flex-direction: row; + align-items: center; + justify-content: left; + height: 100%; + background: rgb(0, 21, 41); + padding: 0 20px; +} + +.btn-box { + display: flex; + flex-direction: column; + align-items: space-between; + overflow: hidden; + margin-right: 20px; + min-width: 120px; +} + +.btn-top { + display: flex; + height: 20px; + line-height: 20px; + font-size: 20px; + color: white; +} + +.btn-bottom { + display: flex; + height: 20px; + line-height: 20px; + font-size: 12px; + color: #8d959b; +} +.btn-outlined { + fontsize: 15px; + color: #fff; +} diff --git a/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.jsx b/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.jsx index 7f1e364ec..e8659219b 100644 --- a/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.jsx +++ b/dashboard/src/components/GlobalHeader/WorkSpaceDropDown.jsx @@ -1,29 +1,32 @@ -import { Menu, Dropdown, Button, message } from 'antd'; +import { Menu, Dropdown, message } from 'antd'; import { DownOutlined } from '@ant-design/icons'; import React from 'react'; import { connect } from 'dva'; +import './WorkSpaceDropDown.css'; -@connect(() => ({})) +@connect((env) => ({ envs: env.envs })) export default class WorkSpaceDropDown extends React.Component { constructor(props) { super(props); this.state = { workSpaceName: '', envs: [], + namespace: '', }; } async componentDidMount() { const envs = await this.props.dispatch({ - type: 'envs/getEnvs', // applist对应models层的命名空间namespace + type: 'envs/getEnvs', }); if (envs) { - const { name = 'test' } = envs.find((a) => { - return a.current === '*'; + const { name, namespace } = envs.find((env) => { + return env.current === '*'; }); this.setState({ - envs, + envs: envs, workSpaceName: name, + namespace: namespace, }); this.props.dispatch({ type: 'globalData/currentEnv', @@ -48,6 +51,7 @@ export default class WorkSpaceDropDown extends React.Component { this.setState( { workSpaceName: e.key, + namespace: e.item.props.title, }, () => { // 值切换存储 @@ -59,25 +63,37 @@ export default class WorkSpaceDropDown extends React.Component { }); }, ); + await this.props.dispatch({ + type: 'envs/getEnvs', // applist对应models层的命名空间namespace + }); }; render() { + const { envs } = this.props; const menu = ( - {/* default - oam-system - linkerd - rio-system */} - {this.state.envs.map((item) => { - return {item.name}; - })} + {envs.envs && + envs.envs.map((item) => { + return ( + +
+
{item.name}
+
{item.namespace}
+
+
+ ); + })}
); return ( - +
+
+
{this.state.workSpaceName}
+
{this.state.namespace}
+
+ +
); } diff --git a/dashboard/src/locales/en-US/menu.js b/dashboard/src/locales/en-US/menu.js index 93bdb0d66..14a048c8b 100644 --- a/dashboard/src/locales/en-US/menu.js +++ b/dashboard/src/locales/en-US/menu.js @@ -11,6 +11,8 @@ export default { 'menu.ApplicationList.CreateApplication': 'CreateApplication', 'menu.Capability': 'Capability', 'menu.Capability.Detail': 'Detail', + 'menu.System': 'System', + 'menu.System.Env': 'Env', 'menu.Workload': 'Workloads', 'menu.Workload.Deployment': 'Deployment', 'menu.Workload.Containerized': 'Containerized', diff --git a/dashboard/src/models/env.js b/dashboard/src/models/env.js index 938b8fd5f..9753deac4 100644 --- a/dashboard/src/models/env.js +++ b/dashboard/src/models/env.js @@ -1,20 +1,49 @@ -import { getEnvs, switchEnv } from '@/services/env'; +import { getEnvs, switchEnv, initialEnvs, deleteEnv } from '@/services/env'; const TestModel = { namespace: 'envs', state: { - // initailState: '8880' + envs: undefined, }, effects: { - *getEnvs({ payload }, { call }) { + *getEnvs({ payload }, { call, put }) { const res = yield call(getEnvs, payload); + yield put({ + type: 'onGetEnvsSuccess', + payload: res, + }); return res; }, *switchEnv({ payload }, { call }) { const res = yield call(switchEnv, payload); return res; }, + *initialEnvs({ payload }, { call, put }) { + yield call(initialEnvs, payload); + const res = yield call(getEnvs, payload); + yield put({ + type: 'onGetEnvsSuccess', + payload: res, + }); + return res; + }, + *deleteEnv({ payload }, { call, put }) { + yield call(deleteEnv, payload); + const res = yield call(getEnvs); + yield put({ + type: 'onGetEnvsSuccess', + payload: res, + }); + return res; + }, + }, + reducers: { + onGetEnvsSuccess(state, { payload }) { + return { + ...state, + envs: payload, + }; + }, }, - reducers: {}, }; export default TestModel; diff --git a/dashboard/src/pages/System/Env/index.jsx b/dashboard/src/pages/System/Env/index.jsx new file mode 100644 index 000000000..c146bffcd --- /dev/null +++ b/dashboard/src/pages/System/Env/index.jsx @@ -0,0 +1,204 @@ +import React, { useEffect, useState } from 'react'; +import { PageContainer } from '@ant-design/pro-layout'; +import { Button, Table, Space, Modal, Form, Input, Tooltip } from 'antd'; +import { ExclamationCircleOutlined } from '@ant-design/icons'; +import './index.less'; +import { connect } from 'dva'; +import * as _ from 'lodash'; + +const { confirm } = Modal; + +const layout = { + labelCol: { + span: 6, + }, + wrapperCol: { + span: 18, + }, +}; + +const TableList = (props) => { + const { dispatch } = props; + const tableEnvs = _.get(props, 'getEnvs.envs', []); + const [form] = Form.useForm(); + const [visible, setVisible] = useState(false); + const [env, setEnv] = useState([]); + + const showModal = () => { + setVisible(true); + }; + + const handleOk = async () => { + const fieldsValue = await form.validateFields(); + await dispatch({ + type: 'envs/initialEnvs', + payload: { + params: fieldsValue, + }, + }); + setEnv(null); + form.resetFields(); + setVisible(false); + }; + + const handleCancel = () => { + setEnv(null); + setVisible(false); + form.resetFields(); + }; + + const deleteEnv = async (record) => { + await dispatch({ + type: 'envs/deleteEnv', + payload: { + envName: record.name, + }, + }); + }; + + const showDeleteConfirm = (record) => { + confirm({ + title: `Are you sure delete env ${record.name}?`, + icon: , + width: 500, + okText: 'Yes', + okType: 'danger', + cancelText: 'No', + onOk() { + deleteEnv(record); + }, + }); + }; + + const specifyNamespace = (record) => { + form.setFieldsValue({ + name: record.name, + namespace: record.namespace, + }); + setEnv(record); + setVisible(true); + }; + + const getInitialData = async () => { + await dispatch({ + type: 'envs/getEnvs', + }); + }; + useEffect(() => { + getInitialData(); + }, []); + + const columns = [ + { + title: 'Env', + dataIndex: 'name', + key: 'name', + render: (text) => { + if (text.length > 20) { + return {text.substr(0, 20)}...; + } + return text; + }, + }, + { + title: 'Namespace', + dataIndex: 'namespace', + key: 'namespace', + render: (text) => { + if (text.length > 20) { + return {text.substr(0, 20)}...; + } + return text; + }, + }, + { + title: 'Current', + dataIndex: 'current', + align: 'center', + key: 'current', + render: (text) => { + return text === '*' ? 'active' : ''; + }, + }, + { + title: 'Operations', + dataIndex: 'Operations', + key: 'Operations', + render: (text, record) => { + return ( + + + + + ); + }, + }, + ]; + return ( + +
+ + + +
+ + {env && env.name ? 'Update' : 'Create'} + , + ]} + > +
+ + + + + + +
+
+ record.name} columns={columns} dataSource={tableEnvs} /> + + ); +}; +export default connect((env) => { + return { + getEnvs: env.envs, + }; +})(TableList); diff --git a/dashboard/src/pages/System/Env/index.less b/dashboard/src/pages/System/Env/index.less new file mode 100644 index 000000000..44a919ec1 --- /dev/null +++ b/dashboard/src/pages/System/Env/index.less @@ -0,0 +1,3 @@ +p { + margin: 0; +} diff --git a/dashboard/src/services/env.js b/dashboard/src/services/env.js index 7e091aced..7967b52e4 100644 --- a/dashboard/src/services/env.js +++ b/dashboard/src/services/env.js @@ -1,24 +1,34 @@ import request from '@/utils/request'; + /* * 初始化 env:Post /api/envs/ */ -export async function initialEnvs() { +export async function initialEnvs({ params }) { return request('/api/envs/', { method: 'post', + data: { + ...params, + }, + headers: { + 'Content-Type': 'application/json', + }, }); } + /* * env 列表:Get /api/envs/ */ export async function getEnvs() { return request('/api/envs/'); } + /* * 查询 env:Get /api/envs/:envName */ export async function searchEnvs({ envName }) { return request(`/api/envs/${envName}`); } + /* * 删除 env:Delete /api/envs/:envName */ @@ -27,6 +37,7 @@ export async function deleteEnv({ envName }) { method: 'delete', }); } + /* * 切换 env:Patch /api/envs/:envName */