/** * 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' 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 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 => (e.level || 'info') === 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 time = new Date(entry.timestamp).toLocaleTimeString(); const cls = getLogClass(entry.level || 'info'); return `
${time} ${escapeHtml(entry.message)}
`; }).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 time = new Date(e.timestamp).toISOString(); return `[${time}] ${e.message}`; }).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 }); }