From 66efeb605810a8d9ea16720f05b7cd33c6d67e4d Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 7 Nov 2025 16:02:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E5=89=8D=E7=AB=AF=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 94 +++++++ .../components/DeployFlowGraphModal.tsx | 236 ++++++++++++------ frontend/src/pages/Dashboard/service.ts | 10 + frontend/src/pages/Dashboard/types.ts | 23 ++ .../Workflow/Design/nodes/ApprovalNode.tsx | 6 + .../Design/nodes/JenkinsBuildNode.tsx | 8 +- .../Design/nodes/NotificationNode.tsx | 6 + 8 files changed, 305 insertions(+), 79 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index aa2f0a4d..c2fc9e8e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -70,6 +70,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.54.2", "react-infinite-scroll-component": "^6.1.0", + "react-lazylog": "^4.5.3", "react-redux": "^9.0.4", "react-router-dom": "^6.21.0", "reactflow": "^11.11.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 7e60acb9..e2070d9b 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -185,6 +185,9 @@ importers: react-infinite-scroll-component: specifier: ^6.1.0 version: 6.1.0(react@18.3.1) + react-lazylog: + specifier: ^4.5.3 + version: 4.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-redux: specifier: ^9.0.4 version: 9.2.0(@types/react@18.3.18)(react@18.3.1)(redux@5.0.1) @@ -890,6 +893,11 @@ packages: '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@mattiasbuelens/web-streams-polyfill@0.2.1': + resolution: {integrity: sha512-oKuFCQFa3W7Hj7zKn0+4ypI8JFm4ZKIoncwAC6wd5WwFW2sL7O1hpPoJdSWpynQ4DJ4lQ6MvFoVDmCLilonDFg==} + engines: {node: '>= 8'} + deprecated: moved to web-streams-polyfill@2.0.0 + '@monaco-editor/loader@1.4.0': resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} peerDependencies: @@ -2211,6 +2219,9 @@ packages: '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/whatwg-streams@0.0.7': + resolution: {integrity: sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A==} + '@typescript-eslint/eslint-plugin@6.21.0': resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -2459,6 +2470,10 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2802,6 +2817,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fetch-readablestream@0.2.0: + resolution: {integrity: sha512-qu4mXWf4wus4idBIN/kVH+XSer8IZ9CwHP+Pd7DL7TuKNC1hP7ykon4kkBjwJF3EMX2WsFp4hH7gU7CyL7ucXw==} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2956,6 +2974,10 @@ packages: immer@10.1.1: resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + immutable@3.8.2: + resolution: {integrity: sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==} + engines: {node: '>=0.10.0'} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -3176,6 +3198,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mitt@1.2.0: + resolution: {integrity: sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==} + mobx-react-lite@4.1.1: resolution: {integrity: sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg==} peerDependencies: @@ -3751,6 +3776,11 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-lazylog@4.5.3: + resolution: {integrity: sha512-lyov32A/4BqihgXgtNXTHCajXSXkYHPlIEmV8RbYjHIMxCFSnmtdg4kDCI3vATz7dURtiFTvrw5yonHnrS+NNg==} + peerDependencies: + react: '>=16.3.0' + react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} @@ -3832,6 +3862,10 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-string-replace@0.4.4: + resolution: {integrity: sha512-FAMkhxmDpCsGTwTZg7p/2v+/GTmxAp73so3fbSvlAcBBX36ujiGRNEaM/1u+jiYQrArhns+7eE92g2pi5E5FUA==} + engines: {node: '>=0.12.0'} + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3853,6 +3887,12 @@ packages: peerDependencies: react: '>=16.8.0' + react-virtualized@9.22.6: + resolution: {integrity: sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==} + peerDependencies: + react: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-window@1.8.11: resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} engines: {node: '>8.0.0'} @@ -4115,6 +4155,9 @@ packages: engines: {node: '>=10'} hasBin: true + text-encoding-utf-8@1.0.2: + resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -4275,6 +4318,9 @@ packages: warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + whatwg-fetch@2.0.4: + resolution: {integrity: sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5125,6 +5171,10 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} + '@mattiasbuelens/web-streams-polyfill@0.2.1': + dependencies: + '@types/whatwg-streams': 0.0.7 + '@monaco-editor/loader@1.4.0(monaco-editor@0.52.2)': dependencies: monaco-editor: 0.52.2 @@ -6564,6 +6614,8 @@ snapshots: '@types/uuid@10.0.0': {} + '@types/whatwg-streams@0.0.7': {} + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.7.2))(eslint@8.57.1)(typescript@5.7.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -6921,6 +6973,8 @@ snapshots: classnames@2.5.1: {} + clsx@1.2.1: {} + clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -7297,6 +7351,8 @@ snapshots: dependencies: reusify: 1.0.4 + fetch-readablestream@0.2.0: {} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -7462,6 +7518,8 @@ snapshots: immer@10.1.1: {} + immutable@3.8.2: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -7656,6 +7714,8 @@ snapshots: minipass@7.1.2: {} + mitt@1.2.0: {} + mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: mobx: 6.15.0 @@ -8311,6 +8371,21 @@ snapshots: react-is@18.3.1: {} + react-lazylog@4.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@mattiasbuelens/web-streams-polyfill': 0.2.1 + fetch-readablestream: 0.2.0 + immutable: 3.8.2 + mitt: 1.2.0 + prop-types: 15.8.1 + react: 18.3.1 + react-string-replace: 0.4.4 + react-virtualized: 9.22.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + text-encoding-utf-8: 1.0.2 + whatwg-fetch: 2.0.4 + transitivePeerDependencies: + - react-dom + react-lifecycles-compat@3.0.4: {} react-number-format@5.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -8387,6 +8462,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-string-replace@0.4.4: + dependencies: + lodash: 4.17.21 + react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1): dependencies: get-nonce: 1.0.1 @@ -8408,6 +8487,17 @@ snapshots: dependencies: react: 18.3.1 + react-virtualized@9.22.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + clsx: 1.2.1 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-lifecycles-compat: 3.0.4 + react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -8729,6 +8819,8 @@ snapshots: source-map-support: 0.5.21 optional: true + text-encoding-utf-8@1.0.2: {} + text-table@0.2.0: {} thenify-all@1.6.0: @@ -8852,6 +8944,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + whatwg-fetch@2.0.4: {} + which@2.0.2: dependencies: isexe: 2.0.0 diff --git a/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx b/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx index 8e3aa10e..a2807dab 100644 --- a/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx +++ b/frontend/src/pages/Dashboard/components/DeployFlowGraphModal.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { ReactFlowProvider, ReactFlow, Background, Node, Edge, Handle, Position, BackgroundVariant } from '@xyflow/react'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; +import { ReactFlowProvider, ReactFlow, Background, Node, Edge, Handle, Position, BackgroundVariant, NodeProps } from '@xyflow/react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -17,7 +17,8 @@ import { Clock, CheckCircle2, XCircle, - AlertTriangle + AlertTriangle, + FileText } from 'lucide-react'; import { cn } from '@/lib/utils'; import '@xyflow/react/dist/style.css'; @@ -32,6 +33,7 @@ import { formatDuration, calculateRunningDuration } from '../utils/dashboardUtils'; +import DeployNodeLogDialog from './DeployNodeLogDialog'; interface DeployFlowGraphModalProps { open: boolean; @@ -39,16 +41,34 @@ interface DeployFlowGraphModalProps { onOpenChange: (open: boolean) => void; } +interface CustomNodeData { + nodeName: string; + nodeType: string; + nodeId: string; + status: string; + startTime?: string | null; + endTime?: string | null; + duration?: number | null; + errorMessage?: string | null; + isUnreachable?: boolean; + processInstanceId?: string; + onViewLog?: (nodeId: string, nodeName: string) => void; +} + /** * 自定义流程节点组件 */ const CustomFlowNode: React.FC = ({ data }) => { - const { nodeName, nodeType, status, startTime, endTime, duration, errorMessage, isUnreachable } = data; + const { nodeName, nodeType, status, startTime, endTime, duration, errorMessage, isUnreachable } = data as CustomNodeData; const statusColor = getNodeStatusColor(status); const isNotStarted = status === 'NOT_STARTED'; const isRunning = status === 'RUNNING'; const hasFailed = status === 'FAILED'; + // 判断是否可以查看日志(具有日志输出能力的节点类型) + const loggableNodeTypes = ['JENKINS_BUILD', 'ServiceTask']; + const canViewLog = loggableNodeTypes.includes(nodeType) && status !== 'NOT_STARTED'; + // 计算显示的时长 const displayDuration = useMemo(() => { if (duration !== null && duration !== undefined) { @@ -69,7 +89,8 @@ const CustomFlowNode: React.FC = ({ data }) => { isNotStarted && 'border-2 border-dashed', !isNotStarted && 'border-2 border-solid shadow-sm', isRunning && 'animate-pulse', - isUnreachable && 'opacity-40' // 不可达节点半透明 + isUnreachable && 'opacity-40', // 不可达节点半透明 + canViewLog && 'cursor-pointer hover:shadow-md hover:scale-[1.02]' // 可查看日志的节点增加交互效果 )} style={{ borderColor: statusColor, @@ -77,7 +98,12 @@ const CustomFlowNode: React.FC = ({ data }) => { }} > {/* 节点名称 */} -
{nodeName}
+
+
{nodeName}
+ {canViewLog && ( + + )} +
{/* 节点状态 */}
= ({ data }) => { 有错误
)} + + {/* 查看日志提示 */} + {canViewLog && ( +
+ 点击查看日志 +
+ )} ); @@ -370,6 +403,34 @@ export const DeployFlowGraphModal: React.FC = ({ }) => { const [loading, setLoading] = useState(false); const [flowData, setFlowData] = useState(null); + + // 日志对话框状态 + const [logDialogOpen, setLogDialogOpen] = useState(false); + const [selectedNodeId, setSelectedNodeId] = useState(''); + const [selectedNodeName, setSelectedNodeName] = useState(''); + + // 查看日志处理函数 + const handleViewLog = (nodeId: string, nodeName: string) => { + setSelectedNodeId(nodeId); + setSelectedNodeName(nodeName); + setLogDialogOpen(true); + }; + + // ReactFlow 节点点击事件处理 + const onNodeClick = useCallback((_event: React.MouseEvent, node: Node) => { + const nodeData = node.data as unknown as CustomNodeData; + + // 可以查看日志的节点类型 + const loggableNodeTypes = ['JENKINS_BUILD', 'ServiceTask']; + const canViewLog = loggableNodeTypes.includes(nodeData.nodeType) && nodeData.status !== 'NOT_STARTED'; + + if (canViewLog) { + console.log('Node clicked, opening log dialog:', nodeData.nodeId, nodeData.nodeName); + handleViewLog(nodeData.nodeId, nodeData.nodeName); + } else { + console.log('Node clicked but cannot view log:', nodeData.nodeType, nodeData.status); + } + }, []); // 加载流程图数据 useEffect(() => { @@ -492,6 +553,7 @@ export const DeployFlowGraphModal: React.FC = ({ data: { nodeName: node.nodeName, nodeType: node.nodeType, + nodeId: node.id, status: instance?.status || 'NOT_STARTED', startTime: instance?.startTime, endTime: instance?.endTime, @@ -499,6 +561,7 @@ export const DeployFlowGraphModal: React.FC = ({ errorMessage: instance?.errorMessage, // 新增:不可达且未执行的节点标记为置灰 isUnreachable: isRunning && isNotStarted && !isReachable, + processInstanceId: flowData.processInstanceId, }, }; }); @@ -595,81 +658,98 @@ export const DeployFlowGraphModal: React.FC = ({ : null; return ( - - - - - {flowData && ( - - #{flowData.deployRecordId} - - )} - 部署流程图 - {deployStatusInfo && ( - - - {deployStatusInfo.text} - - )} - - + <> + + + + + {flowData && ( + + #{flowData.deployRecordId} + + )} + 部署流程图 + {deployStatusInfo && ( + + + {deployStatusInfo.text} + + )} + + -
- {loading ? ( -
-
- -

加载流程图数据...

+
+ {loading ? ( +
+
+ +

加载流程图数据...

+
-
- ) : flowData ? ( - <> - {/* 左侧信息面板 */} - + ) : flowData ? ( + <> + {/* 左侧信息面板 */} + - {/* 右侧流程图可视化 */} -
- - - - - + {/* 右侧流程图可视化 */} +
+ + + + + +
+ + ) : ( +
+
+ +

暂无流程图数据

+
- - ) : ( -
-
- -

暂无流程图数据

-
-
- )} -
- -
+ )} + +
+
+ + {/* 节点日志对话框 */} + {flowData && ( + + )} + ); }; diff --git a/frontend/src/pages/Dashboard/service.ts b/frontend/src/pages/Dashboard/service.ts index a69248c1..e3c50ff0 100644 --- a/frontend/src/pages/Dashboard/service.ts +++ b/frontend/src/pages/Dashboard/service.ts @@ -39,3 +39,13 @@ export const getMyApprovalTasks = (workflowDefinitionKeys?: string[]) => { */ export const completeApproval = (data: CompleteApprovalRequest) => request.post(`${DEPLOY_URL}/complete`, data); + +/** + * 获取部署节点日志 + * @param processInstanceId 流程实例ID + * @param nodeId 节点ID + */ +export const getDeployNodeLogs = (processInstanceId: string, nodeId: string) => + request.get(`${DEPLOY_URL}/logs`, { + params: { processInstanceId, nodeId } + }); diff --git a/frontend/src/pages/Dashboard/types.ts b/frontend/src/pages/Dashboard/types.ts index 2fbb43bb..886e52fe 100644 --- a/frontend/src/pages/Dashboard/types.ts +++ b/frontend/src/pages/Dashboard/types.ts @@ -255,3 +255,26 @@ export interface DeployRecordFlowGraph { graph: WorkflowDefinitionGraph; nodeInstances: WorkflowNodeInstance[]; } + +/** + * 日志级别 + */ +export type LogLevel = 'INFO' | 'WARN' | 'ERROR'; + +/** + * 日志条目 + */ +export interface LogEntry { + sequenceId: number; + timestamp: string; + level: LogLevel; + message: string; +} + +/** + * 部署节点日志响应 + */ +export interface DeployNodeLogDTO { + expired: boolean; + logs: LogEntry[]; +} diff --git a/frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx b/frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx index ecad4db5..19426e90 100644 --- a/frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx +++ b/frontend/src/pages/Workflow/Design/nodes/ApprovalNode.tsx @@ -44,6 +44,12 @@ export const ApprovalNodeDefinition: ConfigurableNodeDefinition = { title: "审批配置", description: "配置审批人和审批规则", properties: { + continueOnFailure: { + type: "boolean", + title: "失败后继续", + description: "当节点执行失败时,是否继续执行后续节点。true: 节点失败时标记为 FAILURE,但流程继续执行后续节点;false: 节点失败时抛出 BpmnError,终止流程", + default: true + }, approvalMode: { type: "string", title: "审批模式", diff --git a/frontend/src/pages/Workflow/Design/nodes/JenkinsBuildNode.tsx b/frontend/src/pages/Workflow/Design/nodes/JenkinsBuildNode.tsx index 48795058..2382fb88 100644 --- a/frontend/src/pages/Workflow/Design/nodes/JenkinsBuildNode.tsx +++ b/frontend/src/pages/Workflow/Design/nodes/JenkinsBuildNode.tsx @@ -44,6 +44,12 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = { title: "输入", description: "当前节点所需数据配置", properties: { + continueOnFailure: { + type: "boolean", + title: "失败后继续", + description: "当节点执行失败时,是否继续执行后续节点。true: 节点失败时标记为 FAILURE,但流程继续执行后续节点;false: 节点失败时抛出 BpmnError,终止流程", + default: true + }, serverId: { type: "number", title: "Jenkins服务器", @@ -58,7 +64,7 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = { // 注意:jobName 暂时使用手动输入,因为需要先选择 serverId 才能级联加载 jobs // 未来如果需要级联下拉,需要使用 CascadeDataSourceType.JENKINS_SERVER_VIEWS_JOBS "x-allow-variable": true - }, + } }, required: ["serverId", "jobName"] }, diff --git a/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx b/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx index 1c25dc8b..457764c0 100644 --- a/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx +++ b/frontend/src/pages/Workflow/Design/nodes/NotificationNode.tsx @@ -44,6 +44,12 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = { title: "输入", description: "当前节点所需数据配置", properties: { + continueOnFailure: { + type: "boolean", + title: "失败后继续", + description: "当节点执行失败时,是否继续执行后续节点。true: 节点失败时标记为 FAILURE,但流程继续执行后续节点;false: 节点失败时抛出 BpmnError,终止流程", + default: true + }, // notificationType: { // type: "string", // title: "通知类型",