diff --git a/src/const/isdp/componentDeploy.ts b/src/const/isdp/componentDeploy.ts index 19f1003..bd6aaff 100644 --- a/src/const/isdp/componentDeploy.ts +++ b/src/const/isdp/componentDeploy.ts @@ -67,35 +67,44 @@ export const startStatusMap = startStatusDic.reduce((obj, item) => { /** * 组件编译状态 - * BUILT_DOING, BUILT_IGNORE,BUILT_DOCKER,BUILT_FAIL + * BUILT_IDLE 空闲中 + * BUILT_RUNNING 编译中 + * BUILT_SUCCESS 编译成功 + * BUILT_FAILED 编译失败 + * BUILT_CANCELED 编译取消 */ export const compileStatusConstant = { - BUILT_DOING: 'built-doing', - BUILT_IGNORE: 'built-ignore', - BUILT_DOCKER: 'built-docker', - BUILT_FAIL: 'built-fail', - // 前端单独使用 - NOT_COMPILED: 'not-compiled', - get(type) { - return compileStatusConstant[type] || compileStatusConstant.NOT_COMPILED; - } + BUILT_IDLE: 'idle', + BUILT_RUNNING: 'running', + BUILT_SUCCESS: 'success', + BUILT_FAILED: 'failed', + BUILT_CANCELED: 'canceled' }; export const compileStatusDic = [ { - label: '编译中', - value: compileStatusConstant.BUILT_DOING + label: '空闲中', + value: compileStatusConstant.BUILT_IDLE, + color: 'blue' }, { - label: '免编译', - value: compileStatusConstant.BUILT_IGNORE + label: '编译中', + value: compileStatusConstant.BUILT_RUNNING, + color: 'gray' }, { - label: '已编译', - value: compileStatusConstant.BUILT_DOCKER + label: '编译成功', + value: compileStatusConstant.BUILT_SUCCESS, + color: 'green' }, { label: '编译失败', - value: compileStatusConstant.BUILT_FAIL + value: compileStatusConstant.BUILT_FAILED, + color: 'red' + }, + { + label: '编译取消', + value: compileStatusConstant.BUILT_CANCELED, + color: 'orange' } ]; export const compileStatusMap = startStatusDic.reduce((obj, item) => { diff --git a/src/hooks/useWebSocket.ts b/src/hooks/useWebSocket.ts index a5329a2..2549639 100644 --- a/src/hooks/useWebSocket.ts +++ b/src/hooks/useWebSocket.ts @@ -58,7 +58,8 @@ const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { const messageStr = typeof message === 'string' ? message : JSON.stringify(message); wsRef.current.send(messageStr); - } else { + } + else { console.warn('WebSocket is not connected. Cannot send message.'); } }, []); @@ -67,25 +68,27 @@ const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => { const connect = useCallback((url: string) => { // 先断开现有连接 disconnect(); - + urlRef.current = url; - + try { const ws = new WebSocket(url); wsRef.current = ws; - + ws.onopen = (event) => { + console.log('WebSocket opened'); setReadyState(WebSocket.OPEN); setIsConnected(true); reconnectAttemptsRef.current = 0; onOpen?.(event); }; - + ws.onclose = (event) => { + console.log('WebSocket closed'); setReadyState(WebSocket.CLOSED); setIsConnected(false); onClose?.(event); - + // 处理重连 if (reconnectAttemptsRef.current < maxReconnectAttempts) { reconnectAttemptsRef.current++; @@ -94,16 +97,18 @@ const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => { }, reconnectInterval); } }; - + ws.onerror = (event) => { + console.log('WebSocket error', event); setIsConnected(false); onError?.(event); }; - + ws.onmessage = (event) => { + console.log('WebSocket message', event); onMessage?.(event); }; - + setReadyState(WebSocket.CONNECTING); } catch (error) { console.error('Failed to create WebSocket connection:', error); diff --git a/src/pages/componentDevelopment/componentDeployment/collapseList.tsx b/src/pages/componentDevelopment/componentDeployment/collapseList.tsx index 8bef740..688f825 100644 --- a/src/pages/componentDevelopment/componentDeployment/collapseList.tsx +++ b/src/pages/componentDevelopment/componentDeployment/collapseList.tsx @@ -25,9 +25,25 @@ interface CollapseListProps { searchKeyword?: string; runStatus?: string; componentClassify?: string; + compileLogsMap: Record; + compileStatusMap: Record; + subscribeInstanceLog: (instanceId: string) => void; + unsubscribeInstanceLog: (instanceId: string) => void; + updateCompileStatus: (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => void; + isWsConnected: boolean; } -const CollapseList: React.FC = ({ searchKeyword, runStatus, componentClassify }) => { +const CollapseList: React.FC = ({ + searchKeyword, + runStatus, + componentClassify, + compileLogsMap, + compileStatusMap, + subscribeInstanceLog, + unsubscribeInstanceLog, + updateCompileStatus, + isWsConnected +}) => { const [collapses, setCollapses] = useState([]); const [visible, setVisible] = useState(false); const [showOffSaleModal, setShowOffSaleModal] = useState(false); @@ -258,7 +274,15 @@ const CollapseList: React.FC = ({ searchKeyword, runStatus, c name={index.toString()} extra={extraNode(item)} > - + ))} diff --git a/src/pages/componentDevelopment/componentDeployment/index.tsx b/src/pages/componentDevelopment/componentDeployment/index.tsx index 1e3909a..7017279 100644 --- a/src/pages/componentDevelopment/componentDeployment/index.tsx +++ b/src/pages/componentDevelopment/componentDeployment/index.tsx @@ -1,11 +1,13 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import styles from './style/index.module.less'; -import { Button, Input, Modal, Radio, Space, Select } from '@arco-design/web-react'; +import { Button, Input, Modal, Radio, Space, Select, Message } from '@arco-design/web-react'; import { IconSearch } from '@arco-design/web-react/icon'; import CollapseList from './collapseList'; import { startStatusConstant } from '@/const/isdp/componentDeploy'; import ConfigTutorial from '@/pages/componentDevelopment/componentEnv/configTutorial'; import { getComponentClassify } from '@/api/componentClassify'; +import useWebSocket from '@/hooks/useWebSocket'; +import { getToken } from '@/utils/auth'; const ComponentDeployment = () => { const [searchKeyword, setSearchKeyword] = useState(''); @@ -15,6 +17,58 @@ const ComponentDeployment = () => { const [selectedStatus, setSelectedStatus] = useState(undefined); const [tutorialVisible, setTutorialVisible] = useState(false); + // 编译日志相关状态 - 按实例 ID 分组存储 + const [compileLogsMap, setCompileLogsMap] = useState>({}); + // 编译状态管理 - 按实例 ID 存储状态 + const [compileStatusMap, setCompileStatusMap] = useState>({}); + const currentCompileIdRef = useRef(null); + + // WebSocket 连接 - 用于接收编译日志 + const { connect: wsConnect, disconnect: wsDisconnect, sendMessage, isConnected } = useWebSocket({ + onMessage: (event) => { + const id = currentCompileIdRef.current; + if (!id) return; + + try { + const data = typeof event.data === 'string' ? event.data : JSON.stringify(event.data); + setCompileLogsMap(prev => ({ + ...prev, + [id]: prev[id] ? prev[id] + '\n' + data : data + })); + } catch (error) { + console.error('解析 WebSocket 消息失败:', error); + } + }, + onOpen: () => { + console.log('编译日志 WebSocket 连接成功'); + }, + onError: (error) => { + console.error('编译日志 WebSocket 错误:', error); + }, + onClose: () => { + console.log('编译日志 WebSocket 连接已关闭'); + } + }); + + // 页面挂载时连接 WebSocket + useEffect(() => { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + let wsUrl; + + if (window.location.host.includes('localhost')) { + wsUrl = `${process.env.NEXT_PUBLIC_DEV_SOCKET_HOST}/ws/v1/bpms-workbench/instance-log?Authorization=Bearer ${getToken()}`; + } else { + wsUrl = `${protocol}//${host}/ws/v1/bpms-workbench/instance-log?Authorization=Bearer ${getToken()}`; + } + + wsConnect(wsUrl); + + return () => { + wsDisconnect(); + }; + }, []); + useEffect(() => { const timer = setTimeout(() => setDebouncedKeyword(searchKeyword), 500); return () => clearTimeout(timer); @@ -28,6 +82,54 @@ const ComponentDeployment = () => { }); }, []); + // 订阅实例编译日志 + const subscribeInstanceLog = (instanceId: string) => { + currentCompileIdRef.current = instanceId; + + // 清空该实例之前的日志 + setCompileLogsMap(prev => ({ + ...prev, + [instanceId]: '' + })); + + // 设置编译状态为 running + setCompileStatusMap(prev => ({ + ...prev, + [instanceId]: 'running' + })); + + if (isConnected) { + sendMessage({ + instanceId, + action: 'subscribe' + }); + } else { + Message.warning('WebSocket 未连接,无法实时接收日志'); + } + }; + + // 取消订阅实例编译日志 + const unsubscribeInstanceLog = (instanceId: string) => { + if (isConnected && instanceId) { + sendMessage({ + instanceId, + action: 'unsubscribe' + }); + } + + if (currentCompileIdRef.current === instanceId) { + currentCompileIdRef.current = null; + } + }; + + // 更新编译状态 + const updateCompileStatus = (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => { + setCompileStatusMap(prev => ({ + ...prev, + [instanceId]: status + })); + }; + // 状态选项配置 const statusOptions = [ { label: '全部', value: undefined }, @@ -88,8 +190,17 @@ const ComponentDeployment = () => {
- +
diff --git a/src/pages/componentDevelopment/componentDeployment/listNode.tsx b/src/pages/componentDevelopment/componentDeployment/listNode.tsx index 74f6e62..100a493 100644 --- a/src/pages/componentDevelopment/componentDeployment/listNode.tsx +++ b/src/pages/componentDevelopment/componentDeployment/listNode.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { Button, Space, @@ -22,20 +22,41 @@ import { refreshInstanceDependency, getComponentResource } from '@/api/componentInstance'; -import { runStatusConstant, runStatusDic, runTypeConstant, runTypeDic } from '@/const/isdp/componentDeploy'; +import { + compileStatusDic, + runStatusConstant, + runStatusDic, + runTypeConstant, + runTypeDic +} from '@/const/isdp/componentDeploy'; import dayjs from 'dayjs'; import EditInstanceModal from './editInstanceModal'; import EnvConfigModal from './envConfigModal'; import ResourceMonitorModal from '@/components/ResourceMonitorModal'; +import { getToken } from '@/utils/auth'; const { RangePicker } = DatePicker; const { TextArea } = Input; interface ListNodeProps { componentData: any; // 组件数据 + compileLogsMap: Record; + compileStatusMap: Record; + subscribeInstanceLog: (instanceId: string) => void; + unsubscribeInstanceLog: (instanceId: string) => void; + updateCompileStatus: (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => void; + isWsConnected: boolean; } -const ListNode: React.FC = ({ componentData }) => { +const ListNode: React.FC = ({ + componentData, + compileLogsMap, + compileStatusMap, + subscribeInstanceLog, + unsubscribeInstanceLog, + updateCompileStatus, + isWsConnected + }) => { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [refreshingIds, setRefreshingIds] = useState>(new Set()); // 记录正在刷新的实例ID @@ -65,6 +86,10 @@ const ListNode: React.FC = ({ componentData }) => { const [resourceModalVisible, setResourceModalVisible] = useState(false); const [resourceData, setResourceData] = useState(null); + // 编译日志 Modal 相关状态 + const [compileModalVisible, setCompileModalVisible] = useState(false); + const [compileInstance, setCompileInstance] = useState(null); + // 获取实例列表 const fetchInstanceList = async () => { if (!componentData?.identifier) return; @@ -164,35 +189,20 @@ const ListNode: React.FC = ({ componentData }) => { // 添加到刷新中的集合 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 && res.data) { - Message.success('依赖刷新成功'); - // 刷新列表 - fetchInstanceList(); + if (res.code === 200) { + Message.success('编译任务已提交'); } else { - Message.error(res.msg || '依赖刷新失败'); + Message.error(res.msg || '编译失败'); } } catch (error) { - // 关闭 loading 消息 - Message.clear(); console.error('编译组件失败:', error); - Message.error('依赖刷新失败,请稍后重试'); + Message.error('编译组件失败,请稍后重试'); } finally { - // 从刷新中的集合移除 setRefreshingIds(prev => { const newSet = new Set(prev); newSet.delete(record.id); @@ -201,6 +211,27 @@ const ListNode: React.FC = ({ componentData }) => { } }; + // 打开编译日志 Modal + const handleOpenCompileLog = (record) => { + setCompileInstance(record); + setCompileModalVisible(true); + + // 通过父组件传递的方法订阅该实例的编译日志 + subscribeInstanceLog(record.id); + }; + + // 关闭编译日志 Modal + const handleCloseCompileModal = () => { + setCompileModalVisible(false); + + // 取消订阅当前实例的日志 + if (compileInstance?.id) { + unsubscribeInstanceLog(compileInstance.id); + } + + setCompileInstance(null); + }; + // 打开日志 Modal const handleOpenLog = (record) => { setCurrentInstance(record); @@ -319,7 +350,8 @@ const ListNode: React.FC = ({ componentData }) => { if (res.code === 200 && res.data) { setResourceData(res.data); setResourceModalVisible(true); - } else { + } + else { Message.error(res.msg || '获取资源信息失败'); } } catch (error) { @@ -338,7 +370,17 @@ const ListNode: React.FC = ({ componentData }) => { { title: '组件标识', dataIndex: 'identifier', - align: 'center' + align: 'center', + render: (identifier, record) => { + return ( + handleOpenEdit(record)} + > + {identifier} + + ); + } }, { title: '实例名称', @@ -355,7 +397,7 @@ const ListNode: React.FC = ({ componentData }) => { } }, { - title: '运行状态', + title: '实例运行状态', dataIndex: 'runStatus', align: 'center', render: (runStatus) => { @@ -363,6 +405,15 @@ const ListNode: React.FC = ({ componentData }) => { return item ? {item.label} : '-'; } }, + { + title: '实例编译状态', + dataIndex: 'currentTaskStatus', + align: 'center', + render: (currentTaskStatus) => { + const item = compileStatusDic.find(d => d.value === currentTaskStatus); + return item ? {item.label} : '-'; + } + }, { title: '创建时间', dataIndex: 'createTime', @@ -381,6 +432,11 @@ const ListNode: React.FC = ({ componentData }) => { const isRefreshing = refreshingIds.has(record.id); const isLocalRun = record.runType === 'LOCAL'; + // 获取当前实例的编译状态 + const compileStatus = compileStatusMap[record.id] || 'idle'; + // 编译状态为 running、failed、canceled 时不显示启停按钮 + const hideStartStopButtons = compileStatus === 'running' || compileStatus === 'failed' || compileStatus === 'canceled'; + return (
@@ -394,6 +450,12 @@ const ListNode: React.FC = ({ componentData }) => { > 编译组件 + )} @@ -403,36 +465,34 @@ const ListNode: React.FC = ({ componentData }) => { {!isLocalRun && isRunning && ( )} - + ) : ( + - {isRunning ? ( - - ) : ( - + onClick={() => handleStart(record)} + loading={startingId === record.id && startLoading} + >启动 + )} + )} {!isRunning && (