spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { EnabledToggle, RunNowButton } from '@/components/admin/actions';4import { AdminError, AdminHeader, JsonInline, RunsTable, SectionTitle, Tile, fmtBytes } from '@/components/admin/ui';5import { RunStatusPill, fmtDuration, fmtInterval, fmtUntil } from '@/components/meta/connectors-table';6import { adminApi, safeAdmin } from '@/lib/admin-api';7import { fmtAgo, fmtDateTime, fmtInt, num } from '@/lib/format';89export const metadata: Metadata = { title: 'Overview' };1011export default async function AdminOverviewPage() {12 const res = await safeAdmin(adminApi.overview());13 if (res.error !== null) {14 return (15 <>16 <AdminHeader title="Overview" />17 <AdminError message={res.error} />18 </>19 );20 }21 const d = res.data.data;22 const now = Date.now();23 const db = d.database;24 const openFlags = d.quality.reduce((acc, q) => acc + (num(q.open) ?? 0), 0);25 return (26 <>27 <AdminHeader title="Overview" lede={`Generated ${fmtDateTime(res.data.meta.generated_at)} · request ${res.data.meta.request_id}`} action={<Link href="/status" className="text-sm text-accent hover:underline">Public status →</Link>} />2829 <div className="grid grid-cols-2 gap-3 md:grid-cols-4 xl:grid-cols-8">30 <Tile label="Satellites" value={fmtInt(db.satellites)} />31 <Tile label="Element sets" value={fmtInt(db.elements)} hint="orbital_state rows" />32 <Tile label="Events" value={fmtInt(db.events)} />33 <Tile label="Raw snapshots" value={fmtInt(db.raw)} hint={`${fmtBytes(db.raw_bytes)} uncompressed`} href="/admin/raw" />34 <Tile label="Database" value={fmtBytes(db.db_bytes)} hint={`${fmtInt(db.db_connections)} connections`} />35 <Tile label="Redis" value={d.redis ? 'ok' : 'down'} tone={d.redis ? 'ok' : 'danger'} hint={`queue depth ${fmtInt(d.queue_depth)}`} />36 <Tile label="Open quality flags" value={fmtInt(openFlags)} tone={openFlags > 0 ? 'warn' : undefined} href="/admin/data-quality" />37 <Tile label="Review queue" value={fmtInt(d.review_open)} tone={d.review_open > 0 ? 'warn' : undefined} href="/admin/entity-resolution" />38 </div>3940 <SectionTitle>Connectors</SectionTitle>41 <div className="overflow-x-auto">42 <table className="data-table stack md:min-w-[1100px]">43 <thead>44 <tr>45 <th>Connector</th>46 <th>Status</th>47 <th>Last success</th>48 <th>Next run</th>49 <th className="num">Duration</th>50 <th className="num">Failures</th>51 <th className="num">Errors 7 d</th>52 <th>Circuit</th>53 <th>Actions</th>54 </tr>55 </thead>56 <tbody>57 {d.connectors.map((c) => {58 const circuitOpen = !!c.circuit_open_until && new Date(c.circuit_open_until).getTime() > now;59 return (60 <tr key={c.name}>61 <td className="primary">62 <Link href={`/admin/connectors/${encodeURIComponent(c.name)}`} className="mono text-sm text-ink hover:text-accent">63 {c.name}64 </Link>65 <div className="text-xs text-ink-3">66 {c.source_name} · every {fmtInterval(c.interval_seconds)} · priority {c.priority}67 {!c.enabled && <span className="ml-2 text-warn">disabled</span>}68 </div>69 {c.description && <div className="mt-0.5 max-w-md text-xs text-ink-3">{c.description}</div>}70 </td>71 <td data-label="Status">72 <RunStatusPill status={c.last_status} />73 </td>74 <td data-label="Last success" title={fmtDateTime(c.last_success_at)} className="text-sm">75 {fmtAgo(c.last_success_at, now)}76 </td>77 <td data-label="Next run" title={fmtDateTime(c.next_run_at)} className="text-sm text-ink-2">78 {c.enabled ? fmtUntil(c.next_run_at, now) : '—'}79 </td>80 <td data-label="Duration" className="num tnum text-sm text-ink-2">81 {fmtDuration(c.last_duration_ms)}82 </td>83 <td data-label="Failures" className={`num tnum text-sm ${c.consecutive_failures > 0 ? 'text-warn' : 'text-ink-2'}`}>84 {fmtInt(c.consecutive_failures)}85 </td>86 <td data-label="Errors 7 d" className={`num tnum text-sm ${(num(c.errors_7d) ?? 0) > 0 ? 'text-warn' : 'text-ink-2'}`}>87 {fmtInt(c.errors_7d)}88 </td>89 <td data-label="Circuit">{circuitOpen ? <span className="text-xs text-danger">open · resets {fmtUntil(c.circuit_open_until, now)}</span> : <span className="text-xs text-ink-3">closed</span>}</td>90 <td data-label="Actions" className="primary md:[grid-column:auto]">91 <div className="flex flex-wrap items-center gap-2">92 <RunNowButton name={c.name} />93 <EnabledToggle name={c.name} enabled={c.enabled} />94 </div>95 </td>96 </tr>97 );98 })}99 </tbody>100 </table>101 </div>102 <p className="mt-2 text-xs text-ink-3">103 “Run now” pushes a job to the Redis queue consumed by the scheduler (<code className="mono">si schedule</code>). If Redis is down the job is not visible to the scheduler and the button says so. Enabling a connector also resets its circuit breaker.104 </p>105106 {d.failed_jobs.length > 0 && (107 <>108 <SectionTitle>Failed jobs</SectionTitle>109 <RunsTable runs={d.failed_jobs} now={now} />110 </>111 )}112113 <SectionTitle>Recent runs</SectionTitle>114 <RunsTable runs={d.recent_runs} now={now} />115 {d.recent_runs.some((r) => r.meta) && (116 <details className="mt-3 text-xs text-ink-3">117 <summary className="cursor-pointer py-1 hover:text-ink">Run metadata (latest 5)</summary>118 <ul className="mt-2 space-y-1">119 {d.recent_runs.slice(0, 5).map((r) => (120 <li key={r.id} className="grid gap-1 sm:grid-cols-[220px_minmax(0,1fr)]">121 <span className="mono">{r.connector_name}</span>122 <JsonInline value={r.meta} />123 </li>124 ))}125 </ul>126 </details>127 )}128 </>129 );130}131