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%
3.3 KB · 70 lines tsx
Raw Blame History
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