|
|
|
|
@ -1,10 +1,32 @@
|
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
|
import { Button, Space, Table, TableColumnProps, Tag, Message } from '@arco-design/web-react';
|
|
|
|
|
import {
|
|
|
|
|
Button,
|
|
|
|
|
Space,
|
|
|
|
|
Table,
|
|
|
|
|
TableColumnProps,
|
|
|
|
|
Tag,
|
|
|
|
|
Message,
|
|
|
|
|
Modal,
|
|
|
|
|
DatePicker,
|
|
|
|
|
InputNumber,
|
|
|
|
|
Input,
|
|
|
|
|
Grid
|
|
|
|
|
} from '@arco-design/web-react';
|
|
|
|
|
import styles from '@/pages/componentDevelopment/componentDeployment/style/collapseList.module.less';
|
|
|
|
|
import { getInstanceList, startInstance, stopInstance } from '@/api/componentInstance';
|
|
|
|
|
import {
|
|
|
|
|
deleteInstance,
|
|
|
|
|
getInstanceList,
|
|
|
|
|
startInstance,
|
|
|
|
|
stopInstance,
|
|
|
|
|
getInstanceLog,
|
|
|
|
|
refreshInstanceDependency
|
|
|
|
|
} from '@/api/componentInstance';
|
|
|
|
|
import { runStatusConstant, runStatusDic, runTypeConstant, runTypeDic } from '@/const/isdp/componentDeploy';
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
const { TextArea } = Input;
|
|
|
|
|
|
|
|
|
|
interface ListNodeProps {
|
|
|
|
|
componentData: any; // 组件数据
|
|
|
|
|
}
|
|
|
|
|
@ -12,6 +34,18 @@ interface ListNodeProps {
|
|
|
|
|
const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
const [data, setData] = useState([]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [refreshingIds, setRefreshingIds] = useState<Set<string>>(new Set()); // 记录正在刷新的实例ID
|
|
|
|
|
|
|
|
|
|
// 日志 Modal 相关状态
|
|
|
|
|
const [logModalVisible, setLogModalVisible] = useState(false);
|
|
|
|
|
const [currentInstance, setCurrentInstance] = useState<any>(null);
|
|
|
|
|
const [logTimeRange, setLogTimeRange] = useState<any[]>([
|
|
|
|
|
dayjs().startOf('day'), // 当天0点
|
|
|
|
|
dayjs().endOf('day') // 当天24点
|
|
|
|
|
]);
|
|
|
|
|
const [logLines, setLogLines] = useState<number>(100);
|
|
|
|
|
const [logContent, setLogContent] = useState<string>('');
|
|
|
|
|
const [logLoading, setLogLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
// 获取实例列表
|
|
|
|
|
const fetchInstanceList = async () => {
|
|
|
|
|
@ -42,6 +76,9 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
// 处理启动实例
|
|
|
|
|
const handleStart = async (record) => {
|
|
|
|
|
try {
|
|
|
|
|
// 先执行刷新依赖
|
|
|
|
|
await refreshInstanceDependency(record.id);
|
|
|
|
|
// 刷新依赖完成后启动
|
|
|
|
|
const res: any = await startInstance(record.id);
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
Message.success('启动成功');
|
|
|
|
|
@ -73,6 +110,141 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 删除实例
|
|
|
|
|
const handleRemove = async (record) => {
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
title: '确认删除',
|
|
|
|
|
content: `确定要删除实例"${record.name || record.identifier}"吗?此操作不可恢复。`,
|
|
|
|
|
okText: '确定',
|
|
|
|
|
cancelText: '取消',
|
|
|
|
|
onOk: async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res: any = await deleteInstance(record.id);
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
Message.success('删除成功');
|
|
|
|
|
fetchInstanceList(); // 刷新列表
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Message.error(res.msg || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除实例失败:', error);
|
|
|
|
|
Message.error('删除失败');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 刷新依赖
|
|
|
|
|
const handleRefresh = async (record) => {
|
|
|
|
|
// 添加到刷新中的集合
|
|
|
|
|
setRefreshingIds(prev => new Set(prev).add(record.id));
|
|
|
|
|
|
|
|
|
|
// 显示提示消息
|
|
|
|
|
const messageKey = `refresh_${record.id}`;
|
|
|
|
|
Message.loading({
|
|
|
|
|
id: messageKey,
|
|
|
|
|
content: '正在刷新依赖,请稍候...',
|
|
|
|
|
duration: 0 // 不自动关闭
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res: any = await refreshInstanceDependency(record.id);
|
|
|
|
|
|
|
|
|
|
// 关闭 loading 消息
|
|
|
|
|
Message.clear();
|
|
|
|
|
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
Message.success('依赖刷新成功');
|
|
|
|
|
// 刷新列表
|
|
|
|
|
fetchInstanceList();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Message.error(res.msg || '依赖刷新失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// 关闭 loading 消息
|
|
|
|
|
Message.clear();
|
|
|
|
|
console.error('刷新依赖失败:', error);
|
|
|
|
|
Message.error('依赖刷新失败,请稍后重试');
|
|
|
|
|
} finally {
|
|
|
|
|
// 从刷新中的集合移除
|
|
|
|
|
setRefreshingIds(prev => {
|
|
|
|
|
const newSet = new Set(prev);
|
|
|
|
|
newSet.delete(record.id);
|
|
|
|
|
return newSet;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 打开日志 Modal
|
|
|
|
|
const handleOpenLog = (record) => {
|
|
|
|
|
setCurrentInstance(record);
|
|
|
|
|
setLogModalVisible(true);
|
|
|
|
|
// 打开时自动查询一次
|
|
|
|
|
fetchLog(record.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 查询日志
|
|
|
|
|
const fetchLog = async (instanceId?: string) => {
|
|
|
|
|
const id = instanceId || currentInstance?.id;
|
|
|
|
|
if (!id) return;
|
|
|
|
|
|
|
|
|
|
setLogLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const params: any = {
|
|
|
|
|
id,
|
|
|
|
|
tail: logLines
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 如果选择了时间范围,转换为时间戳(秒)
|
|
|
|
|
if (logTimeRange && logTimeRange.length === 2) {
|
|
|
|
|
params.since = dayjs(logTimeRange[0]).unix();
|
|
|
|
|
params.until = dayjs(logTimeRange[1]).unix();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res: any = await getInstanceLog(params);
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
const logs = res.data.result || [];
|
|
|
|
|
if (Array.isArray(logs) && logs.length > 0) {
|
|
|
|
|
// 将字符串数组转换为带行号的文本
|
|
|
|
|
const logText = logs.map((line, index) => `${index + 1} ${line}`).join('\n');
|
|
|
|
|
setLogContent(logText);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
setLogContent('暂无日志');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Message.error(res.msg || '获取日志失败');
|
|
|
|
|
setLogContent('');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取日志失败:', error);
|
|
|
|
|
Message.error('获取日志失败');
|
|
|
|
|
setLogContent('');
|
|
|
|
|
} finally {
|
|
|
|
|
setLogLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 重置日志查询条件
|
|
|
|
|
const handleResetLog = () => {
|
|
|
|
|
setLogTimeRange([
|
|
|
|
|
dayjs().startOf('day'), // 当天0点
|
|
|
|
|
dayjs().endOf('day') // 当天24点
|
|
|
|
|
]);
|
|
|
|
|
setLogLines(100);
|
|
|
|
|
setLogContent('');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 关闭日志 Modal
|
|
|
|
|
const handleCloseLogModal = () => {
|
|
|
|
|
setLogModalVisible(false);
|
|
|
|
|
setCurrentInstance(null);
|
|
|
|
|
handleResetLog();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const columns: TableColumnProps[] = [
|
|
|
|
|
{
|
|
|
|
|
title: '#',
|
|
|
|
|
@ -123,11 +295,20 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
render: (col, record, index) => {
|
|
|
|
|
// 根据运行状态判断显示启动还是停止按钮
|
|
|
|
|
const isRunning = record.runStatus === runStatusConstant.RUN || record.runStatus === runStatusConstant.HEALTHY;
|
|
|
|
|
const isRefreshing = refreshingIds.has(record.id);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles['table-handle-box']}>
|
|
|
|
|
<Space size={20}>
|
|
|
|
|
<Button type="text">运行日志</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
onClick={() => handleRefresh(record)}
|
|
|
|
|
loading={isRefreshing}
|
|
|
|
|
disabled={isRefreshing}
|
|
|
|
|
>
|
|
|
|
|
刷新依赖
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="text" onClick={() => handleOpenLog(record)}>运行日志</Button>
|
|
|
|
|
<Button type="text">实例配置</Button>
|
|
|
|
|
<Button type="text"
|
|
|
|
|
icon={<img
|
|
|
|
|
@ -158,11 +339,15 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
onClick={() => handleStart(record)}
|
|
|
|
|
>启动</Button>
|
|
|
|
|
)}
|
|
|
|
|
<Button type="text"
|
|
|
|
|
{!isRunning && (
|
|
|
|
|
<Button
|
|
|
|
|
type="text"
|
|
|
|
|
icon={<img
|
|
|
|
|
src={'/icons/deleteIcon.png'}
|
|
|
|
|
style={{ width: 16, height: 16, marginRight: 5, verticalAlign: 'middle' }} />}
|
|
|
|
|
onClick={() => handleRemove(record)}
|
|
|
|
|
>删除</Button>
|
|
|
|
|
)}
|
|
|
|
|
</Space>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
@ -171,6 +356,7 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Table
|
|
|
|
|
loading={loading}
|
|
|
|
|
pagination={false}
|
|
|
|
|
@ -178,6 +364,65 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
|
|
|
|
|
columns={columns}
|
|
|
|
|
data={data}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* 运行日志 Modal */}
|
|
|
|
|
<Modal
|
|
|
|
|
title={`运行日志 - ${currentInstance?.name || currentInstance?.identifier || ''}`}
|
|
|
|
|
visible={logModalVisible}
|
|
|
|
|
onCancel={handleCloseLogModal}
|
|
|
|
|
footer={null}
|
|
|
|
|
style={{ width: '70%' }}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ marginBottom: 16 }}>
|
|
|
|
|
<Grid.Row gutter={12} align="center">
|
|
|
|
|
<Grid.Col span={10}>
|
|
|
|
|
<Space>
|
|
|
|
|
<span>查询时间:</span>
|
|
|
|
|
<RangePicker
|
|
|
|
|
showTime
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
placeholder={['开始时间', '结束时间']}
|
|
|
|
|
value={logTimeRange}
|
|
|
|
|
onChange={setLogTimeRange}
|
|
|
|
|
/>
|
|
|
|
|
</Space>
|
|
|
|
|
</Grid.Col>
|
|
|
|
|
<Grid.Col span={12}>
|
|
|
|
|
<Space>
|
|
|
|
|
<span>日志行数:</span>
|
|
|
|
|
<InputNumber
|
|
|
|
|
placeholder="日志行数"
|
|
|
|
|
value={logLines}
|
|
|
|
|
onChange={setLogLines}
|
|
|
|
|
min={1}
|
|
|
|
|
max={10000}
|
|
|
|
|
style={{ width: 150 }}
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
onClick={() => fetchLog()}
|
|
|
|
|
loading={logLoading}
|
|
|
|
|
>
|
|
|
|
|
查询
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleResetLog}>重置</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</Grid.Col>
|
|
|
|
|
</Grid.Row>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<TextArea
|
|
|
|
|
value={logContent}
|
|
|
|
|
readOnly
|
|
|
|
|
placeholder="暂无日志"
|
|
|
|
|
style={{
|
|
|
|
|
minHeight: 400,
|
|
|
|
|
fontFamily: 'monospace',
|
|
|
|
|
fontSize: 13
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|