A flow's inputs are the messages it takes from outside — a dashboard control, a run, the API — and its outputs are what a batch run reports. Both existed in the document and in the engine, and neither had any UI: the values looked hard-coded on the canvas and the Run button always used the declared defaults. The canvas now draws each as a labelled endpoint, the way it already draws a dashboard tile or another flow, skipping an input something else already accounts for. The flow panel edits them — mode, name, type, starting value, and for a live flow the value it currently holds with a way to put a new one in. Pressing Run on a batch flow asks for its parameters first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUb8YpL2s3gmN9WTACTt4q
155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
import { useState } from "react"
|
|
|
|
import type { FlowDef_Input } from "@/client"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Switch } from "@/components/ui/switch"
|
|
import { BoundarySections } from "./FlowBoundary"
|
|
import { PANEL_SECTION, PanelTitle, SidePanel } from "./SidePanel"
|
|
|
|
/**
|
|
* The flow's own settings, in the same panel its nodes use — down to where the
|
|
* name sits and how confirming a new one works.
|
|
*/
|
|
export function FlowPanel({
|
|
open,
|
|
definition,
|
|
nodeCount,
|
|
onChange,
|
|
onDelete,
|
|
hasDraft,
|
|
enabled,
|
|
toggling,
|
|
onToggleEnabled,
|
|
onClose,
|
|
}: {
|
|
open: boolean
|
|
definition: FlowDef_Input
|
|
nodeCount: number
|
|
onChange: (next: FlowDef_Input) => void
|
|
onDelete: () => void
|
|
hasDraft: boolean
|
|
enabled: boolean
|
|
toggling: boolean
|
|
onToggleEnabled: (next: boolean) => void
|
|
onClose: () => void
|
|
}) {
|
|
const [confirmOpen, setConfirmOpen] = useState(false)
|
|
|
|
return (
|
|
<>
|
|
<SidePanel
|
|
open={open}
|
|
label="Flow settings"
|
|
testId="flow-panel"
|
|
bodyKey={definition.name}
|
|
onClose={onClose}
|
|
header={
|
|
<PanelTitle
|
|
value={definition.title ?? ""}
|
|
placeholder={definition.name}
|
|
label="Flow title"
|
|
onConfirm={(title) => onChange({ ...definition, title })}
|
|
/>
|
|
}
|
|
footer={
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-destructive hover:text-destructive"
|
|
onClick={() => setConfirmOpen(true)}
|
|
data-testid="delete-flow"
|
|
>
|
|
Delete flow
|
|
</Button>
|
|
}
|
|
>
|
|
<div className="grid gap-6 p-4">
|
|
<div className="grid gap-3">
|
|
<span className={PANEL_SECTION}>Running</span>
|
|
<div className="flex items-center justify-between gap-3">
|
|
<p className="text-sm text-muted-foreground">
|
|
{enabled
|
|
? "The engine runs this flow: subscriptions, schedules and webhooks are live."
|
|
: "Stopped. Nothing of this flow is subscribed, scheduled or reachable."}
|
|
</p>
|
|
<Switch
|
|
checked={enabled}
|
|
disabled={toggling}
|
|
onCheckedChange={onToggleEnabled}
|
|
aria-label="Run this flow"
|
|
data-testid="flow-enabled"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<BoundarySections
|
|
flow={definition.name}
|
|
definition={definition}
|
|
running={enabled}
|
|
onChange={onChange}
|
|
/>
|
|
|
|
<div className="grid gap-3">
|
|
<span className={PANEL_SECTION}>Contents</span>
|
|
<p className="text-sm text-muted-foreground">
|
|
<span className="font-mono">{definition.name}</span> namespaces
|
|
every message in here.{" "}
|
|
{nodeCount === 0
|
|
? "No nodes yet."
|
|
: `${nodeCount} node${nodeCount === 1 ? "" : "s"}.`}
|
|
</p>
|
|
</div>
|
|
|
|
{hasDraft ? (
|
|
<div className="grid gap-3">
|
|
<span className={PANEL_SECTION}>Unpublished changes</span>
|
|
<p className="text-sm text-muted-foreground">
|
|
The engine is still running the last published version of this
|
|
flow. The bar below publishes it, or throws the edit away.
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</SidePanel>
|
|
|
|
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Delete {definition.title || definition.name}?
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
This removes the flow and the code of its{" "}
|
|
{nodeCount === 1 ? "node" : `${nodeCount} nodes`}. Its history
|
|
stays in the flow store's git repository.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setConfirmOpen(false)}>
|
|
Keep it
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={() => {
|
|
setConfirmOpen(false)
|
|
onDelete()
|
|
}}
|
|
data-testid="confirm-delete-flow"
|
|
>
|
|
Delete flow
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|