feat(component-deploy): 实现组件部署列表及新增实例功能

master
钟良源 3 months ago
parent 82ecfe4cf2
commit ececda16a7

@ -0,0 +1,9 @@
import axios from 'axios';
// 公共路径
const urlPrefix = '/api/v1/bpms-workbench';
// 部署列表
export function getDeployList(params) {
return axios.get(`${urlPrefix}/componentDeploy/list`, { params });
}

@ -0,0 +1,280 @@
import React, { useState, useRef, useEffect, useContext, useCallback } from 'react';
import { Button, Table, Input, Select, Form, FormInstance, Switch, DatePicker } from '@arco-design/web-react';
const FormItem = Form.Item;
const EditableContext = React.createContext<{ getForm?: () => FormInstance }>({});
interface EditableTableProps {
columns: ColumnProps[];
data: any[];
onDataChange?: (data: any[]) => void;
showAddButton?: boolean;
addButtonText?: string;
onAdd?: () => void;
showDeleteButton?: boolean;
deleteButtonText?: string;
onDelete?: (key: string) => void;
// Table组件的其他配置属性
tableProps?: any;
// 添加空数据的配置
emptyItem?: any;
}
interface ColumnProps {
title: string;
dataIndex: string;
editable?: boolean;
render?: (text: any, record: any, index: number) => React.ReactNode;
// 用于指定编辑时使用的组件类型
editComponent?: 'input' | 'select' | 'switch' | 'date' | 'custom';
// 用于select组件的选项
options?: { label: string; value: any }[];
// 用于自定义渲染编辑组件
renderEdit?: (props: EditComponentProps) => React.ReactNode;
// 其他列配置属性
[key: string]: any;
}
interface EditComponentProps {
value: any;
onChange: (value: any) => void;
rowData: any;
column: ColumnProps;
}
interface EditableRowProps {
children: React.ReactNode;
record: any;
className: string;
[key: string]: any;
}
interface EditableCellProps {
children: React.ReactNode;
className: string;
rowData: any;
column: ColumnProps;
onHandleSave: (row: any) => void;
}
function EditableRow(props: EditableRowProps) {
const { children, record, className, ...rest } = props;
const refForm = useRef<FormInstance | null>(null);
const getForm = () => refForm.current;
return (
<EditableContext.Provider
value={{
getForm
}}
>
<Form
style={{ display: 'table-row' }}
ref={refForm}
wrapper="tr"
wrapperProps={rest}
className={`${className} editable-row`}
>
{children}
</Form>
</EditableContext.Provider>
);
}
function EditableCell(props: EditableCellProps) {
const { children, className, rowData, column, onHandleSave } = props;
const { getForm } = useContext(EditableContext);
const cellValueChangeHandler = (value: any) => {
const values = {
[column.dataIndex]: value
};
onHandleSave && onHandleSave({ ...rowData, ...values });
};
// 渲染编辑组件
const renderEditComponent = () => {
// 如果有自定义渲染函数,使用它
if (column.renderEdit) {
return column.renderEdit({
value: rowData[column.dataIndex],
onChange: cellValueChangeHandler,
rowData,
column
});
}
// 根据editComponent类型渲染不同的组件
switch (column.editComponent) {
case 'select':
return (
<Select
defaultValue={rowData[column.dataIndex]}
options={column.options || []}
onChange={cellValueChangeHandler}
style={{ width: '100%' }}
/>
);
case 'switch':
return (
<Switch
checked={rowData[column.dataIndex]}
onChange={cellValueChangeHandler}
/>
);
case 'date':
return (
<DatePicker
defaultValue={rowData[column.dataIndex]}
onChange={cellValueChangeHandler}
style={{ width: '100%' }}
/>
);
case 'custom':
// 自定义组件需要通过renderEdit提供
return null;
case 'input':
default:
return (
<Input
defaultValue={rowData[column.dataIndex]}
onChange={(value) => cellValueChangeHandler(value)}
style={{ width: '100%' }}
/>
);
}
};
// 如果列是可编辑的,直接渲染编辑组件
if (column.editable) {
return (
<div>
<FormItem
style={{ marginBottom: 0 }}
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
initialValue={rowData[column.dataIndex]}
field={column.dataIndex}
rules={[{ required: true }]}
>
{renderEditComponent()}
</FormItem>
</div>
);
}
// 非编辑状态直接显示文本
return (
<div className={className}>
{children}
</div>
);
}
const EditableTable: React.FC<EditableTableProps> = ({
columns,
data,
onDataChange,
showAddButton = true,
addButtonText = 'Add',
onAdd,
showDeleteButton = true,
deleteButtonText = 'Delete',
onDelete,
tableProps = {},
emptyItem = {}
}) => {
const [tableData, setTableData] = useState<any[]>(data);
useEffect(() => {
setTableData(data);
}, [data]);
const handleSave = (row: any) => {
const newData = [...tableData];
const index = newData.findIndex((item) => row.key === item.key);
if (index !== -1) {
newData.splice(index, 1, { ...newData[index], ...row });
setTableData(newData);
onDataChange && onDataChange(newData);
}
};
const removeRow = (key: string) => {
const newData = tableData.filter((item) => item.key !== key);
setTableData(newData);
onDataChange && onDataChange(newData);
onDelete && onDelete(key);
};
const addRow = () => {
if (onAdd) {
onAdd();
}
else {
// 添加一条空数据
const newKey = `${Date.now()}`;
const newRow = {
key: newKey,
...emptyItem
};
const newData = [...tableData, newRow];
setTableData(newData);
onDataChange && onDataChange(newData);
}
};
// 处理列配置,添加删除按钮
const processedColumns = [...columns];
if (showDeleteButton && !columns.some(col => col.dataIndex === 'op')) {
processedColumns.push({
title: 'Operation',
dataIndex: 'op',
render: (_: any, record: any) => (
<Button onClick={() => removeRow(record.key)} type="primary" status="danger">
{deleteButtonText}
</Button>
)
});
}
return (
<>
{showAddButton && (
<Button
style={{ marginBottom: 10 }}
type="primary"
onClick={addRow}
>
{addButtonText}
</Button>
)}
<Table
data={tableData}
components={{
body: {
row: EditableRow,
cell: EditableCell
}
}}
columns={processedColumns.map((column) =>
column.editable
? {
...column,
onCell: () => ({
onHandleSave: handleSave
})
}
: column
)}
className="table-demo-editable-cell"
{...tableProps}
/>
</>
);
};
export default EditableTable;

