126 lines
4.4 KiB
TypeScript
126 lines
4.4 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { listPublicationPoints } from "../api/service";
|
|
import type { PublicationPointRecord } from "../api/schemas";
|
|
import { CursorPagerControls } from "../components/CursorPagerControls";
|
|
import { DataTable, type Column } from "../components/DataTable";
|
|
import { PageHeader } from "../components/PageHeader";
|
|
import { Panel } from "../components/Panel";
|
|
import { StatusPill } from "../components/StatusPill";
|
|
import { useCursorPager } from "../lib/cursor";
|
|
import { formatDurationMs, formatInt, truncateMiddle } from "../lib/format";
|
|
import { withRunParam } from "../lib/run";
|
|
import { useRun } from "../lib/useRun";
|
|
|
|
export default function PublicationPointsPage() {
|
|
const { runId } = useRun();
|
|
const navigate = useNavigate();
|
|
const pager = useCursorPager(runId);
|
|
const [needle, setNeedle] = useState("");
|
|
|
|
const ppsQuery = useQuery({
|
|
queryKey: ["pps", runId, pager.cursor],
|
|
queryFn: () => listPublicationPoints(runId, { limit: 50, cursor: pager.cursor }),
|
|
placeholderData: (prev) => prev,
|
|
});
|
|
|
|
const rows = useMemo(() => {
|
|
const items = ppsQuery.data?.items ?? [];
|
|
const q = needle.trim().toLowerCase();
|
|
if (!q) return items;
|
|
return items.filter((pp) =>
|
|
[pp.manifestRsyncUri, pp.publicationPointRsyncUri, pp.rrdpNotificationUri, pp.rsyncBaseUri, pp.ppId]
|
|
.filter(Boolean)
|
|
.some((value) => value!.toLowerCase().includes(q)),
|
|
);
|
|
}, [ppsQuery.data, needle]);
|
|
|
|
const columns: Column<PublicationPointRecord>[] = [
|
|
{
|
|
key: "manifest",
|
|
header: "Publication point",
|
|
render: (pp) => (
|
|
<div>
|
|
<div className="cell-main mono text-small ellipsis" style={{ maxWidth: 380 }} title={pp.manifestRsyncUri ?? pp.ppId}>
|
|
{pp.manifestRsyncUri ? truncateMiddle(pp.manifestRsyncUri, 68) : pp.ppId}
|
|
</div>
|
|
<div className="text-faint text-small mono">{pp.ppId}</div>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: "source",
|
|
header: "Sync source",
|
|
render: (pp) => <span className="chip">{pp.repoSyncSource ?? pp.source ?? "—"}</span>,
|
|
},
|
|
{
|
|
key: "state",
|
|
header: "Terminal state",
|
|
render: (pp) => <StatusPill status={pp.repoTerminalState} />,
|
|
},
|
|
{ key: "objects", header: "Objects", numeric: true, render: (pp) => formatInt(pp.objects) },
|
|
{
|
|
key: "rejected",
|
|
header: "Rejected",
|
|
numeric: true,
|
|
render: (pp) =>
|
|
pp.rejectedObjects ? (
|
|
<span style={{ color: "var(--red-700)", fontWeight: 600 }}>{formatInt(pp.rejectedObjects)}</span>
|
|
) : (
|
|
"0"
|
|
),
|
|
},
|
|
{ key: "warnings", header: "Warnings", numeric: true, render: (pp) => formatInt(pp.warnings) },
|
|
{
|
|
key: "duration",
|
|
header: "Sync time",
|
|
numeric: true,
|
|
render: (pp) => formatDurationMs(pp.repoSyncDurationMs),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="page">
|
|
<PageHeader title="Publication Points" subtitle="All publication points indexed for the active run" />
|
|
<Panel
|
|
title="All publication points"
|
|
tools={
|
|
<input
|
|
type="search"
|
|
value={needle}
|
|
onChange={(e) => setNeedle(e.target.value)}
|
|
placeholder="Filter current page (URI/id)…"
|
|
aria-label="Filter current page"
|
|
style={{ minWidth: 220, padding: "5px 8px", border: "1px solid var(--border)", borderRadius: "var(--radius-m)" }}
|
|
/>
|
|
}
|
|
flush
|
|
>
|
|
<DataTable
|
|
columns={columns}
|
|
rows={rows}
|
|
rowKey={(pp) => pp.ppId}
|
|
loading={ppsQuery.isPending}
|
|
error={ppsQuery.isError ? ppsQuery.error : undefined}
|
|
onRetry={() => ppsQuery.refetch()}
|
|
emptyTitle="No publication points indexed"
|
|
caption="Publication points"
|
|
onRowClick={(pp) =>
|
|
navigate(withRunParam(`/publication-points/${encodeURIComponent(pp.ppId)}`, runId))
|
|
}
|
|
/>
|
|
<CursorPagerControls
|
|
page={pager.page}
|
|
canPrev={pager.canPrev}
|
|
hasNext={ppsQuery.data?.nextCursor != null}
|
|
loading={ppsQuery.isFetching}
|
|
onPrev={pager.goPrev}
|
|
onNext={() => pager.goNext(ppsQuery.data?.nextCursor ?? null)}
|
|
itemCount={rows.length}
|
|
/>
|
|
</Panel>
|
|
</div>
|
|
);
|
|
}
|