CI / Build & Test (push) Successful in 2m52s
CRITICAL:
- certificate-authority.js: declare `regenerated` variable in installRootCA Windows path to prevent ReferenceError crash
HIGH:
- virtual-hosts.js/service-tunnels.js: call hs.removeAllListeners() in catch blocks to prevent stale listeners on failed Holesail instances
- https-proxy.js: destroy rawSocket in TLS error handler to prevent file descriptor exhaustion
- message-router.js (native): move setEventEmitter() to module-level init instead of re-calling on every message
- ssh-manager.js: add error handler to WS server to prevent unhandled error crashes
- init.js: store setInterval ID and clear on beforeunload to prevent interval accumulation
- logs.js: store and remove chrome.runtime.onMessage listener on beforeunload; add duplicate-call guard
- events.js: move pending++ before async sendMessage call to fix SSH/RDP-only import showing "Nothing to import"
- ssh.js: store resizeTimer on activeSshSession and clear in disconnectSsh; fix auto-reconnect race with _sshConnecting lock
- native-messaging.js: track retry timer IDs in array and cancel all on disconnect
- rdp.js: reuse single offscreen canvas per session instead of allocating per bitmap
MEDIUM:
- virtual-hosts.js/service-tunnels.js: clear existing.reconnectTimer before replacing tunnel entries
- message-router.js (native): destroy pingTunnel socket on error path; clear 15s fallback timer via finally()
- startup.js: wrap setImmediate body in try/finally to always resolve proxiesReadyPromise
- https-proxy.js: fix pre-connect upstream error handler to avoid writing raw HTTP into piped TLS stream; move HOP_BY_HOP to module-level constant
- connect-proxy.js: destroy upstreamSocket on clientSocket close; track and destroy active sockets in stop()
- ssh-manager.js: call cancelPasswordWatch on WS disconnect during password collection
- rdp-manager.js: remove dead remotePort variable; add error handlers to both WS servers
- backup-manager.js: log cleanupStaging errors and non-zero exit codes
- rdp.js: null out ws callbacks before closing in disconnectRdp; disconnect MutationObserver on beforeunload
- proxy-ca.js: prune stale entries from validationResults Map in renderValidatorTable
- refresh.js: deduplicate in-flight pings per port via Set
- messaging.js: read chrome.runtime.lastError in sendToNative callback
- servers.js (dashboard): add null check for $('serverEditId') element
LOW:
- port-allocator.js: add dedup check before pushing to tunnelPortFreeList
- servers.js (native): add error listener to server-mode Holesail instances
- virtual-hosts.js: remove dead prevReconnectDelay variable
- tab-lifecycle.js: change swarmRefCount fallback from || 1 to || 0 to prevent premature swarm destroy
- ssh.js/rdp.js: disconnect MutationObservers on beforeunload
113 lines
3.9 KiB
JavaScript
113 lines
3.9 KiB
JavaScript
// Depends on: core/utils.js ($, escapeHtml), ui/toast.js (showToast)
|
|
|
|
let _logsSetup = false;
|
|
|
|
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 });
|
|
}
|