流程定义

This commit is contained in:
戚辰先生 2024-12-10 22:30:46 +08:00
parent 559e335d9a
commit a5793f5367
8 changed files with 225 additions and 20 deletions

View File

@ -0,0 +1,73 @@
import React, { useEffect, useRef, useState } from 'react';
import { ILogData } from '../types';
import { logStreamService } from '../service';
interface LogViewerProps {
processInstanceId: string;
}
const LogViewer: React.FC<LogViewerProps> = ({ processInstanceId }) => {
const [logs, setLogs] = useState<ILogData[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string>();
const scrollRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleLog = (log: ILogData) => {
setLogs((prevLogs) => [...prevLogs, log]);
// 自动滚动到底部
setTimeout(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, 0);
};
const handleError = (error: any) => {
setError('日志流连接已断开');
setIsConnected(false);
};
setIsConnected(true);
logStreamService.connect(processInstanceId, handleLog, handleError);
return () => {
logStreamService.disconnect();
setIsConnected(false);
};
}, [processInstanceId]);
return (
<div className="w-full h-[600px] p-4 border rounded-lg shadow-sm bg-white">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
<div className="flex items-center gap-2">
<span className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
<span className="text-sm text-gray-500">
{isConnected ? '已连接' : '未连接'}
</span>
</div>
</div>
<div
ref={scrollRef}
className="h-[500px] w-full rounded-md border p-4 overflow-auto bg-gray-50"
>
<div className="font-mono text-sm">
{logs.map((log, index) => (
<div
key={index}
className={`mb-1 ${log.type === 'STDERR' ? 'text-red-500' : 'text-gray-800'}`}
>
<span className="text-gray-400">[{new Date(log.timestamp).toLocaleTimeString()}] </span>
{log.data}
</div>
))}
{error && <div className="text-red-500 mt-2">{error}</div>}
</div>
</div>
</div>
);
};
export default LogViewer;

View File

@ -0,0 +1,19 @@
import React from 'react';
import { useParams } from 'react-router-dom';
import LogViewer from './components/LogViewer';
const LogStreamPage: React.FC = () => {
const { processInstanceId } = useParams<{ processInstanceId: string }>();
if (!processInstanceId) {
return <div className="p-4">ID不能为空</div>;
}
return (
<div className="p-4">
<LogViewer processInstanceId={processInstanceId} />
</div>
);
};
export default LogStreamPage;

View File

@ -0,0 +1,54 @@
import { ILogData } from './types';
const LOG_STREAM_URL = '/api/v1/workflow/instance/log';
export class LogStreamService {
private eventSource: EventSource | null = null;
connect(processInstanceId: string, onMessage: (log: ILogData) => void, onError?: (error: any) => void) {
if (this.eventSource) {
this.disconnect();
}
const token = localStorage.getItem('token');
const url = new URL(`${window.location.origin}${LOG_STREAM_URL}/dc65ecc5-b6f3-11ef-840c-326a31fc0fe1`);
if (token) {
url.searchParams.append('token', token);
}
this.eventSource = new EventSource(url.toString());
this.eventSource.addEventListener('STDOUT', (event) => {
onMessage({
type: 'STDOUT',
data: event.data,
timestamp: new Date().toISOString()
});
});
this.eventSource.addEventListener('STDERR', (event) => {
onMessage({
type: 'STDERR',
data: event.data,
timestamp: new Date().toISOString()
});
});
this.eventSource.onerror = (error) => {
if (onError) {
onError(error);
}
this.disconnect();
};
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
}
export const logStreamService = new LogStreamService();

View File

@ -0,0 +1,18 @@
import { BaseResponse } from '@/types/base';
// 日志事件类型
export type LogEventType = 'STDOUT' | 'STDERR';
// 日志数据接口
export interface ILogData {
type: LogEventType;
data: string;
timestamp: string;
}
// 日志流状态
export interface ILogStreamState {
logs: ILogData[];
isConnected: boolean;
error?: string;
}

View File

@ -104,6 +104,23 @@ export const getCurrentUserMenus = async () => {
enabled: true,
createBy: "system",
updateBy: "system"
},
{
id: -24,
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
version: 0,
name: "日志流",
path: "/workflow/log-stream/:processInstanceId",
component: "/pages/LogStream/index",
icon: "file-text",
type: MenuTypeEnum.MENU,
parentId: -2,
sort: 3,
hidden: false,
enabled: true,
createBy: "system",
updateBy: "system"
}
]
};

View File

@ -146,11 +146,10 @@ const WorkflowDefinitionList: React.FC = () => {
// 表格列定义
const columns = [
{
title: '编码',
dataIndex: 'code',
key: 'code',
width: 150,
ellipsis: true,
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: '名称',
@ -160,16 +159,16 @@ const WorkflowDefinitionList: React.FC = () => {
ellipsis: true,
},
{
title: '描述',
dataIndex: 'description',
key: 'description',
width: 200,
title: '标识',
dataIndex: 'key',
key: 'key',
width: 150,
ellipsis: true,
},
{
title: '版本',
dataIndex: 'version',
key: 'version',
dataIndex: 'flowVersion',
key: 'flowVersion',
width: 80,
},
{
@ -188,13 +187,11 @@ const WorkflowDefinitionList: React.FC = () => {
},
},
{
title: '是否启用',
dataIndex: 'enabled',
key: 'enabled',
width: 100,
render: (enabled: boolean) => (
<Tag color={enabled ? 'success' : 'default'}>{enabled ? '已启用' : '已禁用'}</Tag>
),
title: '描述',
dataIndex: 'description',
key: 'description',
width: 200,
ellipsis: true,
},
{
title: '创建时间',
@ -202,6 +199,24 @@ const WorkflowDefinitionList: React.FC = () => {
key: 'createTime',
width: 180,
},
{
title: '创建人',
dataIndex: 'createBy',
key: 'createBy',
width: 100,
},
{
title: '更新时间',
dataIndex: 'updateTime',
key: 'updateTime',
width: 180,
},
{
title: '更新人',
dataIndex: 'updateBy',
key: 'updateBy',
width: 100,
},
{
title: '操作',
key: 'action',
@ -341,7 +356,7 @@ const WorkflowDefinitionList: React.FC = () => {
label="名称"
rules={[{required: true, message: '请输入流程名称'}]}
>
<Input placeholder="请输入流名称"/>
<Input placeholder="请输入流<EFBFBD><EFBFBD><EFBFBD>名称"/>
</Form.Item>
<Form.Item name="description" label="描述">
<Input.TextArea placeholder="请输入流程描述"/>

View File

@ -9,7 +9,7 @@ import {
NodeTypeQuery
} from './types';
const WORKFLOW_DEFINITION_URL = '/api/v1/workflow-definitions';
const WORKFLOW_DEFINITION_URL = '/api/v1/workflow/definition';
const NODE_TYPE_URL = '/api/v1/node-types';
// 创建工作流定义

View File

@ -37,6 +37,7 @@ const WorkflowDefinitionEdit = lazy(() => import('../pages/Workflow/Definition/E
const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance'));
const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor'));
const FlowDesigner = lazy(() => import('../pages/Workflow/Definition/Designer'));
const LogStreamPage = lazy(() => import('../pages/LogStream'));
// 创建路由
const router = createBrowserRouter([
@ -164,6 +165,14 @@ const router = createBrowserRouter([
<WorkflowMonitor/>
</Suspense>
)
},
{
path: 'log-stream/:processInstanceId',
element: (
<Suspense fallback={<LoadingComponent/>}>
<LogStreamPage/>
</Suspense>
)
}
]
},