@ -8,31 +8,36 @@ export const runStatusConstant = {
UNKNOWN: 'unknown', UNKNOWN: 'unknown',
RUN: 'run', RUN: 'run',
ERROR: 'error', ERROR: 'error',
STOP: 'stop', STOP: 'stop'
}; };
export const runStatusDic = [ export const runStatusDic = [
{ {
label: '可用', label: '可用',
value: runStatusConstant.HEALTHY, value: runStatusConstant.HEALTHY,
color: 'green'
}, },
{ {
label: '未知', label: '未知',
value: runStatusConstant.UNKNOWN, value: runStatusConstant.UNKNOWN,
color: 'gray'
}, },
{ {
// 部署 可停止 // 部署 可停止
label: '运行', label: '运行',
value: runStatusConstant.RUN, value: runStatusConstant.RUN,
color: 'blue'
}, },
{ {
label: '异常', label: '异常',
value: runStatusConstant.ERROR, value: runStatusConstant.ERROR,
color: 'orange'
}, },
{ {
// 部署 可启用 // 部署 可启用
label: '停止', label: '停止',
value: runStatusConstant.STOP, value: runStatusConstant.STOP,
}, color: 'red'
}
]; ];
export const runStatusMap = runStatusDic.reduce((obj, item) => { export const runStatusMap = runStatusDic.reduce((obj, item) => {
obj[item.value] = item.label; obj[item.value] = item.label;
@ -43,17 +48,17 @@ export const runStatusMap = runStatusDic.reduce((obj, item) => {
*/ */
export const startStatusConstant = { export const startStatusConstant = {
RUN: 'run', RUN: 'run',
STOP: 'stop', STOP: 'stop'
}; };
export const startStatusDic = [ export const startStatusDic = [
{ {
label: '启用中', label: '启用中',
value: startStatusConstant.RUN, value: startStatusConstant.RUN
}, },
{ {
label: '已下架', label: '已下架',
value: startStatusConstant.STOP, value: startStatusConstant.STOP
}, }
]; ];
export const startStatusMap = startStatusDic.reduce((obj, item) => { export const startStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label; obj[item.value] = item.label;
@ -73,25 +78,25 @@ export const compileStatusConstant = {
NOT_COMPILED: 'not-compiled', NOT_COMPILED: 'not-compiled',
get(type) { get(type) {
return compileStatusConstant[type] || compileStatusConstant.NOT_COMPILED; return compileStatusConstant[type] || compileStatusConstant.NOT_COMPILED;
}, }
}; };
export const compileStatusDic = [ export const compileStatusDic = [
{ {
label: '编译中', label: '编译中',
value: compileStatusConstant.BUILT_DOING, value: compileStatusConstant.BUILT_DOING
}, },
{ {
label: '免编译', label: '免编译',
value: compileStatusConstant.BUILT_IGNORE, value: compileStatusConstant.BUILT_IGNORE
}, },
{ {
label: '已编译', label: '已编译',
value: compileStatusConstant.BUILT_DOCKER, value: compileStatusConstant.BUILT_DOCKER
}, },
{ {
label: '编译失败', label: '编译失败',
value: compileStatusConstant.BUILT_FAIL, value: compileStatusConstant.BUILT_FAIL
}, }
]; ];
export const compileStatusMap = startStatusDic.reduce((obj, item) => { export const compileStatusMap = startStatusDic.reduce((obj, item) => {
obj[item.value] = item.label; obj[item.value] = item.label;
@ -102,17 +107,17 @@ export const compileStatusMap = startStatusDic.reduce((obj, item) => {
*/ */
export const runTypeConstant = { export const runTypeConstant = {
LOCAL: 0, LOCAL: 0,
ONLINE: 1, ONLINE: 1
}; };
export const runTypeDic = [ export const runTypeDic = [
{ {
label: '本地运行', label: '本地运行',
value: runTypeConstant.LOCAL, value: runTypeConstant.LOCAL
}, },
{ {
label: '线上运行', label: '线上运行',
value: runTypeConstant.ONLINE, value: runTypeConstant.ONLINE
}, }
]; ];
export const option = { export const option = {
...globalOption, ...globalOption,
@ -135,14 +140,14 @@ export const option = {
label: 'id', label: 'id',
prop: 'id', prop: 'id',
hide: true, hide: true,
display: false, display: false
}, },
{ {
label: '标识', label: '标识',
prop: 'identifier', prop: 'identifier',
type: 'input', type: 'input',
overHidden: true, overHidden: true,
display: false, display: false
}, },
{ {
label: '实例名称', label: '实例名称',
@ -151,8 +156,8 @@ export const option = {
overHidden: true, overHidden: true,
rules: [ rules: [
{ required: true, message: '必须填写实例名称' }, { required: true, message: '必须填写实例名称' },
{ max: 50, message: '实例名称不能超过50字符' }, { max: 50, message: '实例名称不能超过50字符' }
], ]
}, },
{ {
label: '运行类型', label: '运行类型',
@ -160,7 +165,7 @@ export const option = {
type: 'input', type: 'input',
width: 80, width: 80,
display: false, display: false,
dicData: runTypeDic, dicData: runTypeDic
}, },
{ {
label: '运行状态', label: '运行状态',
@ -169,14 +174,14 @@ export const option = {
width: 80, width: 80,
dicData: runStatusDic, dicData: runStatusDic,
display: false, display: false,
slot: true, slot: true
}, },
{ {
label: '创建时间', label: '创建时间',
prop: 'createTime', prop: 'createTime',
type: 'input', type: 'input',
// width: 160, // width: 160,
display: false, display: false
}, },
{ {
label: '实例描述', label: '实例描述',
@ -184,9 +189,9 @@ export const option = {
type: 'textarea', type: 'textarea',
hide: true, hide: true,
minRows: 3, minRows: 3,
span: 24, span: 24
}, }
], ]
}; };
export const instanceOption = { export const instanceOption = {
...globalOption, ...globalOption,
@ -216,20 +221,20 @@ export const instanceOption = {
action: '/api/blade-isdp/fileSystem/fileUpload', action: '/api/blade-isdp/fileSystem/fileUpload',
propsHttp: { propsHttp: {
res: 'data', res: 'data',
url: 'link', url: 'link'
}, },
fileType: 'img', fileType: 'img',
limit: 1, limit: 1,
fileSize: 1024 * 10, fileSize: 1024 * 10,
accept: 'image/png, image/jpeg, image/jpg', accept: 'image/png, image/jpeg, image/jpg'
// tip: '图片尺寸不超过300*300像素大小限制为1mb以内', // tip: '图片尺寸不超过300*300像素大小限制为1mb以内',
// rules: [{ // rules: [{
// required: true, // required: true,
// message: "请输入图片地址", // message: "请输入图片地址",
// trigger: "blur" // trigger: "blur"
// }] // }]
}, }
], ]
}; };
export const envConfigOption = { export const envConfigOption = {
...globalOption, ...globalOption,
@ -252,11 +257,11 @@ export const envConfigOption = {
formSlot: true, formSlot: true,
rules: [ rules: [
{ required: true, message: '启动项名称不能为空' }, { required: true, message: '启动项名称不能为空' },
{ max: 50, message: '启动项名称最长为50字符' }, { max: 50, message: '启动项名称最长为50字符' }
], ],
width: 120, width: 120,
span: 24, span: 24,
cell: true, cell: true
}, },
{ {
label: '参数名key', label: '参数名key',
@ -264,10 +269,10 @@ export const envConfigOption = {
formSlot: true, formSlot: true,
rules: [ rules: [
{ required: true, message: '请输入参数名key' }, { required: true, message: '请输入参数名key' },
{ max: 50, message: '参数名key最长为50字符' }, { max: 50, message: '参数名key最长为50字符' }
], ],
span: 24, span: 24,
cell: true, cell: true
}, },
{ {
label: '配置值', label: '配置值',
@ -275,7 +280,7 @@ export const envConfigOption = {
span: 24, span: 24,
formSlot: true, formSlot: true,
rules: [{ max: 200, message: '配置值最长为200字符' }], rules: [{ max: 200, message: '配置值最长为200字符' }],
cell: true, cell: true
}, },
{ {
label: '描述说明', label: '描述说明',
@ -284,8 +289,8 @@ export const envConfigOption = {
formSlot: true, formSlot: true,
span: 24, span: 24,
rules: [{ max: 200, message: '描述说明最长为200字符' }], rules: [{ max: 200, message: '描述说明最长为200字符' }],
cell: true, cell: true
}, }
// { // {
// label: "配置类型", // label: "配置类型",
// prop: "unmodifiable", // prop: "unmodifiable",
@ -302,7 +307,7 @@ export const envConfigOption = {
// } // }
// ] // ]
// }, // },
], ]
}; };
export const addInstanceOption = { export const addInstanceOption = {
...globalOption, ...globalOption,
@ -317,7 +322,7 @@ export const addInstanceOption = {
// disabled: true, // disabled: true,
dicData: [], dicData: [],
rules: [{ required: true, message: '请选择环境变量' }], rules: [{ required: true, message: '请选择环境变量' }],
overHidden: true, overHidden: true
}, },
{ {
label: '主机', label: '主机',
@ -327,10 +332,10 @@ export const addInstanceOption = {
props: { props: {
label: 'name', label: 'name',
value: 'id', value: 'id',
desc: 'ip', desc: 'ip'
}, },
rules: [{ required: true, message: '请输入主机' }], rules: [{ required: true, message: '请输入主机' }],
overHidden: true, overHidden: true
}, },
{ {
label: 'CPU核数', label: 'CPU核数',
@ -343,7 +348,7 @@ export const addInstanceOption = {
marks: [1, 4, 8].reduce((obj, pre) => { marks: [1, 4, 8].reduce((obj, pre) => {
obj[pre] = pre + '核'; obj[pre] = pre + '核';
return obj; return obj;
}, {}), }, {})
}, },
// { // {
// label: "容器名称", // label: "容器名称",
@ -362,8 +367,8 @@ export const addInstanceOption = {
value: false, value: false,
dicData: [ dicData: [
{ label: '否', value: false }, { label: '否', value: false },
{ label: '是', value: true }, { label: '是', value: true }
], ]
}, },
{ {
label: '最大内存', label: '最大内存',
@ -379,9 +384,9 @@ export const addInstanceOption = {
1024: '1GB', 1024: '1GB',
2048: '2GB', 2048: '2GB',
4096: '4GB', 4096: '4GB',
6144: '6GB', 6144: '6GB'
}, },
showStops: true, showStops: true
}, },
// 网络模式 选择框 // 网络模式 选择框
{ {
@ -393,19 +398,19 @@ export const addInstanceOption = {
dicData: [ dicData: [
{ {
label: '桥接模式', label: '桥接模式',
value: 'bridge', value: 'bridge'
}, },
{ {
label: '主机模式', label: '主机模式',
value: 'host', value: 'host'
}, },
{ {
label: '禁用网络', label: '禁用网络',
value: 'null', value: 'null'
}, }
], ],
rules: [{ required: true, message: '请选择网络模式' }], rules: [{ required: true, message: '请选择网络模式' }],
overHidden: true, overHidden: true
}, },
// 网卡 // 网卡
{ {
@ -415,7 +420,7 @@ export const addInstanceOption = {
value: 'bridge', value: 'bridge',
span: 9, span: 9,
overHidden: true, overHidden: true,
rules: [{ required: true, message: '请选择网卡' }], rules: [{ required: true, message: '请选择网卡' }]
}, },
// ip // ip
{ {
@ -425,7 +430,7 @@ export const addInstanceOption = {
formSlot: true, formSlot: true,
value: '', value: '',
overHidden: true, overHidden: true,
span: 9, span: 9
}, },
{ {
label: '端口映射', label: '端口映射',
@ -441,23 +446,23 @@ export const addInstanceOption = {
type: 'number', type: 'number',
value: '80', value: '80',
max: 65535, max: 65535,
rules: [{ required: true, message: '请输入主机端口' }], rules: [{ required: true, message: '请输入主机端口' }]
}, },
{ {
label: '容器端口', label: '容器端口',
prop: 'containerPort', prop: 'containerPort',
type: 'number', type: 'number',
max: 65535, max: 65535,
rules: [{ required: true, message: '请输入容器端口' }], rules: [{ required: true, message: '请输入容器端口' }]
}, },
{ {
label: '备注', label: '备注',
prop: 'remake', prop: 'remake',
type: 'input', type: 'input',
value: '', value: ''
}, }
], ]
}, }
}, },
{ {
label: '目录挂载', label: '目录挂载',
@ -472,22 +477,22 @@ export const addInstanceOption = {
prop: 'hostPath', prop: 'hostPath',
type: 'input', type: 'input',
value: '', value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim], rules: [{ required: true, message: '请输入主机路径' }, patternTrim]
}, },
{ {
label: '容器路径', label: '容器路径',
prop: 'containerPath', prop: 'containerPath',
type: 'input', type: 'input',
value: '', value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim], rules: [{ required: true, message: '请输入容器路径' }, patternTrim]
}, },
{ {
label: '备注', label: '备注',
prop: 'remake', prop: 'remake',
type: 'input', type: 'input'
}, }
], ]
}, }
}, },
{ {
// 设备挂载 // 设备挂载
@ -503,14 +508,14 @@ export const addInstanceOption = {
prop: 'hostPath', prop: 'hostPath',
type: 'input', type: 'input',
value: '', value: '',
rules: [{ required: true, message: '请输入主机路径' }, patternTrim], rules: [{ required: true, message: '请输入主机路径' }, patternTrim]
}, },
{ {
label: '容器路径', label: '容器路径',
prop: 'containerPath', prop: 'containerPath',
type: 'input', type: 'input',
value: '', value: '',
rules: [{ required: true, message: '请输入容器路径' }, patternTrim], rules: [{ required: true, message: '请输入容器路径' }, patternTrim]
}, },
{ {
label: '权限', label: '权限',
@ -520,18 +525,18 @@ export const addInstanceOption = {
dicData: [ dicData: [
{ value: 'rwm', label: 'rwm' }, { value: 'rwm', label: 'rwm' },
{ value: 'rw', label: 'rw' }, { value: 'rw', label: 'rw' },
{ value: 'r', label: 'r' }, { value: 'r', label: 'r' }
], ],
rules: [{ required: true, message: '请输入权限' }], rules: [{ required: true, message: '请输入权限' }]
}, },
{ {
label: '备注', label: '备注',
prop: 'remake', prop: 'remake',
type: 'input', type: 'input',
value: '', value: ''
}, }
], ]
}, }
}, },
// 重启策略 // 重启策略
{ {
@ -545,18 +550,18 @@ export const addInstanceOption = {
dicData: [ dicData: [
{ {
label: '始终', label: '始终',
value: 'always', value: 'always'
}, },
{ {
label: '失败', label: '失败',
value: 'on-failure', value: 'on-failure'
}, },
{ {
label: '无', label: '无',
value: 'no', value: 'no'
}, }
],
overHidden: true,
},
], ],
overHidden: true
}
]
}; };

