SHELL可以正确的设置和回显。

This commit is contained in:
dengqichen 2024-12-06 13:31:46 +08:00
parent 41975feedc
commit c58e799c22
2 changed files with 138 additions and 217 deletions

View File

@ -67,218 +67,108 @@ 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 = {};
// 清除旧的执行器配置
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}`} />;
}
return (
// 渲染基本配置表单项
const renderBasicFormItems = () => (
<>
<Form.Item
key={key}
label={title}
name={key}
tooltip={description}
rules={rules}
label="节点名称"
name="name"
rules={[{ required: true, message: '请输入节点名称' }]}
>
{formItem}
<Input placeholder="请输入节点名称" />
</Form.Item>
);
};
<Form.Item
label="节点描述"
name="description"
>
<Input.TextArea placeholder="请输入节点描述" />
</Form.Item>
</>
);
// 获取初始值
const getInitialValues = () => {
const values = { ...nodeDefaultConfig };
// 渲染Shell执行器特定配置
const renderShellExecutorConfig = () => {
if (selectedExecutor !== 'SHELL') return null;
// 如果有执行器配置,添加执行器的默认值
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))
return (
<>
<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()}
<Form
form={form}
layout="vertical"
onValuesChange={onValuesChange}
initialValues={{ ...nodeDefaultConfig, ...executorDefaultConfig }}
>
{renderBasicFormItems()}
<Divider />
<Form.Item
label="执行器"
name="executor"
rules={[{ required: true, message: '请选择执行器' }]}
>
{/* 节点基本配置 */}
{renderNodeConfig()}
<Select
placeholder="请选择执行器"
options={nodeType.executors?.map(executor => ({
label: executor.name,
value: executor.code
}))}
/>
</Form.Item>
{/* 执行器配置 */}
{selectedExecutor && executorSchema && (
<>
<Divider></Divider>
{renderExecutorConfig()}
</>
)}
</Form>
</div>
{renderShellExecutorConfig()}
</Form>
);
};
export default NodeConfig;
export default NodeConfig;

View File

@ -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);
}
});