Files
holesail-browser/extension/dashboard/pages/logs.js
T
Raven Scott f1e98a7edd
CI / Build & Test (push) Successful in 2m54s
docs: add CONTRIBUTING.md, CHANGELOG.md, and JSDoc to entire codebase
Add docs/CONTRIBUTING.md covering the build system, dev workflow, all
npm scripts, how to add new native host message types, code style, and
debugging guidance.

Add CHANGELOG.md at the project root documenting all features and fixes
across the 1.0.0 release.

Add JSDoc (@param, @returns) to all previously undocumented exported
functions across 35 JS files:
- native-host/holesail-manager/ (index, virtual-hosts, service-tunnels,
  servers, port-allocator)
- native-host top-level managers (startup, connect-proxy, https-proxy,
  certificate-authority, ssh-manager, rdp-manager)
- extension/background/ (logs, native-messaging, proxy, message-router)
- extension/dashboard/core/ (utils, navigation, init)
- extension/dashboard/ui/ (modal, toast, state-tag)
- extension/dashboard/pages/ (all 10 page files)
- extension/dashboard/refresh.js, events.js
- extension/dashboard/data/hostname-validator.js
- scripts/ (build-host, run-install)
2026-03-01 00:40:53 -05:00

121 lines
4.3 KiB
JavaScript

/**
* 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 = `
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg>
<div class="empty-state-title">${(logFilter || logLevel !== 'all') ? 'No matching logs' : 'No logs yet'}</div>
<div class="empty-state-desc">${(logFilter || logLevel !== 'all') ? 'Try a different filter' : 'Logs will appear here as the extension runs'}</div>
</div>`;
return;
}
container.innerHTML = filtered.map(entry => {
const time = new Date(entry.timestamp).toLocaleTimeString();
const cls = getLogClass(entry.level || 'info');
return `<div class="log-entry">
<span class="log-time">${time}</span>
<span class="log-msg ${cls}">${escapeHtml(entry.message)}</span>
</div>`;
}).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 });
}