93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
"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>
|
||
);
|
||
}
|