import { LogApp, LogEntry, LogsBody, LogsHeader, LogsSection, LogsTitle, LogText, LogTimestamp, } from '../styles' import { update } from '../update' export interface UnifiedLogLine { time: number app: string text: string } const MAX_LOGS = 200 let _logs: UnifiedLogLine[] = [] let _source: EventSource | undefined const formatTime = (timestamp: number): string => { const d = new Date(timestamp) const pad = (n: number) => String(n).padStart(2, '0') return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}` } const getStatusColor = (status?: number): string | undefined => { if (!status) return undefined if (status >= 200 && status < 300) return '#22c55e' if (status >= 400 && status < 500) return '#f59e0b' if (status >= 500) return '#ef4444' return undefined } function parseLogText(text: string): { method?: string, path?: string, status?: number, rest: string } { // Match patterns like "GET /api/time 200" or "200 GET http://... (0ms)" const httpMatch = text.match(/^(\d{3})\s+(GET|POST|PUT|DELETE|PATCH)\s+\S+/) || text.match(/^(GET|POST|PUT|DELETE|PATCH)\s+(\S+)\s+(\d{3})/) if (httpMatch) { if (httpMatch[1]?.match(/^\d{3}$/)) { return { method: httpMatch[2], status: parseInt(httpMatch[1], 10), rest: text } } else { return { method: httpMatch[1], path: httpMatch[2], status: parseInt(httpMatch[3]!, 10), rest: text } } } return { rest: text } } function LogLineEntry({ log }: { log: UnifiedLogLine }) { const parsed = parseLogText(log.text) const statusColor = getStatusColor(parsed.status) return ( {formatTime(log.time)} {log.app} {log.text} ) } function LogsBodyContent() { return ( <> {_logs.length === 0 ? ( No activity yet ) : ( _logs.map((log, i) => ) )} ) } function renderLogs() { update('#unified-logs-body', ) // Auto-scroll after render requestAnimationFrame(() => { const el = document.getElementById('unified-logs-body') if (el) el.scrollTop = el.scrollHeight }) } export function initUnifiedLogs() { if (_source) return _source = new EventSource('/api/system/logs/stream') _source.onmessage = e => { try { const line = JSON.parse(e.data) as UnifiedLogLine _logs = [..._logs.slice(-(MAX_LOGS - 1)), line] renderLogs() } catch {} } } export function UnifiedLogs() { return ( Logs ) }