import { useState, useMemo } from 'react' import { format } from 'date-fns' import { fr } from 'date-fns/locale' import { AlertTriangle, XCircle, Info, RefreshCw, Trash2, ChevronDown, ChevronRight } from 'lucide-react' import clsx from 'clsx' import { useSystemLogs, useLogSources, useLogCycles, useClearLogs, type LogFilters } from '../hooks/useApi' import { useQueryClient } from '@tanstack/react-query' const LEVELS = ['', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] function LevelBadge({ level }: { level: string }) { const cfg: Record = { INFO: { cls: 'bg-slate-700 text-slate-300', icon: }, WARNING: { cls: 'bg-amber-900/60 text-amber-300 border border-amber-700/40', icon: }, ERROR: { cls: 'bg-red-900/60 text-red-300 border border-red-700/40', icon: }, CRITICAL: { cls: 'bg-red-800 text-white border border-red-600', icon: }, } const { cls, icon } = cfg[level] ?? cfg['INFO'] return ( {icon} {level} ) } function LogRow({ log }: { log: any }) { const [expanded, setExpanded] = useState(false) const hasDetails = !!log.details const ts = (() => { try { return format(new Date(log.ts), 'dd/MM HH:mm:ss', { locale: fr }) } catch { return log.ts } })() const rowCls = clsx('border-b border-slate-800 hover:bg-slate-800/40 transition-colors', { 'bg-amber-950/10': log.level === 'WARNING', 'bg-red-950/20': log.level === 'ERROR' || log.level === 'CRITICAL', }) return ( <> {ts} {log.source} {log.cycle_id ? log.cycle_id.slice(0, 16) + '…' : '—'} {log.ticker ?? '—'}
{hasDetails && ( )} {log.message}
{expanded && hasDetails && (
              {JSON.stringify(JSON.parse(log.details), null, 2)}
            
)} ) } export default function SystemLogs() { const qc = useQueryClient() const [filters, setFilters] = useState({ limit: 300 }) const [tickerInput, setTickerInput] = useState('') const [cycleInput, setCycleInput] = useState('') const { data, isLoading, isRefetching } = useSystemLogs(filters) const { data: sourcesData } = useLogSources() const { data: cyclesData } = useLogCycles() const { mutate: clearLogs, isPending: clearing } = useClearLogs() const logs: any[] = data?.logs ?? [] const sources: string[] = sourcesData?.sources ?? [] const cycles: any[] = cyclesData?.cycles ?? [] // Summary counts const counts = useMemo(() => { const c = { INFO: 0, WARNING: 0, ERROR: 0, CRITICAL: 0 } for (const l of logs) c[l.level as keyof typeof c] = (c[l.level as keyof typeof c] ?? 0) + 1 return c }, [logs]) const applyFilter = (key: keyof LogFilters, val: string) => { setFilters(f => ({ ...f, [key]: val || undefined })) } const handleClear = () => { if (window.confirm('Supprimer les logs de plus de 30 jours ?')) { clearLogs(30, { onSuccess: () => qc.invalidateQueries({ queryKey: ['system-logs'] }) }) } } return (
{/* Header */}

Logs Système

Erreurs, avertissements et événements des cycles IA

{/* Summary chips */}
{[ { level: 'INFO', cls: 'bg-slate-800 text-slate-300' }, { level: 'WARNING', cls: 'bg-amber-900/40 text-amber-300 border border-amber-700/30' }, { level: 'ERROR', cls: 'bg-red-900/40 text-red-300 border border-red-700/30' }, { level: 'CRITICAL', cls: 'bg-red-800/60 text-red-200 border border-red-600/30' }, ].map(({ level, cls }) => ( ))} {logs.length} entrées
{/* Filters */}
setTickerInput(e.target.value.toUpperCase())} onBlur={() => applyFilter('ticker', tickerInput)} onKeyDown={e => e.key === 'Enter' && applyFilter('ticker', tickerInput)} />
applyFilter('date_from', e.target.value)} />
applyFilter('date_to', e.target.value)} />
{/* Log table */}
{isLoading ? (
Chargement…
) : logs.length === 0 ? (
Aucun log trouvé pour ces filtres.
Les WARNING et ERROR des cycles apparaissent ici automatiquement.
) : (
{logs.map(log => )}
Horodatage Niveau Source Cycle Ticker Message
)}
) }