import Link from "next/link";
import { dashboard } from "@/lib/admin/queries";
import { PageTitle } from "@/components/admin-ui";
export default async function Dashboard() {
const d = await dashboard(); const q = Object.fromEntries(d.queue.map((x) => [x.status, x.n])); const j = Object.fromEntries(d.jobs.map((x) => [x.status, x.n]));
const tile = (l: string, v: string | number, href?: string) =>
New paperIngestion queue
{tile("Published", d.counts.published, "/admin/papers?status=published")}{tile("Drafts", d.counts.draft, "/admin/papers?status=draft")}{tile("Reserved", d.counts.reserved, "/admin/papers?status=reserved")}{tile("Withdrawn", d.counts.withdrawn, "/admin/papers?status=withdrawn")}
{tile("Ingest: ready for review", q.ready ?? 0, "/admin/ingest")}{tile("Ingest: processing", (q.queued ?? 0) + (q.processing ?? 0))}{tile("Jobs failed (7 d)", j.failed ?? 0, "/admin/system")}{tile("Jobs queued", (j.queued ?? 0) + (j.running ?? 0), "/admin/system")}
{tile("Embeddings", `${d.ai.embedded}/${d.ai.total} papers · ${d.ai.chunks_embedded}/${d.ai.chunks} chunks`)}{tile("Last backup", d.backup ? `${d.backup.ok ? "OK" : "FAILED"} · ${new Date(d.backup.created_at).toLocaleString("en-CA")}${d.backup.remote_ok === false ? " · remote failed" : ""}` : "none", "/admin/system")}
Views and downloads, last 30 days
{d.views.length ? `${d.views.reduce((a, x) => a + x.views, 0)} views · ${d.views.reduce((a, x) => a + x.downloads, 0)} downloads` : "No aggregated data yet (daily job runs after midnight)."}
Top searches, 30 days
{d.searches.length ? | Query | Count | Avg results |
{d.searches.map((s) => | {s.query} | {s.n} | {s.avg_results} |
)}
: No searches yet.
}
Recent audit entries
| When | Actor | Action | Entity |
{d.audit.map((a) => | {new Date(a.at).toLocaleString("en-CA")} | {a.actor_label} | {a.action} | {a.entity_type} {a.entity_id ?? ""} |
)}
Full audit log →
);
}