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 = (
);
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
*/