import { useEffect, useRef, useState } from 'react'; import { invokeCommand as invoke } from '../ipc'; import { save } from '@tauri-apps/plugin-dialog'; import { FileDown, Trash2, Terminal, Filter } from 'lucide-react'; import { WindowDragRegion } from './WindowDragRegion'; import { useToast } from '../contexts/ToastContext'; interface LogEntry { level: 'Trace' | 'Debug' | 'Info' | 'Warn' | 'Error'; message: string; } export default function LogsView() { const { addToast } = useToast(); const [logs, setLogs] = useState([]); const [levelFilter, setLevelFilter] = useState('All'); const scrollRef = useRef(null); const rawLineCountRef = useRef(0); const clearedThroughRef = useRef(0); const lastSnapshotRef = useRef(''); const MAX_LOG_LINES = 2000; useEffect(() => { let active = true; const refresh = async () => { try { const lines = await invoke('read_logs', { limit: MAX_LOG_LINES }); if (!active) return; if (lines.length < clearedThroughRef.current) { clearedThroughRef.current = 0; } const snapshot = `${lines.length}:${lines[lines.length - 1] || ''}`; if (snapshot === lastSnapshotRef.current) return; lastSnapshotRef.current = snapshot; rawLineCountRef.current = lines.length; setLogs(lines.slice(clearedThroughRef.current).map(message => { const level = message.includes('[ERROR]') ? 'Error' : message.includes('[WARN]') ? 'Warn' : message.includes('[INFO]') ? 'Info' : message.includes('[TRACE]') ? 'Trace' : 'Debug'; return { level, message }; })); } catch { if (active) setLogs([]); } }; void refresh(); const interval = window.setInterval(refresh, 2000); return () => { active = false; window.clearInterval(interval); }; }, []); useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [logs]); const handleExport = async () => { try { const path = await save({ defaultPath: 'Firelink-Support-Logs.log', filters: [{ name: 'Log Files', extensions: ['log'] }], }); if (!path) return; await invoke('export_logs', { destPath: path }); addToast({ message: 'Support logs exported', variant: 'success' }); } catch (e) { console.error('Export failed:', e); addToast({ message: `Could not export logs: ${String(e)}`, variant: 'error', isActionable: true }); } }; const handleClear = () => { clearedThroughRef.current = rawLineCountRef.current; setLogs([]); }; const severityClass = (level: string) => { switch (level) { case 'Error': return 'log-error'; case 'Warn': return 'log-warn'; case 'Info': return 'log-info'; default: return 'log-debug'; } }; return (
{/* Toolbar */}
Logs ({logs.length} entries)
{/* Console */}
{logs.length === 0 && (
No persisted log entries are available yet.
)} {logs.filter(entry => levelFilter === 'All' || entry.level === levelFilter).map((entry, i) => (
[{entry.level}] {entry.message}
))}
); }