revert: remove Statistics tab and all associated changes
CI / Build & Test (push) Successful in 3m7s

This commit is contained in:
Raven Scott
2026-03-01 23:07:55 -05:00
parent f9c651c6f5
commit 21b01f1461
10 changed files with 4 additions and 551 deletions
-1
View File
@@ -16,7 +16,6 @@ importScripts(
'background/logs.js',
'background/state.js',
'background/proxy.js',
'background/stats-tracker.js',
'background/native-messaging.js',
'background/tab-lifecycle.js',
'background/message-router.js'
+4 -12
View File
@@ -123,9 +123,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
sshConnections: payload.sshConnections || [],
rdpConnections: payload.rdpConnections || [],
trafficStats: payload.trafficStats || null,
stats: { ...extensionState.stats, ...payload.stats },
statsHistory: getStatsHistory(),
activeConnections: Array.from(activeConnections.values()),
stats: { ...extensionState.stats, ...payload.stats }
}
});
} else {
@@ -141,9 +139,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats,
statsHistory: getStatsHistory(),
activeConnections: Array.from(activeConnections.values()),
stats: extensionState.stats
}
});
}
@@ -162,9 +158,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats,
statsHistory: getStatsHistory(),
activeConnections: Array.from(activeConnections.values()),
stats: extensionState.stats
}
});
});
@@ -182,9 +176,7 @@ browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
proxyPort: null,
connectProxyPort: null,
caInstalled: false,
stats: extensionState.stats,
statsHistory: getStatsHistory(),
activeConnections: Array.from(activeConnections.values()),
stats: extensionState.stats
}
});
}
-2
View File
@@ -155,7 +155,6 @@ function connect() {
peerKey: peerInfo.publicKey || '',
createdAt: Date.now()
});
onConnection();
updateExtensionState();
} else if (msg.event === 'error') {
const connId = payload.connId;
@@ -230,7 +229,6 @@ function connect() {
log('Native host disconnected');
extensionState.hostConnected = false;
activeConnections.clear();
onDisconnect();
updateExtensionState();
clearProxy();
port = null;
-61
View File
@@ -1,61 +0,0 @@
/**
* Extension-side statistics tracker.
* Maintains a cumulative connection counter, reconnect history, and a
* per-minute connection-rate ring buffer (last 30 buckets) for sparkline display.
* Depends on nothing — must be loaded before native-messaging.js.
*/
const BUCKET_INTERVAL_MS = 60 * 1000; // 1 minute
const MAX_BUCKETS = 30;
const _statsHistory = {
totalConnectionsEver: 0,
reconnectCount: 0,
lastDisconnectAt: null,
connectionRateHistory: [], // [{t: epochMs, count: number}] newest last
_bucketCount: 0,
_bucketStart: Date.now(),
};
function _flushBucket() {
const now = Date.now();
// How many full minutes have elapsed since the bucket started?
const elapsed = now - _statsHistory._bucketStart;
const fullBuckets = Math.floor(elapsed / BUCKET_INTERVAL_MS);
if (fullBuckets === 0) return;
for (let i = 0; i < fullBuckets; i++) {
const t = _statsHistory._bucketStart + i * BUCKET_INTERVAL_MS;
// Only the first bucket gets the accumulated count; the rest are zero (idle minutes)
_statsHistory.connectionRateHistory.push({ t, count: i === 0 ? _statsHistory._bucketCount : 0 });
}
// Trim to last MAX_BUCKETS entries
if (_statsHistory.connectionRateHistory.length > MAX_BUCKETS) {
_statsHistory.connectionRateHistory = _statsHistory.connectionRateHistory.slice(-MAX_BUCKETS);
}
_statsHistory._bucketCount = 0;
_statsHistory._bucketStart = _statsHistory._bucketStart + fullBuckets * BUCKET_INTERVAL_MS;
}
function onConnection() {
_flushBucket();
_statsHistory._bucketCount++;
_statsHistory.totalConnectionsEver++;
}
function onDisconnect() {
_statsHistory.lastDisconnectAt = Date.now();
_statsHistory.reconnectCount++;
}
function getStatsHistory() {
_flushBucket();
return {
totalConnectionsEver: _statsHistory.totalConnectionsEver,
reconnectCount: _statsHistory.reconnectCount,
lastDisconnectAt: _statsHistory.lastDisconnectAt,
connectionRateHistory: _statsHistory.connectionRateHistory.slice(),
};
}
-1
View File
@@ -14,7 +14,6 @@ const PAGE_TITLES = {
rdp: 'Remote Desktop',
backups: 'Backups',
logs: 'Logs',
stats: 'Statistics',
settings: 'Settings'
};
-13
View File
@@ -106,14 +106,6 @@
</svg>
Logs
</div>
<div class="nav-item" data-page="stats">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
Statistics
</div>
<div class="nav-item" data-page="settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
@@ -560,10 +552,6 @@
</div>
</div>
<!-- ── Statistics page ────────────────────────── -->
<div class="page" id="page-stats">
</div>
<!-- ── Settings page ──────────────────────────── -->
<div class="page" id="page-settings">
<div class="card">
@@ -1298,7 +1286,6 @@
<script src="pages/logs.js"></script>
<script src="pages/ssh.js"></script>
<script src="pages/rdp.js"></script>
<script src="pages/stats.js"></script>
<!-- Orchestration -->
<script src="refresh.js"></script>
-1
View File
@@ -242,5 +242,4 @@ function setupEvents() {
setupServerEvents();
setupServiceTunnelEvents();
setupLogsEvents();
setupStatsEvents();
}
-398
View File
@@ -1,398 +0,0 @@
/**
* Statistics page — real-time native host process metrics, system resources,
* tunnel health, traffic stats, and peer connection history.
* Depends on: core/utils.js ($, escapeHtml, truncate), core/messaging.js (sendToNative),
* ui/toast.js (showToast)
*/
// ── Helpers ──────────────────────────────────────────────────────────────────
function _fmtBytes(n) {
if (n == null || isNaN(n)) return '—';
if (n >= 1073741824) return (n / 1073741824).toFixed(2) + ' GB';
if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB';
if (n >= 1024) return (n / 1024).toFixed(1) + ' KB';
return n + ' B';
}
function _fmtUptime(ms) {
if (!ms || ms < 0) return '—';
const s = Math.floor(ms / 1000);
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = s % 60;
if (d > 0) return `${d}d ${h}h ${m}m`;
if (h > 0) return `${h}h ${m}m ${sec}s`;
if (m > 0) return `${m}m ${sec}s`;
return `${sec}s`;
}
function _fmtTime(epochMs) {
if (!epochMs) return '—';
return new Date(epochMs).toLocaleString();
}
function _fmtDuration(createdAt) {
if (!createdAt) return '—';
return _fmtUptime(Date.now() - createdAt);
}
function _memBar(used, total) {
const pct = total ? Math.min(100, Math.round((used / total) * 100)) : 0;
const color = pct > 85 ? 'var(--red)' : pct > 65 ? 'var(--amber)' : 'var(--cyan)';
return `
<div style="display:flex;align-items:center;gap:8px;flex:1;">
<div style="flex:1;height:6px;background:var(--border);border-radius:99px;overflow:hidden;">
<div style="width:${pct}%;height:100%;background:${color};border-radius:99px;transition:width 0.4s ease;"></div>
</div>
<span style="font-size:11px;color:var(--text3);min-width:32px;text-align:right;">${pct}%</span>
</div>`;
}
function _sparkline(history) {
if (!history || history.length < 2) {
return `<span style="font-size:11px;color:var(--text4);">Not enough data yet</span>`;
}
const counts = history.map(b => b.count);
const w = 240, h = 44;
const max = Math.max(...counts, 1);
const pts = counts.map((v, i) => {
const x = (i / (counts.length - 1)) * w;
const y = h - 4 - (v / max) * (h - 8);
return `${x.toFixed(1)},${y.toFixed(1)}`;
}).join(' ');
const fillPts = `0,${h} ` + pts + ` ${w},${h}`;
return `
<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" style="overflow:visible;display:block;">
<defs>
<linearGradient id="sparkGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="var(--cyan)" stop-opacity="0.25"/>
<stop offset="100%" stop-color="var(--cyan)" stop-opacity="0"/>
</linearGradient>
</defs>
<polygon points="${fillPts}" fill="url(#sparkGrad)"/>
<polyline points="${pts}" fill="none" stroke="var(--cyan)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>
</svg>`;
}
function _statChip(label, value, color) {
return `
<div style="display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 14px;background:var(--elevated);border:1px solid var(--border);border-radius:var(--radius-sm);min-width:72px;">
<span style="font-size:15px;font-weight:700;color:${color || 'var(--text)'};">${value}</span>
<span style="font-size:10px;color:var(--text4);text-transform:uppercase;letter-spacing:0.06em;">${label}</span>
</div>`;
}
function _sectionCard(title, iconSvg, content) {
return `
<div class="card" style="margin-bottom:16px;">
<div class="card-header" style="display:flex;align-items:center;gap:8px;">
<span style="color:var(--cyan);display:flex;">${iconSvg}</span>
<span class="card-title">${title}</span>
</div>
<div style="padding:0 16px 16px;">${content}</div>
</div>`;
}
// ── Section renderers ─────────────────────────────────────────────────────────
function _renderHostProcess(state, ps) {
const connected = state.hostConnected;
const sh = state.statsHistory || {};
const swUptime = state.stats?.uptime ? _fmtUptime(Date.now() - state.stats.uptime) : '—';
let processRows = '';
if (connected && ps && ps.process) {
const mem = ps.process.memoryUsage;
processRows = `
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px;">
<div>
<div style="font-size:11px;color:var(--text3);margin-bottom:4px;">Heap Used / Total</div>
<div style="display:flex;align-items:center;gap:8px;">
<span style="font-size:13px;font-weight:600;color:var(--text);">${_fmtBytes(mem.heapUsed)} / ${_fmtBytes(mem.heapTotal)}</span>
</div>
<div style="margin-top:6px;">${_memBar(mem.heapUsed, mem.heapTotal)}</div>
</div>
<div>
<div style="font-size:11px;color:var(--text3);margin-bottom:4px;">RSS (Physical)</div>
<div style="font-size:13px;font-weight:600;color:var(--text);">${_fmtBytes(mem.rss)}</div>
<div style="font-size:11px;color:var(--text4);margin-top:2px;">External: ${_fmtBytes(mem.external)}</div>
</div>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:4px;">
${_statChip('PID', ps.process.pid, 'var(--text2)')}
${_statChip('Uptime', _fmtUptime(ps.process.uptimeMs), 'var(--cyan)')}
${_statChip('SW Uptime', swUptime, 'var(--text2)')}
</div>`;
} else if (!connected) {
processRows = `<div style="color:var(--text4);font-size:13px;padding:8px 0;">Native host is not connected.</div>`;
} else {
processRows = `<div style="color:var(--text4);font-size:13px;padding:8px 0;">Loading process stats…</div>`;
}
const reconnectInfo = `
<div style="display:flex;gap:16px;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
<div>
<div style="font-size:11px;color:var(--text3);">Reconnects</div>
<div style="font-size:14px;font-weight:600;color:${(sh.reconnectCount || 0) > 0 ? 'var(--amber)' : 'var(--text)'};">${sh.reconnectCount || 0}</div>
</div>
<div>
<div style="font-size:11px;color:var(--text3);">Last Disconnect</div>
<div style="font-size:13px;color:var(--text2);">${_fmtTime(sh.lastDisconnectAt)}</div>
</div>
</div>`;
const statusBadge = connected
? `<span class="badge badge-green" style="margin-left:auto;">Connected</span>`
: `<span class="badge" style="margin-left:auto;background:var(--red-dim);color:var(--red);">Disconnected</span>`;
const icon = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="6" height="6" rx="1"/><rect x="16" y="3" width="6" height="6" rx="1"/><rect x="9" y="15" width="6" height="6" rx="1"/><line x1="5" y1="9" x2="12" y2="15"/><line x1="19" y1="9" x2="12" y2="15"/></svg>`;
return _sectionCard(
`<span style="display:flex;align-items:center;gap:8px;width:100%;">Native Host Process ${statusBadge}</span>`,
icon,
processRows + reconnectInfo
);
}
function _renderSystemResources(ps) {
if (!ps || !ps.os) {
return _sectionCard('System Resources',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>`,
`<div style="color:var(--text4);font-size:13px;padding:8px 0;">Waiting for native host…</div>`
);
}
const o = ps.os;
const usedMem = o.totalMemory - o.freeMemory;
const loadAvg = Array.isArray(o.loadAvg) ? o.loadAvg : [0, 0, 0];
const loadColors = loadAvg.map(l => l > (o.cpuCount * 0.8) ? 'var(--red)' : l > (o.cpuCount * 0.5) ? 'var(--amber)' : 'var(--green)');
const content = `
<div style="margin-bottom:14px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;">
<span style="font-size:12px;color:var(--text3);">System Memory</span>
<span style="font-size:12px;color:var(--text2);">${_fmtBytes(usedMem)} / ${_fmtBytes(o.totalMemory)}</span>
</div>
${_memBar(usedMem, o.totalMemory)}
</div>
<div style="margin-bottom:14px;">
<div style="font-size:12px;color:var(--text3);margin-bottom:8px;">CPU Load Average</div>
<div style="display:flex;gap:8px;flex-wrap:wrap;">
${_statChip('1 min', loadAvg[0]?.toFixed(2) ?? '—', loadColors[0])}
${_statChip('5 min', loadAvg[1]?.toFixed(2) ?? '—', loadColors[1])}
${_statChip('15 min', loadAvg[2]?.toFixed(2) ?? '—', loadColors[2])}
${_statChip('Cores', o.cpuCount, 'var(--text2)')}
</div>
</div>
<div style="display:flex;gap:16px;font-size:12px;color:var(--text3);">
<span>Platform: <strong style="color:var(--text2);">${escapeHtml(o.platform || '—')}</strong></span>
<span>Arch: <strong style="color:var(--text2);">${escapeHtml(o.arch || '—')}</strong></span>
</div>`;
return _sectionCard('System Resources',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>`,
content
);
}
function _renderTunnelHealth(state, ps) {
const servers = state.servers || [];
const vhosts = state.virtualHosts || [];
const svcTunnels = state.serviceTunnels || [];
function stateCounts(arr) {
const c = { ready: 0, error: 0, closed: 0, connecting: 0 };
for (const t of arr) {
const s = t.state || 'connecting';
if (s in c) c[s]++; else c.connecting++;
}
return c;
}
function stateRow(label, arr) {
const c = stateCounts(arr);
const total = arr.length;
if (total === 0) return `
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border);">
<span style="font-size:13px;color:var(--text2);">${label}</span>
<span style="font-size:12px;color:var(--text4);">None</span>
</div>`;
return `
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0;border-bottom:1px solid var(--border);gap:8px;flex-wrap:wrap;">
<span style="font-size:13px;font-weight:500;color:var(--text);">${label} <span style="color:var(--text3);font-weight:400;">(${total})</span></span>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
${c.ready ? `<span class="badge badge-green">${c.ready} ready</span>` : ''}
${c.connecting ? `<span class="badge" style="background:var(--amber-dim);color:var(--amber);">${c.connecting} connecting</span>` : ''}
${c.error ? `<span class="badge" style="background:var(--red-dim);color:var(--red);">${c.error} error</span>` : ''}
${c.closed ? `<span class="badge badge-neutral">${c.closed} closed</span>` : ''}
</div>
</div>`;
}
// Aggregate byState from native host if available
let aggregateRow = '';
if (ps && ps.tunnels && ps.tunnels.byState) {
const b = ps.tunnels.byState;
const total = (ps.tunnels.serverCount || 0) + (ps.tunnels.virtualHostCount || 0) + (ps.tunnels.serviceTunnelCount || 0);
aggregateRow = `
<div style="display:flex;align-items:center;justify-content:space-between;padding:8px 0 0;flex-wrap:wrap;gap:8px;">
<span style="font-size:12px;color:var(--text3);">All tunnels (${total})</span>
<div style="display:flex;gap:6px;flex-wrap:wrap;">
${b.ready ? `<span class="badge badge-green">${b.ready} ready</span>` : ''}
${b.connecting ? `<span class="badge" style="background:var(--amber-dim);color:var(--amber);">${b.connecting} connecting</span>` : ''}
${b.error ? `<span class="badge" style="background:var(--red-dim);color:var(--red);">${b.error} error</span>` : ''}
${b.closed ? `<span class="badge badge-neutral">${b.closed} closed</span>` : ''}
</div>
</div>`;
}
const content = `
${stateRow('Server Tunnels', servers)}
${stateRow('Virtual Hosts', vhosts)}
${stateRow('Service Tunnels', svcTunnels)}
${aggregateRow}`;
return _sectionCard('Tunnel Health',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`,
content
);
}
function _renderTraffic(state) {
const ts = state.trafficStats;
if (!ts) return '';
const content = `
<div style="display:flex;gap:8px;flex-wrap:wrap;">
${_statChip('Bytes In', _fmtBytes(ts.bytesIn), 'var(--green)')}
${_statChip('Bytes Out', _fmtBytes(ts.bytesOut), 'var(--cyan)')}
${_statChip('Requests', (ts.requests ?? 0).toLocaleString(), 'var(--text2)')}
</div>`;
return _sectionCard('Proxy Traffic',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22,12 18,12 15,21 9,3 6,12 2,12"/></svg>`,
content
);
}
function _renderConnections(state) {
const sh = state.statsHistory || {};
const active = state.activeConnections || [];
const liveCount = state.stats?.totalConnections ?? active.length;
const sparkHtml = _sparkline(sh.connectionRateHistory);
let tableHtml = '';
if (active.length > 0) {
const rows = active.map(c => `
<tr>
<td class="mono" style="font-size:11px;color:var(--text3);">${escapeHtml(truncate(c.swarmId || '—', 18))}</td>
<td class="mono" style="font-size:11px;color:var(--cyan);">${escapeHtml(truncate(c.peerKey || '—', 18))}</td>
<td style="font-size:12px;color:var(--text2);">${_fmtDuration(c.createdAt)}</td>
</tr>`).join('');
tableHtml = `
<div style="margin-top:14px;overflow-x:auto;">
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr style="font-size:11px;color:var(--text4);text-transform:uppercase;letter-spacing:0.06em;">
<th style="text-align:left;padding:4px 8px 6px 0;border-bottom:1px solid var(--border);">Swarm ID</th>
<th style="text-align:left;padding:4px 8px 6px 0;border-bottom:1px solid var(--border);">Peer Key</th>
<th style="text-align:left;padding:4px 0 6px 0;border-bottom:1px solid var(--border);">Duration</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>`;
} else {
tableHtml = `<div style="font-size:12px;color:var(--text4);margin-top:12px;">No active peer connections.</div>`;
}
const content = `
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px;">
${_statChip('Active', liveCount, liveCount > 0 ? 'var(--green)' : 'var(--text2)')}
${_statChip('Total Ever', sh.totalConnectionsEver || 0, 'var(--text2)')}
</div>
<div style="margin-bottom:4px;">
<div style="font-size:11px;color:var(--text3);margin-bottom:6px;">Connections / minute (last 30 min)</div>
${sparkHtml}
</div>
${tableHtml}`;
return _sectionCard('Peer Connections',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="2"/><circle cx="4" cy="6" r="2"/><circle cx="20" cy="6" r="2"/><circle cx="4" cy="18" r="2"/><circle cx="20" cy="18" r="2"/><line x1="6" y1="6" x2="10" y2="11"/><line x1="18" y1="6" x2="14" y2="11"/><line x1="6" y1="18" x2="10" y2="13"/><line x1="18" y1="18" x2="14" y2="13"/></svg>`,
content
);
}
function _renderProxyInfo(state) {
let version = '—';
try { version = 'v' + chrome.runtime.getManifest().version; } catch (_) {}
const content = `
<div style="display:flex;gap:8px;flex-wrap:wrap;">
${_statChip('Extension', version, 'var(--text2)')}
${_statChip('HTTPS Port', state.proxyPort ?? '—', 'var(--cyan)')}
${_statChip('CONNECT Port', state.connectProxyPort ?? '—', 'var(--cyan)')}
</div>`;
return _sectionCard('Extension & Proxy',
`<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>`,
content
);
}
// ── Main render ───────────────────────────────────────────────────────────────
let _lastProcessStats = null;
/**
* Re-render the Statistics page with the latest state.
* Also fires a getProcessStats request to the native host for live CPU/memory.
* @param {object} state - Full extension state from fetchState().
*/
function updateStatsPage(state) {
const el = $('page-stats');
if (!el || !el.classList.contains('active')) return;
// Fire async request for process stats; re-render when it arrives
if (state.hostConnected) {
chrome.runtime.sendMessage(
{ target: 'holesail-native', action: 'send', payload: { type: 'getProcessStats', payload: {} } },
(response) => {
if (chrome.runtime.lastError) return;
if (response && response.ok) {
_lastProcessStats = response;
_renderStatsPage(state, _lastProcessStats);
}
}
);
} else {
_lastProcessStats = null;
}
_renderStatsPage(state, _lastProcessStats);
}
function _renderStatsPage(state, ps) {
const el = $('page-stats');
if (!el) return;
el.innerHTML = `
<div style="max-width:860px;">
${_renderHostProcess(state, ps)}
${_renderSystemResources(ps)}
${_renderTunnelHealth(state, ps)}
${_renderTraffic(state)}
${_renderConnections(state)}
${_renderProxyInfo(state)}
</div>`;
}
/**
* Attach event listeners for the Statistics page.
* Called once during dashboard initialisation.
*/
function setupStatsEvents() {
// No modals or interactive controls on this page — all data is read-only.
}
-1
View File
@@ -102,7 +102,6 @@ async function refresh() {
updateTabsTable(state);
updateServiceTunnelsTable(state);
updateSettingsUI();
updateStatsPage(state);
}
const sshCountEl = $('sshCount');
if (sshCountEl) sshCountEl.textContent = sshConnections.length;
-61
View File
@@ -9,23 +9,6 @@ const { STORAGE_PATH } = require('./paths.js');
const { log, debugLog } = require('./logger.js');
const { initStartup, getProxiesReadyPromise, getTunnelsRestoredPromise, setTunnelsRestoredPromise, restorePersistedTunnels } = require('./startup.js');
const os = require('os');
const _hostStartTime = Date.now();
function _computeTunnelStateCounts(manager) {
const counts = { ready: 0, error: 0, closed: 0, connecting: 0 };
const all = [
...(manager.getServers() || []),
...(manager.getVirtualHosts() || []),
...(manager.getServiceTunnels() || []),
];
for (const t of all) {
const s = t.state || 'connecting';
if (s in counts) counts[s]++;
else counts.connecting++;
}
return counts;
}
const holesailManager = require('../holesail-manager.js');
holesailManager.setStoragePath(STORAGE_PATH);
@@ -348,50 +331,6 @@ async function handleMessageAsync(send, msg) {
break;
}
case 'getProcessStats': {
const mem = process.memoryUsage();
let loadAvg = [0, 0, 0];
let cpuCount = 1;
let platform = '';
let arch = '';
let totalMemory = 0;
let freeMemory = 0;
try { const la = os.loadavg(); loadAvg = Array.isArray(la) ? la : [0, 0, 0]; } catch (_) {}
try { cpuCount = os.cpus().length; } catch (_) {}
try { platform = os.platform(); } catch (_) {}
try { arch = os.arch(); } catch (_) {}
try { totalMemory = os.totalmem(); } catch (_) {}
try { freeMemory = os.freemem(); } catch (_) {}
reply({
ok: true,
process: {
memoryUsage: {
rss: mem.rss || 0,
heapTotal: mem.heapTotal || 0,
heapUsed: mem.heapUsed || 0,
external: mem.external || 0,
},
uptimeMs: Date.now() - _hostStartTime,
pid: process.pid,
},
os: {
totalMemory,
freeMemory,
cpuCount,
loadAvg,
platform,
arch,
},
tunnels: {
serverCount: holesailManager.getServers().length,
virtualHostCount: holesailManager.getVirtualHosts().length,
serviceTunnelCount: holesailManager.getServiceTunnels().length,
byState: _computeTunnelStateCounts(holesailManager),
},
});
break;
}
default:
reply({ ok: false, error: `Unknown command: ${type}` });
}