1
This commit is contained in:
parent
4fded64d7c
commit
ff88de9aa8
@ -38,6 +38,10 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// 固定的节点信息(不通过 schema,直接管理)
|
||||||
|
const [nodeName, setNodeName] = useState('');
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
|
||||||
// 动态数据源缓存
|
// 动态数据源缓存
|
||||||
const [dataSourceCache, setDataSourceCache] = useState<Record<string, DataSourceOption[]>>({});
|
const [dataSourceCache, setDataSourceCache] = useState<Record<string, DataSourceOption[]>>({});
|
||||||
const [loadingDataSources, setLoadingDataSources] = useState(false);
|
const [loadingDataSources, setLoadingDataSources] = useState(false);
|
||||||
@ -45,12 +49,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
// 获取节点定义
|
// 获取节点定义
|
||||||
const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null;
|
const nodeDefinition: WorkflowNodeDefinition | null = node?.data?.nodeDefinition || null;
|
||||||
|
|
||||||
// ✅ 生成 Zod Schema
|
// ✅ 生成 Zod Schema(仅输入映射)
|
||||||
const configSchema = useMemo(() => {
|
|
||||||
if (!nodeDefinition?.configSchema) return null;
|
|
||||||
return convertJsonSchemaToZod(nodeDefinition.configSchema);
|
|
||||||
}, [nodeDefinition?.configSchema]);
|
|
||||||
|
|
||||||
const inputMappingSchema = useMemo(() => {
|
const inputMappingSchema = useMemo(() => {
|
||||||
if (!nodeDefinition || !isConfigurableNode(nodeDefinition) || !nodeDefinition.inputMappingSchema) {
|
if (!nodeDefinition || !isConfigurableNode(nodeDefinition) || !nodeDefinition.inputMappingSchema) {
|
||||||
return null;
|
return null;
|
||||||
@ -58,31 +57,22 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
return convertJsonSchemaToZod(nodeDefinition.inputMappingSchema);
|
return convertJsonSchemaToZod(nodeDefinition.inputMappingSchema);
|
||||||
}, [nodeDefinition]);
|
}, [nodeDefinition]);
|
||||||
|
|
||||||
// ✅ 创建表单实例(基本配置)
|
// ✅ 创建表单实例(仅输入映射)
|
||||||
const configForm = useForm({
|
|
||||||
resolver: configSchema ? zodResolver(configSchema) : undefined,
|
|
||||||
defaultValues: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ 创建表单实例(输入映射)
|
|
||||||
const inputForm = useForm({
|
const inputForm = useForm({
|
||||||
resolver: inputMappingSchema ? zodResolver(inputMappingSchema) : undefined,
|
resolver: inputMappingSchema ? zodResolver(inputMappingSchema) : undefined,
|
||||||
defaultValues: {}
|
defaultValues: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ 预加载动态数据源
|
// ✅ 预加载动态数据源(仅输入映射)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!visible || !nodeDefinition) return;
|
if (!visible || !nodeDefinition) return;
|
||||||
|
|
||||||
const loadDynamicData = async () => {
|
const loadDynamicData = async () => {
|
||||||
const configTypes = extractDataSourceTypes(nodeDefinition.configSchema);
|
|
||||||
const inputTypes = isConfigurableNode(nodeDefinition) && nodeDefinition.inputMappingSchema
|
const inputTypes = isConfigurableNode(nodeDefinition) && nodeDefinition.inputMappingSchema
|
||||||
? extractDataSourceTypes(nodeDefinition.inputMappingSchema)
|
? extractDataSourceTypes(nodeDefinition.inputMappingSchema)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const allTypes = [...new Set([...configTypes, ...inputTypes])];
|
if (inputTypes.length === 0) {
|
||||||
|
|
||||||
if (allTypes.length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +80,7 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
try {
|
try {
|
||||||
const cache: Record<string, DataSourceOption[]> = {};
|
const cache: Record<string, DataSourceOption[]> = {};
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
allTypes.map(async (type) => {
|
inputTypes.map(async (type) => {
|
||||||
const data = await loadDataSource(type as DataSourceType);
|
const data = await loadDataSource(type as DataSourceType);
|
||||||
cache[type] = data;
|
cache[type] = data;
|
||||||
})
|
})
|
||||||
@ -116,21 +106,11 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
if (visible && node && nodeDefinition) {
|
if (visible && node && nodeDefinition) {
|
||||||
const nodeData = node.data || {};
|
const nodeData = node.data || {};
|
||||||
|
|
||||||
// 设置基本配置默认值
|
// 设置固定节点信息(从 configs 或 nodeDefinition 获取)
|
||||||
const defaultConfig = {
|
setNodeName(nodeData.configs?.nodeName || nodeDefinition.nodeName);
|
||||||
nodeName: nodeDefinition.nodeName,
|
setDescription(nodeData.configs?.description || nodeDefinition.description || '');
|
||||||
nodeCode: nodeDefinition.nodeCode,
|
|
||||||
description: nodeDefinition.description || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
// 转换 UUID 格式为显示名称格式
|
// 设置输入映射默认值(转换 UUID 为显示名称)
|
||||||
const displayConfigs = convertObjectToDisplayName(
|
|
||||||
{ ...defaultConfig, ...(nodeData.configs || {}) },
|
|
||||||
allNodes
|
|
||||||
);
|
|
||||||
configForm.reset(displayConfigs);
|
|
||||||
|
|
||||||
// 设置输入映射默认值(也需要转换)
|
|
||||||
if (isConfigurableNode(nodeDefinition)) {
|
if (isConfigurableNode(nodeDefinition)) {
|
||||||
const displayInputMapping = convertObjectToDisplayName(
|
const displayInputMapping = convertObjectToDisplayName(
|
||||||
nodeData.inputMapping || {},
|
nodeData.inputMapping || {},
|
||||||
@ -148,8 +128,18 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
if (!node || !nodeDefinition) return;
|
if (!node || !nodeDefinition) return;
|
||||||
|
|
||||||
// 使用 handleSubmit 验证并获取数据
|
// 验证节点名称(必填)
|
||||||
configForm.handleSubmit(async (configData) => {
|
if (!nodeName || !nodeName.trim()) {
|
||||||
|
toast({
|
||||||
|
title: '保存失败',
|
||||||
|
description: '节点名称不能为空',
|
||||||
|
variant: 'destructive'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 handleSubmit 验证输入映射(如果存在)
|
||||||
|
const submitForm = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 获取输入映射数据
|
// 获取输入映射数据
|
||||||
@ -158,14 +148,20 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
inputData = inputForm.getValues();
|
inputData = inputForm.getValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换显示名称格式为 UUID 格式
|
// 转换显示名称格式为 UUID 格式(仅输入映射需要)
|
||||||
const uuidConfigs = convertObjectToUUID(configData as Record<string, any>, allNodes);
|
|
||||||
const uuidInputMapping = convertObjectToUUID(inputData as Record<string, any>, allNodes);
|
const uuidInputMapping = convertObjectToUUID(inputData as Record<string, any>, allNodes);
|
||||||
|
|
||||||
|
// 构建 configs(包含元数据)
|
||||||
|
const configs = {
|
||||||
|
nodeName: nodeName.trim(),
|
||||||
|
nodeCode: nodeDefinition.nodeCode, // 只读,从定义获取
|
||||||
|
description: description.trim()
|
||||||
|
};
|
||||||
|
|
||||||
// 构建更新数据
|
// 构建更新数据
|
||||||
const updatedData: Partial<FlowNodeData> = {
|
const updatedData: Partial<FlowNodeData> = {
|
||||||
label: (configData as any).nodeName || nodeDefinition.nodeName,
|
label: nodeName.trim(),
|
||||||
configs: uuidConfigs,
|
configs,
|
||||||
inputMapping: uuidInputMapping,
|
inputMapping: uuidInputMapping,
|
||||||
outputs: isConfigurableNode(nodeDefinition) ? nodeDefinition.outputs || [] : []
|
outputs: isConfigurableNode(nodeDefinition) ? nodeDefinition.outputs || [] : []
|
||||||
};
|
};
|
||||||
@ -189,19 +185,25 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
};
|
||||||
|
|
||||||
|
// 如果有输入映射schema,使用表单验证
|
||||||
|
if (inputMappingSchema) {
|
||||||
|
inputForm.handleSubmit(submitForm)();
|
||||||
|
} else {
|
||||||
|
submitForm();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ 重置表单
|
// ✅ 重置表单
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (!node || !nodeDefinition) return;
|
if (!node || !nodeDefinition) return;
|
||||||
|
|
||||||
const defaultConfig = {
|
// 重置节点信息
|
||||||
nodeName: nodeDefinition.nodeName,
|
setNodeName(nodeDefinition.nodeName);
|
||||||
nodeCode: nodeDefinition.nodeCode,
|
setDescription(nodeDefinition.description || '');
|
||||||
description: nodeDefinition.description || ''
|
|
||||||
};
|
// 重置输入映射
|
||||||
configForm.reset(defaultConfig);
|
|
||||||
inputForm.reset({});
|
inputForm.reset({});
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@ -413,18 +415,62 @@ const NodeConfigModal: React.FC<NodeConfigModalProps> = ({
|
|||||||
<div className="flex-1 overflow-y-auto px-6 py-4">
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
<Accordion
|
<Accordion
|
||||||
type="multiple"
|
type="multiple"
|
||||||
defaultValue={["config"]}
|
defaultValue={["info", "input"]}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
{/* 基本配置 */}
|
{/* 节点信息(固定表单,不通过 schema) */}
|
||||||
<AccordionItem value="config" className="border-b">
|
<AccordionItem value="info" className="border-b">
|
||||||
<AccordionTrigger className="text-base font-semibold flex-row-reverse justify-end gap-2 hover:no-underline">
|
<AccordionTrigger className="text-base font-semibold flex-row-reverse justify-end gap-2 hover:no-underline">
|
||||||
基本配置
|
节点信息
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="px-1 space-y-4">
|
<AccordionContent className="px-1 space-y-4">
|
||||||
<Form {...configForm}>
|
{/* 节点编码(只读) */}
|
||||||
{renderFormFields(nodeDefinition.configSchema, configForm)}
|
<div className="space-y-2">
|
||||||
</Form>
|
<label className="text-sm font-medium">
|
||||||
|
节点编码
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={nodeDefinition.nodeCode}
|
||||||
|
disabled
|
||||||
|
className="bg-muted cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
节点的唯一标识符(只读)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 节点名称 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">
|
||||||
|
节点名称 <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={nodeName}
|
||||||
|
onChange={(e) => setNodeName(e.target.value)}
|
||||||
|
placeholder="请输入节点名称"
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
节点在流程图中显示的名称
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 节点描述 */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">
|
||||||
|
节点描述
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
placeholder="请输入节点描述"
|
||||||
|
disabled={loading}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
节点的详细说明
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
|
|||||||
@ -35,33 +35,6 @@ export const EndEventNodeDefinition: BaseNodeDefinition = {
|
|||||||
showBadge: false, // 结束节点不显示徽章
|
showBadge: false, // 结束节点不显示徽章
|
||||||
showHoverMenu: false // 结束节点不显示菜单
|
showHoverMenu: false // 结束节点不显示菜单
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
configSchema: {
|
|
||||||
type: "object",
|
|
||||||
title: "基本配置",
|
|
||||||
description: "节点的基本信息",
|
|
||||||
properties: {
|
|
||||||
nodeName: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点名称",
|
|
||||||
description: "节点在流程图中显示的名称",
|
|
||||||
default: "结束"
|
|
||||||
},
|
|
||||||
nodeCode: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点编码",
|
|
||||||
description: "节点的唯一标识符",
|
|
||||||
default: "END_EVENT"
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点描述",
|
|
||||||
description: "节点的详细说明",
|
|
||||||
default: "工作流的结束节点"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["nodeName", "nodeCode"]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -38,33 +38,8 @@ export const JenkinsBuildNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 基本配置Schema
|
// 基本配置Schema(仅业务配置,元数据已移至固定表单)
|
||||||
configSchema: {
|
configSchema: undefined,
|
||||||
type: "object",
|
|
||||||
title: "基本配置",
|
|
||||||
description: "节点的基本信息和Jenkins构建配置参数",
|
|
||||||
properties: {
|
|
||||||
nodeName: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点名称",
|
|
||||||
description: "节点在流程图中显示的名称",
|
|
||||||
default: "Jenkins构建"
|
|
||||||
},
|
|
||||||
nodeCode: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点编码",
|
|
||||||
description: "节点的唯一标识符",
|
|
||||||
default: "JENKINS_BUILD"
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点描述",
|
|
||||||
description: "节点的详细说明",
|
|
||||||
default: "通过Jenkins执行构建任务"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["nodeName", "nodeCode", "jenkinsServerId"]
|
|
||||||
},
|
|
||||||
inputMappingSchema: {
|
inputMappingSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
title: "输入",
|
title: "输入",
|
||||||
|
|||||||
@ -38,33 +38,8 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 基本配置 Schema
|
// 基本配置Schema(仅业务配置,元数据已移至固定表单)
|
||||||
configSchema: {
|
configSchema: undefined,
|
||||||
type: "object",
|
|
||||||
title: "基本配置",
|
|
||||||
description: "通知节点的基本配置信息",
|
|
||||||
properties: {
|
|
||||||
nodeName: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点名称",
|
|
||||||
description: "节点在流程图中显示的名称",
|
|
||||||
default: "通知"
|
|
||||||
},
|
|
||||||
nodeCode: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点编码",
|
|
||||||
description: "节点的唯一标识符",
|
|
||||||
default: "NOTIFICATION"
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点描述",
|
|
||||||
description: "节点的详细说明",
|
|
||||||
default: "发送通知消息到指定渠道"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["nodeName", "nodeCode", "notificationType", "title", "content"]
|
|
||||||
},
|
|
||||||
inputMappingSchema: {
|
inputMappingSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
title: "输入",
|
title: "输入",
|
||||||
@ -78,11 +53,17 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
// enumNames: ["邮件", "钉钉", "企业微信", "短信"],
|
// enumNames: ["邮件", "钉钉", "企业微信", "短信"],
|
||||||
// default: "EMAIL"
|
// default: "EMAIL"
|
||||||
// },
|
// },
|
||||||
notificationType: {
|
// notificationChannelType: {
|
||||||
|
// type: "string",
|
||||||
|
// title: "通知类型",
|
||||||
|
// description: "选择通知发送的渠道",
|
||||||
|
// 'x-dataSource': DataSourceType.NOTIFICATION_CHANNEL_TYPES
|
||||||
|
// },
|
||||||
|
notificationChannel: {
|
||||||
type: "string",
|
type: "string",
|
||||||
title: "通知类型",
|
title: "通知渠道",
|
||||||
description: "选择通知发送的渠道",
|
description: "选择通知发送的渠道",
|
||||||
'x-dataSource': DataSourceType.NOTIFICATION_CHANNEL_TYPES
|
'x-dataSource': DataSourceType.NOTIFICATION_CHANNELS
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@ -98,7 +79,7 @@ export const NotificationNodeDefinition: ConfigurableNodeDefinition = {
|
|||||||
default: ""
|
default: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
required: ["jenkinsServerId"]
|
required: ["notificationChannel", "title", "content"]
|
||||||
},
|
},
|
||||||
outputs: [{
|
outputs: [{
|
||||||
name: "status",
|
name: "status",
|
||||||
|
|||||||
@ -35,33 +35,6 @@ export const StartEventNodeDefinition: BaseNodeDefinition = {
|
|||||||
showBadge: false, // 开始节点不显示徽章
|
showBadge: false, // 开始节点不显示徽章
|
||||||
showHoverMenu: false // 开始节点不显示菜单
|
showHoverMenu: false // 开始节点不显示菜单
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
configSchema: {
|
|
||||||
type: "object",
|
|
||||||
title: "基本配置",
|
|
||||||
description: "节点的基本信息",
|
|
||||||
properties: {
|
|
||||||
nodeName: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点名称",
|
|
||||||
description: "节点在流程图中显示的名称",
|
|
||||||
default: "开始"
|
|
||||||
},
|
|
||||||
nodeCode: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点编码",
|
|
||||||
description: "节点的唯一标识符",
|
|
||||||
default: "START_EVENT"
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: "string",
|
|
||||||
title: "节点描述",
|
|
||||||
description: "节点的详细说明",
|
|
||||||
default: "工作流的起始节点"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: ["nodeName", "nodeCode"]
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const schema = definition.inputMappingSchema;
|
const schema = definition.inputMappingSchema;
|
||||||
|
const inputMapping = nodeData.inputMapping || {};
|
||||||
|
|
||||||
// 获取所有定义的输入字段(从 schema)
|
// 获取所有定义的输入字段(从 schema)
|
||||||
if (!schema.properties) {
|
if (!schema.properties) {
|
||||||
@ -33,10 +34,19 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
|
|||||||
|
|
||||||
const allInputs = Object.keys(schema.properties).map(key => {
|
const allInputs = Object.keys(schema.properties).map(key => {
|
||||||
const fieldSchema = schema.properties![key];
|
const fieldSchema = schema.properties![key];
|
||||||
|
const fieldValue = inputMapping[key];
|
||||||
|
|
||||||
|
// 检查字段是否已填写(非空、非null、非undefined、非空字符串)
|
||||||
|
const isFilled = fieldValue !== undefined &&
|
||||||
|
fieldValue !== null &&
|
||||||
|
fieldValue !== '' &&
|
||||||
|
(typeof fieldValue !== 'number' || !isNaN(fieldValue));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
title: fieldSchema.title || key,
|
title: fieldSchema.title || key,
|
||||||
description: fieldSchema.description,
|
description: fieldSchema.description,
|
||||||
|
isFilled,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,13 +61,19 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
|
|||||||
{/* 输入字段标签 */}
|
{/* 输入字段标签 */}
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{allInputs.map((input, index) => (
|
{allInputs.map((input, index) => (
|
||||||
<code
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="text-xs px-2 py-1 bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300 rounded-md font-mono inline-flex items-center"
|
className={`
|
||||||
title={input.description}
|
text-xs px-2 py-1 rounded-md font-medium inline-flex items-center
|
||||||
|
${input.isFilled
|
||||||
|
? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'
|
||||||
|
: 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
title={`${input.description}${input.isFilled ? ' ✓ 已填写' : ' ✗ 未填写'}`}
|
||||||
>
|
>
|
||||||
{input.title}
|
{input.title}
|
||||||
</code>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -76,13 +92,13 @@ const BaseNode: React.FC<NodeProps> = ({ data, selected }) => {
|
|||||||
{/* 输出字段标签 */}
|
{/* 输出字段标签 */}
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{nodeData.outputs.map((output, index) => (
|
{nodeData.outputs.map((output, index) => (
|
||||||
<code
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="text-xs px-2 py-1 bg-secondary text-secondary-foreground rounded-md font-mono inline-flex items-center"
|
className="text-xs px-2 py-1 bg-secondary text-secondary-foreground rounded-md font-medium inline-flex items-center"
|
||||||
title={output.description}
|
title={output.description}
|
||||||
>
|
>
|
||||||
{output.title || output.name}
|
{output.title || output.name}
|
||||||
</code>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,8 @@ export enum DataSourceType {
|
|||||||
K8S_CLUSTERS = 'K8S_CLUSTERS',
|
K8S_CLUSTERS = 'K8S_CLUSTERS',
|
||||||
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
|
GIT_REPOSITORIES = 'GIT_REPOSITORIES',
|
||||||
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
|
DOCKER_REGISTRIES = 'DOCKER_REGISTRIES',
|
||||||
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES'
|
NOTIFICATION_CHANNEL_TYPES = 'NOTIFICATION_CHANNEL_TYPES',
|
||||||
|
NOTIFICATION_CHANNELS = 'NOTIFICATION_CHANNELS'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,6 +27,7 @@ export interface DataSourceConfig {
|
|||||||
export interface DataSourceOption {
|
export interface DataSourceOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: any;
|
value: any;
|
||||||
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ export interface DataSourceOption {
|
|||||||
export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
||||||
[DataSourceType.JENKINS_SERVERS]: {
|
[DataSourceType.JENKINS_SERVERS]: {
|
||||||
url: '/api/v1/external-system/list',
|
url: '/api/v1/external-system/list',
|
||||||
params: { type: 'JENKINS', enabled: true },
|
params: {type: 'JENKINS', enabled: true},
|
||||||
transform: (data: any[]) => {
|
transform: (data: any[]) => {
|
||||||
return data.map((item: any) => ({
|
return data.map((item: any) => ({
|
||||||
label: `${item.name} (${item.url})`,
|
label: `${item.name} (${item.url})`,
|
||||||
@ -54,9 +56,18 @@ export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[DataSourceType.NOTIFICATION_CHANNELS]: {
|
||||||
|
url: '/api/v1/notification-channel/list',
|
||||||
|
transform: (data: any[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: `(${item.channelType})-${item.name}`,
|
||||||
|
value: item.id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
[DataSourceType.K8S_CLUSTERS]: {
|
[DataSourceType.K8S_CLUSTERS]: {
|
||||||
url: '/api/v1/k8s-cluster/list',
|
url: '/api/v1/k8s-cluster/list',
|
||||||
params: { enabled: true },
|
params: {enabled: true},
|
||||||
transform: (data: any[]) => {
|
transform: (data: any[]) => {
|
||||||
return data.map((item: any) => ({
|
return data.map((item: any) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
@ -67,7 +78,7 @@ export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
|||||||
},
|
},
|
||||||
[DataSourceType.GIT_REPOSITORIES]: {
|
[DataSourceType.GIT_REPOSITORIES]: {
|
||||||
url: '/api/v1/git-repo/list',
|
url: '/api/v1/git-repo/list',
|
||||||
params: { enabled: true },
|
params: {enabled: true},
|
||||||
transform: (data: any[]) => {
|
transform: (data: any[]) => {
|
||||||
return data.map((item: any) => ({
|
return data.map((item: any) => ({
|
||||||
label: `${item.name} (${item.url})`,
|
label: `${item.name} (${item.url})`,
|
||||||
@ -78,7 +89,7 @@ export const DATA_SOURCE_REGISTRY: Record<DataSourceType, DataSourceConfig> = {
|
|||||||
},
|
},
|
||||||
[DataSourceType.DOCKER_REGISTRIES]: {
|
[DataSourceType.DOCKER_REGISTRIES]: {
|
||||||
url: '/api/v1/docker-registry/list',
|
url: '/api/v1/docker-registry/list',
|
||||||
params: { enabled: true },
|
params: {enabled: true},
|
||||||
transform: (data: any[]) => {
|
transform: (data: any[]) => {
|
||||||
return data.map((item: any) => ({
|
return data.map((item: any) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
@ -103,7 +114,7 @@ export const loadDataSource = async (type: DataSourceType): Promise<DataSourceOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await request.get(config.url, { params: config.params });
|
const response = await request.get(config.url, {params: config.params});
|
||||||
// request 拦截器已经提取了 data 字段,response 直接是数组
|
// request 拦截器已经提取了 data 字段,response 直接是数组
|
||||||
return config.transform(response || []);
|
return config.transform(response || []);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user