mirror of
https://github.com/lklynet/hypermind.git
synced 2026-05-03 09:30:36 +00:00
(feat): Bandwidth Graph Overlay
This commit is contained in:
+130
-15
@@ -99,6 +99,21 @@ document.addEventListener('keydown', (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
const formatBandwidth = (bytes, short = false) => {
|
||||
const kb = bytes / 1024;
|
||||
const mb = kb / 1024;
|
||||
const gb = mb / 1024;
|
||||
const space = short ? '' : ' ';
|
||||
|
||||
if (gb >= 1) {
|
||||
return gb.toFixed(short ? 1 : 2) + space + 'GB';
|
||||
} else if (mb >= 1) {
|
||||
return mb.toFixed(short ? 1 : 2) + space + 'MB';
|
||||
} else {
|
||||
return kb.toFixed(short ? 0 : 1) + space + 'KB';
|
||||
}
|
||||
};
|
||||
|
||||
const evtSource = new EventSource("/events");
|
||||
|
||||
evtSource.onmessage = (event) => {
|
||||
@@ -117,21 +132,7 @@ evtSource.onmessage = (event) => {
|
||||
|
||||
if (data.diagnostics) {
|
||||
const d = data.diagnostics;
|
||||
|
||||
const formatBandwidth = (bytes) => {
|
||||
const kb = bytes / 1024;
|
||||
const mb = kb / 1024;
|
||||
const gb = mb / 1024;
|
||||
|
||||
if (gb >= 1) {
|
||||
return gb.toFixed(2) + ' GB';
|
||||
} else if (mb >= 1) {
|
||||
return mb.toFixed(2) + ' MB';
|
||||
} else {
|
||||
return kb.toFixed(1) + ' KB';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.getElementById('diag-heartbeats-rx').innerText = d.heartbeatsReceived.toLocaleString();
|
||||
document.getElementById('diag-heartbeats-tx').innerText = d.heartbeatsRelayed.toLocaleString();
|
||||
document.getElementById('diag-new-peers').innerText = d.newPeersAdded.toLocaleString();
|
||||
@@ -141,6 +142,12 @@ evtSource.onmessage = (event) => {
|
||||
document.getElementById('diag-bandwidth-in').innerText = formatBandwidth(d.bytesReceived);
|
||||
document.getElementById('diag-bandwidth-out').innerText = formatBandwidth(d.bytesRelayed);
|
||||
document.getElementById('diag-leave').innerText = d.leaveMessages.toLocaleString();
|
||||
|
||||
addBandwidthData(d.bytesReceived, d.bytesRelayed);
|
||||
drawBandwidthGraph();
|
||||
|
||||
document.getElementById('current-in').innerText = formatBandwidth(d.bytesReceived);
|
||||
document.getElementById('current-out').innerText = formatBandwidth(d.bytesRelayed);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,3 +161,111 @@ countEl.classList.add('loaded');
|
||||
updateParticles(initialCount);
|
||||
animate();
|
||||
|
||||
const bandwidthHistory = { timestamps: [], bytesIn: [], bytesOut: [] };
|
||||
let selectedTimeRange = 300;
|
||||
const bandwidthCanvas = document.getElementById('bandwidthGraph');
|
||||
const bandwidthCtx = bandwidthCanvas.getContext('2d');
|
||||
const bandwidthOverlay = document.getElementById('bandwidthOverlay');
|
||||
|
||||
function resizeBandwidthCanvas() {
|
||||
const rect = bandwidthCanvas.getBoundingClientRect();
|
||||
bandwidthCanvas.width = rect.width;
|
||||
bandwidthCanvas.height = rect.height;
|
||||
drawBandwidthGraph();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeBandwidthCanvas);
|
||||
setTimeout(resizeBandwidthCanvas, 100);
|
||||
|
||||
const toggleBandwidthGraph = () => {
|
||||
bandwidthOverlay.classList.toggle('collapsed');
|
||||
document.querySelector('.bandwidth-overlay .close-btn').textContent =
|
||||
bandwidthOverlay.classList.contains('collapsed') ? '+' : '−';
|
||||
};
|
||||
|
||||
const timePills = document.querySelectorAll('.time-pill');
|
||||
timePills.forEach(pill => {
|
||||
pill.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
timePills.forEach(p => p.classList.remove('active'));
|
||||
pill.classList.add('active');
|
||||
const value = pill.dataset.value;
|
||||
selectedTimeRange = value === 'all' ? 'all' : parseInt(value);
|
||||
drawBandwidthGraph();
|
||||
});
|
||||
});
|
||||
|
||||
timePills[0].classList.add('active');
|
||||
|
||||
const addBandwidthData = (bytesIn, bytesOut) => {
|
||||
bandwidthHistory.timestamps.push(Date.now());
|
||||
bandwidthHistory.bytesIn.push(bytesIn);
|
||||
bandwidthHistory.bytesOut.push(bytesOut);
|
||||
|
||||
if (bandwidthHistory.timestamps.length > 360) {
|
||||
bandwidthHistory.timestamps.shift();
|
||||
bandwidthHistory.bytesIn.shift();
|
||||
bandwidthHistory.bytesOut.shift();
|
||||
}
|
||||
};
|
||||
|
||||
const getFilteredData = () => {
|
||||
if (selectedTimeRange === 'all') return bandwidthHistory;
|
||||
|
||||
const cutoff = Date.now() - (selectedTimeRange * 1000);
|
||||
const startIndex = bandwidthHistory.timestamps.findIndex(t => t >= cutoff);
|
||||
|
||||
if (startIndex === -1) return bandwidthHistory;
|
||||
|
||||
return {
|
||||
timestamps: bandwidthHistory.timestamps.slice(startIndex),
|
||||
bytesIn: bandwidthHistory.bytesIn.slice(startIndex),
|
||||
bytesOut: bandwidthHistory.bytesOut.slice(startIndex)
|
||||
};
|
||||
};
|
||||
|
||||
const drawBandwidthGraph = () => {
|
||||
const w = bandwidthCanvas.width;
|
||||
const h = bandwidthCanvas.height;
|
||||
|
||||
if (w === 0 || h === 0) return;
|
||||
|
||||
const pad = { t: 10, r: 10, b: 20, l: 50 };
|
||||
|
||||
bandwidthCtx.clearRect(0, 0, w, h);
|
||||
|
||||
const data = getFilteredData();
|
||||
if (data.timestamps.length < 2) return;
|
||||
|
||||
const max = Math.max(...data.bytesIn, ...data.bytesOut);
|
||||
if (max === 0) return;
|
||||
|
||||
bandwidthCtx.fillStyle = '#9ca3af';
|
||||
bandwidthCtx.font = '10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto';
|
||||
bandwidthCtx.textAlign = 'right';
|
||||
[max, max / 2, 0].forEach((val, i) => {
|
||||
bandwidthCtx.fillText(formatBandwidth(val, true), pad.l - 5, pad.t + ((h - pad.t - pad.b) / 2) * i + 4);
|
||||
});
|
||||
|
||||
const drawLine = (points, color) => {
|
||||
bandwidthCtx.strokeStyle = color;
|
||||
bandwidthCtx.lineWidth = 2;
|
||||
bandwidthCtx.beginPath();
|
||||
points.forEach((val, i) => {
|
||||
const x = pad.l + (i / (points.length - 1)) * (w - pad.l - pad.r);
|
||||
const y = pad.t + (h - pad.t - pad.b) - (val / max) * (h - pad.t - pad.b);
|
||||
i === 0 ? bandwidthCtx.moveTo(x, y) : bandwidthCtx.lineTo(x, y);
|
||||
});
|
||||
bandwidthCtx.stroke();
|
||||
|
||||
bandwidthCtx.lineTo(pad.l + (w - pad.l - pad.r), pad.t + (h - pad.t - pad.b));
|
||||
bandwidthCtx.lineTo(pad.l, pad.t + (h - pad.t - pad.b));
|
||||
bandwidthCtx.closePath();
|
||||
bandwidthCtx.fillStyle = color + '33';
|
||||
bandwidthCtx.fill();
|
||||
};
|
||||
|
||||
drawLine(data.bytesIn, '#60a5fa');
|
||||
drawLine(data.bytesOut, '#f97316');
|
||||
};
|
||||
|
||||
|
||||
@@ -65,6 +65,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bandwidthOverlay" class="bandwidth-overlay">
|
||||
<div class="bandwidth-graph-container">
|
||||
<canvas id="bandwidthGraph"></canvas>
|
||||
</div>
|
||||
<div class="bandwidth-footer">
|
||||
<div class="bandwidth-legend">
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #60a5fa;"></span>
|
||||
<span class="stat-value">In: <span id="current-in">0 KB</span></span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="legend-color" style="background: #f97316;"></span>
|
||||
<span class="stat-value">Out: <span id="current-out">0 KB</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-pills">
|
||||
<button class="time-pill" data-value="300">5m</button>
|
||||
<button class="time-pill" data-value="1800">30m</button>
|
||||
<button class="time-pill" data-value="all">All</button>
|
||||
<button class="close-btn" onclick="toggleBandwidthGraph()">−</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -85,3 +85,59 @@ a { color: #4b5563; text-decoration: none; border-bottom: 1px dotted #4b5563; }
|
||||
color: #333;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.bandwidth-overlay {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background: transparent;
|
||||
}
|
||||
.bandwidth-graph-container {
|
||||
padding: 0.5rem 0 0;
|
||||
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
||||
max-height: 150px;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bandwidth-overlay.collapsed .bandwidth-graph-container { max-height: 0; opacity: 0; padding: 0; }
|
||||
.bandwidth-overlay .close-btn {
|
||||
position: static;
|
||||
font-size: 0.65rem;
|
||||
font-weight: bold;
|
||||
padding: 0.2rem 0.5rem;
|
||||
color: #9ca3af;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.bandwidth-overlay .close-btn:hover { color: #cbd5e1; border-color: #4b5563; }
|
||||
#bandwidthGraph { width: 100%; height: 100px; display: block; background: transparent; }
|
||||
.bandwidth-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1.5rem 0.75rem;
|
||||
}
|
||||
.bandwidth-legend { display: flex; gap: 1.5rem; }
|
||||
.legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.7rem; }
|
||||
.legend-color { width: 10px; height: 10px; border-radius: 2px; }
|
||||
.time-pills { display: flex; gap: 0.3rem; }
|
||||
.bandwidth-overlay.collapsed .time-pill { display: none; }
|
||||
.time-pill {
|
||||
background: transparent;
|
||||
color: #4b5563;
|
||||
border: 1px solid #333;
|
||||
padding: 0.2rem 0.5rem;
|
||||
font-size: 0.65rem;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
.time-pill:hover { color: #9ca3af; border-color: #4b5563; }
|
||||
.time-pill.active { background: #1a1a1a; color: #4ade80; border-color: #4ade80; }
|
||||
|
||||
Reference in New Issue
Block a user