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%
8.2 KB · 185 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { AdminTitle, JsonPre, Mono, StatusChip, Trunc } from '@/components/admin/ui';4import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';5import { LiveAgo } from '@/components/ui/live';6import { Stat, StatGrid } from '@/components/ui/section';7import { Unavailable } from '@/components/ui/unavailable';8import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';9import { fmtBytes, fmtDuration, fmtInt, num } from '@/lib/format';1011export const metadata: Metadata = { title: 'Overview', robots: { index: false, follow: false } };12export const dynamic = 'force-dynamic';1314export default async function AdminOverviewPage() {15  await requireAdmin();16  const res = await load(adminApi.overview());17  if (!res.ok) {18    return (19      <>20        <AdminTitle title="Overview" />21        <Unavailable what="Overview" reason={res.error} />22      </>23    );24  }25  const o = res.data;26  const s = o.stats ?? {};27  const hb = Object.entries(o.heartbeats ?? {});28  const queue = Object.entries(o.queue ?? {});29  const health = Object.entries(o.connectors ?? {});30  return (31    <>32      <AdminTitle title="Overview" lede={s.computed_at ? <>stats computed <LiveAgo at={String(s.computed_at)} /></> : undefined} />33      <StatGrid cols={4}>34        <Stat label="Entities" value={fmtInt(s.entities_total)} />35        <Stat label="Documents" value={fmtInt(s.documents)} hint={`${fmtInt(s.snapshots)} snapshots`} />36        <Stat label="Current claims" value={fmtInt(s.claims_current)} hint={`${fmtInt(s.claims)} total`} />37        <Stat label="Events · 24 h" value={fmtInt(s.change_events_24h)} hint={`${fmtInt(s.change_events)} total`} />38        <Stat label="Current prices" value={fmtInt(s.prices_current)} />39        <Stat label="Benchmark results" value={fmtInt(s.benchmark_results)} />40        <Stat label="Review pending" value={fmtInt(o.review_pending)} href="/admin/review" />41        <Stat label="Recent errors" value={fmtInt(o.recent_errors)} href="/admin/errors" />42      </StatGrid>4344      <div className="mt-8 grid gap-8 lg:grid-cols-3">45        <section>46          <p className="eyebrow mb-2">Queue depth</p>47          {queue.length === 0 ? (48            <p className="text-sm text-ink-3">Queue empty.</p>49          ) : (50            <DataTable compact stack={false} caption="Queue depth">51              <thead>52                <tr>53                  <Th>Kind</Th>54                  <Th>Status</Th>55                  <Th num>n</Th>56                </tr>57              </thead>58              <tbody>59                {queue.flatMap(([kind, byStatus]) =>60                  Object.entries(byStatus).map(([st, n]) => (61                    <tr key={`${kind}-${st}`}>62                      <Td><Mono>{kind}</Mono></Td>63                      <Td><StatusChip value={st} /></Td>64                      <Td num className="tnum">{fmtInt(n)}</Td>65                    </tr>66                  )),67                )}68              </tbody>69            </DataTable>70          )}71          <p className="mt-2 text-xs"><Link href="/admin/jobs" className="link">Jobs →</Link></p>72        </section>73        <section>74          <p className="eyebrow mb-2">Connector health</p>75          <ul className="divide-y divide-rule border-y border-rule text-sm">76            {health.map(([k, n]) => (77              <li key={k} className="flex items-center justify-between py-1.5">78                <StatusChip value={k} />79                <span className="tnum">{fmtInt(n)}</span>80              </li>81            ))}82            {health.length === 0 && <li className="py-2 text-ink-3">No connectors.</li>}83          </ul>84          <p className="mt-2 text-xs"><Link href="/admin/connectors" className="link">Connectors →</Link></p>85          {o.review_by_kind && (86            <>87              <p className="eyebrow mt-6 mb-2">Review by kind</p>88              <ul className="divide-y divide-rule border-y border-rule text-sm">89                {Object.entries(o.review_by_kind).map(([k, n]) => (90                  <li key={k} className="flex items-center justify-between py-1.5">91                    <Link href={`/admin/review?kind=${encodeURIComponent(k)}`} className="mono text-xs text-ink-2 hover:text-accent">{k}</Link>92                    <span className="tnum">{fmtInt(n)}</span>93                  </li>94                ))}95              </ul>96            </>97          )}98        </section>99        <section>100          <p className="eyebrow mb-2">Heartbeats</p>101          {hb.length === 0 ? <p className="text-sm text-ink-3">None reported (scheduler / workers have not written a heartbeat).</p> : <JsonPre value={o.heartbeats} maxHeight="14rem" />}102          {o.archive && (103            <>104              <p className="eyebrow mt-6 mb-2">Archive</p>105              <p className="tnum text-sm text-ink-2">106                raw {fmtBytes(o.archive.raw_bytes)} · {fmtInt(o.archive.raw_files)} files · text {fmtBytes(o.archive.text_bytes)} · {fmtInt(o.archive.text_files)} files107              </p>108            </>109          )}110        </section>111      </div>112113      <section className="mt-8">114        <p className="eyebrow mb-2">115          LLM factory · 24 h {o.llm?.available === false && <StatusChip value="unavailable" className="ml-2" />}116        </p>117        <p className="tnum text-sm text-ink-2">118          {fmtInt(o.llm?.jobs_24h)} jobs · {fmtInt(o.llm?.tokens_24h)} tokens{o.llm?.failed_24h !== undefined && <> · <span className={num(o.llm.failed_24h) ? 'text-danger' : ''}>{fmtInt(o.llm.failed_24h)} failed</span></>}119        </p>120        {o.llm?.by_stage?.length ? (121          <DataTable compact stack={false} caption="LLM jobs by stage" className="mt-2 max-w-md">122            <thead>123              <tr>124                <Th>Stage</Th>125                <Th>Status</Th>126                <Th num>n</Th>127              </tr>128            </thead>129            <tbody>130              {o.llm.by_stage.map((r, i) => (131                <tr key={i}>132                  <Td><Mono>{r.stage}</Mono></Td>133                  <Td><StatusChip value={r.status} /></Td>134                  <Td num className="tnum">{fmtInt(r.n)}</Td>135                </tr>136              ))}137            </tbody>138          </DataTable>139        ) : (140          <p className="mt-2 text-xs text-ink-3">No LLM jobs in the last 24 h.</p>141        )}142        <p className="mt-2 text-xs"><Link href="/admin/llm-jobs" className="link">LLM accounting →</Link></p>143      </section>144145      <section className="mt-8">146        <p className="eyebrow mb-2">Recent runs</p>147        <DataTable compact scroll caption="Recent runs">148          <thead>149            <tr>150              <Th>Connector</Th>151              <Th>Started</Th>152              <Th>Status</Th>153              <Th num>Duration</Th>154              <Th num>Fetched</Th>155              <Th num>Changed</Th>156              <Th num>Failed</Th>157              <Th num>Claims</Th>158              <Th num>Events</Th>159              <Th>Error</Th>160            </tr>161          </thead>162          <tbody>163            {!o.recent_runs?.length && <EmptyRow cols={10}>No runs yet.</EmptyRow>}164            {o.recent_runs?.map((r) => (165              <tr key={r.id}>166                <Td><Link href={`/admin/runs?connector=${encodeURIComponent(r.connector_name)}`} className="mono text-xs text-ink hover:text-accent">{r.connector_name}</Link></Td>167                <Td className="text-xs"><LiveAgo at={r.started_at} /></Td>168                <Td><StatusChip value={r.status} /></Td>169                <Td num className="tnum text-xs">{num(r.duration_ms) === null ? '—' : fmtDuration(Math.round((num(r.duration_ms) ?? 0) / 1000))}</Td>170                <Td num className="tnum text-xs">{fmtInt(r.docs_fetched)}</Td>171                <Td num className="tnum text-xs">{fmtInt(r.docs_changed)}</Td>172                <Td num className={`tnum text-xs ${num(r.docs_failed) ? 'text-danger' : ''}`}>{fmtInt(r.docs_failed)}</Td>173                <Td num className="tnum text-xs">{fmtInt(r.claims_written)}</Td>174                <Td num className="tnum text-xs">{fmtInt(r.events_emitted)}</Td>175                <Td className="text-xs text-ink-2"><Trunc text={r.error} max={60} /></Td>176              </tr>177            ))}178          </tbody>179        </DataTable>180        <p className="mt-2 text-xs"><Link href="/admin/runs" className="link">All runs →</Link></p>181      </section>182    </>183  );184}185