SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
4.6 KB · 105 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { AdminTitle, Bool, JsonPre, Mono } from '@/components/admin/ui';3import { HBars } from '@/components/charts/charts';4import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';5import { KeyValue } from '@/components/ui/key-value';6import { Unavailable } from '@/components/ui/unavailable';7import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';8import { fmtBytes, fmtDuration, fmtInt, num } from '@/lib/format';910export const metadata: Metadata = { title: 'Infrastructure', robots: { index: false, follow: false } };11export const dynamic = 'force-dynamic';1213export default async function AdminInfrastructurePage() {14  await requireAdmin();15  const res = await load(adminApi.infrastructure());16  if (!res.ok) {17    return (18      <>19        <AdminTitle title="Infrastructure" />20        <Unavailable what="Infrastructure" reason={res.error} />21      </>22    );23  }24  const i = res.data;25  const db = i.database;26  const tables = [...(db?.tables ?? [])].sort((a, b) => (num(b.total_bytes) ?? 0) - (num(a.total_bytes) ?? 0));27  const hb = Object.entries(i.heartbeats ?? {});28  return (29    <>30      <AdminTitle title="Infrastructure" lede={i.hostname ? <Mono>{i.hostname}</Mono> : undefined} />31      <div className="grid gap-8 lg:grid-cols-2">32        <section>33          <p className="eyebrow mb-2">API process</p>34          <KeyValue35            dense36            rows={[37              { key: 'hostname', label: 'Host', value: <Mono>{i.hostname ?? '—'}</Mono> },38              { key: 'env', label: 'Environment', raw: i.env },39              { key: 'python', label: 'Python', raw: i.python },40              { key: 'platform', label: 'Platform', raw: i.platform },41              { key: 'uptime', label: 'API uptime', raw: num(i.api_uptime_s) === null ? null : fmtDuration(i.api_uptime_s) },42              { key: 'data_dir', label: 'Data dir', value: <Bool v={i.data_dir_exists} /> },43            ]}44          />45          <p className="eyebrow mt-6 mb-2">Archive</p>46          {i.archive ? (47            <KeyValue48              dense49              rows={[50                { key: 'raw', label: 'Raw', value: <span className="tnum">{fmtBytes(i.archive.raw_bytes)} · {fmtInt(i.archive.raw_files)} files</span> },51                { key: 'text', label: 'Text', value: <span className="tnum">{fmtBytes(i.archive.text_bytes)} · {fmtInt(i.archive.text_files)} files</span> },52              ]}53            />54          ) : (55            <p className="text-sm text-ink-3">Archive size unavailable.</p>56          )}57          <p className="eyebrow mt-6 mb-2">Heartbeats</p>58          {hb.length === 0 ? <p className="text-sm text-ink-3">None reported.</p> : <JsonPre value={i.heartbeats} maxHeight="14rem" />}59        </section>60        <section>61          <p className="eyebrow mb-2">Database</p>62          {db ? (63            <KeyValue64              dense65              rows={[66                { key: 'database', label: 'Database', value: <Mono>{db.database ?? '—'}</Mono> },67                { key: 'size', label: 'Size', raw: fmtBytes(db.db_bytes) },68                { key: 'pg', label: 'Server', raw: db.pg_version?.split(',')[0] ?? db.pg_version },69                { key: 'conn', label: 'Connections', value: <span className="tnum">{fmtInt(db.connections)} / {fmtInt(db.max_connections)}</span> },70              ]}71            />72          ) : (73            <p className="text-sm text-ink-3">Database metrics unavailable.</p>74          )}75          <p className="eyebrow mt-6 mb-2">Table sizes</p>76          <HBars data={tables.slice(0, 12).map((t) => ({ label: t.table, value: num(t.total_bytes) ?? 0, sub: `${fmtInt(t.rows_estimate)} rows` }))} format={(v) => fmtBytes(v)} />77        </section>78      </div>79      <section className="mt-8">80        <DataTable compact caption="Table sizes">81          <thead>82            <tr>83              <Th>Table</Th>84              <Th num>Total</Th>85              <Th num>Data</Th>86              <Th num>Rows (est.)</Th>87            </tr>88          </thead>89          <tbody>90            {tables.length === 0 && <EmptyRow cols={4}>No table statistics.</EmptyRow>}91            {tables.map((t) => (92              <tr key={t.table}>93                <Td primary><Mono className="text-ink">{t.table}</Mono></Td>94                <Td num label="Total" className="tnum text-xs">{fmtBytes(t.total_bytes)}</Td>95                <Td num label="Data" className="tnum text-xs text-ink-2">{fmtBytes(t.data_bytes)}</Td>96                <Td num label="Rows" className="tnum text-xs">{fmtInt(t.rows_estimate)}</Td>97              </tr>98            ))}99          </tbody>100        </DataTable>101      </section>102    </>103  );104}105