Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 163 additions & 9 deletions public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ const animate = () => {

const openDiagnostics = () => {
document.getElementById("diagnosticsModal").classList.add("active");
// Ensure bandwidth graph is correctly sized and drawn after modal opens
setTimeout(() => {
if (typeof resizeBandwidthCanvas === "function") {
resizeBandwidthCanvas();
}
}, 50);
};

const closeDiagnostics = () => {
Expand Down Expand Up @@ -587,7 +593,6 @@ terminalInput.addEventListener("keypress", async (e) => {
}
}


let scope = "GLOBAL";
let target = null;

Expand Down Expand Up @@ -686,6 +691,21 @@ terminalInput.addEventListener("keypress", async (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) => {
Expand Down Expand Up @@ -796,14 +816,19 @@ evtSource.onmessage = (event) => {
d.invalidPoW.toLocaleString();
document.getElementById("diag-invalid-sig").innerText =
d.invalidSig.toLocaleString();
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();

if (typeof addBandwidthData === "function") {
addBandwidthData(d.bytesReceived, d.bytesRelayed);
drawBandwidthGraph();
document.getElementById("current-in").innerText = formatBandwidth(
d.bytesReceived
);
document.getElementById("current-out").innerText = formatBandwidth(
d.bytesRelayed
);
}
}
};

Expand All @@ -817,6 +842,135 @@ countEl.classList.add("loaded");
updateParticles(initialCount);
animate();

const bandwidthHistory = { timestamps: [], bytesIn: [], bytesOut: [] };
let selectedTimeRange = 300;
const bandwidthCanvas = document.getElementById("bandwidthGraph");
const bandwidthCtx = bandwidthCanvas ? bandwidthCanvas.getContext("2d") : null;
const bandwidthOverlay = document.getElementById("bandwidthOverlay");

function resizeBandwidthCanvas() {
if (!bandwidthCanvas) return;
const rect = bandwidthCanvas.getBoundingClientRect();
bandwidthCanvas.width = rect.width;
bandwidthCanvas.height = rect.height;
drawBandwidthGraph();
}

window.addEventListener("resize", resizeBandwidthCanvas);
setTimeout(resizeBandwidthCanvas, 100);

const toggleBandwidthGraph = () => {
if (!bandwidthOverlay) return;
bandwidthOverlay.classList.toggle("collapsed");
const closeBtn = document.querySelector(".bandwidth-overlay .close-btn");
if (closeBtn) {
closeBtn.textContent = bandwidthOverlay.classList.contains("collapsed")
? "+"
: "−";
}
// Redraw after transition
setTimeout(resizeBandwidthCanvas, 350);
};

window.toggleBandwidthGraph = toggleBandwidthGraph;

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();
});
});

if (timePills.length > 0) {
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 = () => {
if (!bandwidthCanvas || !bandwidthCtx) return;
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");
};

const themes = [
"hypermind.css",
"hypermind-classic.css",
Expand All @@ -843,11 +997,11 @@ function showThemeNotification(themeName) {
notification.classList.remove("hidden");

notification.offsetHeight;

notification.classList.add("show");

if (notificationTimeout) clearTimeout(notificationTimeout);

notificationTimeout = setTimeout(() => {
notification.classList.remove("show");
setTimeout(() => {
Expand Down
39 changes: 31 additions & 8 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,41 @@
<span class="stat-label">Invalid Signatures</span>
<span class="stat-value" id="diag-invalid-sig">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Bandwidth In</span>
<span class="stat-value" id="diag-bandwidth-in">0 KB</span>
</div>
<div class="stat-row">
<span class="stat-label">Bandwidth Out</span>
<span class="stat-value" id="diag-bandwidth-out">0 KB</span>
</div>
<div class="stat-row">
<span class="stat-label">LEAVE Messages</span>
<span class="stat-value" id="diag-leave">0</span>
</div>

<div id="bandwidthOverlay" class="bandwidth-overlay">
<div class="bandwidth-title">Bandwidth</div>
<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>
<div class="update-time" id="last-update">last 10 seconds</div>
</div>
</div>
Expand Down
56 changes: 56 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,62 @@ a { color: var(--color-text-anchor-link); text-decoration: none; border-bottom:
margin-top: 1rem;
}

.bandwidth-overlay {
padding-top: .5rem;
}
.bandwidth-title {
font-size: 0.85rem;
color: var(--color-modal-stat-label);
margin-bottom: 0.5rem;
letter-spacing: 0.5px;
}
.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 0 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; }
.theme-btn {
position: fixed;
bottom: 1.5rem;
Expand Down