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 { AdminError, AdminHeader, fmtBytes } from '@/components/admin/ui';4import { Pagination } from '@/components/ui/pagination';5import { adminApi, safeAdmin } from '@/lib/admin-api';6import { fmtAgo, fmtDateTime, fmtInt } from '@/lib/format';78export const metadata: Metadata = { title: 'Raw records' };910const KNOWN_CONNECTORS = ['celestrak_gp', 'celestrak_groups', 'celestrak_satcat', 'derived_analytics'];1112export default async function AdminRawPage({ searchParams }: { searchParams: Promise<{ page?: string; connector?: string }> }) {13 const sp = await searchParams;14 const page = Math.max(1, Number(sp.page) || 1);15 const connector = sp.connector || null;16 const [res, overview] = await Promise.all([safeAdmin(adminApi.raw(page, connector, 50)), safeAdmin(adminApi.overview())]);17 const connectors = overview.data ? overview.data.data.connectors.map((c) => c.name) : KNOWN_CONNECTORS;18 const now = Date.now();19 const href = (p: number, c: string | null = connector) => `/admin/raw?${new URLSearchParams({ ...(c ? { connector: c } : {}), ...(p > 1 ? { page: String(p) } : {}) }).toString()}`;20 return (21 <>22 <AdminHeader title="Raw records" lede="Every upstream payload is stored as an immutable gzip snapshot before normalization (SI_DATA_DIR/raw). Nothing here is ever rewritten." />23 <div className="no-scrollbar -mx-4 flex gap-1.5 overflow-x-auto px-4 md:mx-0 md:flex-wrap md:px-0">24 <Link href={href(1, null)} className={`inline-flex min-h-9 shrink-0 items-center rounded-md border px-3 text-xs ${!connector ? 'border-accent/40 bg-accent-soft text-accent' : 'border-rule text-ink-2 hover:bg-plane-2'}`}>25 All connectors26 </Link>27 {connectors.map((c) => (28 <Link key={c} href={href(1, c)} className={`mono inline-flex min-h-9 shrink-0 items-center rounded-md border px-3 text-xs ${connector === c ? 'border-accent/40 bg-accent-soft text-accent' : 'border-rule text-ink-2 hover:bg-plane-2'}`}>29 {c}30 </Link>31 ))}32 </div>3334 {res.error !== null ? (35 <div className="mt-6">36 <AdminError message={res.error} />37 </div>38 ) : res.data.data.length === 0 ? (39 <p className="mt-6 rounded-md border border-dashed border-rule-strong px-4 py-8 text-center text-sm text-ink-3">No raw records{connector ? ` for ${connector}` : ''}</p>40 ) : (41 <>42 <div className="mt-5 overflow-x-auto">43 <table className="data-table stack md:min-w-[960px]">44 <thead>45 <tr>46 <th>Record</th>47 <th>Connector</th>48 <th>Fetched</th>49 <th>Processing</th>50 <th className="num">Records</th>51 <th className="num">Size</th>52 <th>Type</th>53 </tr>54 </thead>55 <tbody>56 {res.data.data.map((r) => (57 <tr key={r.id}>58 <td className="primary">59 <Link href={`/admin/raw/${encodeURIComponent(r.id)}`} className="mono text-sm text-ink hover:text-accent">60 {r.source_native_id ?? r.id}61 </Link>62 <div className="mono truncate text-[11px] text-ink-3" title={r.id}>63 {r.id}64 </div>65 </td>66 <td data-label="Connector" className="mono text-xs text-ink-2">67 {r.connector_name}68 </td>69 <td data-label="Fetched" title={fmtDateTime(r.fetched_at)} className="text-sm">70 {fmtAgo(r.fetched_at, now)}71 </td>72 <td data-label="Processing">73 <span className={`mono text-xs ${r.processing_status === 'processed' ? 'text-active' : r.processing_status === 'failed' ? 'text-danger' : 'text-ink-2'}`}>{r.processing_status ?? '—'}</span>74 {r.error && (75 <span className="mono block max-w-[240px] truncate text-[11px] text-danger" title={r.error}>76 {r.error}77 </span>78 )}79 </td>80 <td data-label="Records" className="num tnum text-sm">81 {fmtInt(r.record_count)}82 </td>83 <td data-label="Size" className="num tnum text-sm text-ink-2">84 {fmtBytes(r.byte_size)}85 </td>86 <td data-label="Type" className="mono text-xs text-ink-3">87 {r.content_type ?? '—'}88 </td>89 </tr>90 ))}91 </tbody>92 </table>93 </div>94 <Pagination className="mt-4" page={res.data.pagination.page} pages={res.data.pagination.pages} total={res.data.pagination.total} pageSize={res.data.pagination.page_size} makeHref={(p) => href(p)} />95 </>96 )}97 </>98 );99}100