diff --git a/webui/src/pages/Logs.tsx b/webui/src/pages/Logs.tsx index 7ba786e..76b487e 100644 --- a/webui/src/pages/Logs.tsx +++ b/webui/src/pages/Logs.tsx @@ -18,7 +18,7 @@ const Logs: React.FC = () => { if (!r.ok) return; const j = await r.json(); if (Array.isArray(j.logs)) { - setLogs(j.logs as LogEntry[]); + setLogs(j.logs.map(normalizeLog)); } } catch (e) { console.error('load recent logs failed', e); @@ -48,11 +48,11 @@ const Logs: React.FC = () => { lines.forEach(line => { try { - const log: LogEntry = JSON.parse(line); + const log = normalizeLog(JSON.parse(line)); setLogs(prev => [...prev.slice(-1000), log]); } catch (e) { // Fallback for non-JSON logs - setLogs(prev => [...prev.slice(-1000), { time: new Date().toISOString(), level: 'INFO', msg: line }]); + setLogs(prev => [...prev.slice(-1000), normalizeLog({ time: new Date().toISOString(), level: 'INFO', msg: line })]); } }); } @@ -82,8 +82,28 @@ const Logs: React.FC = () => { const clearLogs = () => setLogs([]); + const normalizeLog = (v: any): LogEntry => ({ + time: typeof v?.time === 'string' && v.time ? v.time : new Date().toISOString(), + level: typeof v?.level === 'string' && v.level ? v.level : 'INFO', + msg: typeof v?.msg === 'string' ? v.msg : JSON.stringify(v), + ...v, + }); + + const formatTime = (raw: string) => { + try { + if (!raw) return '--:--:--'; + if (raw.includes('T')) { + const right = raw.split('T')[1] || ''; + return (right.split('.')[0] || right).trim() || '--:--:--'; + } + return raw; + } catch { + return '--:--:--'; + } + }; + const getLevelColor = (level: string) => { - switch (level.toUpperCase()) { + switch ((level || 'INFO').toUpperCase()) { case 'ERROR': return 'text-red-400'; case 'WARN': return 'text-amber-400'; case 'DEBUG': return 'text-blue-400'; @@ -135,8 +155,8 @@ const Logs: React.FC = () => { )} {logs.map((log, i) => (