Files
triggershell/src/app/(app)/runs/[runId]/page.tsx
T
valknarandClaude Sonnet 5 adeb21be19 Fix Re-run button squeezing the run metadata row in the header
CardAction's default row-span-2 reserved header column 2 across both
the title row and the metadata dl row below it, shrinking the dl's
available width (cramping the Run ID column) to fit around the
button. Scope the button to just the title row and let the dl span
the full header width on its own row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 03:05:55 +02:00

147 lines
5.0 KiB
TypeScript

export const dynamic = "force-dynamic";
import type { Metadata } from "next";
import Link from "next/link";
import { eq } from "drizzle-orm";
import { notFound } from "next/navigation";
import { RotateCw } from "lucide-react";
import { getDb } from "@/lib/db/client";
import { runs } from "@/lib/db/schema";
import { getScript } from "@/lib/config/load";
import { readLogTail } from "@/lib/runner/log-file";
import { RunTerminal } from "@/components/runs/run-terminal";
import { buttonVariants } from "@/components/ui/button";
import {
Card,
CardAction,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card";
interface RunDetailPageProps {
params: Promise<{ runId: string }>;
}
function formatVariableValue(value: unknown): string {
if (value === undefined || value === null || value === "") return "—";
if (Array.isArray(value)) return value.length ? value.join(", ") : "—";
if (typeof value === "boolean") return value ? "true" : "false";
return String(value);
}
export async function generateMetadata({
params,
}: RunDetailPageProps): Promise<Metadata> {
const { runId } = await params;
const run = getDb()
.select({ scriptName: runs.scriptName })
.from(runs)
.where(eq(runs.id, runId))
.get();
return { title: run ? `${run.scriptName} run` : "Run not found" };
}
export default async function RunDetailPage({ params }: RunDetailPageProps) {
const { runId } = await params;
const db = getDb();
const run = db.select().from(runs).where(eq(runs.id, runId)).get();
if (!run) notFound();
const { text: initialLog, size: initialLogBytes } = readLogTail(
run.logFilePath,
);
const script = getScript(run.scriptId);
const variableEntries = script
? script.variables.map((variable) => ({
key: variable.name,
label: variable.label ?? variable.name,
value: run.variables[variable.name],
}))
: Object.entries(run.variables).map(([key, value]) => ({
key,
label: key,
value,
}));
return (
<div className="mx-auto flex max-w-5xl flex-col gap-4">
<Card>
<CardHeader>
<CardTitle>{run.scriptName}</CardTitle>
{script && (
<CardAction className="row-span-1">
<Link
href={`/scripts/${run.scriptId}?fromRun=${run.id}`}
className={buttonVariants({ variant: "outline", size: "sm" })}
>
<RotateCw className="size-3.5" />
Re-run
</Link>
</CardAction>
)}
<dl className="text-muted-foreground col-span-2 grid grid-cols-2 gap-x-4 gap-y-2 text-xs sm:grid-cols-3">
<div>
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
Triggered by
</dt>
<dd className="font-mono">{run.triggeredBy}</dd>
</div>
<div>
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
Started
</dt>
<dd className="font-mono">
{run.startedAt ? new Date(run.startedAt).toLocaleString() : "-"}
</dd>
</div>
<div>
<dt className="font-mono text-[0.7rem] font-medium tracking-widest uppercase">
Run ID
</dt>
<dd className="truncate font-mono">{run.id}</dd>
</div>
</dl>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<span className="text-muted-foreground font-mono text-[0.7rem] font-medium tracking-widest uppercase">
Command
</span>
<pre className="bg-muted border-primary/30 overflow-x-auto rounded-md border-l-2 p-3 font-mono text-xs whitespace-pre">
{run.resolvedCommand}
</pre>
</div>
{variableEntries.length > 0 && (
<div className="flex flex-col gap-1.5">
<span className="text-muted-foreground font-mono text-[0.7rem] font-medium tracking-widest uppercase">
Variables
</span>
<dl className="grid gap-x-6 gap-y-2 rounded-md border p-3 text-xs sm:grid-cols-2 lg:grid-cols-3">
{variableEntries.map(({ key, label, value }) => (
<div key={key} className="flex flex-col gap-0.5">
<dt className="text-muted-foreground">{label}</dt>
<dd className="font-mono break-all">
{formatVariableValue(value)}
</dd>
</div>
))}
</dl>
</div>
)}
<RunTerminal
runId={run.id}
initialStatus={run.status}
initialLog={initialLog}
initialLogBytes={initialLogBytes}
initialExitCode={run.exitCode}
/>
</CardContent>
</Card>
</div>
);
}