spb/cancerindex
Public
TypeScript 97.2%
SQL 1.5%
CSS 0.6%
JavaScript 0.5%
1import Link from 'next/link';2import { Section, KV } from '@/components/ui/section';3import { StatusBadge } from '@/components/ui/badge';4import { EmptyState } from '@/components/ui/empty-state';5import { adminOverview } from '@/lib/queries/admin';6import { listRuns } from '@/lib/queries/sources';7import { fmtDateTime, fmtDuration, fmtInt } from '@/lib/format';89export default async function AdminOverviewPage() {10 const [o, runs] = await Promise.all([adminOverview(), listRuns({ limit: 15 })]);11 return (12 <div className="space-y-8">13 <header className="pt-4">14 <h1 className="text-3xl">Overview</h1>15 </header>16 <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">17 {[18 { label: 'Runs, last 24 h', value: o.runs24h, href: '/admin/connectors' },19 { label: 'Failed runs, 7 d', value: o.failedRuns7d, href: '/admin/connectors' },20 { label: 'Open unresolved labels', value: o.openUnresolved, href: '/admin/unresolved' },21 { label: 'Current ranking snapshots', value: o.currentSnapshots, href: '/admin/rankings' },22 ].map((k) => (23 <Link key={k.label} href={k.href} className="border border-rule px-3 py-2 no-underline hover:border-accent">24 <span className="ci-num block font-display text-2xl">{fmtInt(k.value)}</span>25 <span className="text-[12.5px] text-ink-2">{k.label}</span>26 </Link>27 ))}28 </div>2930 <div className="grid gap-8 lg:grid-cols-[1fr_1fr]">31 <Section id="tables" kicker="Database" title="Row counts">32 <div className="ci-table-wrap">33 <table className="ci-table">34 <thead>35 <tr>36 <th>Table</th>37 <th className="num">Rows</th>38 </tr>39 </thead>40 <tbody>41 {o.tables.map((t) => (42 <tr key={t.table}>43 <td className="ci-mono">{t.table}</td>44 <td className={`num ${t.n === 0 ? 'text-ink-4' : ''}`}>{fmtInt(t.n)}</td>45 </tr>46 ))}47 </tbody>48 </table>49 </div>50 </Section>51 <div className="space-y-8">52 <Section id="runs" kicker="Connectors" title="Last runs">53 {runs.length ? (54 <div className="ci-table-wrap">55 <table className="ci-table">56 <thead>57 <tr>58 <th>Run</th>59 <th>Status</th>60 <th>Started</th>61 <th className="num">Duration</th>62 <th className="num">Fetched</th>63 </tr>64 </thead>65 <tbody>66 {runs.map((r) => (67 <tr key={r.id}>68 <td>69 <Link className="ci-mono ci-link text-[11.5px]" href={`/admin/runs/${r.id}`}>70 {r.id}71 </Link>72 </td>73 <td>74 <StatusBadge status={r.status} />75 </td>76 <td className="whitespace-nowrap text-[12.5px]">{fmtDateTime(r.started_at)}</td>77 <td className="num">{fmtDuration(r.duration_ms)}</td>78 <td className="num">{fmtInt(r.records_fetched)}</td>79 </tr>80 ))}81 </tbody>82 </table>83 </div>84 ) : (85 <EmptyState compact>No ingest run recorded.</EmptyState>86 )}87 </Section>88 <Section id="audit" kicker="Audit" title="Recent audit log">89 {o.auditRecent.length ? (90 <ul className="divide-y divide-rule text-[13px]">91 {o.auditRecent.map((a) => (92 <li key={a.id} className="py-1.5">93 <span className="ci-mono">{a.action}</span> by {a.actor}94 {a.entity_type ? (95 <>96 {' '}97 on {a.entity_type} <span className="ci-mono">{a.entity_id}</span>98 </>99 ) : null}100 {a.reason ? <span className="text-ink-3"> — {a.reason}</span> : null}101 <span className="block text-[11.5px] text-ink-3">{fmtDateTime(a.created_at)}</span>102 </li>103 ))}104 </ul>105 ) : (106 <EmptyState compact>No audit entry yet.</EmptyState>107 )}108 </Section>109 <KV items={[{ k: 'Environment', v: process.env.NODE_ENV ?? 'development' }, { k: 'API proxy', v: <span className="ci-mono">{process.env.CI_API_URL ?? `http://127.0.0.1:${process.env.API_PORT ?? 8251}`}</span> }]} />110 </div>111 </div>112 </div>113 );114}115