From e35e8fc839dee936877000d97f8e4448da31ebf6 Mon Sep 17 00:00:00 2001 From: mrhid6 Date: Mon, 20 Jul 2026 11:53:30 +0100 Subject: [PATCH] feat(web): workflow run detail page with live logs --- web/app/workflows/[id]/runs/[runId]/page.tsx | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 web/app/workflows/[id]/runs/[runId]/page.tsx diff --git a/web/app/workflows/[id]/runs/[runId]/page.tsx b/web/app/workflows/[id]/runs/[runId]/page.tsx new file mode 100644 index 0000000..8b3d8a5 --- /dev/null +++ b/web/app/workflows/[id]/runs/[runId]/page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useParams } from "next/navigation"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { api, ServerRun, StepRun } from "@/lib/api"; +import { Button, Badge, Card } from "@/components/ui"; + +type BadgeVariant = "success" | "warning" | "danger" | "neutral" | "accent"; + +const statusVariant: Record = { + success: "success", + failed: "danger", + running: "accent", + queued: "neutral", + skipped: "neutral", + cancelled: "warning", +}; + +function StatusBadge({ status }: { status: string }) { + return {status}; +} + +export default function RunDetail() { + const { runId } = useParams<{ runId: string }>(); + const queryClient = useQueryClient(); + + const { data: run, isLoading } = useQuery({ + queryKey: ["run", runId], + queryFn: () => api.getRun(runId), + refetchInterval: (query) => (query.state.data?.status === "running" ? 2000 : false), + }); + + const cancel = async () => { + await api.cancelRun(runId); + queryClient.invalidateQueries({ queryKey: ["run", runId] }); + }; + + if (isLoading || !run) { + return
Loading…
; + } + + return ( +
+
+
+

{run.name}

+

+ Run {run.run_id.slice(0, 8)} + +

+
+ {run.status === "running" && ( + + )} +
+ +
+ {run.server_runs.map((sr: ServerRun) => ( + +
+ {sr.hostname} + +
+
+ {sr.steps.map((st: StepRun) => ( +
+ + {st.name} + + + attempts: {st.attempts} + {st.status === "failed" ? ` · exit ${st.exit_code}` : ""} + + + + + {(st.stdout || st.stderr) && ( +
+                      {st.stdout}
+                      {st.stderr ? `\n${st.stderr}` : ""}
+                    
+ )} +
+ ))} + {sr.steps.length === 0 && ( +

No steps yet.

+ )} +
+
+ ))} + {run.server_runs.length === 0 && ( +

No servers targeted by this run.

+ )} +
+
+ ); +}