"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.sast":
return ;
case "flow-nodes-base.imageScan":
return ;
case "flow-nodes-base.push":
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.sast",
"flow-nodes-base.imageScan",
"flow-nodes-base.push",
"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") : [];
}
function getBool(node: PipelineNode, key: string, fallback: boolean): boolean {
const v = node.parameters?.[key];
if (typeof v === "boolean") return v;
return fallback;
}
// --- forms ----------------------------------------------------------------
function TriggerForm({ node, onChange }: NodeFormProps) {
const mode = getString(node, "mode", "manual");
const cron = getString(node, "cron", "0 * * * *");
const fromBranch = getString(node, "fromBranch", "main");
const toBranch = getString(node, "toBranch", "production");
return (
How runs are dispatched. Webhook + manual are wired today; schedule
lands once the cron worker exists.
In docker-compose, registry:5000 is the bundled
local registry (host port 5050 for docker pull).
For ghcr.io, the agent’s PAT must have{" "}
write:packages.
Each target runs in parallel. Image is pulled from the source once and
pushed concurrently — no docker daemon needed. For GHCR, password is a
PAT with write:packages.
Promote uses the agent’s PAT to talk to GitHub.{" "}
open-pr opens a PR (idempotent — re-finds an existing
one); merge POSTs to /merges;{" "}
merge-pr opens then merges.