"use client"; import { useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { toast } from "sonner"; import { Plus, Trash2 } from "lucide-react"; import { api, type Connection, type Pipeline } from "@/components/api"; import { fmtTime } from "@/components/format"; import { ConfirmDialog } from "@/components/confirm-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; export default function PipelinesPage() { const [pipelines, setPipelines] = useState([]); const [connMap, setConnMap] = useState>({}); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [deleting, setDeleting] = useState(null); const [deleteLoading, setDeleteLoading] = useState(false); const load = useCallback(() => { Promise.all([ api("/api/pipelines"), api("/api/connections"), ]) .then(([p, conns]) => { setPipelines(p); setConnMap(Object.fromEntries(conns.map((c) => [c.id, c.name]))); }) .catch((e) => setError(e instanceof Error ? e.message : "加载失败")) .finally(() => setLoading(false)); }, []); useEffect(load, [load]); async function doDelete() { if (!deleting) return; setDeleteLoading(true); try { await api(`/api/pipelines/${deleting.id}`, { method: "DELETE" }); toast.success(`已删除流水线「${deleting.name}」`); setDeleting(null); load(); } catch (e) { toast.error(e instanceof Error ? e.message : "删除失败"); } finally { setDeleteLoading(false); } } const connName = (id: number) => connMap[id] ?? `#${id}`; return (

Pipelines

{loading ? (
加载中…
) : error ? (
加载失败:{error}
) : pipelines.length === 0 ? (
还没有流水线,点击右上角「新建流水线」创建数据同步任务。
) : (
名称 源 → 目标 Mode Schema 同步 更新时间 操作 {pipelines.map((p) => ( {p.name} {connName(p.source_conn_id)} → {connName(p.target_conn_id)} {p.mode} {p.schema_sync ? ( ) : ( )} {fmtTime(p.updated_at)}
))}
)} !o && setDeleting(null)} title={`删除流水线「${deleting?.name ?? ""}」?`} description="删除后其运行历史仍会保留。" loading={deleteLoading} onConfirm={doDelete} />
); }