Files
triggershell/app/src/app/(app)/runs/page.tsx
T
valknarandClaude Sonnet 5 ced99a8e75 Initial implementation of TriggerShell
A Python CLI (typer) that bootstraps Node/pnpm and launches a Next.js 16 web
app for running configured shell scripts: YAML config validated by a shared
Zod schema, dynamic per-script forms mapped to shadcn controls, argv-safe
execa execution with live WebSocket streaming, SQLite/Drizzle run history,
and optional argon2 session + API token auth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 18:37:30 +02:00

89 lines
2.7 KiB
TypeScript

export const dynamic = "force-dynamic";
import Link from "next/link";
import { desc } from "drizzle-orm";
import { getDb } from "@/lib/db/client";
import { runs } from "@/lib/db/schema";
import { RunStatusBadge } from "@/components/runs/run-status-badge";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
function formatDuration(startedAt: Date | null, endedAt: Date | null): string {
if (!startedAt) return "-";
const end = endedAt ?? new Date();
const seconds = Math.max(
0,
Math.round((end.getTime() - startedAt.getTime()) / 1000),
);
if (seconds < 60) return `${seconds}s`;
const minutes = Math.floor(seconds / 60);
return `${minutes}m ${seconds % 60}s`;
}
export default function RunsPage() {
const db = getDb();
const rows = db
.select()
.from(runs)
.orderBy(desc(runs.createdAt))
.limit(100)
.all();
return (
<div className="flex flex-col gap-4">
<h1 className="text-2xl font-semibold tracking-tight">Run History</h1>
{rows.length === 0 ? (
<p className="text-muted-foreground py-12 text-center">No runs yet.</p>
) : (
<div className="overflow-x-auto rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead>Script</TableHead>
<TableHead>Status</TableHead>
<TableHead>Triggered by</TableHead>
<TableHead>Started</TableHead>
<TableHead>Duration</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((run) => (
<TableRow key={run.id} className="cursor-pointer">
<TableCell>
<Link
href={`/runs/${run.id}`}
className="font-medium hover:underline"
>
{run.scriptName}
</Link>
</TableCell>
<TableCell>
<RunStatusBadge status={run.status} />
</TableCell>
<TableCell className="text-muted-foreground">
{run.triggeredBy}
</TableCell>
<TableCell className="text-muted-foreground">
{run.startedAt
? new Date(run.startedAt).toLocaleString()
: "-"}
</TableCell>
<TableCell className="text-muted-foreground">
{formatDuration(run.startedAt, run.endedAt)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
);
}