feat: implement 13 features — auto-reconnect, notifications, bulk actions, latency, traffic, theme, export/import, scheduled backups, peer lookup, SSH auto-reconnect, log filters, keyboard shortcut, and readyTimeout default
CI / Build & Test (push) Successful in 2m44s

- Change readyTimeoutMs default from 0 to 30000 in both state files
- Register Alt+Shift+H keyboard shortcut via manifest _execute_action command
- Add severity filter buttons (All/Info/Warn/Error) and Download .txt to Logs page; background logs.js now tags entries with proper level field
- Fire browser notifications on tunnelError events (notifyOnTunnelError setting)
- Add exponential-backoff auto-reconnect for virtual hosts and service tunnels (tunnelAutoReconnect setting, 5s–120s backoff)
- Add backupIntervalHours setting and scheduled auto-backup timer in message-router.js
- Add TCP connect latency badges to Virtual Hosts and Service Tunnels tables via new pingTunnel native message
- Add bytesIn/bytesOut/requests counters to https-proxy.js; expose in Overview status bar via getState
- Add Export/Import connection configs (JSON, no CA key) in Settings
- Add autoReconnect flag and exponential-backoff reconnect to SSH connection cards
- Add light theme CSS variables and theme toggle in Settings (persisted to localStorage)
- Add checkbox column and bulk Stop/Remove actions to Virtual Hosts, Service Tunnels, and Servers tables
- Add Peer Lookup UI card on Overview page using existing lookup message handler
This commit is contained in:
Raven Scott
2026-02-28 23:51:14 -05:00
parent 31f30b2974
commit 849897f324
24 changed files with 726 additions and 47 deletions
+41 -10
View File
@@ -4,6 +4,7 @@ function setupLogsEvents() {
let logs = [];
let autoScroll = true;
let logFilter = '';
let logLevel = 'all'; // 'all' | 'info' | 'warn' | 'error'
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'registerDashboard' },
@@ -19,31 +20,34 @@ function setupLogsEvents() {
}
});
function getLogClass(msg) {
const m = (msg || '').toLowerCase();
if (m.includes('error') || m.includes('fail') || m.includes('err:')) return 'is-error';
if (m.includes('warn') || m.includes('warning')) return 'is-warn';
function getLogClass(level) {
if (level === 'error') return 'is-error';
if (level === 'warn') return 'is-warn';
return '';
}
function updateLogsDisplay() {
const container = $('logsContainer');
if (!container) return;
const filtered = logFilter
? logs.filter(e => (e.message || '').toLowerCase().includes(logFilter.toLowerCase()))
: logs;
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 ? 'No matching logs' : 'No logs yet'}</div>
<div class="empty-state-desc">${logFilter ? 'Try a different filter' : 'Logs will appear here as the extension runs'}</div>
<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.message);
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>
@@ -68,6 +72,33 @@ function setupLogsEvents() {
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.sendMessage({ target: 'holesail-native', action: 'unregisterDashboard' }, () => {});
});