|
|
import React, { useEffect, useState } from 'react';
|
|
|
import styles from './style/index.module.less';
|
|
|
import {
|
|
|
Button,
|
|
|
Input,
|
|
|
Space,
|
|
|
Select,
|
|
|
Divider,
|
|
|
Table,
|
|
|
TableColumnProps,
|
|
|
Message,
|
|
|
Modal,
|
|
|
Notification,
|
|
|
Tag
|
|
|
} from '@arco-design/web-react';
|
|
|
import { getComponentClassify } from '@/api/componentClassify';
|
|
|
import { IconSearch } from '@arco-design/web-react/icon';
|
|
|
import ContainerModal from './containerModal';
|
|
|
import AddModal from './addModal';
|
|
|
import { getEnvConfigList, deleteEnvConfig, testEnv } from '@/api/componentDeployEnv';
|
|
|
import { startContainer, stopContainer } from '@/api/userContainer';
|
|
|
|
|
|
const Option = Select.Option;
|
|
|
|
|
|
const ComponentEnv = () => {
|
|
|
const [envType, setEnvType] = useState([]); // 环境类型
|
|
|
const [architectureType, setArchitectureType] = useState(['x86_64', 'aarch64']); // 结构类型
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
const [visible1, setVisible1] = useState(false);
|
|
|
const [data, setData] = useState([]);
|
|
|
const [editingRecord, setEditingRecord] = useState(null);
|
|
|
const [selectedEnvType, setSelectedEnvType] = useState(null); // 选中的环境类型
|
|
|
const [selectedArch, setSelectedArch] = useState(null); // 选中的架构类型
|
|
|
const [searchText, setSearchText] = useState(''); // 搜索文本
|
|
|
const [addItem, setAddItem] = useState(null); // 点击环境配置按钮时记录当前信息
|
|
|
const [loadingActions, setLoadingActions] = useState<{ id: number | null; action: string | null }>({
|
|
|
id: null,
|
|
|
action: null
|
|
|
}); // 按钮loading状态
|
|
|
|
|
|
const columns: TableColumnProps[] = [
|
|
|
{
|
|
|
title: '环境IP',
|
|
|
dataIndex: 'ip'
|
|
|
},
|
|
|
{
|
|
|
title: 'docker端口',
|
|
|
dataIndex: 'dockerTcpPort'
|
|
|
},
|
|
|
{
|
|
|
title: '环境类型',
|
|
|
dataIndex: 'type'
|
|
|
},
|
|
|
{
|
|
|
title: '架构类型',
|
|
|
dataIndex: 'arch'
|
|
|
},
|
|
|
{
|
|
|
title: '环境别名',
|
|
|
dataIndex: 'name'
|
|
|
},
|
|
|
{
|
|
|
title: '备注',
|
|
|
dataIndex: 'description'
|
|
|
},
|
|
|
{
|
|
|
title: '实例数量',
|
|
|
dataIndex: 'instanceCount'
|
|
|
},
|
|
|
{
|
|
|
title: '用户容器状态',
|
|
|
dataIndex: 'isEnable',
|
|
|
render: (_, record: any) => (
|
|
|
<div>
|
|
|
{record.isEnable === 'NOT_EXIST' && (
|
|
|
<Tag color="gray">
|
|
|
未注册
|
|
|
</Tag>
|
|
|
)}
|
|
|
{record.isEnable === 'RUNNING' && (
|
|
|
<Tag color="green">
|
|
|
运行
|
|
|
</Tag>
|
|
|
)}
|
|
|
{record.isEnable === 'STOPPED' && (
|
|
|
<Tag color="red">
|
|
|
停止
|
|
|
</Tag>
|
|
|
)}
|
|
|
</div>
|
|
|
)
|
|
|
},
|
|
|
{
|
|
|
title: '测试状态',
|
|
|
dataIndex: 'available',
|
|
|
render: (_, record: any) => (
|
|
|
<div>
|
|
|
{record.available == '0' && (
|
|
|
<Tag color="gray">
|
|
|
未测试
|
|
|
</Tag>
|
|
|
)}
|
|
|
{record.available == '1' && (
|
|
|
<Tag color="green">
|
|
|
测试通过
|
|
|
</Tag>
|
|
|
)}
|
|
|
{record.available == '-1' && (
|
|
|
<Tag color="red">
|
|
|
测试失败
|
|
|
</Tag>
|
|
|
)}
|
|
|
</div>
|
|
|
)
|
|
|
},
|
|
|
{
|
|
|
title: '操作',
|
|
|
width: 230,
|
|
|
align: 'center',
|
|
|
render: (_, record: any) => (
|
|
|
<Space>
|
|
|
{record.isEnable === 'NOT_EXIST' && record.available == '1' && (
|
|
|
<Button type="text" onClick={() => {
|
|
|
setAddItem(record);
|
|
|
setVisible1(true);
|
|
|
}}>容器配置</Button>
|
|
|
)}
|
|
|
{record.isEnable === 'RUNNING' && (
|
|
|
<Button
|
|
|
type="text"
|
|
|
status="danger"
|
|
|
loading={loadingActions.id === record.id && loadingActions.action === 'stop'}
|
|
|
onClick={() => {
|
|
|
handleStop(record.id);
|
|
|
}}
|
|
|
>
|
|
|
停止
|
|
|
</Button>
|
|
|
)}
|
|
|
{record.isEnable === 'STOPPED' && (
|
|
|
<Button
|
|
|
type="text"
|
|
|
loading={loadingActions.id === record.id && loadingActions.action === 'start'}
|
|
|
onClick={() => handleStart(record.id)}
|
|
|
>
|
|
|
启动
|
|
|
</Button>
|
|
|
)}
|
|
|
<Button
|
|
|
type="text"
|
|
|
loading={loadingActions.id === record.id && loadingActions.action === 'test'}
|
|
|
onClick={() => handleTestEnv(record)}
|
|
|
>
|
|
|
环境测试
|
|
|
</Button>
|
|
|
<Button type="text" onClick={() => handleConfigEnv(record)}>环境配置</Button>
|
|
|
<Button type="text" status="danger" onClick={() => handleDeleteEnv(record)}>删除</Button>
|
|
|
</Space>
|
|
|
)
|
|
|
}
|
|
|
];
|
|
|
|
|
|
const handleStop = async (id) => {
|
|
|
setLoadingActions({ id, action: 'stop' });
|
|
|
const loadingMessage = Message.loading('正在停止容器,请稍候...');
|
|
|
try {
|
|
|
const res: any = await stopContainer(id);
|
|
|
loadingMessage();
|
|
|
if (res.code === 200) {
|
|
|
Message.success('停止成功');
|
|
|
getEnvList();
|
|
|
}
|
|
|
else {
|
|
|
Message.error('停止失败: ' + res.message);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
loadingMessage();
|
|
|
Message.error('停止失败: ' + error.message);
|
|
|
} finally {
|
|
|
setLoadingActions({ id: null, action: null });
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleStart = async (id) => {
|
|
|
setLoadingActions({ id, action: 'start' });
|
|
|
const loadingMessage = Message.loading('正在启动容器,请稍候...');
|
|
|
try {
|
|
|
const res: any = await startContainer(id);
|
|
|
loadingMessage();
|
|
|
if (res.code === 200) {
|
|
|
Message.success('启动成功');
|
|
|
getEnvList();
|
|
|
}
|
|
|
else {
|
|
|
Message.error('启动失败: ' + res.message);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
loadingMessage();
|
|
|
Message.error('启动失败: ' + error.message);
|
|
|
} finally {
|
|
|
setLoadingActions({ id: null, action: null });
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 环境测试处理函数
|
|
|
const handleTestEnv = async (record: any) => {
|
|
|
setLoadingActions({ id: record.id, action: 'test' });
|
|
|
Message.loading({
|
|
|
content: `正在测试环境 ${record.name}...`,
|
|
|
duration: 0
|
|
|
});
|
|
|
try {
|
|
|
const res: any = await testEnv(record.id);
|
|
|
if (res.code === 200 && res.data) {
|
|
|
Notification.success({
|
|
|
closable: false,
|
|
|
title: '测试成功',
|
|
|
content: `环境 【${record.name}】 测试成功`
|
|
|
});
|
|
|
}
|
|
|
else {
|
|
|
Notification.error({
|
|
|
closable: false,
|
|
|
title: '测试失败',
|
|
|
content: `环境 【${record.name}】 测试失败: ${res.message}`
|
|
|
});
|
|
|
}
|
|
|
} finally {
|
|
|
Message.clear();
|
|
|
setLoadingActions({ id: null, action: null });
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 环境配置处理函数
|
|
|
const handleConfigEnv = (record: any) => {
|
|
|
// 设置编辑记录
|
|
|
setEditingRecord(record);
|
|
|
|
|
|
// 打开模态框
|
|
|
setVisible(true);
|
|
|
};
|
|
|
|
|
|
// 删除环境处理函数
|
|
|
const handleDeleteEnv = (record: any) => {
|
|
|
Modal.confirm({
|
|
|
title: '删除环境',
|
|
|
content: (
|
|
|
<div>
|
|
|
<p>删除环境后,组件实例将无法继续部署到该环境下。</p>
|
|
|
<p style={{ color: 'red', fontWeight: 'bold' }}>确认删除环境 {record.name}?</p>
|
|
|
<p>请输入环境名称 【<strong>{record.name}</strong>】 以确认删除:</p>
|
|
|
<Input placeholder={`请输入 ${record.name}`} id="confirm-env-name" />
|
|
|
</div>
|
|
|
),
|
|
|
okButtonProps: { status: 'danger' },
|
|
|
onOk: async () => {
|
|
|
// 获取用户输入的环境名称
|
|
|
const confirmInput = document.getElementById('confirm-env-name') as HTMLInputElement;
|
|
|
if (!confirmInput || confirmInput.value !== record.name) {
|
|
|
Message.error('环境名称输入不匹配,请重新输入');
|
|
|
return Promise.reject();
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const res: any = await deleteEnvConfig(record.id);
|
|
|
if (res.code === 200) {
|
|
|
Message.success('环境删除成功');
|
|
|
// 重新加载数据
|
|
|
getEnvList();
|
|
|
}
|
|
|
else {
|
|
|
Message.error('环境删除失败: ' + res.message);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
Message.error('环境删除失败: ' + error.message);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
|
|
|
// 获取环境列表,根据选择的类型和架构进行过滤
|
|
|
const getEnvList = async (filterParams?: any) => {
|
|
|
// 构造查询参数
|
|
|
const params: any = {
|
|
|
current: 1,
|
|
|
size: 10
|
|
|
};
|
|
|
|
|
|
// 合并筛选参数
|
|
|
if (filterParams) {
|
|
|
Object.assign(params, filterParams);
|
|
|
}
|
|
|
else {
|
|
|
// 如果没有传入参数,使用当前状态值
|
|
|
if (selectedEnvType) params.type = selectedEnvType;
|
|
|
if (selectedArch) params.arch = selectedArch;
|
|
|
if (searchText) params.name = searchText;
|
|
|
}
|
|
|
|
|
|
const res: any = await getEnvConfigList(params);
|
|
|
if (res.code === 200) setData(res.data.list);
|
|
|
};
|
|
|
|
|
|
const getEnvType = async () => {
|
|
|
const res: any = await getComponentClassify('docker-env');
|
|
|
if (res.code === 200) setEnvType(res.data);
|
|
|
};
|
|
|
|
|
|
useEffect(() => {
|
|
|
getEnvType();
|
|
|
getEnvList();
|
|
|
}, []);
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
<div className={styles['component-env']}>
|
|
|
<div className={styles['component-env-header']}>
|
|
|
<div className={styles['component-env-header-left']}>
|
|
|
<Space size={20}>
|
|
|
<div className={styles['handle-row-item']}>
|
|
|
<span>环境类型:</span>
|
|
|
<Select
|
|
|
placeholder="选择环境类型"
|
|
|
style={{ width: 154 }}
|
|
|
allowClear
|
|
|
value={selectedEnvType}
|
|
|
onChange={(value) => {
|
|
|
setSelectedEnvType(value);
|
|
|
// 构建查询参数,包含所有当前筛选条件
|
|
|
const params: any = {};
|
|
|
if (value) params.type = value;
|
|
|
if (selectedArch) params.arch = selectedArch;
|
|
|
if (searchText) params.name = searchText;
|
|
|
getEnvList(params);
|
|
|
}}
|
|
|
>
|
|
|
{envType.map((option, index) => (
|
|
|
<Option key={option.id} value={option.classifyName}>
|
|
|
{option.classifyName}
|
|
|
</Option>
|
|
|
))}
|
|
|
</Select>
|
|
|
</div>
|
|
|
<div className={styles['handle-row-item']}>
|
|
|
<span>架构类型:</span>
|
|
|
<Select
|
|
|
placeholder="选择架构类型"
|
|
|
style={{ width: 154 }}
|
|
|
allowClear
|
|
|
value={selectedArch}
|
|
|
onChange={(value) => {
|
|
|
setSelectedArch(value);
|
|
|
// 构建查询参数,包含所有当前筛选条件
|
|
|
const params: any = {};
|
|
|
if (selectedEnvType) params.type = selectedEnvType;
|
|
|
if (value) params.arch = value;
|
|
|
if (searchText) params.name = searchText;
|
|
|
getEnvList(params);
|
|
|
}}
|
|
|
>
|
|
|
{architectureType.map((option, index) => (
|
|
|
<Option key={option} value={option}>
|
|
|
{option}
|
|
|
</Option>
|
|
|
))}
|
|
|
</Select>
|
|
|
</div>
|
|
|
</Space>
|
|
|
</div>
|
|
|
|
|
|
<div className={styles['component-env-header-right']}>
|
|
|
<Space split={<Divider type="vertical" />}>
|
|
|
<div>
|
|
|
<Input
|
|
|
prefix={<IconSearch />}
|
|
|
placeholder={'搜索'}
|
|
|
style={{ width: 236, marginRight: 5 }}
|
|
|
value={searchText}
|
|
|
onChange={(value) => setSearchText(value)}
|
|
|
onPressEnter={() => {
|
|
|
// 构建查询参数,包含所有当前筛选条件
|
|
|
const params: any = {};
|
|
|
if (selectedEnvType) params.type = selectedEnvType;
|
|
|
if (selectedArch) params.arch = selectedArch;
|
|
|
if (searchText) params.name = searchText;
|
|
|
getEnvList(params);
|
|
|
}}
|
|
|
/>
|
|
|
<Button
|
|
|
type="primary"
|
|
|
style={{ borderRadius: 4 }}
|
|
|
onClick={() => {
|
|
|
// 构建查询参数,包含所有当前筛选条件
|
|
|
const params: any = {};
|
|
|
if (selectedEnvType) params.type = selectedEnvType;
|
|
|
if (selectedArch) params.arch = selectedArch;
|
|
|
if (searchText) params.name = searchText;
|
|
|
getEnvList(params);
|
|
|
}}
|
|
|
>
|
|
|
搜索
|
|
|
</Button>
|
|
|
</div>
|
|
|
<Button
|
|
|
type="primary"
|
|
|
style={{ borderRadius: 4 }}
|
|
|
onClick={() => {
|
|
|
setEditingRecord(null);
|
|
|
setVisible(true);
|
|
|
}}
|
|
|
>
|
|
|
+ 新增
|
|
|
</Button>
|
|
|
</Space>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<Table columns={columns} data={data} />
|
|
|
|
|
|
<AddModal
|
|
|
visible={visible}
|
|
|
envType={envType}
|
|
|
setVisible={setVisible}
|
|
|
onOk={(value) => {
|
|
|
setEditingRecord(value);
|
|
|
getEnvList(); // 新增完成后刷新列表
|
|
|
}}
|
|
|
record={editingRecord}
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
<ContainerModal
|
|
|
addItem={addItem}
|
|
|
envType={envType}
|
|
|
visible={visible1}
|
|
|
setVisible={setVisible1}
|
|
|
onSuccess={getEnvList}
|
|
|
/>
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default ComponentEnv;
|