spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import Link from 'next/link';2import { t } from '@/i18n';3import { adminApi, adminErrorMessage } from '@/lib/admin-api';4import { grouped } from '@/lib/format';5import { routes } from '@/lib/site';6import { AdminSection, AdminTable, Pill, statusTone, ts } from '@/components/admin/admin-ui';78export const dynamic = 'force-dynamic';910export default async function AdminRunsPage({ searchParams }: { searchParams: Promise<{ connector?: string; status?: string }> }) {11 const sp = await searchParams;12 try {13 const r = await adminApi.runs({ connector: sp.connector, status: sp.status, limit: 300 });14 const connectors = Array.from(new Set(r.items.map((x) => x.connector).filter((x): x is string => !!x))).sort();15 return (16 <AdminSection17 title={t('admin.runs.title')}18 sub={t('admin.runs.sub', { n: grouped(r.n) })}19 actions={20 <div className="flex flex-wrap gap-1 text-xs">21 <Link href={routes.admin('runs')} className={`inline-flex h-8 items-center rounded-sm px-2 ${!sp.connector ? 'bg-ink text-paper' : 'text-ink-2 hover:bg-surface-2'}`}>22 {t('common.all')}23 </Link>24 {connectors.map((c) => (25 <Link key={c} href={`${routes.admin('runs')}?connector=${c}`} className={`inline-flex h-8 items-center rounded-sm px-2 ${sp.connector === c ? 'bg-ink text-paper' : 'text-ink-2 hover:bg-surface-2'}`}>26 {c}27 </Link>28 ))}29 </div>30 }31 >32 <AdminTable33 rows={r.items}34 rowKey={(x, i) => `${x.run_id}-${x.dataset}-${i}`}35 columns={[36 { key: 'run', header: t('admin.runs.run'), cell: (x) => (x.run_id ? <Link href={`${routes.admin('raw')}?run_id=${encodeURIComponent(x.run_id)}`} className="font-mono text-accent hover:underline">{x.run_id}</Link> : t('admin.na')) },37 { key: 'c', header: t('admin.connectors.connector'), cell: (x) => x.connector },38 { key: 'd', header: t('source.runs.dataset'), cell: (x) => <span className="break-all font-mono">{x.dataset}</span>, className: 'max-w-[22rem]' },39 { key: 's', header: t('source.runs.status'), cell: (x) => <Pill tone={statusTone(x.status)}>{x.status ?? t('admin.na')}</Pill> },40 { key: 'st', header: t('source.runs.started'), cell: (x) => ts(x.started_at) },41 { key: 'rows', header: t('source.runs.rows'), numeric: true, cell: (x) => (x.rows_valid != null ? grouped(x.rows_valid) : t('admin.na')) },42 { key: 'w', header: t('source.runs.warnings'), numeric: true, cell: (x) => (x.warnings != null ? grouped(x.warnings) : t('admin.na')) },43 { key: 'e', header: t('source.runs.errors'), numeric: true, cell: (x) => (x.errors ? <span className="text-down">{grouped(x.errors)}</span> : '0') },44 { key: 'm', header: t('admin.issues.message'), cell: (x) => <span className="text-ink-2">{x.message}</span>, className: 'max-w-[24rem]' },45 ]}46 />47 </AdminSection>48 );49 } catch (e) {50 return <p className="mt-3 rounded-sm border border-down/40 px-3 py-2 text-xs text-down">{adminErrorMessage(e)}</p>;51 }52}53