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