"use client"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import type { PipelineNode } from "@/lib/pipeline-graph"; interface NodeFormProps { node: PipelineNode; onChange: (next: PipelineNode) => void; } // Typed forms per node type. Anything we don't know about renders a generic // JSON view (handled by the inspector — this component returns null in that // case so the parent shows the JSON fallback). // // Each form mutates node.parameters and calls onChange with the updated node. // We deliberately keep these dumb (no internal state); the inspector owns // debouncing and persistence. export function NodeForm({ node, onChange }: NodeFormProps) { switch (node.type) { case "flow-nodes-base.trigger": return ; case "flow-nodes-base.build": return ; case "flow-nodes-base.test": return ; case "flow-nodes-base.eval": return ; case "flow-nodes-base.policy": return ; case "flow-nodes-base.waitForApproval": return ; case "flow-nodes-base.deploy": return ; case "flow-nodes-base.promote": return ; case "flow-nodes-base.rollback": return ; default: return null; } } /** Returns true if we render a typed form for this type (so the JSON * fallback can be hidden). */ export function hasTypedForm(type: string): boolean { return [ "flow-nodes-base.trigger", "flow-nodes-base.build", "flow-nodes-base.test", "flow-nodes-base.eval", "flow-nodes-base.policy", "flow-nodes-base.waitForApproval", "flow-nodes-base.deploy", "flow-nodes-base.promote", "flow-nodes-base.rollback", ].includes(type); } // --- helpers -------------------------------------------------------------- function setParam(node: PipelineNode, key: string, value: T): PipelineNode { return { ...node, parameters: { ...(node.parameters ?? {}), [key]: value }, }; } function getString(node: PipelineNode, key: string, fallback = ""): string { const v = node.parameters?.[key]; return typeof v === "string" ? v : fallback; } function getNumber(node: PipelineNode, key: string, fallback = 0): number { const v = node.parameters?.[key]; return typeof v === "number" ? v : fallback; } function getStringArray(node: PipelineNode, key: string): string[] { const v = node.parameters?.[key]; return Array.isArray(v) ? v.filter((x): x is string => typeof x === "string") : []; } // --- forms ---------------------------------------------------------------- function TriggerForm({ node, onChange }: NodeFormProps) { const mode = getString(node, "mode", "manual"); const cron = getString(node, "cron", "0 * * * *"); return (

How runs are dispatched. Webhook + manual are wired today; schedule lands once the cron worker exists.

{mode === "schedule" && (
onChange(setParam(node, "cron", e.target.value))} placeholder="0 * * * *" className="font-mono text-xs" />
)}
); } function BuildForm({ node, onChange }: NodeFormProps) { const command = getString( node, "command", "docker build -t $AGENT_NAME:$COMMIT_SHA ." ); const workdir = getString(node, "workdir", "."); const timeout = getNumber(node, "timeoutSeconds", 600); return (