SHELL可以正确的设置和回显。
This commit is contained in:
parent
41975feedc
commit
c58e799c22
@ -67,217 +67,107 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
|
|||||||
return config || {};
|
return config || {};
|
||||||
}, [selectedExecutor, nodeType.executors]);
|
}, [selectedExecutor, nodeType.executors]);
|
||||||
|
|
||||||
// 当执行器变更时,重置执行器相关的配置
|
// 渲染基本配置表单项
|
||||||
useEffect(() => {
|
const renderBasicFormItems = () => (
|
||||||
if (selectedExecutor) {
|
<>
|
||||||
const fields = form.getFieldsValue() as FormFields;
|
<Form.Item
|
||||||
const executorFields: FormFields = {};
|
label="节点名称"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入节点名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入节点名称" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="节点描述"
|
||||||
|
name="description"
|
||||||
|
>
|
||||||
|
<Input.TextArea placeholder="请输入节点描述" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
// 清除旧的执行器配置
|
// 渲染Shell执行器特定配置
|
||||||
Object.keys(fields).forEach(key => {
|
const renderShellExecutorConfig = () => {
|
||||||
if (key !== 'executor' && key !== 'name' && key !== 'description') {
|
if (selectedExecutor !== 'SHELL') return null;
|
||||||
executorFields[key] = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置新的执行器默认配置
|
|
||||||
form.setFieldsValue({
|
|
||||||
...executorFields,
|
|
||||||
...executorDefaultConfig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [selectedExecutor, form, executorDefaultConfig]);
|
|
||||||
|
|
||||||
// 根据属性类型渲染表单控件
|
|
||||||
const renderFormItem = (key: string, property: any, required: boolean = false) => {
|
|
||||||
const {
|
|
||||||
type,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
minimum,
|
|
||||||
maximum,
|
|
||||||
minLength,
|
|
||||||
maxLength,
|
|
||||||
enum: enumValues,
|
|
||||||
enumNames,
|
|
||||||
pattern,
|
|
||||||
format,
|
|
||||||
} = property;
|
|
||||||
|
|
||||||
const rules: Rule[] = [];
|
|
||||||
|
|
||||||
// 添加必填规则
|
|
||||||
if (required) {
|
|
||||||
rules.push({ required: true, message: `请输入${title}` } as Rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加长度限制
|
|
||||||
if (minLength !== undefined) {
|
|
||||||
rules.push({ type: 'string', min: minLength, message: `最少输入${minLength}个字符` } as Rule);
|
|
||||||
}
|
|
||||||
if (maxLength !== undefined) {
|
|
||||||
rules.push({ type: 'string', max: maxLength, message: `最多输入${maxLength}个字符` } as Rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加数值范围限制
|
|
||||||
if (type === 'number') {
|
|
||||||
// 添加数字类型验证
|
|
||||||
rules.push({
|
|
||||||
type: 'number',
|
|
||||||
message: '请输入有效的数字',
|
|
||||||
transform(value) {
|
|
||||||
if (value === '' || value === undefined || value === null) return undefined;
|
|
||||||
const num = Number(value);
|
|
||||||
return isNaN(num) ? undefined : num;
|
|
||||||
},
|
|
||||||
} as Rule);
|
|
||||||
|
|
||||||
// 添加整数验证
|
|
||||||
rules.push({
|
|
||||||
validator: async (_: any, value: any) => {
|
|
||||||
if (value === undefined || value === null || value === '') return;
|
|
||||||
if (!Number.isInteger(Number(value))) {
|
|
||||||
throw new Error('请输入整数');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as Rule);
|
|
||||||
|
|
||||||
// 最小值验证
|
|
||||||
if (minimum !== undefined) {
|
|
||||||
rules.push({
|
|
||||||
validator: async (_: any, value: any) => {
|
|
||||||
if (value === undefined || value === null || value === '') return;
|
|
||||||
const num = Number(value);
|
|
||||||
if (num < minimum) {
|
|
||||||
throw new Error(`不能小于${minimum}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as Rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最大值验证
|
|
||||||
if (maximum !== undefined) {
|
|
||||||
rules.push({
|
|
||||||
validator: async (_: any, value: any) => {
|
|
||||||
if (value === undefined || value === null || value === '') return;
|
|
||||||
const num = Number(value);
|
|
||||||
if (num > maximum) {
|
|
||||||
throw new Error(`不能大于${maximum}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as Rule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加正则校验
|
|
||||||
if (pattern) {
|
|
||||||
try {
|
|
||||||
rules.push({ pattern: new RegExp(pattern), message: `格式不正确` } as Rule);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('正则表达式解析错误:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let formItem;
|
|
||||||
switch (type) {
|
|
||||||
case 'string':
|
|
||||||
if (enumValues) {
|
|
||||||
formItem = (
|
|
||||||
<Select
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder={`请选择${title}`}
|
|
||||||
options={enumValues.map((value: string, index: number) => ({
|
|
||||||
label: enumNames?.[index] || value,
|
|
||||||
value,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (format === 'shell') {
|
|
||||||
formItem = <Input.TextArea rows={6} placeholder={`请输入${title}`} />;
|
|
||||||
} else {
|
|
||||||
formItem = <Input placeholder={`请输入${title}`} />;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
formItem = (
|
|
||||||
<InputNumber
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder={`请输入${title}`}
|
|
||||||
min={minimum}
|
|
||||||
max={maximum}
|
|
||||||
precision={0} // 只允许整数
|
|
||||||
step={1} // 步长为1
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
formItem = <Switch checkedChildren="是" unCheckedChildren="否" />;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
formItem = <Input placeholder={`请输入${title}`} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<>
|
||||||
key={key}
|
<Form.Item
|
||||||
label={title}
|
label="脚本内容"
|
||||||
name={key}
|
name="script"
|
||||||
tooltip={description}
|
rules={[{ required: true, message: '请输入脚本内容' }]}
|
||||||
rules={rules}
|
>
|
||||||
>
|
<Input.TextArea rows={6} placeholder="请输入脚本内容" />
|
||||||
{formItem}
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
);
|
label="工作目录"
|
||||||
};
|
name="workingDirectory"
|
||||||
|
>
|
||||||
// 获取初始值
|
<Input placeholder="请输入工作目录" />
|
||||||
const getInitialValues = () => {
|
</Form.Item>
|
||||||
const values = { ...nodeDefaultConfig };
|
<Form.Item
|
||||||
|
label="超时时间"
|
||||||
// 如果有执行器配置,添加执行器的默认值
|
name="timeout"
|
||||||
if (selectedExecutor && executorDefaultConfig) {
|
rules={[{ type: 'number', min: 0, message: '超时时间必须大于0' }]}
|
||||||
Object.assign(values, executorDefaultConfig);
|
>
|
||||||
}
|
<InputNumber placeholder="请输入超时时间(秒)" style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
return values;
|
<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(([key, property]) =>
|
</Form.Item>
|
||||||
renderFormItem(key, property, nodeSchema.required?.includes(key))
|
<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(([key, property]) =>
|
<Form.Item
|
||||||
renderFormItem(key, property, executorSchema.required?.includes(key))
|
label="环境变量"
|
||||||
|
name="environment"
|
||||||
|
>
|
||||||
|
<Input.TextArea placeholder="请输入环境变量" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="成功退出码"
|
||||||
|
name="successExitCode"
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入成功退出码" />
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="node-config">
|
<Form
|
||||||
<Form
|
form={form}
|
||||||
form={form}
|
layout="vertical"
|
||||||
layout="vertical"
|
onValuesChange={onValuesChange}
|
||||||
onValuesChange={onValuesChange}
|
initialValues={{ ...nodeDefaultConfig, ...executorDefaultConfig }}
|
||||||
initialValues={getInitialValues()}
|
>
|
||||||
>
|
{renderBasicFormItems()}
|
||||||
{/* 节点基本配置 */}
|
<Divider />
|
||||||
{renderNodeConfig()}
|
|
||||||
|
|
||||||
{/* 执行器配置 */}
|
<Form.Item
|
||||||
{selectedExecutor && executorSchema && (
|
label="执行器"
|
||||||
<>
|
name="executor"
|
||||||
<Divider>执行器配置</Divider>
|
rules={[{ required: true, message: '请选择执行器' }]}
|
||||||
{renderExecutorConfig()}
|
>
|
||||||
</>
|
<Select
|
||||||
)}
|
placeholder="请选择执行器"
|
||||||
</Form>
|
options={nodeType.executors?.map(executor => ({
|
||||||
</div>
|
label: executor.name,
|
||||||
|
value: executor.code
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{renderShellExecutorConfig()}
|
||||||
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,9 @@ interface NodeData {
|
|||||||
retryInterval?: number;
|
retryInterval?: number;
|
||||||
script?: string;
|
script?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
workingDir?: string;
|
workingDirectory?: string;
|
||||||
|
environment?: string;
|
||||||
|
successExitCode?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -259,6 +261,8 @@ const FlowDesigner: React.FC = () => {
|
|||||||
graph.on('node:click', ({node}: {node: Node}) => {
|
graph.on('node:click', ({node}: {node: Node}) => {
|
||||||
setCurrentNode(node);
|
setCurrentNode(node);
|
||||||
const data = node.getData() as NodeData;
|
const data = node.getData() as NodeData;
|
||||||
|
console.log('Node clicked, data:', data);
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
// 获取节点类型
|
// 获取节点类型
|
||||||
const nodeType = nodeTypes.find(type => type.code === data.type);
|
const nodeType = nodeTypes.find(type => type.code === data.type);
|
||||||
@ -270,20 +274,23 @@ const FlowDesigner: React.FC = () => {
|
|||||||
name: data.name || nodeType.name,
|
name: data.name || nodeType.name,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
executor: data.config?.executor,
|
executor: data.config?.executor,
|
||||||
retryTimes: data.config?.retryTimes,
|
|
||||||
retryInterval: data.config?.retryInterval,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果是Shell节点,添加执行器特定配置
|
// 如果是Shell节点,添加执行器特定配置
|
||||||
if (data.type === 'SHELL' && data.config?.executor === 'SHELL') {
|
if (data.type === 'SHELL' && data.config?.executor === 'SHELL') {
|
||||||
|
console.log('Shell node config:', data.config);
|
||||||
Object.assign(formValues, {
|
Object.assign(formValues, {
|
||||||
script: data.config?.script || '',
|
script: data.config.script,
|
||||||
timeout: data.config?.timeout || 300,
|
timeout: data.config.timeout,
|
||||||
workingDir: data.config?.workingDir || '/tmp'
|
workingDirectory: data.config.workingDirectory,
|
||||||
|
environment: data.config.environment,
|
||||||
|
successExitCode: data.config.successExitCode,
|
||||||
|
retryTimes: data.config.retryTimes,
|
||||||
|
retryInterval: data.config.retryInterval,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置表单值
|
console.log('Setting form values:', formValues);
|
||||||
form.setFieldsValue(formValues);
|
form.setFieldsValue(formValues);
|
||||||
setConfigVisible(true);
|
setConfigVisible(true);
|
||||||
} else {
|
} else {
|
||||||
@ -415,6 +422,23 @@ const FlowDesigner: React.FC = () => {
|
|||||||
setConfigVisible(true);
|
setConfigVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 验证Shell节点配置
|
||||||
|
function validateShellConfig(config: any): boolean {
|
||||||
|
if (!config.script?.trim()) {
|
||||||
|
throw new Error('脚本内容不能为空');
|
||||||
|
}
|
||||||
|
if (config.timeout !== undefined && config.timeout <= 0) {
|
||||||
|
throw new Error('超时时间必须大于0');
|
||||||
|
}
|
||||||
|
if (config.retryTimes !== undefined && config.retryTimes < 0) {
|
||||||
|
throw new Error('重试次数不能为负数');
|
||||||
|
}
|
||||||
|
if (config.retryInterval !== undefined && config.retryInterval < 0) {
|
||||||
|
throw new Error('重试间隔不能为负数');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理配置保存
|
// 处理配置保存
|
||||||
const handleConfigSave = async () => {
|
const handleConfigSave = async () => {
|
||||||
try {
|
try {
|
||||||
@ -423,6 +447,11 @@ const FlowDesigner: React.FC = () => {
|
|||||||
const data = currentNode.getData() as NodeData;
|
const data = currentNode.getData() as NodeData;
|
||||||
const { name, description, ...config } = values;
|
const { name, description, ...config } = values;
|
||||||
|
|
||||||
|
// 如果是Shell节点,验证配置
|
||||||
|
if (data.type === 'SHELL' && config.executor === 'SHELL') {
|
||||||
|
validateShellConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
// 更新节点数据
|
// 更新节点数据
|
||||||
currentNode.setData({
|
currentNode.setData({
|
||||||
...data,
|
...data,
|
||||||
@ -438,7 +467,8 @@ const FlowDesigner: React.FC = () => {
|
|||||||
setConfigVisible(false);
|
setConfigVisible(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 表单验证失败
|
// 表单验证失败或Shell配置验证失败
|
||||||
|
message.error((error as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -533,22 +563,23 @@ const FlowDesigner: React.FC = () => {
|
|||||||
|
|
||||||
// 加载节点配置数据
|
// 加载节点配置数据
|
||||||
if (detail.nodeConfig) {
|
if (detail.nodeConfig) {
|
||||||
|
console.log('Loading node config:', detail.nodeConfig);
|
||||||
const nodeConfigData = JSON.parse(detail.nodeConfig);
|
const nodeConfigData = JSON.parse(detail.nodeConfig);
|
||||||
const nodeConfigs = nodeConfigData.nodes.reduce((acc: Record<string, any>, node: any) => {
|
console.log('Parsed node config:', nodeConfigData);
|
||||||
acc[node.id] = node;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// 更新节点数据和显示
|
// 更新节点数据和显示
|
||||||
graph.getNodes().forEach(node => {
|
graph.getNodes().forEach(node => {
|
||||||
const nodeConfig = nodeConfigs[node.id];
|
const nodeConfig = nodeConfigData.nodes.find((n: any) => n.id === node.id);
|
||||||
if (nodeConfig) {
|
if (nodeConfig) {
|
||||||
node.setData({
|
console.log('Node config found:', nodeConfig);
|
||||||
|
const nodeData = {
|
||||||
type: nodeConfig.type,
|
type: nodeConfig.type,
|
||||||
name: nodeConfig.name,
|
name: nodeConfig.name,
|
||||||
description: nodeConfig.description,
|
description: nodeConfig.description,
|
||||||
config: nodeConfig.config || {}
|
config: nodeConfig.config
|
||||||
});
|
};
|
||||||
|
console.log('Setting node data:', nodeData);
|
||||||
|
node.setData(nodeData);
|
||||||
node.setAttrByPath('label/text', nodeConfig.name);
|
node.setAttrByPath('label/text', nodeConfig.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user