SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
5.0 KB · 100 lines tsx
Raw Blame History
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