SHELL可以正确的设置和回显。
This commit is contained in:
parent
41975feedc
commit
c58e799c22
@ -67,217 +67,107 @@ const NodeConfig: React.FC<NodeConfigProps> = ({ nodeType, form, onValuesChange
|
||||
return config || {};
|
||||
}, [selectedExecutor, nodeType.executors]);
|
||||
|
||||
// 当执行器变更时,重置执行器相关的配置
|
||||
useEffect(() => {
|
||||
if (selectedExecutor) {
|
||||
const fields = form.getFieldsValue() as FormFields;
|
||||
const executorFields: FormFields = {};
|
||||
// 渲染基本配置表单项
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
// 清除旧的执行器配置
|
||||
Object.keys(fields).forEach(key => {
|
||||
if (key !== 'executor' && key !== 'name' && key !== 'description') {
|
||||
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}`} />;
|
||||
}
|
||||
// 渲染Shell执行器特定配置
|
||||
const renderShellExecutorConfig = () => {
|
||||
if (selectedExecutor !== 'SHELL') return null;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
key={key}
|
||||
label={title}
|
||||
name={key}
|
||||
tooltip={description}
|
||||
rules={rules}
|
||||
>
|
||||
{formItem}
|
||||
</Form.Item>
|
||||
);
|
||||
};
|
||||
|
||||
// 获取初始值
|
||||
const getInitialValues = () => {
|
||||
const values = { ...nodeDefaultConfig };
|
||||
|
||||
// 如果有执行器配置,添加执行器的默认值
|
||||
if (selectedExecutor && executorDefaultConfig) {
|
||||
Object.assign(values, executorDefaultConfig);
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
// 渲染节点基本配置
|
||||
const renderNodeConfig = () => {
|
||||
if (!nodeSchema?.properties) return null;
|
||||
return Object.entries(nodeSchema.properties).map(([key, property]) =>
|
||||
renderFormItem(key, property, nodeSchema.required?.includes(key))
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染执行器配置
|
||||
const renderExecutorConfig = () => {
|
||||
if (!executorSchema?.properties) return null;
|
||||
return Object.entries(executorSchema.properties).map(([key, property]) =>
|
||||
renderFormItem(key, property, executorSchema.required?.includes(key))
|
||||
<>
|
||||
<Form.Item
|
||||
label="脚本内容"
|
||||
name="script"
|
||||
rules={[{ required: true, message: '请输入脚本内容' }]}
|
||||
>
|
||||
<Input.TextArea rows={6} placeholder="请输入脚本内容" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="工作目录"
|
||||
name="workingDirectory"
|
||||
>
|
||||
<Input placeholder="请输入工作目录" />
|
||||
</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 (
|
||||
<div className="node-config">
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={onValuesChange}
|
||||
initialValues={getInitialValues()}
|
||||
>
|
||||
{/* 节点基本配置 */}
|
||||
{renderNodeConfig()}
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onValuesChange={onValuesChange}
|
||||
initialValues={{ ...nodeDefaultConfig, ...executorDefaultConfig }}
|
||||
>
|
||||
{renderBasicFormItems()}
|
||||
<Divider />
|
||||
|
||||
{/* 执行器配置 */}
|
||||
{selectedExecutor && executorSchema && (
|
||||
<>
|
||||
<Divider>执行器配置</Divider>
|
||||
{renderExecutorConfig()}
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="执行器"
|
||||
name="executor"
|
||||
rules={[{ required: true, message: '请选择执行器' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择执行器"
|
||||
options={nodeType.executors?.map(executor => ({
|
||||
label: executor.name,
|
||||
value: executor.code
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{renderShellExecutorConfig()}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -30,7 +30,9 @@ interface NodeData {
|
||||
retryInterval?: number;
|
||||
script?: string;
|
||||
timeout?: number;
|
||||
workingDir?: string;
|
||||
workingDirectory?: string;
|
||||
environment?: string;
|
||||
successExitCode?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
@ -259,6 +261,8 @@ const FlowDesigner: React.FC = () => {
|
||||
graph.on('node:click', ({node}: {node: Node}) => {
|
||||
setCurrentNode(node);
|
||||
const data = node.getData() as NodeData;
|
||||
console.log('Node clicked, data:', data);
|
||||
|
||||
if (data) {
|
||||
// 获取节点类型
|
||||
const nodeType = nodeTypes.find(type => type.code === data.type);
|
||||
@ -270,20 +274,23 @@ const FlowDesigner: React.FC = () => {
|
||||
name: data.name || nodeType.name,
|
||||
description: data.description,
|
||||
executor: data.config?.executor,
|
||||
retryTimes: data.config?.retryTimes,
|
||||
retryInterval: data.config?.retryInterval,
|
||||
};
|
||||
|
||||
// 如果是Shell节点,添加执行器特定配置
|
||||
if (data.type === 'SHELL' && data.config?.executor === 'SHELL') {
|
||||
console.log('Shell node config:', data.config);
|
||||
Object.assign(formValues, {
|
||||
script: data.config?.script || '',
|
||||
timeout: data.config?.timeout || 300,
|
||||
workingDir: data.config?.workingDir || '/tmp'
|
||||
script: data.config.script,
|
||||
timeout: data.config.timeout,
|
||||
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);
|
||||
setConfigVisible(true);
|
||||
} else {
|
||||
@ -415,6 +422,23 @@ const FlowDesigner: React.FC = () => {
|
||||
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 () => {
|
||||
try {
|
||||
@ -423,6 +447,11 @@ const FlowDesigner: React.FC = () => {
|
||||
const data = currentNode.getData() as NodeData;
|
||||
const { name, description, ...config } = values;
|
||||
|
||||
// 如果是Shell节点,验证配置
|
||||
if (data.type === 'SHELL' && config.executor === 'SHELL') {
|
||||
validateShellConfig(config);
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
currentNode.setData({
|
||||
...data,
|
||||
@ -438,7 +467,8 @@ const FlowDesigner: React.FC = () => {
|
||||
setConfigVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
// 表单验证失败
|
||||
// 表单验证失败或Shell配置验证失败
|
||||
message.error((error as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -533,22 +563,23 @@ const FlowDesigner: React.FC = () => {
|
||||
|
||||
// 加载节点配置数据
|
||||
if (detail.nodeConfig) {
|
||||
console.log('Loading node config:', detail.nodeConfig);
|
||||
const nodeConfigData = JSON.parse(detail.nodeConfig);
|
||||
const nodeConfigs = nodeConfigData.nodes.reduce((acc: Record<string, any>, node: any) => {
|
||||
acc[node.id] = node;
|
||||
return acc;
|
||||
}, {});
|
||||
console.log('Parsed node config:', nodeConfigData);
|
||||
|
||||
// 更新节点数据和显示
|
||||
graph.getNodes().forEach(node => {
|
||||
const nodeConfig = nodeConfigs[node.id];
|
||||
const nodeConfig = nodeConfigData.nodes.find((n: any) => n.id === node.id);
|
||||
if (nodeConfig) {
|
||||
node.setData({
|
||||
console.log('Node config found:', nodeConfig);
|
||||
const nodeData = {
|
||||
type: nodeConfig.type,
|
||||
name: nodeConfig.name,
|
||||
description: nodeConfig.description,
|
||||
config: nodeConfig.config || {}
|
||||
});
|
||||
config: nodeConfig.config
|
||||
};
|
||||
console.log('Setting node data:', nodeData);
|
||||
node.setData(nodeData);
|
||||
node.setAttrByPath('label/text', nodeConfig.name);
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user