This commit is contained in:
dengqichen 2025-10-21 10:23:08 +08:00
parent c08c99f422
commit a8c512eee6
5 changed files with 332 additions and 351 deletions

View File

@ -0,0 +1,327 @@
import React, { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from './types';
import type { FlowNodeData } from '../types';
/**
* Jenkins构建节点定义
*/
export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
nodeCode: "JENKINS_BUILD",
nodeName: "Jenkins构建",
nodeType: NodeType.JENKINS_BUILD,
category: NodeCategory.TASK,
description: "通过Jenkins执行构建任务",
uiConfig: {
size: { width: 160, height: 80 },
style: {
fill: '#52c41a',
stroke: '#389e08',
strokeWidth: 2,
icon: 'jenkins',
iconColor: '#fff'
}
},
// 基本配置Schema
configSchema: {
type: "object",
title: "基本配置",
description: "节点的基本信息和Jenkins构建配置参数",
properties: {
nodeName: {
type: "string",
title: "节点名称",
description: "节点在流程图中显示的名称",
default: "Jenkins构建"
},
nodeCode: {
type: "string",
title: "节点编码",
description: "节点的唯一标识符",
default: "JENKINS_BUILD"
},
description: {
type: "string",
title: "节点描述",
description: "节点的详细说明",
default: "通过Jenkins执行构建任务"
},
jenkinsUrl: {
type: "string",
title: "Jenkins服务器地址",
description: "Jenkins服务器的完整URL地址",
default: "http://jenkins.example.com:8080",
format: "uri"
},
jobName: {
type: "string",
title: "Job名称",
description: "要执行的Jenkins Job名称",
default: ""
},
username: {
type: "string",
title: "用户名",
description: "Jenkins认证用户名",
default: ""
},
apiToken: {
type: "string",
title: "API Token",
description: "Jenkins API Token或密码",
default: "",
format: "password"
},
timeout: {
type: "number",
title: "超时时间(秒)",
description: "构建超时时间",
default: 600,
minimum: 60,
maximum: 7200
},
waitForCompletion: {
type: "boolean",
title: "等待构建完成",
description: "是否等待Jenkins构建完成",
default: true
},
retryCount: {
type: "number",
title: "重试次数",
description: "构建失败时的重试次数",
default: 1,
minimum: 0,
maximum: 5
},
branchName: {
type: "string",
title: "分支名称",
description: "要构建的Git分支名称",
default: "main"
}
},
required: ["nodeName", "nodeCode", "jenkinsUrl", "jobName", "username", "apiToken"]
},
// 输入映射Schema
inputMappingSchema: {
type: "object",
title: "输入映射",
description: "从上游节点接收的数据映射配置",
properties: {
sourceCodeUrl: {
type: "string",
title: "源代码仓库地址",
description: "Git仓库的URL地址",
default: "${upstream.gitUrl}"
},
buildParameters: {
type: "object",
title: "构建参数",
description: "传递给Jenkins Job的构建参数",
properties: {},
additionalProperties: true,
default: {}
},
environmentVariables: {
type: "object",
title: "环境变量",
description: "构建时的环境变量",
properties: {},
additionalProperties: true,
default: {}
},
commitId: {
type: "string",
title: "提交ID",
description: "要构建的Git提交ID",
default: "${upstream.commitId}"
},
projectName: {
type: "string",
title: "项目名称",
description: "项目的名称标识",
default: "${upstream.projectName}"
},
buildVersion: {
type: "string",
title: "构建版本",
description: "构建版本号",
default: "${upstream.version}"
}
},
required: ["sourceCodeUrl", "buildParameters"]
},
// 输出映射Schema
outputMappingSchema: {
type: "object",
title: "输出映射",
description: "传递给下游节点的数据映射配置",
properties: {
buildId: {
type: "string",
title: "构建ID",
description: "Jenkins构建的唯一标识符",
default: "${jenkins.buildNumber}"
},
buildStatus: {
type: "string",
title: "构建状态",
description: "构建完成状态",
enum: ["SUCCESS", "FAILURE", "UNSTABLE", "ABORTED", "NOT_BUILT"],
default: "SUCCESS"
},
buildUrl: {
type: "string",
title: "构建URL",
description: "Jenkins构建页面的URL",
default: "${jenkins.buildUrl}"
},
buildDuration: {
type: "number",
title: "构建耗时",
description: "构建耗时(毫秒)",
default: 0
},
artifactsUrl: {
type: "string",
title: "构建产物URL",
description: "构建产物的下载地址",
default: "${jenkins.artifactsUrl}"
},
consoleLog: {
type: "string",
title: "控制台日志",
description: "构建的控制台输出日志",
default: "${jenkins.consoleText}"
},
testResults: {
type: "object",
title: "测试结果",
description: "构建中的测试结果统计",
properties: {
totalCount: { type: "number", title: "总测试数", default: 0 },
passCount: { type: "number", title: "通过数", default: 0 },
failCount: { type: "number", title: "失败数", default: 0 },
skipCount: { type: "number", title: "跳过数", default: 0 }
},
default: {
totalCount: 0,
passCount: 0,
failCount: 0,
skipCount: 0
}
}
},
required: ["buildId", "buildStatus", "buildUrl"]
}
};
/**
* Jenkins构建节点渲染组件
*/
const JenkinsBuildNode: React.FC<NodeProps> = ({ data, selected }) => {
const nodeData = data as FlowNodeData;
return (
<div
style={{
padding: '12px 16px',
borderRadius: '8px',
border: `2px solid ${selected ? '#3b82f6' : '#52c41a'}`,
background: 'white',
minWidth: '140px',
minHeight: '70px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
}}
>
{/* 输入连接点 */}
<Handle
type="target"
position={Position.Left}
style={{
background: '#52c41a',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
{/* 节点内容 */}
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '6px'
}}>
{/* 图标 */}
<div style={{
fontSize: '20px',
color: '#52c41a',
}}>
🔨
</div>
{/* 标签 */}
<div style={{
fontSize: '13px',
color: '#374151',
fontWeight: '500',
textAlign: 'center',
lineHeight: '1.2'
}}>
{nodeData.label || 'Jenkins构建'}
</div>
</div>
{/* 输出连接点 */}
<Handle
type="source"
position={Position.Right}
style={{
background: '#52c41a',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
{/* 配置指示器 */}
{nodeData.configs && Object.keys(nodeData.configs).length > 3 && (
<div
style={{
position: 'absolute',
top: '-6px',
right: '-6px',
width: '12px',
height: '12px',
borderRadius: '50%',
background: '#10b981',
border: '2px solid white',
fontSize: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white'
}}
>
</div>
)}
</div>
);
};
export default memo(JenkinsBuildNode);

View File

@ -1,177 +0,0 @@
import React, { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from './types';
import type { FlowNodeData } from '../types';
/**
*
*/
export const ServiceTaskNodeDefinition: ConfigurableNodeDefinition = {
nodeCode: "SERVICE_TASK",
nodeName: "服务任务",
nodeType: NodeType.SERVICE_TASK,
category: NodeCategory.TASK,
description: "自动执行的服务任务节点",
uiConfig: {
size: { width: 120, height: 80 },
style: {
fill: '#722ed1',
stroke: '#531dab',
strokeWidth: 2,
icon: 'api',
iconColor: '#fff'
}
},
configSchema: {
type: "object",
title: "基本配置",
description: "节点的基本信息和服务配置",
properties: {
nodeName: {
type: "string",
title: "节点名称",
default: "服务任务"
},
nodeCode: {
type: "string",
title: "节点编码",
default: "SERVICE_TASK"
},
description: {
type: "string",
title: "节点描述",
default: "自动执行的服务任务节点"
},
serviceUrl: {
type: "string",
title: "服务地址",
description: "调用的服务URL",
format: "uri"
},
method: {
type: "string",
title: "请求方法",
enum: ["GET", "POST", "PUT", "DELETE"],
default: "POST"
},
timeout: {
type: "number",
title: "超时时间(秒)",
default: 30,
minimum: 1,
maximum: 300
},
retryCount: {
type: "number",
title: "重试次数",
default: 0,
minimum: 0,
maximum: 5
}
},
required: ["nodeName", "nodeCode", "serviceUrl"]
},
inputMappingSchema: {
type: "object",
title: "输入映射",
properties: {
requestBody: {
type: "object",
title: "请求体"
},
headers: {
type: "object",
title: "请求头"
}
}
},
outputMappingSchema: {
type: "object",
title: "输出映射",
properties: {
responseData: {
type: "object",
title: "响应数据"
},
statusCode: {
type: "number",
title: "状态码"
}
}
}
};
/**
*
*/
const ServiceTaskNode: React.FC<NodeProps> = ({ data, selected }) => {
const nodeData = data as FlowNodeData;
return (
<div
style={{
padding: '12px 16px',
borderRadius: '8px',
border: `2px solid ${selected ? '#3b82f6' : '#722ed1'}`,
background: 'white',
minWidth: '120px',
minHeight: '60px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
}}
>
{/* 输入连接点 */}
<Handle
type="target"
position={Position.Left}
style={{
background: '#722ed1',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
{/* 图标和标签 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<span style={{ fontSize: '18px' }}></span>
<span style={{
fontSize: '14px',
color: '#722ed1',
fontWeight: '500'
}}>
{nodeData.label || '服务任务'}
</span>
</div>
{/* 输出连接点 */}
<Handle
type="source"
position={Position.Right}
style={{
background: '#722ed1',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
</div>
);
};
export default memo(ServiceTaskNode);

View File

@ -1,158 +0,0 @@
import React, { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
import { ConfigurableNodeDefinition, NodeType, NodeCategory } from './types';
import type { FlowNodeData } from '../types';
/**
*
*/
export const UserTaskNodeDefinition: ConfigurableNodeDefinition = {
nodeCode: "USER_TASK",
nodeName: "用户任务",
nodeType: NodeType.USER_TASK,
category: NodeCategory.TASK,
description: "需要用户手动处理的任务节点",
uiConfig: {
size: { width: 120, height: 80 },
style: {
fill: '#1890ff',
stroke: '#096dd9',
strokeWidth: 2,
icon: 'user',
iconColor: '#fff'
}
},
configSchema: {
type: "object",
title: "基本配置",
description: "节点的基本信息和任务配置",
properties: {
nodeName: {
type: "string",
title: "节点名称",
default: "用户任务"
},
nodeCode: {
type: "string",
title: "节点编码",
default: "USER_TASK"
},
description: {
type: "string",
title: "节点描述",
default: "需要用户手动处理的任务节点"
},
assignee: {
type: "string",
title: "处理人",
description: "指定任务处理人"
},
candidateGroups: {
type: "string",
title: "候选组",
description: "候选用户组,多个用逗号分隔"
},
dueDate: {
type: "string",
title: "截止日期",
format: "date-time"
}
},
required: ["nodeName", "nodeCode"]
},
inputMappingSchema: {
type: "object",
title: "输入映射",
properties: {
taskData: {
type: "object",
title: "任务数据"
}
}
},
outputMappingSchema: {
type: "object",
title: "输出映射",
properties: {
result: {
type: "string",
title: "处理结果"
}
}
}
};
/**
*
*/
const UserTaskNode: React.FC<NodeProps> = ({ data, selected }) => {
const nodeData = data as FlowNodeData;
return (
<div
style={{
padding: '12px 16px',
borderRadius: '8px',
border: `2px solid ${selected ? '#3b82f6' : '#1890ff'}`,
background: 'white',
minWidth: '120px',
minHeight: '60px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
boxShadow: selected ? '0 4px 8px rgba(59, 130, 246, 0.3)' : '0 2px 4px rgba(0,0,0,0.1)',
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
}}
>
{/* 输入连接点 */}
<Handle
type="target"
position={Position.Left}
style={{
background: '#1890ff',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
{/* 图标和标签 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<span style={{ fontSize: '18px' }}>👤</span>
<span style={{
fontSize: '14px',
color: '#1890ff',
fontWeight: '500'
}}>
{nodeData.label || '用户任务'}
</span>
</div>
{/* 输出连接点 */}
<Handle
type="source"
position={Position.Right}
style={{
background: '#1890ff',
border: '2px solid white',
width: '10px',
height: '10px',
}}
/>
</div>
);
};
export default memo(UserTaskNode);

View File

@ -7,8 +7,7 @@
import StartEventNode, { StartEventNodeDefinition } from './StartEventNode'; import StartEventNode, { StartEventNodeDefinition } from './StartEventNode';
import EndEventNode, { EndEventNodeDefinition } from './EndEventNode'; import EndEventNode, { EndEventNodeDefinition } from './EndEventNode';
import UserTaskNode, { UserTaskNodeDefinition } from './UserTaskNode'; import JenkinsBuildNode, { JenkinsBuildNodeDefinition } from './JenkinsBuildNode';
import ServiceTaskNode, { ServiceTaskNodeDefinition } from './ServiceTaskNode';
import type { WorkflowNodeDefinition } from './types'; import type { WorkflowNodeDefinition } from './types';
/** /**
@ -17,8 +16,7 @@ import type { WorkflowNodeDefinition } from './types';
export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [ export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
StartEventNodeDefinition, StartEventNodeDefinition,
EndEventNodeDefinition, EndEventNodeDefinition,
UserTaskNodeDefinition, JenkinsBuildNodeDefinition,
ServiceTaskNodeDefinition,
]; ];
/** /**
@ -27,8 +25,7 @@ export const NODE_DEFINITIONS: WorkflowNodeDefinition[] = [
export const nodeTypes = { export const nodeTypes = {
START_EVENT: StartEventNode, START_EVENT: StartEventNode,
END_EVENT: EndEventNode, END_EVENT: EndEventNode,
USER_TASK: UserTaskNode, JENKINS_BUILD: JenkinsBuildNode,
SERVICE_TASK: ServiceTaskNode,
}; };
/** /**
@ -38,16 +35,13 @@ export {
// 组件 // 组件
StartEventNode, StartEventNode,
EndEventNode, EndEventNode,
UserTaskNode, JenkinsBuildNode,
ServiceTaskNode,
// 定义 // 定义
StartEventNodeDefinition, StartEventNodeDefinition,
EndEventNodeDefinition, EndEventNodeDefinition,
UserTaskNodeDefinition, JenkinsBuildNodeDefinition,
ServiceTaskNodeDefinition,
}; };
// 导出类型 // 导出类型
export * from './types'; export * from './types';

View File

@ -10,14 +10,9 @@ export enum NodeCategory {
export enum NodeType { export enum NodeType {
START_EVENT = 'START_EVENT', START_EVENT = 'START_EVENT',
END_EVENT = 'END_EVENT', END_EVENT = 'END_EVENT',
USER_TASK = 'USER_TASK',
SERVICE_TASK = 'SERVICE_TASK',
SCRIPT_TASK = 'SCRIPT_TASK', SCRIPT_TASK = 'SCRIPT_TASK',
DEPLOY_NODE = 'DEPLOY_NODE',
JENKINS_BUILD = 'JENKINS_BUILD', JENKINS_BUILD = 'JENKINS_BUILD',
GATEWAY_NODE = 'GATEWAY_NODE', GATEWAY_NODE = 'GATEWAY_NODE',
SUB_PROCESS = 'SUB_PROCESS',
CALL_ACTIVITY = 'CALL_ACTIVITY'
} }
// JSON Schema 定义 // JSON Schema 定义