@ -0,0 +1,289 @@
import React, { useState } from 'react' ;
import { Modal, Form, Select, Grid, Slider, Switch, Input } from '@arco-design/web-react';
import EditableTable from '@/components/EditableTable';
const FormItem = Form.Item;
const Option = Select.Option;
const runTypes = [
{
label: '本地运行',
value: 'local'
},
{
label: '线上运行',
value: 'online'
}
];
const portColumns = [
{
title: '主机端口',
dataIndex: 'name',
editable: true
},
{
title: '容器端口',
dataIndex: 'salary',
editable: true
},
{
title: '备注',
dataIndex: 'email',
editable: true
}
];
const directoryColumns = [
{
title: '主机路径',
dataIndex: 'name',
editable: true
},
{
title: '容器路径',
dataIndex: 'salary',
editable: true
},
{
title: '备注',
dataIndex: 'email',
editable: true
}
];
const deviceColumns = [
{
title: '主机路径',
dataIndex: 'name',
editable: true
},
{
title: '容器路径',
dataIndex: 'salary',
editable: true
},
{
title: '权限',
dataIndex: 'email',
editable: true
},
{
title: '备注',
dataIndex: 'email',
editable: true
}
];
const AddModal = ({ visible, setVisible }) => {
const [currentRunType, setCurrentRunType] = useState('local');
const [tableData, setTableData] = useState([
{
key: '1',
name: 'Jane Doe',
salary: 23000,
email: 'jane.doe@example.com'
}
]);
return (
<Modal
title="新增实例"
visible={visible}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
autoFocus={false}
focusLock={true}
style={{ width: '75%' }}
>
<Form autoComplete="off" labelCol={{ flex: 'none' }}>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="部署方式:">
<Select
placeholder="请选择"
style={{ width: '60%' }}
defaultValue={currentRunType}
onChange={(value) => setCurrentRunType(value)}
>
{runTypes.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
{
currentRunType === 'online' && (
<>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="部署环境:" field="tags">
<Select
placeholder="请选择部署环境"
style={{ width: '90%' }}
>
{runTypes.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem label="主机:" field="type">
<Select
placeholder="请选择主机"
style={{ width: '90%' }}
>
{runTypes.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
</Grid.Row>
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<FormItem label="CPU核数" field="a">
<Slider
defaultValue={1}
min={1}
max={4}
step={1}
showTicks={true}
marks={{
1: '1核',
2: '',
3: '',
4: '4核'
}}
style={{
width: '90%',
whiteSpace: 'nowrap'
}}
/>
</FormItem>
</Grid.Col>
<Grid.Col span={12}>
<FormItem label="是否使用GPU" field="b">
<Switch checkedText="ON" uncheckedText="OFF" />
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem label="最大内存:">
<Slider
defaultValue={2048}
min={64}
max={4069}
step={64}
marks={{
64: '64MB',
1024: '1GB',
2048: '2GB',
4069: '4GB'
}}
showInput={{
hideControl: false,
suffix: 'MB',
style: {
width: 100
}
}}
style={{ width: '100%' }}
/>
</FormItem>
<Grid.Row gutter={8}>
<Grid.Col span={8}>
<FormItem label="网络模式:" field="tags">
<Select
placeholder="请选择网络模式"
style={{ width: '90%' }}
>
{runTypes.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={8}>
<FormItem label="网卡:" field="type">
<Select
placeholder="请选择网卡"
style={{ width: '90%' }}
>
{runTypes.map((option, index) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</FormItem>
</Grid.Col>
<Grid.Col span={8}>
<FormItem label="IP" field="type">
<Input style={{ width: '90%' }} allowClear placeholder="请输入IP" />
</FormItem>
</Grid.Col>
</Grid.Row>
<FormItem label="端口映射:">
<EditableTable
columns={portColumns}
data={tableData}
onDataChange={(newData) => setTableData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="目录挂载:">
<EditableTable
columns={directoryColumns}
data={tableData}
onDataChange={(newData) => setTableData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
<FormItem label="设备挂载:">
<EditableTable
columns={deviceColumns}
data={tableData}
onDataChange={(newData) => setTableData(newData)}
showAddButton={true}
addButtonText="添加行"
showDeleteButton={true}
deleteButtonText="删除"
tableProps={{
pagination: { pageSize: 5 },
scroll: { y: 400 }
}}
/>
</FormItem>
</>
)
}
</Form>
</Modal>
);
};
export default AddModal;

@ -1,27 +1,53 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Collapse, Space, Tag, Button, Table, TableColumnProps } from '@arco-design/web-react'; import { Collapse, Space, Tag, Button, Table, TableColumnProps } from '@arco-design/web-react';
import styles from './style/collapseList.module.less'; import styles from './style/collapseList.module.less';
import { IconEye } from '@arco-design/web-react/icon'; import ListNode from '@/pages/componentDevelopment/componentDeployment/listNode';
import AddModal from '@/pages/componentDevelopment/componentDeployment/addModal';
import { getDeployList } from '@/api/componentDeploy';
import { runStatusConstant, runStatusDic } from '@/const/isdp/componentDeploy';
const CollapseItem = Collapse.Item; const CollapseItem = Collapse.Item;
const CollapseList = () => {
const [collapses, setCollapses] = useState([]);
const [visible, setVisible] = useState(false);
const getList = async () => {
const params = {
current: 1,
size: 10
};
const res: any = await getDeployList(params);
if (res.code === 200) setCollapses(res.data.records);
};
useEffect(() => {
getList();
}, []);
const headerNode = (item) => {
const getRunStatus = () => {
return runStatusDic.find((v) => v.value === runStatusConstant[item.runStatus]) || {
color: 'gray',
label: '未知'
};
};
const headerNode = () => {
return ( return (
<div className={styles['header-node']}> <div className={styles['header-node']}>
<Space size={7} style={{ marginRight: 50 }}> <Space size={7} style={{ marginRight: 50 }}>
<img src="https://picsum.photos/200/300" <img src="https://picsum.photos/200/300"
style={{ width: 50, height: 50, borderRadius: 6, overflow: 'hidden' }} /> style={{ width: 50, height: 50, borderRadius: 6, overflow: 'hidden' }} />
<div></div> <div>{item.name}</div>
</Space> </Space>
<Space size={10}> <Space size={10}>
<Tag>AI</Tag> <Tag>{item.componentClassify}</Tag>
<Tag>X86_64</Tag> <Tag>{item.arches}</Tag>
<Tag color={'green'}></Tag> <Tag color={getRunStatus().color}>{getRunStatus().label}</Tag>
<div className={styles['flex-box']}> <div className={styles['flex-box']}>
<img src={'/icons/countIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} /> <img src={'/icons/countIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}>4</span> <span style={{ color: '#A2A2AB' }}>{item.instanceCount}</span>
</div> </div>
</Space> </Space>
</div> </div>
@ -32,15 +58,15 @@ const extraNode = () => {
return ( return (
<div className={styles['extra-node']}> <div className={styles['extra-node']}>
<Space size={20}> <Space size={20}>
<div className={styles['flex-box']}> {/*<div className={styles['flex-box']}>*/}
<img src={'/icons/compileIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} /> {/* <img src={'/icons/compileIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />*/}
<span style={{ color: '#A2A2AB' }}></span> {/* <span style={{ color: '#A2A2AB' }}>未编译</span>*/}
</div> {/*</div>*/}
<div className={styles['flex-box']}> {/*<div className={styles['flex-box']}>*/}
<img src={'/icons/recompileIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} /> {/* <img src={'/icons/recompileIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />*/}
<span style={{ color: '#A2A2AB' }}></span> {/* <span style={{ color: '#A2A2AB' }}>重新编译</span>*/}
</div> {/*</div>*/}
<div className={styles['flex-box']}> <div className={styles['flex-box']} onClick={() => setVisible(true)}>
<img src={'/icons/addIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} /> <img src={'/icons/addIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}></span> <span style={{ color: '#A2A2AB' }}></span>
</div> </div>
@ -57,171 +83,31 @@ const extraNode = () => {
); );
}; };
const extraNode1 = () => {
return (
<div className={styles['extra-node']}>
<Space size={20}>
<div className={styles['flex-box']}>
<img src={'/icons/compiledIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#40BF6D' }}></span>
</div>
<div className={styles['flex-box']}>
<img src={'/icons/recompileIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}></span>
</div>
<div className={styles['flex-box']}>
<img src={'/icons/addIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}></span>
</div>
<div className={styles['flex-box']}>
<img src={'/icons/removedIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}></span>
</div>
<div className={styles['flex-box']}>
<img src={'/icons/moreIcon.png'} style={{ width: 16, height: 16, marginRight: 5 }} />
<span style={{ color: '#A2A2AB' }}></span>
</div>
</Space>
</div>
);
};
const columns: TableColumnProps[] = [
{
title: '#',
dataIndex: 'key',
align: 'center'
},
{
title: '组件标识',
dataIndex: 'name',
align: 'center'
},
{
title: '实例名称',
dataIndex: 'salary',
align: 'center'
},
{
title: '运行类型',
dataIndex: 'address',
align: 'center'
},
{
title: '运行状态',
dataIndex: 'email',
align: 'center'
},
{
title: '创建时间',
dataIndex: 'email',
align: 'center'
},
{
title: '操作',
dataIndex: '',
align: 'center',
render: (col, record, index) => (
<div className={styles['table-handle-box']}>
<Space size={20}>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"
icon={<img
src={'/icons/editIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
<Button type="text"
icon={<img
src={'/icons/powerUpIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
<Button type="text"
icon={<img
src={'/icons/deleteIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
</Space>
</div>
)
}
];
const data = [
{
key: '1',
name: 'Jane Doe',
salary: 23000,
address: '32 Park Road, London',
email: 'jane.doe@example.com'
},
{
key: '2',
name: 'Alisa Ross',
salary: 25000,
address: '35 Park Road, London',
email: 'alisa.ross@example.com'
},
{
key: '3',
name: 'Kevin Sandra',
salary: 22000,
address: '31 Park Road, London',
email: 'kevin.sandra@example.com'
},
{
key: '4',
name: 'Ed Hellen',
salary: 17000,
address: '42 Park Road, London',
email: 'ed.hellen@example.com'
},
{
key: '5',
name: 'William Smith',
salary: 27000,
address: '62 Park Road, London',
email: 'william.smith@example.com'
}
];
const listNode = () => {
return (
<Table pagination={false} border={false} columns={columns} data={data} />
);
};
const CollapseList = () => {
return ( return (
<>
<div className={styles['collapse-list']}> <div className={styles['collapse-list']}>
<Collapse <Collapse
expandIconPosition={'right'} expandIconPosition={'right'}
bordered={false} bordered={false}
> >
{collapses.map((item, index) => (
<CollapseItem <CollapseItem
header={headerNode()} key={item.identifier}
header={headerNode(item)}
name="1" name="1"
extra={extraNode()} extra={extraNode()}
> >
{listNode()} <ListNode />
</CollapseItem>
<CollapseItem
header={headerNode()}
name="2"
extra={extraNode1()}>
{listNode()}
</CollapseItem>
<CollapseItem
header={headerNode()}
name="3"
extra={extraNode()}>
{listNode()}
</CollapseItem> </CollapseItem>
))}
</Collapse> </Collapse>
</div> </div>
<AddModal
visible={visible}
setVisible={setVisible}
/>
</>
); );
}; };

@ -0,0 +1,112 @@
import React from "react";
import { Button, Space, Table, TableColumnProps } from '@arco-design/web-react';
import styles from '@/pages/componentDevelopment/componentDeployment/style/collapseList.module.less';
const ListNode = () => {
const columns: TableColumnProps[] = [
{
title: '#',
dataIndex: 'key',
align: 'center'
},
{
title: '组件标识',
dataIndex: 'name',
align: 'center'
},
{
title: '实例名称',
dataIndex: 'salary',
align: 'center'
},
{
title: '运行类型',
dataIndex: 'address',
align: 'center'
},
{
title: '运行状态',
dataIndex: 'email',
align: 'center'
},
{
title: '创建时间',
dataIndex: 'email',
align: 'center'
},
{
title: '操作',
dataIndex: '',
align: 'center',
render: (col, record, index) => (
<div className={styles['table-handle-box']}>
<Space size={20}>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"></Button>
<Button type="text"
icon={<img
src={'/icons/editIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
<Button type="text"
icon={<img
src={'/icons/powerUpIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
<Button type="text"
icon={<img
src={'/icons/deleteIcon.png'}
style={{ width: 16, height: 16, marginRight: 5 }} />}
></Button>
</Space>
</div>
)
}
];
const data = [
{
key: '1',
name: 'Jane Doe',
salary: 23000,
address: '32 Park Road, London',
email: 'jane.doe@example.com'
},
{
key: '2',
name: 'Alisa Ross',
salary: 25000,
address: '35 Park Road, London',
email: 'alisa.ross@example.com'
},
{
key: '3',
name: 'Kevin Sandra',
salary: 22000,
address: '31 Park Road, London',
email: 'kevin.sandra@example.com'
},
{
key: '4',
name: 'Ed Hellen',
salary: 17000,
address: '42 Park Road, London',
email: 'ed.hellen@example.com'
},
{
key: '5',
name: 'William Smith',
salary: 27000,
address: '62 Park Road, London',
email: 'william.smith@example.com'
}
];
return (
<Table pagination={false} border={false} columns={columns} data={data} />
)
}
export default ListNode;
Loading…
Cancel
Save