SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
4.8 KB · 115 lines tsx
Raw Blame History
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