20260728_2 修复rpki-explorer体验问题7项(#134):证书资源可展开、CMS对象新增EE Parsed Tab、Overview增加repo数KPI/修复Object types标签/Top repositories按objects排序(后端sort参数)/reject reasons按前缀聚类可下钻、Run选择器30s轮询

This commit is contained in:
2026-07-28 13:21:58 +08:00
parent 0d6bbec9f3
commit f7f80fe684
20 changed files with 676 additions and 70 deletions

View File

@@ -15,6 +15,7 @@ import {
import { Clock, GitBranch, Timer } from "lucide-react";
import {
getStatsObjectTypes,
getStatsOverview,
getStatsReasons,
getStatsValidation,
listRepos,
@@ -35,6 +36,7 @@ import {
objectTypeLabel,
truncateMiddle,
} from "../lib/format";
import { clusterReasons } from "../lib/reasons";
import { withRunParam } from "../lib/run";
import { useRun, runErrorTitle } from "../lib/useRun";
@@ -61,7 +63,7 @@ export default function OverviewPage() {
const { runId, runQuery } = useRun();
const navigate = useNavigate();
const [validationQuery, typesQuery, reasonsQuery, reposQuery] = useQueries({
const [validationQuery, typesQuery, reasonsQuery, reposQuery, overviewQuery] = useQueries({
queries: [
{
queryKey: ["stats", runId, "validation"],
@@ -80,7 +82,12 @@ export default function OverviewPage() {
},
{
queryKey: ["repos", runId, "top"],
queryFn: () => listRepos(runId, { limit: 8 }),
queryFn: () => listRepos(runId, { limit: 8, sort: "objects", order: "desc" }),
staleTime: 60_000,
},
{
queryKey: ["stats", runId, "overview"],
queryFn: () => getStatsOverview(runId),
staleTime: 60_000,
},
],
@@ -109,13 +116,10 @@ export default function OverviewPage() {
.slice(0, 8);
}, [typesQuery.data]);
const reasonRows = useMemo(() => {
const map = reasonsQuery.data ?? {};
return Object.entries(map)
.map(([reason, count]) => ({ reason, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 8);
}, [reasonsQuery.data]);
const reasonRows = useMemo(
() => clusterReasons(reasonsQuery.data ?? {}),
[reasonsQuery.data],
);
const maxReason = reasonRows[0]?.count ?? 0;
@@ -224,6 +228,11 @@ export default function OverviewPage() {
value={formatInt(counts?.publicationPoints)}
to={withRunParam("/publication-points", runId)}
/>
<KpiCard
label="Repositories"
value={formatInt(overviewQuery.data?.repos)}
to={withRunParam("/repositories", runId)}
/>
<KpiCard
label="Rejected"
value={formatInt(counts?.rejectedObjects)}
@@ -304,7 +313,7 @@ export default function OverviewPage() {
<YAxis
type="category"
dataKey="name"
width={104}
width={150}
tickLine={false}
axisLine={false}
tick={{ fontSize: 12, fill: "var(--text-2)" }}
@@ -325,7 +334,7 @@ export default function OverviewPage() {
<div className="overview-bottom">
<Panel
title="Top repositories"
subtitle="First repositories by index order"
subtitle="Repositories with the most objects"
tools={
<Link className="btn small" to={withRunParam("/repositories", runId)}>
View all
@@ -353,7 +362,7 @@ export default function OverviewPage() {
<Panel
title="Top reject reasons"
subtitle="Click a reason to inspect matching objects"
subtitle="Grouped by reason prefix — click to inspect matching objects"
tools={
<Link className="btn small" to={withRunParam("/validation", runId)}>
Validation
@@ -370,12 +379,17 @@ export default function OverviewPage() {
<div className="reason-list">
{reasonRows.map((row) => (
<Link
key={row.reason}
key={row.key}
className="reason-row"
to={withRunParam(`/validation?reason=${encodeURIComponent(row.reason)}`, runId)}
title={row.reason}
to={withRunParam(`/validation?reason=${encodeURIComponent(row.key)}`, runId)}
title={row.key}
>
<span className="reason-text">{row.reason}</span>
<span className="reason-text">
{row.key}
{row.variants > 1 ? (
<span className="text-faint text-small"> ×{row.variants}</span>
) : null}
</span>
<span className="reason-count">{formatInt(row.count)}</span>
<span className="reason-bar" aria-hidden="true">
<span style={{ width: `${maxReason ? (row.count / maxReason) * 100 : 0}%` }} />