增加了动态生成跟节点验证。
This commit is contained in:
parent
472c9862c4
commit
6e45e3b502
129
frontend/package-lock.json
generated
129
frontend/package-lock.json
generated
@ -23,6 +23,8 @@
|
||||
"@logicflow/core": "^2.0.9",
|
||||
"@logicflow/extension": "^2.0.13",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"antd": "^5.22.2",
|
||||
"axios": "^1.6.2",
|
||||
"dagre": "^0.8.5",
|
||||
@ -1149,6 +1151,22 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -1174,6 +1192,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -2343,27 +2367,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"peer": true,
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
@ -3197,6 +3228,22 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -3222,6 +3269,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eslint/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -3344,6 +3397,11 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.3.tgz",
|
||||
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw=="
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz",
|
||||
@ -3956,9 +4014,9 @@
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
@ -5452,6 +5510,14 @@
|
||||
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz",
|
||||
@ -5635,6 +5701,37 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/screenfull": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/screenfull/-/screenfull-5.2.0.tgz",
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
"@logicflow/core": "^2.0.9",
|
||||
"@logicflow/extension": "^2.0.13",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"antd": "^5.22.2",
|
||||
"axios": "^1.6.2",
|
||||
"dagre": "^0.8.5",
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import { Form, Input, Select, InputNumber, Switch, Divider } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Form, Input, Select, InputNumber, Divider, Tooltip } from 'antd';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import type { Rule } from 'antd/es/form';
|
||||
import { NodeType } from '../../../../types';
|
||||
import { validateNodeConfig } from './validate';
|
||||
|
||||
interface NodeConfigProps {
|
||||
nodeType: NodeType;
|
||||
@ -9,42 +11,80 @@ interface NodeConfigProps {
|
||||
onValuesChange?: (changedValues: any, allValues: any) => void;
|
||||
}
|
||||
|
||||
interface FormFields {
|
||||
[key: string]: any;
|
||||
interface JsonSchema {
|
||||
type: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
properties?: Record<string, JsonSchema>;
|
||||
required?: string[];
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
format?: string;
|
||||
default?: any;
|
||||
enum?: any[];
|
||||
enumNames?: string[];
|
||||
additionalProperties?: JsonSchema;
|
||||
}
|
||||
|
||||
// 处理JSON字符串中的转义字符
|
||||
const parseJsonSafely = (jsonString: string) => {
|
||||
const parseJsonSafely = (jsonString: string): any => {
|
||||
if (!jsonString) return {};
|
||||
|
||||
try {
|
||||
// 处理Windows风格的换行符
|
||||
const processed = jsonString.replace(/\r\n/g, '\n')
|
||||
// 处理转义字符
|
||||
.replace(/\\/g, '\\\\')
|
||||
// 处理引号
|
||||
.replace(/\\"/g, '\\"');
|
||||
return JSON.parse(processed);
|
||||
// 如果已经是对象,直接返回
|
||||
if (typeof jsonString === 'object') {
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
// 打印原始字符串内容,帮助调试
|
||||
console.log('原始 JSON 字符串:', jsonString);
|
||||
|
||||
// 尝试直接解析
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
console.log('直接解析失败,尝试预处理...');
|
||||
}
|
||||
|
||||
// 预处理字符串
|
||||
let processedString = jsonString;
|
||||
|
||||
// 1. 处理换行符
|
||||
processedString = processedString.replace(/[\r\n]+/g, ' ');
|
||||
|
||||
// 2. 处理转义字符
|
||||
processedString = processedString.replace(/\\/g, '\\\\')
|
||||
.replace(/\\\\"/g, '\\"')
|
||||
.replace(/\\\\n/g, '\\n')
|
||||
.replace(/\\\\r/g, '\\r')
|
||||
.replace(/\\\\t/g, '\\t');
|
||||
|
||||
// 3. 确保属性名称正确引用
|
||||
processedString = processedString.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2":');
|
||||
|
||||
console.log('处理后的 JSON 字符串:', processedString);
|
||||
|
||||
return JSON.parse(processedString);
|
||||
} catch (error) {
|
||||
console.error('JSON解析错误:', error);
|
||||
console.debug('原始JSON字符串:', jsonString);
|
||||
return null;
|
||||
console.error('解析失败的字符串:', jsonString);
|
||||
// 返回空对象而不是 null,避免后续操作出错
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange }) => {
|
||||
// 解析节点配置模式
|
||||
const nodeSchema = useMemo(() => {
|
||||
if (!nodeType.configSchema) {
|
||||
console.warn('节点配置模式为空');
|
||||
return null;
|
||||
}
|
||||
if (!nodeType.configSchema) return null;
|
||||
return parseJsonSafely(nodeType.configSchema);
|
||||
}, [nodeType.configSchema]);
|
||||
|
||||
// 解析节点默认配置
|
||||
const nodeDefaultConfig = useMemo(() => {
|
||||
if (!nodeType.defaultConfig) return {};
|
||||
const config = parseJsonSafely(nodeType.defaultConfig);
|
||||
return config || {};
|
||||
return parseJsonSafely(nodeType.defaultConfig) || {};
|
||||
}, [nodeType.defaultConfig]);
|
||||
|
||||
// 当前选中的执行器
|
||||
@ -54,160 +94,145 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
|
||||
const executorSchema = useMemo(() => {
|
||||
if (!selectedExecutor || !nodeType.executors) return null;
|
||||
const executor = nodeType.executors.find(e => e.code === selectedExecutor);
|
||||
if (!executor || !executor.configSchema) return null;
|
||||
if (!executor?.configSchema) return null;
|
||||
return parseJsonSafely(executor.configSchema);
|
||||
}, [selectedExecutor, nodeType.executors]);
|
||||
|
||||
// 获取当前执行器的默认配置
|
||||
const executorDefaultConfig = useMemo(() => {
|
||||
if (!selectedExecutor || !nodeType.executors) return {};
|
||||
const executor = nodeType.executors.find(e => e.code === selectedExecutor);
|
||||
if (!executor || !executor.defaultConfig) return {};
|
||||
const config = parseJsonSafely(executor.defaultConfig);
|
||||
return config || {};
|
||||
}, [selectedExecutor, nodeType.executors]);
|
||||
// 验证配置
|
||||
const validateField = async (_: any, value: any) => {
|
||||
const allValues = form.getFieldsValue();
|
||||
const validationResult = validateNodeConfig(nodeType, allValues);
|
||||
|
||||
// 渲染基本配置表单项
|
||||
const renderBasicFormItems = () => (
|
||||
<>
|
||||
<Form.Item
|
||||
label="节点名称"
|
||||
name="name"
|
||||
rules={[{ required: true, message: '请输入节点名称' }]}
|
||||
>
|
||||
<Input placeholder="请输入节点名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="节点描述"
|
||||
name="description"
|
||||
>
|
||||
<Input.TextArea placeholder="请输入节点描述" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
// 渲染网关节点配置
|
||||
const renderGatewayConfig = () => {
|
||||
if (!nodeType.code?.includes('GATEWAY')) return null;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
label="网关类型"
|
||||
name="type"
|
||||
rules={[{ required: true, message: '请选择网关类型' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择网关类型"
|
||||
options={[
|
||||
{ label: '并行网关', value: 'PARALLEL' },
|
||||
{ label: '排他网关', value: 'EXCLUSIVE' },
|
||||
{ label: '包容网关', value: 'INCLUSIVE' }
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
if (!validationResult.valid) {
|
||||
const fieldErrors = validationResult.errors.filter(error =>
|
||||
error.toLowerCase().includes(_?.field?.toLowerCase() || '')
|
||||
);
|
||||
if (fieldErrors.length > 0) {
|
||||
throw new Error(fieldErrors.join('; '));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
// 渲染定时器节点配置
|
||||
const renderTimerConfig = () => {
|
||||
if (!nodeType.code?.includes('TIMER')) return null;
|
||||
// 根据schema生成表单验证规则
|
||||
const generateRules = (schema: JsonSchema, fieldName: string): Rule[] => {
|
||||
const rules: Rule[] = [];
|
||||
|
||||
// 从schema中获取cron字段的配置
|
||||
const cronField = nodeSchema?.properties?.cron;
|
||||
if (!cronField) return null;
|
||||
if (schema.required?.includes(fieldName)) {
|
||||
rules.push({ required: true, message: `请输入${schema.title || fieldName}` });
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
label={cronField.title || "CRON表达式"}
|
||||
name="cron"
|
||||
tooltip={cronField.description}
|
||||
rules={[
|
||||
{
|
||||
required: nodeSchema?.required?.includes('cron'),
|
||||
message: '请输入CRON表达式'
|
||||
},
|
||||
...(cronField.pattern ? [{
|
||||
pattern: new RegExp(cronField.pattern),
|
||||
message: '请输入有效的CRON表达式'
|
||||
}] : [])
|
||||
]}
|
||||
>
|
||||
<Input placeholder={`例如: ${cronField.examples?.[0] || ''}`} />
|
||||
</Form.Item>
|
||||
);
|
||||
if (schema.type === 'string') {
|
||||
if (schema.minLength !== undefined) {
|
||||
rules.push({ min: schema.minLength, message: `${schema.title || fieldName}最少${schema.minLength}个字符` });
|
||||
}
|
||||
if (schema.maxLength !== undefined) {
|
||||
rules.push({ max: schema.maxLength, message: `${schema.title || fieldName}最多${schema.maxLength}个字符` });
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.type === 'number') {
|
||||
if (schema.minimum !== undefined) {
|
||||
rules.push({ type: 'number', min: schema.minimum, message: `${schema.title || fieldName}不能小于${schema.minimum}` });
|
||||
}
|
||||
if (schema.maximum !== undefined) {
|
||||
rules.push({ type: 'number', max: schema.maximum, message: `${schema.title || fieldName}不能大于${schema.maximum}` });
|
||||
}
|
||||
}
|
||||
|
||||
rules.push({ validator: validateField });
|
||||
return rules;
|
||||
};
|
||||
|
||||
// 渲染Shell执行器特定配置
|
||||
const renderShellExecutorConfig = () => {
|
||||
if (!nodeType.code?.includes('SHELL')) return null;
|
||||
// 根据schema生成表单项
|
||||
const renderFormItem = (fieldName: string, fieldSchema: JsonSchema) => {
|
||||
const rules = generateRules(fieldSchema, fieldName);
|
||||
const commonProps = {
|
||||
label: (
|
||||
<span>
|
||||
{fieldSchema.title || fieldName}
|
||||
{fieldSchema.description && (
|
||||
<Tooltip title={fieldSchema.description}>
|
||||
<InfoCircleOutlined style={{ marginLeft: 4 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
name: fieldName,
|
||||
rules
|
||||
};
|
||||
|
||||
switch (fieldSchema.type) {
|
||||
case 'string':
|
||||
if (fieldSchema.enum) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="执行器"
|
||||
name="executor"
|
||||
rules={[{ required: true, message: '请选择执行器' }]}
|
||||
>
|
||||
<Form.Item {...commonProps} key={fieldName}>
|
||||
<Select
|
||||
placeholder="请选择执行器"
|
||||
options={nodeType.executors?.map(executor => ({
|
||||
label: executor.name,
|
||||
value: executor.code
|
||||
placeholder={`请选择${fieldSchema.title || fieldName}`}
|
||||
options={fieldSchema.enum.map((value, index) => ({
|
||||
label: fieldSchema.enumNames?.[index] || value,
|
||||
value
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
if (fieldSchema.format === 'shell') {
|
||||
return (
|
||||
<Form.Item {...commonProps} key={fieldName}>
|
||||
<Input.TextArea rows={6} placeholder={`请输入${fieldSchema.title || fieldName}`} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Form.Item {...commonProps} key={fieldName}>
|
||||
<Input placeholder={`请输入${fieldSchema.title || fieldName}`} />
|
||||
</Form.Item>
|
||||
);
|
||||
|
||||
{selectedExecutor === 'SHELL' && (
|
||||
<>
|
||||
<Form.Item
|
||||
label="脚本内容"
|
||||
name="script"
|
||||
rules={[{ required: true, message: '请输入脚本内容' }]}
|
||||
>
|
||||
<Input.TextArea rows={6} placeholder="请输入脚本内容" />
|
||||
case 'number':
|
||||
return (
|
||||
<Form.Item {...commonProps} key={fieldName}>
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={fieldSchema.minimum}
|
||||
max={fieldSchema.maximum}
|
||||
placeholder={`请输入${fieldSchema.title || fieldName}`}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="工作目录"
|
||||
name="workingDirectory"
|
||||
>
|
||||
<Input placeholder="请输入工作目录" />
|
||||
);
|
||||
|
||||
case 'object':
|
||||
if (fieldSchema.additionalProperties) {
|
||||
return (
|
||||
<Form.Item {...commonProps} key={fieldName}>
|
||||
<Input.TextArea
|
||||
placeholder={`请输入${fieldSchema.title || fieldName},格式为 key=value`}
|
||||
rows={4}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="超时时间"
|
||||
name="timeout"
|
||||
rules={[{ type: 'number', min: 0, message: '超时时间必须大于0' }]}
|
||||
>
|
||||
<InputNumber placeholder="请输入超时时间(秒)" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="重试次数"
|
||||
name="retryTimes"
|
||||
rules={[{ type: 'number', min: 0, message: '重试次数不能为负数' }]}
|
||||
>
|
||||
<InputNumber placeholder="请输入重试次数" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="重试间隔"
|
||||
name="retryInterval"
|
||||
rules={[{ type: 'number', min: 0, message: '重试间隔不能为负数' }]}
|
||||
>
|
||||
<InputNumber placeholder="请输入重试间隔(秒)" style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="环境变量"
|
||||
name="environment"
|
||||
>
|
||||
<Input.TextArea placeholder="请输入环境变量" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="成功退出码"
|
||||
name="successExitCode"
|
||||
>
|
||||
<Input placeholder="请输入成功退出码" />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染节点基本配置
|
||||
const renderNodeConfig = () => {
|
||||
if (!nodeSchema?.properties) return null;
|
||||
return Object.entries(nodeSchema.properties).map(([fieldName, fieldSchema]) =>
|
||||
renderFormItem(fieldName, fieldSchema)
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染执行器配置
|
||||
const renderExecutorConfig = () => {
|
||||
if (!executorSchema?.properties) return null;
|
||||
return Object.entries(executorSchema.properties).map(([fieldName, fieldSchema]) =>
|
||||
renderFormItem(fieldName, fieldSchema)
|
||||
);
|
||||
};
|
||||
|
||||
@ -216,13 +241,15 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={onValuesChange}
|
||||
initialValues={{ ...nodeDefaultConfig, ...executorDefaultConfig }}
|
||||
initialValues={{ ...nodeDefaultConfig }}
|
||||
>
|
||||
{renderBasicFormItems()}
|
||||
{renderNodeConfig()}
|
||||
{selectedExecutor && (
|
||||
<>
|
||||
<Divider />
|
||||
{renderGatewayConfig()}
|
||||
{renderTimerConfig()}
|
||||
{renderShellExecutorConfig()}
|
||||
{renderExecutorConfig()}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import Ajv from 'ajv';
|
||||
import addFormats from 'ajv-formats';
|
||||
import { NodeType } from '../../../../types';
|
||||
|
||||
const ajv = new Ajv({
|
||||
allErrors: true,
|
||||
verbose: true,
|
||||
$data: true,
|
||||
strict: false
|
||||
});
|
||||
|
||||
addFormats(ajv);
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export const validateNodeConfig = (
|
||||
nodeType: NodeType,
|
||||
formData: any
|
||||
): ValidationResult => {
|
||||
console.log('Validating node config:', { nodeType, formData });
|
||||
|
||||
try {
|
||||
// 1. 解析节点的 schema
|
||||
const nodeSchema = JSON.parse(nodeType.configSchema);
|
||||
console.log('Node schema:', nodeSchema);
|
||||
|
||||
let errors: string[] = [];
|
||||
|
||||
// 2. 验证基本配置
|
||||
const validateNode = ajv.compile(nodeSchema);
|
||||
const nodeValid = validateNode(formData);
|
||||
console.log('Node validation result:', { valid: nodeValid, errors: validateNode.errors });
|
||||
|
||||
if (!nodeValid && validateNode.errors) {
|
||||
errors = validateNode.errors.map(err => {
|
||||
const field = err.instancePath.replace('/', '') || '配置';
|
||||
return `${field}: ${err.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 如果有执行器配置,验证执行器配置
|
||||
if (formData.executor && nodeType.executors?.length > 0) {
|
||||
const executor = nodeType.executors.find(e => e.code === formData.executor);
|
||||
if (executor?.configSchema) {
|
||||
console.log('Validating executor config:', executor.configSchema);
|
||||
const executorSchema = JSON.parse(executor.configSchema);
|
||||
const validateExecutor = ajv.compile(executorSchema);
|
||||
const executorValid = validateExecutor(formData);
|
||||
console.log('Executor validation result:', { valid: executorValid, errors: validateExecutor.errors });
|
||||
|
||||
if (!executorValid && validateExecutor.errors) {
|
||||
errors = errors.concat(
|
||||
validateExecutor.errors.map(err => {
|
||||
const field = err.instancePath.replace('/', '') || '执行器配置';
|
||||
return `${field}: ${err.message}`;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
console.log('Final validation result:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Schema validation error:', error);
|
||||
return {
|
||||
valid: false,
|
||||
errors: ['配置验证失败:schema 解析错误']
|
||||
};
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user