HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { ActionButton, AdminFilters, AdminTitle, JsonPre, Mono, Notice, StatusChip } from '@/components/admin/ui';4import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';5import { LiveAgo } from '@/components/ui/live';6import { Pagination, withParams } from '@/components/ui/pagination';7import { Note } from '@/components/ui/section';8import { EmptyState, Unavailable } from '@/components/ui/unavailable';9import { quarantineAction } from '@/lib/admin/actions';10import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';11import { fmtDateTime, fmtInt } from '@/lib/format';1213export const metadata: Metadata = { title: 'Quarantine', robots: { index: false, follow: false } };14export const dynamic = 'force-dynamic';15const LIMIT = 50;1617export default async function AdminQuarantinePage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {18 await requireAdmin();19 const sp = await searchParams;20 const current: Record<string, string | undefined> = { status: sp.status ?? 'pending' };21 if (sp.offset) current.offset = sp.offset;22 const offset = Math.max(0, Number(current.offset) || 0);23 const href = (patch: Record<string, string | number | undefined | null>) => withParams('/admin/quarantine', current, patch);24 const ret = href({});25 const res = await load(adminApi.quarantine({ status: current.status, limit: LIMIT, offset }));26 return (27 <>28 <AdminTitle title="Quarantine" count={res.ok ? fmtInt(res.data.total) : undefined} lede="Connector runs held back by the writer (breakage suspicion, implausible volumes). Release writes the held facts; discard drops them. Both are audited.">29 <Link href="/admin/runs" className="text-xs text-ink-3 hover:text-ink">30 Runs →31 </Link>32 </AdminTitle>33 <Notice notice={sp.notice} level={sp.level} />34 <AdminFilters action="/admin/quarantine" className="mb-4" fields={[{ kind: 'select', name: 'status', label: 'Status', value: current.status, any: 'pending', options: ['pending', 'released', 'discarded', 'all'].map((s) => ({ value: s, label: s })) }]} />35 {!res.ok ? (36 <Unavailable what="Quarantine" reason={res.error} />37 ) : res.data.items.length === 0 ? (38 <EmptyState title="Nothing in quarantine">No held run for this status.</EmptyState>39 ) : (40 <>41 <DataTable compact scroll caption="Quarantined runs">42 <thead>43 <tr>44 <Th>Id</Th>45 <Th>Connector</Th>46 <Th>Run</Th>47 <Th>Reason</Th>48 <Th>Counts</Th>49 <Th>Status</Th>50 <Th>Created</Th>51 <Th>Actions</Th>52 </tr>53 </thead>54 <tbody>55 {res.data.items.length === 0 && <EmptyRow cols={8}>No rows.</EmptyRow>}56 {res.data.items.map((q) => (57 <tr key={q.id}>58 <Td primary>59 <Mono>{q.id}</Mono>60 </Td>61 <Td>62 <Mono>{q.connector_name ?? '—'}</Mono>63 </Td>64 <Td>{q.run_id ? <Link href={`/admin/runs?connector=${encodeURIComponent(q.connector_name ?? '')}`} className="mono text-[11px] text-accent hover:underline">{q.run_id}</Link> : '—'}</Td>65 <Td className="max-w-[20rem] text-xs text-ink-2">{q.reason ?? '—'}</Td>66 <Td className="text-xs">{q.counts ? <JsonPre value={q.counts} maxHeight="6rem" /> : '—'}</Td>67 <Td>68 <StatusChip value={q.status} />69 </Td>70 <Td className="text-xs text-ink-3" title={q.created_at ? fmtDateTime(q.created_at) : undefined}>{q.created_at ? <LiveAgo at={q.created_at} /> : '—'}</Td>71 <Td>72 {q.status === 'pending' ? (73 <div className="flex items-center gap-1">74 <form action={quarantineAction}>75 <input type="hidden" name="id" value={q.id} />76 <input type="hidden" name="action" value="release" />77 <input type="hidden" name="return" value={ret} />78 <ActionButton tone="positive" title="Write the held facts">Release</ActionButton>79 </form>80 <form action={quarantineAction}>81 <input type="hidden" name="id" value={q.id} />82 <input type="hidden" name="action" value="discard" />83 <input type="hidden" name="return" value={ret} />84 <ActionButton tone="danger" title="Drop the held facts (audited)">Discard</ActionButton>85 </form>86 </div>87 ) : (88 <span className="text-xs text-ink-3">—</span>89 )}90 </Td>91 </tr>92 ))}93 </tbody>94 </DataTable>95 <Pagination total={res.data.total} limit={LIMIT} offset={offset} makeHref={(o) => href({ offset: o || undefined })} className="mt-4" />96 </>97 )}98 <Note className="mt-4">The release / discard endpoints answer 501 while the canonical service has not shipped them; the notice above reports the API's answer verbatim.</Note>99 </>100 );101}102