mirror of
https://github.com/open-gitagent/langship.sh.git
synced 2026-08-03 07:21:04 +02:00
feat: add Gateway page and Runs page with real-time updates
- Implemented a new Gateway page that provides a uniform ingress for deployed agents. - Created a Runs page that lists recent pipeline executions with auto-refresh and error handling. - Added a RunsListener component to handle real-time notifications for new runs via SSE. - Updated the AppSidebar to include links to the new Gateway and Runs pages. - Enhanced FlowNode component to improve status display and styling. - Introduced EmptySection component for consistent empty state presentation. - Updated API utility to include a new endpoint for the runs stream.
This commit is contained in:
@@ -28,33 +28,34 @@ export function FlowNode({ data, selected }: NodeProps) {
|
||||
const outputs = entry?.outputs ?? 1;
|
||||
const isTrigger = pn.type === "flow-nodes-base.trigger";
|
||||
|
||||
// Per-type accent colour for the TYPE label. Keeps the existing
|
||||
// catalog `color` (full bg) but extracts the hue for header text.
|
||||
const typeAccent = typeAccentClass(entry?.color);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"min-w-[200px] rounded-lg border bg-card text-card-foreground shadow-sm transition-shadow",
|
||||
"min-w-[260px] rounded-xl border bg-card px-4 py-3 text-card-foreground shadow-sm transition-shadow",
|
||||
selected ? "ring-2 ring-primary shadow-md" : "hover:shadow-md",
|
||||
!entry && "border-destructive/60",
|
||||
status === "running" && "ring-2 ring-sky-500 animate-pulse",
|
||||
status === "success" && "ring-2 ring-emerald-500",
|
||||
status === "running" && "ring-2 ring-sky-500/60",
|
||||
status === "success" && "ring-1 ring-emerald-500/40",
|
||||
status === "failed" && "ring-2 ring-rose-500",
|
||||
status === "paused" && "ring-2 ring-amber-500"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-t-lg px-3 py-2 text-xs font-medium text-white",
|
||||
entry?.color ?? "bg-destructive"
|
||||
)}
|
||||
>
|
||||
<Icon className="size-3.5" />
|
||||
<span className="truncate">{entry?.label ?? "Unsupported"}</span>
|
||||
</div>
|
||||
<div className="px-3 py-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="truncate text-sm font-medium">{pn.name}</span>
|
||||
<StatusIcon status={status} />
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className={cn("flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-widest", typeAccent)}>
|
||||
<Icon className="size-3.5" />
|
||||
<span>{(entry?.label ?? "Unsupported")}</span>
|
||||
</div>
|
||||
<div className="truncate text-[11px] text-muted-foreground">{pn.type}</div>
|
||||
<StatusPill status={status} />
|
||||
</div>
|
||||
|
||||
<div className="mt-1.5 flex items-center gap-2">
|
||||
<span className="truncate text-xl font-semibold leading-tight">
|
||||
{pn.name}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Input handle: triggers have no inputs */}
|
||||
@@ -85,15 +86,75 @@ export function FlowNode({ data, selected }: NodeProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function StatusIcon({ status }: { status?: NodeRunStatus }) {
|
||||
if (!status || status === "pending") return null;
|
||||
if (status === "running")
|
||||
return <Loader2 className="size-3.5 animate-spin text-sky-500" />;
|
||||
if (status === "success")
|
||||
return <CheckCircle2 className="size-3.5 text-emerald-500" />;
|
||||
if (status === "failed")
|
||||
return <XCircle className="size-3.5 text-rose-500" />;
|
||||
if (status === "paused")
|
||||
return <PauseCircle className="size-3.5 text-amber-500" />;
|
||||
function StatusPill({ status }: { status?: NodeRunStatus }) {
|
||||
if (!status || status === "pending") {
|
||||
return (
|
||||
<span className="rounded-full border bg-muted/40 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
|
||||
pending
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === "running") {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-sky-500/40 bg-sky-500/15 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-sky-700 dark:text-sky-400">
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
running
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === "success") {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-emerald-500/40 bg-emerald-500/15 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-emerald-700 dark:text-emerald-400">
|
||||
<CheckCircle2 className="size-3" />
|
||||
succeeded
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === "failed") {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-rose-500/40 bg-rose-500/15 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-rose-700 dark:text-rose-400">
|
||||
<XCircle className="size-3" />
|
||||
failed
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (status === "paused") {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/40 bg-amber-500/15 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-widest text-amber-700 dark:text-amber-400">
|
||||
<PauseCircle className="size-3" />
|
||||
paused
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// typeAccentClass picks a tailwind text-color class that pairs with the
|
||||
// node's catalog `color` (bg-X-500) so the TYPE row reads as a distinct
|
||||
// accent against the card background.
|
||||
function typeAccentClass(bg?: string): string {
|
||||
switch (bg) {
|
||||
case "bg-emerald-500":
|
||||
return "text-emerald-500";
|
||||
case "bg-amber-500":
|
||||
return "text-amber-500";
|
||||
case "bg-sky-500":
|
||||
return "text-sky-500";
|
||||
case "bg-violet-500":
|
||||
return "text-violet-500";
|
||||
case "bg-indigo-500":
|
||||
return "text-indigo-500";
|
||||
case "bg-fuchsia-500":
|
||||
return "text-fuchsia-500";
|
||||
case "bg-rose-500":
|
||||
case "bg-red-600":
|
||||
return "text-rose-500";
|
||||
case "bg-orange-500":
|
||||
return "text-orange-500";
|
||||
case "bg-slate-500":
|
||||
case "bg-slate-400":
|
||||
return "text-slate-500";
|
||||
default:
|
||||
return "text-primary";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user