HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { AdminFilters, AdminTitle, KindChip, Mono, Trunc } from '@/components/admin/ui';4import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';5import { LiveAgo } from '@/components/ui/live';6import { Unavailable } from '@/components/ui/unavailable';7import { adminApi, load, requireAdmin } from '@/lib/admin/admin-api';8import { fmtDateTime, fmtInt } from '@/lib/format';910export const metadata: Metadata = { title: 'Errors', robots: { index: false, follow: false } };11export const dynamic = 'force-dynamic';1213export default async function AdminErrorsPage({ searchParams }: { searchParams: Promise<Record<string, string | undefined>> }) {14 await requireAdmin();15 const sp = await searchParams;16 const limit = Math.min(500, Math.max(10, Number(sp.limit) || 100));17 const [errs, connectors] = await Promise.all([load(adminApi.errors({ connector: sp.connector, limit })), load(adminApi.connectors())]);18 const names = connectors.ok ? connectors.data.items.map((c) => c.name).sort() : [];19 return (20 <>21 <AdminTitle title="Errors" count={errs.ok ? fmtInt(errs.data.total) : undefined} />22 <AdminFilters23 action="/admin/errors"24 className="mb-4"25 fields={[26 { kind: 'select', name: 'connector', label: 'Connector', value: sp.connector, options: names.map((n) => ({ value: n, label: n })) },27 { kind: 'select', name: 'limit', label: 'Rows', value: String(limit), any: '100', options: ['50', '100', '250', '500'].map((v) => ({ value: v, label: v })) },28 ]}29 />30 {!errs.ok ? (31 <Unavailable what="Errors" reason={errs.error} />32 ) : (33 <DataTable compact scroll caption="Fetch and parse errors">34 <thead>35 <tr>36 <Th>Time</Th>37 <Th>Connector</Th>38 <Th>Type</Th>39 <Th>Message</Th>40 <Th>URL</Th>41 <Th>Run</Th>42 </tr>43 </thead>44 <tbody>45 {errs.data.items.length === 0 && <EmptyRow cols={6}>No errors recorded.</EmptyRow>}46 {errs.data.items.map((e) => (47 <tr key={String(e.id)}>48 <Td className="text-xs" title={fmtDateTime(e.created_at)}><LiveAgo at={e.created_at} /></Td>49 <Td>{e.connector_name ? <Link href={`/admin/errors?connector=${encodeURIComponent(e.connector_name)}`} className="mono text-xs text-ink hover:text-accent">{e.connector_name}</Link> : <span className="text-ink-3">—</span>}</Td>50 <Td>{e.error_type ? <KindChip value={e.error_type} /> : <span className="text-ink-3">—</span>}</Td>51 <Td className="text-xs text-ink-2"><Trunc text={e.message} max={120} /></Td>52 <Td className="text-xs">53 {e.url ? (54 <a href={e.url} target="_blank" rel="noopener noreferrer" className="text-ink-2 hover:text-accent" title={e.url}>55 {e.url.replace(/^https?:\/\/(www\.)?/, '').slice(0, 60)}56 </a>57 ) : (58 <span className="text-ink-3">—</span>59 )}60 </Td>61 <Td>{e.run_id ? <Mono>{e.run_id}</Mono> : <span className="text-ink-3">—</span>}</Td>62 </tr>63 ))}64 </tbody>65 </DataTable>66 )}67 </>68 );69}70