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
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:
@@ -19,15 +19,38 @@ function debugLog(...args) {
|
||||
if (process.stderr) process.stderr.write(msg + '\n');
|
||||
}
|
||||
|
||||
const virtualHosts = new Map(); // hostname -> { hsUrl, holesail, localHost, localPort, state, createdAt }
|
||||
const virtualHosts = new Map(); // hostname -> { hsUrl, holesail, localHost, localPort, state, createdAt, reconnectTimer, reconnectDelay }
|
||||
let _saveState = null;
|
||||
let _emit = null;
|
||||
let _getReadyTimeoutMs = null;
|
||||
let _getAutoReconnect = null;
|
||||
|
||||
function init(saveStateFn, emitFn, getReadyTimeoutMsFn) {
|
||||
const RECONNECT_BASE_MS = 5000;
|
||||
const RECONNECT_MAX_MS = 120000;
|
||||
|
||||
function init(saveStateFn, emitFn, getReadyTimeoutMsFn, getAutoReconnectFn) {
|
||||
_saveState = saveStateFn;
|
||||
_emit = emitFn;
|
||||
_getReadyTimeoutMs = getReadyTimeoutMsFn;
|
||||
_getAutoReconnect = getAutoReconnectFn;
|
||||
}
|
||||
|
||||
function _scheduleVhostReconnect(hostname) {
|
||||
const v = virtualHosts.get(hostname);
|
||||
if (!v) return;
|
||||
if (v.reconnectTimer) { clearTimeout(v.reconnectTimer); v.reconnectTimer = null; }
|
||||
const autoReconnect = _getAutoReconnect ? _getAutoReconnect() : false;
|
||||
if (!autoReconnect) return;
|
||||
const delay = v.reconnectDelay || RECONNECT_BASE_MS;
|
||||
v.reconnectDelay = Math.min(delay * 2, RECONNECT_MAX_MS);
|
||||
if (process.stderr) process.stderr.write('[holesail-manager] vhost ' + hostname + ' reconnecting in ' + Math.round(delay / 1000) + 's\n');
|
||||
v.reconnectTimer = setTimeout(async () => {
|
||||
v.reconnectTimer = null;
|
||||
const cur = virtualHosts.get(hostname);
|
||||
if (!cur || cur.state === 'ready') return;
|
||||
if (process.stderr) process.stderr.write('[holesail-manager] vhost ' + hostname + ' auto-reconnect attempt\n');
|
||||
await setVirtualHost({ hostname, hsUrl: cur.hsUrl });
|
||||
}, delay);
|
||||
}
|
||||
|
||||
function readyWithTimeout(hs, label) {
|
||||
@@ -61,17 +84,18 @@ async function setVirtualHost(payload) {
|
||||
hs.on('error', (err) => {
|
||||
if (process.stderr) process.stderr.write('[holesail-manager] tunnel error ' + hostname + ': ' + (err && err.message) + '\n');
|
||||
const v = virtualHosts.get(hostname);
|
||||
if (v && v.holesail === hs) v.state = 'error';
|
||||
if (v && v.holesail === hs) { v.state = 'error'; _scheduleVhostReconnect(hostname); }
|
||||
if (_emit) _emit('tunnelError', { hostname, error: err && err.message });
|
||||
});
|
||||
hs.on('close', () => {
|
||||
const v = virtualHosts.get(hostname);
|
||||
if (v && v.holesail === hs) v.state = 'closed';
|
||||
if (v && v.holesail === hs) { v.state = 'closed'; _scheduleVhostReconnect(hostname); }
|
||||
if (_emit) _emit('tunnelClosed', { hostname });
|
||||
});
|
||||
}
|
||||
await readyWithTimeout(hs, 'vhost:' + hostname);
|
||||
virtualHosts.set(hostname, { hsUrl, holesail: hs, localHost: TUNNEL_HOST, localPort, state: 'ready', createdAt: (existing && existing.createdAt) || Date.now() });
|
||||
const prevReconnectDelay = existing ? existing.reconnectDelay : undefined;
|
||||
virtualHosts.set(hostname, { hsUrl, holesail: hs, localHost: TUNNEL_HOST, localPort, state: 'ready', createdAt: (existing && existing.createdAt) || Date.now(), reconnectTimer: null, reconnectDelay: RECONNECT_BASE_MS });
|
||||
if (_emit) _emit('tunnelReady', { hostname, hsUrl, localHost: TUNNEL_HOST, localPort });
|
||||
if (_saveState) _saveState();
|
||||
debugLog('setVirtualHost: ok hostname=', hostname, 'localPort=', localPort);
|
||||
@@ -91,6 +115,7 @@ async function removeVirtualHost(payload) {
|
||||
debugLog('removeVirtualHost: hostname=', hostname);
|
||||
const entry = virtualHosts.get(hostname);
|
||||
if (!entry) return { ok: false, error: 'Virtual host not found' };
|
||||
if (entry.reconnectTimer) { clearTimeout(entry.reconnectTimer); entry.reconnectTimer = null; }
|
||||
if (entry.localPort) releaseTunnelPort(entry.localPort);
|
||||
if (entry.holesail) { try { await entry.holesail.close(); } catch (_) {} }
|
||||
virtualHosts.delete(hostname);
|
||||
@@ -129,9 +154,10 @@ function getVirtualHostMap() {
|
||||
|
||||
async function cleanupVirtualHosts() {
|
||||
for (const [, v] of virtualHosts) {
|
||||
if (v.reconnectTimer) { clearTimeout(v.reconnectTimer); v.reconnectTimer = null; }
|
||||
if (v.holesail) { try { await v.holesail.close(); } catch (_) {} }
|
||||
}
|
||||
virtualHosts.clear();
|
||||
}
|
||||
|
||||
module.exports = { init, setVirtualHost, removeVirtualHost, getVirtualHosts, getLocalPortForHostname, getLocalBackend, getVirtualHostMap, cleanupVirtualHosts };
|
||||
module.exports = { init, setVirtualHost, removeVirtualHost, getVirtualHosts, getLocalPortForHostname, getLocalBackend, getVirtualHostMap, cleanupVirtualHosts, RECONNECT_BASE_MS, RECONNECT_MAX_MS };
|
||||
|
||||
Reference in New Issue
Block a user