SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
6.5 KB · 99 lines tsx
Raw Blame History
1import Link from 'next/link';2import { requireAdmin } from '@/lib/admin/auth';3import { dashboard } from '@/lib/admin/queries';4import { queueDepths } from '@/lib/admin/queue';5import { adminLogout } from '@/lib/admin/actions';6import { AdminShell, Kpi, StatusPill, fmtTs, n } from '@/components/admin/shell';7import { Table, th, td, tdNum } from '@/components/ui/primitives';8import { getRouter } from '@rareindex/ai';910export default async function AdminHome() {11  await requireAdmin();12  const [{ counts, recentRuns, recentEvents }, queues] = await Promise.all([dashboard(), queueDepths()]);13  const c = (counts ?? {}) as Record<string, unknown>;14  const ai = getRouter();15  return (16    <AdminShell17      current="/admin"18      title="Pipeline overview"19      subtitle="Counts, lag and freshness across ingestion, canonical data and analytics"20      actions={21        <form action={adminLogout}>22          <button type="submit" className="rounded-md border border-border px-2.5 py-1 text-xs hover:bg-inset">Sign out</button>23        </form>24      }25    >26      <div className="grid grid-cols-2 gap-3 md:grid-cols-4 xl:grid-cols-6">27        <Kpi label="Assets" value={n(c.assets)} sub={`${n(c.variants)} variants`} />28        <Kpi label="Sales" value={n(c.sales)} sub={`${n(c.sales_flagged)} flagged · ${n(c.sales_excluded)} excluded`} />29        <Kpi label="Live listings" value={n(c.listings_live)} />30        <Kpi label="Price observations" value={n(c.observations)} />31        <Kpi label="Raw records" value={n(c.raw_records)} sub={`${n(c.raw_unprocessed)} unprocessed · ${n(c.raw_errors)} errors`} tone={Number(c.raw_errors) > 0 ? 'alert' : undefined} />32        <Kpi label="Normalized pending" value={n(c.normalized_pending)} sub={`${n(c.normalized_unmatched)} unmatched`} tone={Number(c.normalized_pending) > 1000 ? 'alert' : undefined} />33        <Kpi label="Connectors" value={`${n(c.connectors_active)} active`} sub={`${n(c.connectors_paused)} paused · ${n(c.connectors_unhealthy)} unhealthy`} tone={Number(c.connectors_unhealthy) > 0 ? 'loss' : undefined} />34        <Kpi label="Taxonomy proposals" value={n(c.proposals_pending)} sub="pending review" />35        <Kpi label="Latest fetch" value={fmtTs(c.latest_fetch).slice(0, 16)} />36        <Kpi label="Latest sale" value={fmtTs(c.latest_sale).slice(0, 10)} sub={`valuation ${fmtTs(c.latest_valuation).slice(0, 16)}`} />37        <Kpi label="Latest index date" value={c.latest_index ? String(c.latest_index).slice(0, 10) : '—'} />38        <Kpi label="Spend 24h" value={`$${n(c.cost_24h_usd, 2)}`} sub={`FC ${n(c.firecrawl_credits_24h)} · SF ${n(c.scrapfly_credits_24h)} credits`} />39      </div>4041      <div className="mt-6 grid gap-6 lg:grid-cols-3">42        <section className="card lg:col-span-2">43          <header className="flex items-center justify-between border-b border-border px-4 py-2.5">44            <h2 className="text-sm font-semibold">Recent connector runs</h2>45            <Link href="/admin/connectors" className="text-xs text-muted hover:text-fg">Control center →</Link>46          </header>47          <Table>48            <thead><tr><th className={th}>Connector</th><th className={th}>Status</th><th className={th}>Trigger</th><th className={th}>Started</th><th className={`${th} text-right`}>Pages</th><th className={`${th} text-right`}>Raw</th><th className={`${th} text-right`}>Norm.</th><th className={`${th} text-right`}>Dup.</th><th className={th}>Error</th></tr></thead>49            <tbody>50              {recentRuns.length === 0 ? <tr><td className={td} colSpan={9}><span className="text-subtle">No runs yet. Trigger one from the control center.</span></td></tr> : null}51              {recentRuns.map((r) => (52                <tr key={String(r.id)}>53                  <td className={td}><Link href={`/admin/connectors/${String(r.connector_id)}`} className="font-medium hover:underline">{String(r.connector_id)}</Link></td>54                  <td className={td}><StatusPill status={String(r.status)} /></td>55                  <td className={td}>{String(r.trigger)}</td>56                  <td className={td}>{fmtTs(r.started_at)}</td>57                  <td className={tdNum}>{n(r.pages_success)}/{n(r.pages_attempted)}</td>58                  <td className={tdNum}>{n(r.records_raw)}</td>59                  <td className={tdNum}>{n(r.records_normalized)}</td>60                  <td className={tdNum}>{n(r.records_duplicate)}</td>61                  <td className={`${td} max-w-[240px] truncate text-loss`} title={String(r.error ?? '')}>{r.error ? String(r.error) : ''}</td>62                </tr>63              ))}64            </tbody>65          </Table>66        </section>67        <div className="space-y-6">68          <section className="card">69            <header className="border-b border-border px-4 py-2.5"><h2 className="text-sm font-semibold">Queues (pg-boss)</h2></header>70            <ul className="divide-y divide-border text-xs">71              {queues.length === 0 ? <li className="px-4 py-3 text-subtle">Queue tables not initialised yet (the worker creates them on first start).</li> : null}72              {queues.map((q) => (73                <li key={q.name} className="flex items-center justify-between px-4 py-2"><span className="font-mono">{q.name}</span><span className="num text-muted">{q.queued} queued · {q.active} active · {q.failed} failed</span></li>74              ))}75            </ul>76          </section>77          <section className="card">78            <header className="border-b border-border px-4 py-2.5"><h2 className="text-sm font-semibold">Events (24h)</h2></header>79            <ul className="divide-y divide-border text-xs">80              {recentEvents.length === 0 ? <li className="px-4 py-3 text-subtle">No events yet.</li> : null}81              {recentEvents.map((e) => (82                <li key={String(e.type)} className="flex items-center justify-between px-4 py-2"><span className="font-mono">{String(e.type)}</span><span className="num text-muted">{n(e.n)}</span></li>83              ))}84            </ul>85          </section>86          <section className="card">87            <header className="border-b border-border px-4 py-2.5"><h2 className="text-sm font-semibold">AI model routing</h2></header>88            <ul className="divide-y divide-border text-xs">89              {ai.table().map((r) => (90                <li key={r.role} className="flex items-center justify-between px-4 py-2"><span>{r.role}</span><span className="font-mono text-muted">{r.provider ? `${r.provider} · ${r.model}` : 'not configured'}</span></li>91              ))}92            </ul>93          </section>94        </div>95      </div>96    </AdminShell>97  );98}99