HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
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