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%
4.7 KB · 87 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { RollbackButton } from '@/components/admin/rollback-button';4import { AdminFilters, AdminTitle, Mono, Notice, StatusChip, Trunc } from '@/components/admin/ui';5import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';6import { LiveAgo } from '@/components/ui/live';7import { Unavailable } from '@/components/ui/unavailable';8import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';9import { fmtDateTime, fmtDuration, fmtInt, num } from '@/lib/format';1011export const metadata: Metadata = { title: 'Runs', robots: { index: false, follow: false } };12export const dynamic = 'force-dynamic';1314export default async function AdminRunsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {15  await requireAdmin();16  const sp = await searchParams;17  const limit = Math.min(500, Math.max(10, Number(sp.limit) || 100));18  const [runs, connectors] = await Promise.all([load(adminApi.runs({ connector: sp.connector, limit })), load(adminApi.connectors())]);19  const names = connectors.ok ? connectors.data.items.map((c) => c.name).sort() : [];20  const ret = `/admin/runs${sp.connector ? `?connector=${encodeURIComponent(sp.connector)}` : ''}`;21  return (22    <>23      <AdminTitle title="Runs" count={runs.ok ? fmtInt(runs.data.total) : undefined} lede="Rollback retracts a run's claims, closes its relations / prices / results and flags its events as backfill — only rows carrying that run_id; nothing is deleted." />24      <Notice notice={sp.notice} level={sp.level} />25      <AdminFilters26        action="/admin/runs"27        className="mb-4"28        fields={[29          { kind: 'select', name: 'connector', label: 'Connector', value: sp.connector, options: names.map((n) => ({ value: n, label: n })) },30          { kind: 'select', name: 'limit', label: 'Rows', value: String(limit), any: '100', options: ['50', '100', '250', '500'].map((v) => ({ value: v, label: v })) },31        ]}32      />33      {!runs.ok ? (34        <Unavailable what="Runs" reason={runs.error} />35      ) : (36        <DataTable compact scroll caption="Connector runs">37          <thead>38            <tr>39              <Th>Connector</Th>40              <Th>Started</Th>41              <Th>Status</Th>42              <Th num>Duration</Th>43              <Th num>Disc.</Th>44              <Th num>Fetched</Th>45              <Th num>Changed</Th>46              <Th num>Unch.</Th>47              <Th num>Failed</Th>48              <Th num>Ent. +/~</Th>49              <Th num>Claims</Th>50              <Th num>Rel.</Th>51              <Th num>Events</Th>52              <Th>Error</Th>53              <Th>Run id</Th>54              <Th>Rollback</Th>55            </tr>56          </thead>57          <tbody>58            {runs.data.items.length === 0 && <EmptyRow cols={16}>No runs match.</EmptyRow>}59            {runs.data.items.map((r) => (60              <tr key={r.id}>61                <Td><Link href={`/admin/runs?connector=${encodeURIComponent(r.connector_name)}`} className="mono text-xs font-medium text-ink hover:text-accent">{r.connector_name}</Link></Td>62                <Td className="text-xs" title={fmtDateTime(r.started_at)}><LiveAgo at={r.started_at} /></Td>63                <Td><StatusChip value={r.status} /></Td>64                <Td num className="tnum text-xs">{num(r.duration_ms) === null ? '—' : fmtDuration(Math.round((num(r.duration_ms) ?? 0) / 1000))}</Td>65                <Td num className="tnum text-xs">{fmtInt(r.docs_discovered)}</Td>66                <Td num className="tnum text-xs">{fmtInt(r.docs_fetched)}</Td>67                <Td num className="tnum text-xs">{fmtInt(r.docs_changed)}</Td>68                <Td num className="tnum text-xs text-ink-3">{fmtInt(r.docs_unchanged)}</Td>69                <Td num className={`tnum text-xs ${num(r.docs_failed) ? 'text-danger' : ''}`}>{fmtInt(r.docs_failed)}</Td>70                <Td num className="tnum text-xs">{fmtInt(r.entities_created)} / {fmtInt(r.entities_updated)}</Td>71                <Td num className="tnum text-xs">{fmtInt(r.claims_written)}</Td>72                <Td num className="tnum text-xs">{fmtInt(r.relations_written)}</Td>73                <Td num className="tnum text-xs">{fmtInt(r.events_emitted)}</Td>74                <Td className="text-xs text-danger"><Trunc text={r.error} max={50} /></Td>75                <Td><Mono>{r.id}</Mono></Td>76                <Td>77                  <RollbackButton runId={r.id} connector={r.connector_name} returnTo={ret} disabled={r.status === 'running' || r.status === 'queued' || Boolean((r.meta as { rolled_back?: unknown } | null)?.rolled_back)} />78                </Td>79              </tr>80            ))}81          </tbody>82        </DataTable>83      )}84    </>85  );86}87