[WIP] workflow builder

This commit is contained in:
2026-02-23 20:45:10 -06:00
parent d629da32fa
commit 53a3fbb6b1
66 changed files with 7887 additions and 1608 deletions

View File

@@ -18,11 +18,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
const isEditing = !!pack;
// Store initial/database state for reset
const initialConfSchema = pack?.conf_schema || {
type: "object",
properties: {},
required: [],
};
const initialConfSchema = pack?.conf_schema || {};
const initialConfig = pack?.config || {};
// Form state
@@ -47,15 +43,17 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
const createPack = useCreatePack();
const updatePack = useUpdatePack();
// Check if schema has properties
// Check if schema has properties (flat format: each key is a parameter name)
const hasSchemaProperties =
confSchema?.properties && Object.keys(confSchema.properties).length > 0;
confSchema &&
typeof confSchema === "object" &&
Object.keys(confSchema).length > 0;
// Sync config values when schema changes (for ad-hoc packs only)
useEffect(() => {
if (!isStandard && hasSchemaProperties) {
// Get current schema property names
const schemaKeys = Object.keys(confSchema.properties || {});
// Get current schema property names (flat format: keys are parameter names)
const schemaKeys = Object.keys(confSchema);
// Create new config with only keys that exist in schema
const syncedConfig: Record<string, any> = {};
@@ -65,7 +63,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
syncedConfig[key] = configValues[key];
} else {
// Use default from schema if available
const defaultValue = confSchema.properties[key]?.default;
const defaultValue = confSchema[key]?.default;
if (defaultValue !== undefined) {
syncedConfig[key] = defaultValue;
}
@@ -99,10 +97,14 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
newErrors.version = "Version is required";
}
// Validate conf_schema
if (confSchema && confSchema.type !== "object") {
newErrors.confSchema =
'Config schema must have type "object" at root level';
// Validate conf_schema (flat format: each value should be an object defining a parameter)
if (confSchema && typeof confSchema === "object") {
for (const [key, val] of Object.entries(confSchema)) {
if (!val || typeof val !== "object" || Array.isArray(val)) {
newErrors.confSchema = `Invalid parameter definition for "${key}" — each parameter must be an object`;
break;
}
}
}
// Validate meta JSON
@@ -126,7 +128,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
}
const parsedConfSchema =
Object.keys(confSchema.properties || {}).length > 0 ? confSchema : {};
Object.keys(confSchema || {}).length > 0 ? confSchema : {};
const parsedMeta = meta.trim() ? JSON.parse(meta) : {};
const tagsList = tags
.split(",")
@@ -201,78 +203,75 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
};
const insertSchemaExample = (type: "api" | "database" | "webhook") => {
let example;
let example: Record<string, any>;
switch (type) {
case "api":
example = {
type: "object",
properties: {
api_key: {
type: "string",
description: "API authentication key",
},
endpoint: {
type: "string",
description: "API endpoint URL",
default: "https://api.example.com",
},
api_key: {
type: "string",
description: "API authentication key",
required: true,
secret: true,
},
endpoint: {
type: "string",
description: "API endpoint URL",
default: "https://api.example.com",
},
required: ["api_key"],
};
break;
case "database":
example = {
type: "object",
properties: {
host: {
type: "string",
description: "Database host",
default: "localhost",
},
port: {
type: "integer",
description: "Database port",
default: 5432,
},
database: {
type: "string",
description: "Database name",
},
username: {
type: "string",
description: "Database username",
},
password: {
type: "string",
description: "Database password",
},
host: {
type: "string",
description: "Database host",
default: "localhost",
required: true,
},
port: {
type: "integer",
description: "Database port",
default: 5432,
},
database: {
type: "string",
description: "Database name",
required: true,
},
username: {
type: "string",
description: "Database username",
required: true,
},
password: {
type: "string",
description: "Database password",
required: true,
secret: true,
},
required: ["host", "database", "username", "password"],
};
break;
case "webhook":
example = {
type: "object",
properties: {
webhook_url: {
type: "string",
description: "Webhook destination URL",
},
auth_token: {
type: "string",
description: "Authentication token",
},
timeout: {
type: "integer",
description: "Request timeout in seconds",
minimum: 1,
maximum: 300,
default: 30,
},
webhook_url: {
type: "string",
description: "Webhook destination URL",
required: true,
},
auth_token: {
type: "string",
description: "Authentication token",
secret: true,
},
timeout: {
type: "integer",
description: "Request timeout in seconds",
minimum: 1,
maximum: 300,
default: 30,
},
required: ["webhook_url"],
};
break;
}
@@ -282,15 +281,11 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
// Immediately sync config values with schema defaults
const syncedConfig: Record<string, any> = {};
if (example.properties) {
Object.entries(example.properties).forEach(
([key, propDef]: [string, any]) => {
if (propDef.default !== undefined) {
syncedConfig[key] = propDef.default;
}
},
);
}
Object.entries(example).forEach(([key, propDef]: [string, any]) => {
if (propDef.default !== undefined) {
syncedConfig[key] = propDef.default;
}
});
setConfigValues(syncedConfig);
};
@@ -578,7 +573,7 @@ export default function PackForm({ pack, onSuccess, onCancel }: PackFormProps) {
</p>
</div>
<ParamSchemaForm
schema={confSchema.properties}
schema={confSchema}
values={configValues}
onChange={setConfigValues}
errors={errors}

View File

@@ -123,6 +123,10 @@ export default function RuleForm({ rule, onSuccess, onCancel }: RuleFormProps) {
newErrors.label = "Label is required";
}
if (!description.trim()) {
newErrors.description = "Description is required";
}
if (!packId) {
newErrors.pack = "Pack is required";
}
@@ -347,7 +351,7 @@ export default function RuleForm({ rule, onSuccess, onCancel }: RuleFormProps) {
htmlFor="description"
className="block text-sm font-medium text-gray-700 mb-1"
>
Description
Description <span className="text-red-500">*</span>
</label>
<textarea
id="description"
@@ -355,8 +359,13 @@ export default function RuleForm({ rule, onSuccess, onCancel }: RuleFormProps) {
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe what this rule does..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.description ? "border-red-500" : "border-gray-300"
}`}
/>
{errors.description && (
<p className="mt-1 text-sm text-red-600">{errors.description}</p>
)}
</div>
{/* Enabled Toggle */}

View File

@@ -30,16 +30,8 @@ export default function TriggerForm({
const [description, setDescription] = useState("");
const [webhookEnabled, setWebhookEnabled] = useState(false);
const [enabled, setEnabled] = useState(true);
const [paramSchema, setParamSchema] = useState<Record<string, any>>({
type: "object",
properties: {},
required: [],
});
const [outSchema, setOutSchema] = useState<Record<string, any>>({
type: "object",
properties: {},
required: [],
});
const [paramSchema, setParamSchema] = useState<Record<string, any>>({});
const [outSchema, setOutSchema] = useState<Record<string, any>>({});
const [errors, setErrors] = useState<Record<string, string>>({});
// Fetch packs
@@ -58,20 +50,8 @@ export default function TriggerForm({
setDescription(initialData.description || "");
setWebhookEnabled(initialData.webhook_enabled || false);
setEnabled(initialData.enabled ?? true);
setParamSchema(
initialData.param_schema || {
type: "object",
properties: {},
required: [],
},
);
setOutSchema(
initialData.out_schema || {
type: "object",
properties: {},
required: [],
},
);
setParamSchema(initialData.param_schema || {});
setOutSchema(initialData.out_schema || {});
if (isEditing) {
// Find pack by pack_ref
@@ -129,13 +109,8 @@ export default function TriggerForm({
description: description.trim() || undefined,
enabled,
param_schema:
Object.keys(paramSchema.properties || {}).length > 0
? paramSchema
: undefined,
out_schema:
Object.keys(outSchema.properties || {}).length > 0
? outSchema
: undefined,
Object.keys(paramSchema).length > 0 ? paramSchema : undefined,
out_schema: Object.keys(outSchema).length > 0 ? outSchema : undefined,
};
if (isEditing && initialData?.ref) {