mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user