import { Fragment, useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import toast from 'react-hot-toast'; import { ChevronDown, ChevronRight, Trash2, Search } from 'lucide-react'; import { api } from '../../lib/api-client'; import { AppShell, PageHeader, PageBody } from '../../layout/AppShell'; import { Badge, Button, Card, Input, Select } from '../../components/ui/primitives'; import { Table, THead, TBody, TR, TH, TD } from '../../components/ui/table'; interface ErrorEntry { id: number; source: string; severity: string; message: string; stack: string | null; context: unknown; actor_email: string | null; request_path: string | null; created_at: string; } const SEVERITY_VARIANTS: Record = { fatal: 'danger', error: 'danger', warn: 'warn', warning: 'warn', info: 'info', }; export function ErrorLog() { const qc = useQueryClient(); const [filters, setFilters] = useState({ source: '', severity: '', q: '' }); const [expanded, setExpanded] = useState>(new Set()); const { data } = useQuery({ queryKey: ['errors', filters], queryFn: () => api.get<{ entries: ErrorEntry[]; sources: string[] }>('/errors', { source: filters.source || undefined, severity: filters.severity || undefined, q: filters.q || undefined, limit: 200, }), refetchInterval: 30000, }); const purgeMut = useMutation({ mutationFn: () => api.delete<{ purged: number }>('/errors'), onSuccess: (res) => { toast.success(`${res.purged} oude entries verwijderd`); qc.invalidateQueries({ queryKey: ['errors'] }); }, onError: (e: Error) => toast.error(e.message), }); const entries = data?.entries ?? []; const sources = data?.sources ?? []; const toggle = (id: number) => { const next = new Set(expanded); if (next.has(id)) next.delete(id); else next.add(id); setExpanded(next); }; return ( { if (confirm('Alle entries ouder dan 30 dagen verwijderen?')) purgeMut.mutate(); }} > Oude entries wissen } />
setFilters({ ...filters, q: e.target.value })} />
Auto-refresh 30s
{entries.map((e) => { const isOpen = expanded.has(e.id); return ( toggle(e.id)} className="cursor-pointer"> {isOpen && ( )} ); })}
Tijd Source Severity Bericht Path
{isOpen ? ( ) : ( )} {new Date(e.created_at).toLocaleString('nl-NL')} {e.source} {e.severity} {e.message} {e.request_path ?? '—'}

Bericht

{e.message}

{e.stack && (

Stack

                                  {e.stack}
                                
)} {e.context != null && (

Context

                                  {JSON.stringify(e.context, null, 2)}
                                
)}
{entries.length === 0 && (
Geen errors — lekker rustig.
)}
); }