sing-ui/app/runs/page.tsx

93 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { api, type RunWithPipeline } from "@/components/api";
import { StatusBadge } from "@/components/status-badge";
import { fmtTime, fmtDuration } from "@/components/format";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function RunsPage() {
const [runs, setRuns] = useState<RunWithPipeline[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const load = useCallback(() => {
api<RunWithPipeline[]>("/api/runs")
.then(setRuns)
.catch((e) => setError(e instanceof Error ? e.message : "加载失败"))
.finally(() => setLoading(false));
}, []);
useEffect(load, [load]);
// 有运行中的任务时轮询刷新
const hasActive = runs.some(
(r) => r.status === "running" || r.status === "queued"
);
useEffect(() => {
if (!hasActive) return;
const t = setInterval(load, 4000);
return () => clearInterval(t);
}, [hasActive, load]);
return (
<div className="flex flex-col gap-4">
<h1 className="text-xl font-semibold">Runs</h1>
{loading ? (
<div className="text-sm text-muted-foreground"></div>
) : error ? (
<div className="text-sm text-red-500">{error}</div>
) : runs.length === 0 ? (
<div className="rounded-md border border-dashed p-8 text-center text-sm text-muted-foreground">
</div>
) : (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-16">ID</TableHead>
<TableHead>线</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="w-20"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{runs.map((r) => (
<TableRow key={r.id}>
<TableCell className="font-mono">#{r.id}</TableCell>
<TableCell>
{r.pipeline_name ?? `(已删除 #${r.pipeline_id})`}
</TableCell>
<TableCell>
<StatusBadge status={r.status} />
</TableCell>
<TableCell>{fmtTime(r.started_at)}</TableCell>
<TableCell>{fmtDuration(r.started_at, r.finished_at)}</TableCell>
<TableCell>
<Button variant="link" size="sm">
<Link href={`/runs/${r.id}`}></Link>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
);
}