"use client"; import { Suspense, useEffect, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; import { Play, Save, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { api, type StoredFlow } from "@/lib/api"; import { formatDate } from "@/lib/utils"; export default function FlowDetailPage() { return ( Loading…}> ); } function FlowDetail() { const router = useRouter(); const params = useSearchParams(); const id = params.get("id") ?? ""; const [flow, setFlow] = useState(null); const [name, setName] = useState(""); const [definition, setDefinition] = useState(""); const [input, setInput] = useState("[{}]"); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [lastExecId, setLastExecId] = useState(null); async function load() { if (!id) return; try { const f = await api.getFlow(id); setFlow(f); setName(f.name); setDefinition(JSON.stringify(f.definition, null, 2)); setError(null); } catch (e) { setError(e instanceof Error ? e.message : "load failed"); } } useEffect(() => { load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]); async function onSave() { setSaving(true); setError(null); try { const parsed = JSON.parse(definition); await api.updateFlow(id, { name, definition: parsed }); await load(); } catch (e) { setError(e instanceof Error ? e.message : "save failed"); } finally { setSaving(false); } } async function onRun() { setRunning(true); setError(null); try { let parsedInput: unknown[] | undefined; if (input.trim()) { const v = JSON.parse(input); if (!Array.isArray(v)) throw new Error("Input must be a JSON array"); parsedInput = v; } const res = await api.executeWorkflow({ workflow_id: id, input: parsedInput }); setLastExecId(res.execution_id); router.push(`/executions/view/?id=${encodeURIComponent(res.execution_id)}`); } catch (e) { setError(e instanceof Error ? e.message : "run failed"); } finally { setRunning(false); } } async function onDelete() { if (!confirm("Delete this flow? This cannot be undone.")) return; try { await api.deleteFlow(id); router.push("/"); } catch (e) { setError(e instanceof Error ? e.message : "delete failed"); } } if (!id) { return (
Missing id query param.
); } return (
{flow && (
{flow.id} · updated {formatDate(flow.updatedAt)} {flow.nodeCount} nodes
)}

{name || "Untitled flow"}

Edit, save, and run this workflow against the orchestrator.

{error && ( {error} )}
Definition n8n-format JSON
setName(e.target.value)} />