feat(component): 增强组件部署功能

master
钟良源 3 months ago
parent c096108124
commit 8f46a0adf3

@ -5,22 +5,27 @@ const urlPrefix = '/api/v1/bpms-workbench';
// 新增实例
export function createInstance(params) {
return axios.post(`${urlPrefix}/componentInstance/create`, { params });
return axios.post(`${urlPrefix}/componentInstance/create`, params);
}
// 停止实例
export function stopInstance(params) {
return axios.get(`${urlPrefix}/componentInstance/stop`, { params });
export function stopInstance(id) {
return axios.get(`${urlPrefix}/componentInstance/stop`, { params: { id } });
}
// 启动实例
export function startInstance(params) {
return axios.get(`${urlPrefix}/componentInstance/start`, { params });
export function startInstance(id) {
return axios.get(`${urlPrefix}/componentInstance/start`, { params: { id } });
}
// 删除实例
export function deleteInstance(id) {
return axios.get(`${urlPrefix}/componentInstance/remove`, { params: { id } });
}
// 刷新实例依赖
export function refreshInstanceDependency(params) {
return axios.get(`${urlPrefix}/componentInstance/refreshDeps`, { params });
export function refreshInstanceDependency(id) {
return axios.get(`${urlPrefix}/componentInstance/refreshDeps`, { params: { id } });
}
// 组件实例日志

@ -156,11 +156,11 @@ const AddModal = ({ addItem, visible, setVisible }) => {
confirmLoading={loading}
autoFocus={false}
focusLock={true}
style={{ width: '35%' }}
style={{ width: '40%' }}
>
<Form form={form} autoComplete="off">
<Grid.Row gutter={8}>
<Grid.Col span={12}>
<Grid.Col span={24}>
<FormItem label="部署方式:">
<Select
placeholder="请选择"
@ -195,7 +195,7 @@ const AddModal = ({ addItem, visible, setVisible }) => {
{/* </Select>*/}
{/* </FormItem>*/}
{/*</Grid.Col>*/}
<Grid.Col span={12}>
<Grid.Col span={24}>
<FormItem
label="主机:"
field="deployEnvId"
@ -213,7 +213,7 @@ const AddModal = ({ addItem, visible, setVisible }) => {
>
<Select
placeholder="请选择主机"
style={{ width: '90%' }}
style={{ width: '60%' }}
allowClear={true}
>
{hostOptions.map((option, index) => (

@ -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"
icon={<img
src={'/icons/deleteIcon.png'}
style={{ width: 16, height: 16, marginRight: 5, verticalAlign: 'middle' }} />}
></Button>
{!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,13 +356,73 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
];
return (
<Table
loading={loading}
pagination={false}
border={false}
columns={columns}
data={data}
/>
<>
<Table
loading={loading}
pagination={false}
border={false}
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>
</>
);
};

Loading…
Cancel
Save