Merge branch 'master' into production

production
钟良源 1 month ago
commit c6937367c4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

@ -67,35 +67,51 @@ 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
* BUILT_NEVER
*/
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',
BUILT_NEVER: 'never',
};
export const compileStatusDic = [
{
label: '编译中',
value: compileStatusConstant.BUILT_DOING
label: '未编译',
value: compileStatusConstant.BUILT_NEVER,
color: 'gray'
},
{
label: '免编译',
value: compileStatusConstant.BUILT_IGNORE
label: '空闲中',
value: compileStatusConstant.BUILT_IDLE,
color: 'blue'
},
{
label: '已编译',
value: compileStatusConstant.BUILT_DOCKER
label: '编译中',
value: compileStatusConstant.BUILT_RUNNING,
color: 'blue'
},
{
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) => {

@ -20,7 +20,7 @@ interface WebSocketHook {
const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => {
const {
reconnectInterval = 3000,
maxReconnectAttempts = 0,
maxReconnectAttempts = 99,
onOpen,
onClose,
onError,
@ -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,25 @@ 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) => {
setReadyState(WebSocket.OPEN);
setIsConnected(true);
reconnectAttemptsRef.current = 0;
onOpen?.(event);
};
ws.onclose = (event) => {
setReadyState(WebSocket.CLOSED);
setIsConnected(false);
onClose?.(event);
// 处理重连
if (reconnectAttemptsRef.current < maxReconnectAttempts) {
reconnectAttemptsRef.current++;
@ -94,16 +95,16 @@ const useWebSocket = (options: WebSocketOptions = {}): WebSocketHook => {
}, reconnectInterval);
}
};
ws.onerror = (event) => {
setIsConnected(false);
onError?.(event);
};
ws.onmessage = (event) => {
onMessage?.(event);
};
setReadyState(WebSocket.CONNECTING);
} catch (error) {
console.error('Failed to create WebSocket connection:', error);

@ -25,9 +25,25 @@ interface CollapseListProps {
searchKeyword?: string;
runStatus?: string;
componentClassify?: string;
compileLogsMap: Record<string, string>;
compileStatusMap: Record<string, 'idle' | 'running' | 'success' | 'failed' | 'canceled'>;
subscribeInstanceLog: (instanceId: string) => void;
unsubscribeInstanceLog: (instanceId: string) => void;
updateCompileStatus: (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => void;
isWsConnected: boolean;
}
const CollapseList: React.FC<CollapseListProps> = ({ searchKeyword, runStatus, componentClassify }) => {
const CollapseList: React.FC<CollapseListProps> = ({
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<CollapseListProps> = ({ searchKeyword, runStatus, c
name={index.toString()}
extra={extraNode(item)}
>
<ListNode componentData={item} />
<ListNode
componentData={item}
compileLogsMap={compileLogsMap}
compileStatusMap={compileStatusMap}
subscribeInstanceLog={subscribeInstanceLog}
unsubscribeInstanceLog={unsubscribeInstanceLog}
updateCompileStatus={updateCompileStatus}
isWsConnected={isWsConnected}
/>
</CollapseItem>
))}
</Collapse>

@ -1,11 +1,14 @@
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';
import { isJSON } from '@/utils/common';
const ComponentDeployment = () => {
const [searchKeyword, setSearchKeyword] = useState('');
@ -15,6 +18,74 @@ const ComponentDeployment = () => {
const [selectedStatus, setSelectedStatus] = useState<string | undefined>(undefined);
const [tutorialVisible, setTutorialVisible] = useState(false);
// 编译日志相关状态 - 按实例 ID 分组存储
const [compileLogsMap, setCompileLogsMap] = useState<Record<string, string>>({});
// 编译状态管理 - 按实例 ID 存储状态
const [compileStatusMap, setCompileStatusMap] = useState<Record<string, 'idle' | 'running' | 'success' | 'failed' | 'canceled'>>({});
const compileLogSequenceRef = useRef<Record<string, number>>({});
const currentCompileIdRef = useRef<string | null>(null);
// WebSocket 连接 - 用于接收编译日志
const { connect: wsConnect, disconnect: wsDisconnect, sendMessage, isConnected } = useWebSocket({
onMessage: (event) => {
const id = currentCompileIdRef.current;
if (!id) return;
try {
const currentSequence = (compileLogSequenceRef.current[id] || 0) + 1;
compileLogSequenceRef.current[id] = currentSequence;
if (isJSON(event.data)) {
const parseData = JSON.parse(event.data);
const message = parseData.message || parseData.line || event.data;
const sequenceMessage = `${currentSequence}. ${message}`;
setCompileLogsMap(prev => ({
...prev,
[id]: prev[id] ? prev[id] + '\n' + sequenceMessage : sequenceMessage
}));
}
else {
const data = `${currentSequence}. ${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 +99,57 @@ const ComponentDeployment = () => {
});
}, []);
// 订阅实例编译日志
const subscribeInstanceLog = (instanceId: string) => {
currentCompileIdRef.current = instanceId;
compileLogSequenceRef.current[instanceId] = 0;
// 清空该实例之前的日志
setCompileLogsMap(prev => ({
...prev,
[instanceId]: ''
}));
// 设置编译状态为 running
setCompileStatusMap(prev => ({
...prev,
[instanceId]: 'running'
}));
if (isConnected) {
sendMessage({
instanceId,
type: 'subscribe'
});
}
else {
Message.warning('WebSocket 未连接,无法实时接收日志');
}
};
// 取消订阅实例编译日志
const unsubscribeInstanceLog = (instanceId: string) => {
if (isConnected && instanceId) {
sendMessage({
instanceId,
type: '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 +210,17 @@ const ComponentDeployment = () => {
</Space>
</div>
<div className={styles['content']}>
<CollapseList searchKeyword={debouncedKeyword} runStatus={selectedStatus}
componentClassify={selectedClassify} />
<CollapseList
searchKeyword={debouncedKeyword}
runStatus={selectedStatus}
componentClassify={selectedClassify}
compileLogsMap={compileLogsMap}
compileStatusMap={compileStatusMap}
subscribeInstanceLog={subscribeInstanceLog}
unsubscribeInstanceLog={unsubscribeInstanceLog}
updateCompileStatus={updateCompileStatus}
isWsConnected={isConnected}
/>
</div>
</div>

@ -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<string, string>;
compileStatusMap: Record<string, 'idle' | 'running' | 'success' | 'failed' | 'canceled'>;
subscribeInstanceLog: (instanceId: string) => void;
unsubscribeInstanceLog: (instanceId: string) => void;
updateCompileStatus: (instanceId: string, status: 'idle' | 'running' | 'success' | 'failed' | 'canceled') => void;
isWsConnected: boolean;
}
const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
const ListNode: React.FC<ListNodeProps> = ({
componentData,
compileLogsMap,
compileStatusMap,
subscribeInstanceLog,
unsubscribeInstanceLog,
updateCompileStatus,
isWsConnected
}) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [refreshingIds, setRefreshingIds] = useState<Set<string>>(new Set()); // 记录正在刷新的实例ID
@ -65,6 +86,10 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
const [resourceModalVisible, setResourceModalVisible] = useState(false);
const [resourceData, setResourceData] = useState<any>(null);
// 编译日志 Modal 相关状态
const [compileModalVisible, setCompileModalVisible] = useState(false);
const [compileInstance, setCompileInstance] = useState<any>(null);
// 获取实例列表
const fetchInstanceList = async () => {
if (!componentData?.identifier) return;
@ -164,35 +189,20 @@ const ListNode: React.FC<ListNodeProps> = ({ 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<ListNodeProps> = ({ 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);
@ -278,7 +309,6 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
// 处理编辑实例确定
const handleEditOk = async (values: any) => {
try {
console.log('更新实例信息:', { ...editingInstance, ...values });
Message.success('更新成功');
setEditModalVisible(false);
setEditingInstance(null);
@ -319,7 +349,8 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
if (res.code === 200 && res.data) {
setResourceData(res.data);
setResourceModalVisible(true);
} else {
}
else {
Message.error(res.msg || '获取资源信息失败');
}
} catch (error) {
@ -338,7 +369,17 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
{
title: '组件标识',
dataIndex: 'identifier',
align: 'center'
align: 'center',
render: (identifier, record) => {
return (
<span
style={{ color: '#3491FA', cursor: 'pointer' }}
onClick={() => handleOpenEdit(record)}
>
{identifier}
</span>
);
}
},
{
title: '实例名称',
@ -355,7 +396,7 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
}
},
{
title: '运行状态',
title: '实例运行状态',
dataIndex: 'runStatus',
align: 'center',
render: (runStatus) => {
@ -363,6 +404,15 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
return item ? <Tag color={item.color}>{item.label}</Tag> : '-';
}
},
{
title: '实例编译状态',
dataIndex: 'currentTaskStatus',
align: 'center',
render: (currentTaskStatus) => {
const item = compileStatusDic.find(d => d.value === currentTaskStatus);
return item ? <Tag color={item.color}>{item.label}</Tag> : '-';
}
},
{
title: '创建时间',
dataIndex: 'createTime',
@ -381,6 +431,11 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
const isRefreshing = refreshingIds.has(record.id);
const isLocalRun = record.runType === 'LOCAL';
// 获取当前实例的编译状态
const compileStatus = compileStatusMap[record.currentTaskStatus] || 'idle';
// 编译状态为 running、failed、canceled 时不显示启停按钮
const hideStartStopButtons = compileStatus === 'running' || compileStatus === 'failed' || compileStatus === 'canceled';
return (
<div className={styles['table-handle-box']}>
<Space size={20}>
@ -394,45 +449,49 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
>
</Button>
<Button
type="text"
onClick={() => handleOpenCompileLog(record)}
>
</Button>
<Button type="text" onClick={() => handleOpenLog(record)}></Button>
</>
)}
{!isRunning && (
<Button type="text" onClick={() => handleOpenEnvConfig(record)}></Button>
)}
{!isLocalRun && isRunning && (
<Button type="text" onClick={() => handeViewResource(record)}></Button>
)}
<Button type="text"
onClick={() => handleOpenEdit(record)}
{/*{!isLocalRun && isRunning && (*/}
{/* <Button type="text" onClick={() => handeViewResource(record)}>组件资源</Button>*/}
{/*)}*/}
{!hideStartStopButtons && (
<>
{isRunning ? (
<Button
type="text"
style={{ color: 'rgb(var(--danger-6))' }}
icon={<img
src={'/icons/powerUpIcon.png'}
style={{
width: 16,
height: 16,
marginRight: 5,
verticalAlign: 'middle',
filter: 'invert(27%) sepia(96%) saturate(4392%) hue-rotate(348deg) brightness(95%) contrast(95%)'
}} />}
onClick={() => handleStop(record)}
></Button>
) : (
<Button
type="text"
icon={<img
src={'/icons/editIcon.png'}
src={'/icons/powerUpIcon.png'}
style={{ width: 16, height: 16, marginRight: 5, verticalAlign: 'middle' }} />}
></Button>
{isRunning ? (
<Button
type="text"
style={{ color: 'rgb(var(--danger-6))' }}
icon={<img
src={'/icons/powerUpIcon.png'}
style={{
width: 16,
height: 16,
marginRight: 5,
verticalAlign: 'middle',
filter: 'invert(27%) sepia(96%) saturate(4392%) hue-rotate(348deg) brightness(95%) contrast(95%)'
}} />}
onClick={() => handleStop(record)}
></Button>
) : (
<Button
type="text"
icon={<img
src={'/icons/powerUpIcon.png'}
style={{ width: 16, height: 16, marginRight: 5, verticalAlign: 'middle' }} />}
onClick={() => handleStart(record)}
loading={startingId === record.id && startLoading}
></Button>
onClick={() => handleStart(record)}
loading={startingId === record.id && startLoading}
></Button>
)}
</>
)}
{!isRunning && (
<Button
@ -518,6 +577,26 @@ const ListNode: React.FC<ListNodeProps> = ({ componentData }) => {
/>
</Modal>
{/* 编译日志 Modal */}
<Modal
title={`编译日志 - ${compileInstance?.name || compileInstance?.identifier || ''}`}
visible={compileModalVisible}
onCancel={handleCloseCompileModal}
footer={null}
style={{ width: '70%' }}
>
<TextArea
value={compileLogsMap[compileInstance?.id] || ''}
readOnly
placeholder="等待编译输出..."
style={{
minHeight: 400,
fontFamily: 'monospace',
fontSize: 13
}}
/>
</Modal>
{/* 编辑实例信息 Modal */}
<EditInstanceModal
visible={editModalVisible}

@ -111,7 +111,6 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
else {
// WebSocket连接前置
const res: any = await startTestCase(instance.id);
console.log('res:', res);
// 构建WebSocket URL根据你的实际后端配置调整
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';

@ -61,7 +61,7 @@ function Overview() {
const [loading, setLoading] = useState(true);
const t = useLocale(locale);
const userInfo = useSelector((state: any) => state.userInfo || {});
const userInfo = useSelector((state: any) => state.user?.userInfo || {});
const fetchData = async () => {
setLoading(true);
@ -79,7 +79,7 @@ function Overview() {
<Card>
<Typography.Title heading={5}>
{t['workplace.welcomeBack']}
{userInfo.name}
{userInfo.username}
</Typography.Title>
<Divider />
<Row>

@ -309,6 +309,8 @@ const Market: React.FC<MarketProps> = ({ updateProjectComp }) => {
// 转换为数组格式
const resultComponents = Object.values(mergedComponents);
console.log('resultComponents', resultComponents);
// 如果没有组件,显示提示信息
if (resultComponents.length === 0) {
return (
@ -365,7 +367,18 @@ const Market: React.FC<MarketProps> = ({ updateProjectComp }) => {
<div className={styles['component-list']}>
<div className={styles['component-info']}>
<img src="/icons/compIcon.png" alt="" />
<span>{component.label || component.flowName}</span>
<div className={styles['component-text']}>
<div className={styles['component-header']}>
<span className={styles['component-flowName']}>{component.label || component.flowName}</span>
{component?.comp?.codeLanguage && (
<Tag size="small" color="arcoblue" className={styles['component-language-tag']}>
{component.comp.codeLanguage}
</Tag>
)}
</div>
<span className={styles['component-identifier']}>{component?.comp?.identifier}</span>
<span className={styles['component-author']}>{component?.author}</span>
</div>
</div>
{/*两种状态 未添加的是primary 已添加的是secondary*/}
{

@ -44,16 +44,57 @@
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding-top: 10px;
.component-info {
min-width: 0;
flex: 1;
display: flex;
align-items: center;
img {
width: 40px;
height: 40px;
margin-right: 10px;
width: 45px;
height: 45px;
margin-right: 15px;
flex-shrink: 0;
}
.component-text {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
.component-header {
min-width: 0;
display: flex;
align-items: center;
gap: 8px;
}
.component-flowName {
min-width: 0;
font-size: 16px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.component-language-tag {
flex-shrink: 0;
}
.component-identifier,
.component-author {
font-size: 12px;
color: #adadad;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}

@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit';
export interface UserState {
userInfo?: {
name?: string;
username?: string;
avatar?: string;
job?: string;
organization?: string;

Loading…
Cancel
Save