/** * Logs page — registers the dashboard with the background to receive live log events, * renders a filterable/searchable log list with level filtering and auto-scroll toggle. * Depends on: core/utils.js ($, escapeHtml), ui/toast.js (showToast) */ let _logsSetup = false; /** * Set up the Logs page: register for live log broadcasts, wire up filter/search/scroll controls. * Guarded by `_logsSetup` to prevent duplicate registration. */ function setupLogsEvents() { if (_logsSetup) return; _logsSetup = true; let logs = []; let autoScroll = true; let logFilter = ''; let logLevel = 'all'; // 'all' | 'info' | 'warn' | 'error' | 'debug' chrome.runtime.sendMessage( { target: 'holesail-native', action: 'registerDashboard' }, (response) => { if (response && response.logs) { logs = response.logs; updateLogsDisplay(); } } ); function _onLogsMessage(message) { if (message.type === 'holesail-logs' && message.logs) { logs = message.logs; updateLogsDisplay(); } } chrome.runtime.onMessage.addListener(_onLogsMessage); function getLogClass(level) { if (level === 'error') return 'is-error'; if (level === 'warn') return 'is-warn'; return ''; } function normalizeLogLevel(level) { return level === 'debug' ? 'debug' : (level || 'info'); } function updateLogsDisplay() { const container = $('logsContainer'); if (!container) return; let filtered = logs; if (logFilter) { filtered = filtered.filter(e => (e.message || '').toLowerCase().includes(logFilter.toLowerCase())); } if (logLevel !== 'all') { filtered = filtered.filter(e => normalizeLogLevel(e.level) === logLevel); } if (filtered.length === 0) { container.innerHTML = `
${(logFilter || logLevel !== 'all') ? 'No matching logs' : 'No logs yet'}
${(logFilter || logLevel !== 'all') ? 'Try a different filter' : 'Logs will appear here as the extension runs'}
`; return; } container.innerHTML = filtered.map(entry => { const rawTs = entry && entry.timestamp; const d = new Date(typeof rawTs === 'number' ? rawTs : (typeof rawTs === 'string' ? rawTs : Date.now())); const time = Number.isNaN(d.getTime()) ? '--:--:--' : d.toLocaleTimeString(); const level = normalizeLogLevel(entry.level); const cls = getLogClass(level); const msg = typeof entry === 'string' ? entry : (entry && entry.message != null ? entry.message : String(entry)); return `
${time} ${escapeHtml(msg)}
`; }).join(''); if (autoScroll) container.scrollTop = container.scrollHeight; } $('btnClearLogs')?.addEventListener('click', () => { logs = []; updateLogsDisplay(); }); $('btnAutoScroll')?.addEventListener('click', () => { autoScroll = !autoScroll; const btn = $('btnAutoScroll'); if (btn) { const svgPart = btn.querySelector('svg')?.outerHTML || ''; btn.innerHTML = svgPart + ' Auto-scroll: ' + (autoScroll ? 'ON' : 'OFF'); } }); $('logsFilter')?.addEventListener('input', (e) => { logFilter = e.target.value; updateLogsDisplay(); }); // Severity filter buttons document.querySelectorAll('.log-level-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.log-level-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); logLevel = btn.dataset.level; updateLogsDisplay(); }); }); // Download logs as .txt $('btnDownloadLogs')?.addEventListener('click', () => { if (!logs.length) { showToast('No logs to download', 'default'); return; } const lines = logs.map(e => { const rawTs = e && e.timestamp; const d = new Date(typeof rawTs === 'number' ? rawTs : (typeof rawTs === 'string' ? rawTs : Date.now())); const time = Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString(); const level = normalizeLogLevel(e && e.level).toUpperCase(); const msg = typeof e === 'string' ? e : (e && e.message != null ? e.message : String(e)); return `[${time}] [${level}] ${msg}`; }).join('\n'); const blob = new Blob([lines], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'holesail-logs-' + new Date().toISOString().slice(0, 19).replace(/:/g, '-') + '.txt'; a.click(); setTimeout(() => URL.revokeObjectURL(url), 5000); showToast('Logs downloaded', 'success'); }); window.addEventListener('beforeunload', () => { chrome.runtime.onMessage.removeListener(_onLogsMessage); chrome.runtime.sendMessage({ target: 'holesail-native', action: 'unregisterDashboard' }, () => {}); }, { once: true }); }