CI / Build & Test (push) Successful in 2m54s
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)
121 lines
4.3 KiB
JavaScript
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 });
|
|
}
|