Merge pull request #230 from wenxinnnnn/env

Development on env
This commit is contained in:
Sun Jianbo
2020-08-28 19:13:53 +08:00
committed by GitHub
8 changed files with 395 additions and 21 deletions
+13
View File
@@ -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,
@@ -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;
}
@@ -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 = (
<Menu onClick={this.handleMenuClick}>
{/* <Menu.Item key="default">default</Menu.Item>
<Menu.Item key="am-system">oam-system</Menu.Item>
<Menu.Item key="linkerd">linkerd</Menu.Item>
<Menu.Item key="rio-system">rio-system</Menu.Item> */}
{this.state.envs.map((item) => {
return <Menu.Item key={item.name}>{item.name}</Menu.Item>;
})}
{envs.envs &&
envs.envs.map((item) => {
return (
<Menu.Item key={item.name} title={item.namespace}>
<div className="box">
<div className="box1">{item.name}</div>
<div className="box2">{item.namespace}</div>
</div>
</Menu.Item>
);
})}
</Menu>
);
return (
<Dropdown overlay={menu}>
<Button style={{ marginTop: '10px' }}>
{this.state.workSpaceName} <DownOutlined />
</Button>
<div className="drop-box">
<div className="btn-box">
<div className="btn-top">{this.state.workSpaceName}</div>
<div className="btn-bottom">{this.state.namespace}</div>
</div>
<DownOutlined style={{ fontSize: '15px', color: '#ffffff' }} />
</div>
</Dropdown>
);
}
+2
View File
@@ -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',
+33 -4
View File
@@ -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;
+204
View File
@@ -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: <ExclamationCircleOutlined />,
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 <Tooltip title={text}>{text.substr(0, 20)}...</Tooltip>;
}
return text;
},
},
{
title: 'Namespace',
dataIndex: 'namespace',
key: 'namespace',
render: (text) => {
if (text.length > 20) {
return <Tooltip title={text}>{text.substr(0, 20)}...</Tooltip>;
}
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 (
<Space>
<Button onClick={() => specifyNamespace(record)}>update</Button>
<Button disabled={record.current} onClick={() => showDeleteConfirm(record)}>
remove
</Button>
</Space>
);
},
},
];
return (
<PageContainer>
<div style={{ marginBottom: '16px' }}>
<Space>
<Button type="primary" onClick={showModal}>
Create
</Button>
</Space>
</div>
<Modal
getContainer={false}
title={env && env.name ? 'Update Env' : 'Create Env'}
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="submit" type="primary" onClick={handleOk}>
{env && env.name ? 'Update' : 'Create'}
</Button>,
]}
>
<Form {...layout} form={form} name="control-ref" labelAlign="left">
<Form.Item
name="name"
label="Env"
rules={[
{
required: true,
message: 'Please input Evn!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message: 'Should be combination of numbers,alphabets,underline',
},
]}
>
<Input disabled={!!(env && env.name)} />
</Form.Item>
<Form.Item
name="namespace"
label="Namespace"
rules={[
{
required: true,
message: 'Please specify a Namespace!',
},
{
pattern: new RegExp('^[0-9a-zA-Z_]{1,32}$', 'g'),
message:
'The maximum length is 63, should be combination of numbers,alphabets,underline',
},
]}
>
<Input />
</Form.Item>
</Form>
</Modal>
<Table rowKey={(record) => record.name} columns={columns} dataSource={tableEnvs} />
</PageContainer>
);
};
export default connect((env) => {
return {
getEnvs: env.envs,
};
})(TableList);
@@ -0,0 +1,3 @@
p {
margin: 0;
}
+12 -1
View File
@@ -1,24 +1,34 @@
import request from '@/utils/request';
/*
* 初始化 envPost /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/');
}
/*
* 查询 envGet /api/envs/:envName
*/
export async function searchEnvs({ envName }) {
return request(`/api/envs/${envName}`);
}
/*
* 删除 envDelete /api/envs/:envName
*/
@@ -27,6 +37,7 @@ export async function deleteEnv({ envName }) {
method: 'delete',
});
}
/*
* 切换 envPatch /api/envs/:envName
*/