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
+13 -3
View File
@@ -35,6 +35,11 @@ let proxyServer = null;
let proxyPort = null;
let proxyCertsDirOrCA = null;
const trafficStats = { bytesIn: 0, bytesOut: 0, requests: 0 };
function getTrafficStats() { return { ...trafficStats }; }
function resetTrafficStats() { trafficStats.bytesIn = 0; trafficStats.bytesOut = 0; trafficStats.requests = 0; }
/** Resolver: hostname -> { host, port } | port | null */
let getBackendForHostname = null;
@@ -313,7 +318,7 @@ h1{color:#c0392b}code{background:#f4f4f4;padding:2px 6px;border-radius:3px;font-
try { res.setHeader(k, proxyRes.headers[k]); } catch (_) {}
}
}
proxyRes.on('data', (chunk) => !timedOut && res.write(chunk));
proxyRes.on('data', (chunk) => { if (!timedOut) { trafficStats.bytesOut += chunk.length; res.write(chunk); } });
proxyRes.on('end', () => !timedOut && !res.writableEnded && res.end());
// Destroy the client socket on a mid-stream backend error so the browser
// receives a connection reset rather than a silently truncated 200 body.
@@ -327,7 +332,8 @@ h1{color:#c0392b}code{background:#f4f4f4;padding:2px 6px;border-radius:3px;font-
res.end('Proxy error: ' + (timedOut ? 'timeout' : err.message));
}
});
req.on('data', (chunk) => proxyReq.write(chunk));
trafficStats.requests++;
req.on('data', (chunk) => { trafficStats.bytesIn += chunk.length; proxyReq.write(chunk); });
req.on('end', () => proxyReq.end());
}
@@ -367,6 +373,8 @@ function onUpgrade (req, socket, head) {
const requestLine = (req.method || 'GET') + ' ' + (req.url || '/') + ' HTTP/1.1\r\n';
upstream.write(requestLine + headers + '\r\n\r\n');
if (head && head.length > 0) upstream.write(head);
socket.on('data', (c) => { trafficStats.bytesIn += c.length; });
upstream.on('data', (c) => { trafficStats.bytesOut += c.length; });
socket.pipe(upstream);
upstream.pipe(socket);
});
@@ -595,5 +603,7 @@ module.exports = {
start,
stop,
restart,
getPort
getPort,
getTrafficStats,
resetTrafficStats
};