Skip to content

Commit fecb019

Browse files
authored
Merge pull request #60 from lklynet/pr-38
Pr 38
2 parents 31b8338 + 5e9d2f9 commit fecb019

File tree

3 files changed

+250
-17
lines changed

3 files changed

+250
-17
lines changed

public/app.js

Lines changed: 163 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ const animate = () => {
8989

9090
const openDiagnostics = () => {
9191
document.getElementById("diagnosticsModal").classList.add("active");
92+
// Ensure bandwidth graph is correctly sized and drawn after modal opens
93+
setTimeout(() => {
94+
if (typeof resizeBandwidthCanvas === "function") {
95+
resizeBandwidthCanvas();
96+
}
97+
}, 50);
9298
};
9399

94100
const closeDiagnostics = () => {
@@ -587,7 +593,6 @@ terminalInput.addEventListener("keypress", async (e) => {
587593
}
588594
}
589595

590-
591596
let scope = "GLOBAL";
592597
let target = null;
593598

@@ -686,6 +691,21 @@ terminalInput.addEventListener("keypress", async (e) => {
686691
}
687692
});
688693

694+
const formatBandwidth = (bytes, short = false) => {
695+
const kb = bytes / 1024;
696+
const mb = kb / 1024;
697+
const gb = mb / 1024;
698+
const space = short ? "" : " ";
699+
700+
if (gb >= 1) {
701+
return gb.toFixed(short ? 1 : 2) + space + "GB";
702+
} else if (mb >= 1) {
703+
return mb.toFixed(short ? 1 : 2) + space + "MB";
704+
} else {
705+
return kb.toFixed(short ? 0 : 1) + space + "KB";
706+
}
707+
};
708+
689709
const evtSource = new EventSource("/events");
690710

691711
evtSource.onmessage = (event) => {
@@ -796,14 +816,19 @@ evtSource.onmessage = (event) => {
796816
d.invalidPoW.toLocaleString();
797817
document.getElementById("diag-invalid-sig").innerText =
798818
d.invalidSig.toLocaleString();
799-
document.getElementById("diag-bandwidth-in").innerText = formatBandwidth(
800-
d.bytesReceived
801-
);
802-
document.getElementById("diag-bandwidth-out").innerText = formatBandwidth(
803-
d.bytesRelayed
804-
);
805819
document.getElementById("diag-leave").innerText =
806820
d.leaveMessages.toLocaleString();
821+
822+
if (typeof addBandwidthData === "function") {
823+
addBandwidthData(d.bytesReceived, d.bytesRelayed);
824+
drawBandwidthGraph();
825+
document.getElementById("current-in").innerText = formatBandwidth(
826+
d.bytesReceived
827+
);
828+
document.getElementById("current-out").innerText = formatBandwidth(
829+
d.bytesRelayed
830+
);
831+
}
807832
}
808833
};
809834

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

845+
const bandwidthHistory = { timestamps: [], bytesIn: [], bytesOut: [] };
846+
let selectedTimeRange = 300;
847+
const bandwidthCanvas = document.getElementById("bandwidthGraph");
848+
const bandwidthCtx = bandwidthCanvas ? bandwidthCanvas.getContext("2d") : null;
849+
const bandwidthOverlay = document.getElementById("bandwidthOverlay");
850+
851+
function resizeBandwidthCanvas() {
852+
if (!bandwidthCanvas) return;
853+
const rect = bandwidthCanvas.getBoundingClientRect();
854+
bandwidthCanvas.width = rect.width;
855+
bandwidthCanvas.height = rect.height;
856+
drawBandwidthGraph();
857+
}
858+
859+
window.addEventListener("resize", resizeBandwidthCanvas);
860+
setTimeout(resizeBandwidthCanvas, 100);
861+
862+
const toggleBandwidthGraph = () => {
863+
if (!bandwidthOverlay) return;
864+
bandwidthOverlay.classList.toggle("collapsed");
865+
const closeBtn = document.querySelector(".bandwidth-overlay .close-btn");
866+
if (closeBtn) {
867+
closeBtn.textContent = bandwidthOverlay.classList.contains("collapsed")
868+
? "+"
869+
: "−";
870+
}
871+
// Redraw after transition
872+
setTimeout(resizeBandwidthCanvas, 350);
873+
};
874+
875+
window.toggleBandwidthGraph = toggleBandwidthGraph;
876+
877+
const timePills = document.querySelectorAll(".time-pill");
878+
timePills.forEach((pill) => {
879+
pill.addEventListener("click", (e) => {
880+
e.stopPropagation();
881+
timePills.forEach((p) => p.classList.remove("active"));
882+
pill.classList.add("active");
883+
const value = pill.dataset.value;
884+
selectedTimeRange = value === "all" ? "all" : parseInt(value);
885+
drawBandwidthGraph();
886+
});
887+
});
888+
889+
if (timePills.length > 0) {
890+
timePills[0].classList.add("active");
891+
}
892+
893+
const addBandwidthData = (bytesIn, bytesOut) => {
894+
bandwidthHistory.timestamps.push(Date.now());
895+
bandwidthHistory.bytesIn.push(bytesIn);
896+
bandwidthHistory.bytesOut.push(bytesOut);
897+
898+
if (bandwidthHistory.timestamps.length > 360) {
899+
bandwidthHistory.timestamps.shift();
900+
bandwidthHistory.bytesIn.shift();
901+
bandwidthHistory.bytesOut.shift();
902+
}
903+
};
904+
905+
const getFilteredData = () => {
906+
if (selectedTimeRange === "all") return bandwidthHistory;
907+
908+
const cutoff = Date.now() - selectedTimeRange * 1000;
909+
const startIndex = bandwidthHistory.timestamps.findIndex((t) => t >= cutoff);
910+
911+
if (startIndex === -1) return bandwidthHistory;
912+
913+
return {
914+
timestamps: bandwidthHistory.timestamps.slice(startIndex),
915+
bytesIn: bandwidthHistory.bytesIn.slice(startIndex),
916+
bytesOut: bandwidthHistory.bytesOut.slice(startIndex),
917+
};
918+
};
919+
920+
const drawBandwidthGraph = () => {
921+
if (!bandwidthCanvas || !bandwidthCtx) return;
922+
const w = bandwidthCanvas.width;
923+
const h = bandwidthCanvas.height;
924+
925+
if (w === 0 || h === 0) return;
926+
927+
const pad = { t: 10, r: 10, b: 20, l: 50 };
928+
929+
bandwidthCtx.clearRect(0, 0, w, h);
930+
931+
const data = getFilteredData();
932+
if (data.timestamps.length < 2) return;
933+
934+
const max = Math.max(...data.bytesIn, ...data.bytesOut);
935+
if (max === 0) return;
936+
937+
bandwidthCtx.fillStyle = "#9ca3af";
938+
bandwidthCtx.font =
939+
'10px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto';
940+
bandwidthCtx.textAlign = "right";
941+
[max, max / 2, 0].forEach((val, i) => {
942+
bandwidthCtx.fillText(
943+
formatBandwidth(val, true),
944+
pad.l - 5,
945+
pad.t + ((h - pad.t - pad.b) / 2) * i + 4
946+
);
947+
});
948+
949+
const drawLine = (points, color) => {
950+
bandwidthCtx.strokeStyle = color;
951+
bandwidthCtx.lineWidth = 2;
952+
bandwidthCtx.beginPath();
953+
points.forEach((val, i) => {
954+
const x = pad.l + (i / (points.length - 1)) * (w - pad.l - pad.r);
955+
const y = pad.t + (h - pad.t - pad.b) - (val / max) * (h - pad.t - pad.b);
956+
i === 0 ? bandwidthCtx.moveTo(x, y) : bandwidthCtx.lineTo(x, y);
957+
});
958+
bandwidthCtx.stroke();
959+
960+
bandwidthCtx.lineTo(
961+
pad.l + (w - pad.l - pad.r),
962+
pad.t + (h - pad.t - pad.b)
963+
);
964+
bandwidthCtx.lineTo(pad.l, pad.t + (h - pad.t - pad.b));
965+
bandwidthCtx.closePath();
966+
bandwidthCtx.fillStyle = color + "33";
967+
bandwidthCtx.fill();
968+
};
969+
970+
drawLine(data.bytesIn, "#60a5fa");
971+
drawLine(data.bytesOut, "#f97316");
972+
};
973+
820974
const themes = [
821975
"hypermind.css",
822976
"hypermind-classic.css",
@@ -843,11 +997,11 @@ function showThemeNotification(themeName) {
843997
notification.classList.remove("hidden");
844998

845999
notification.offsetHeight;
846-
1000+
8471001
notification.classList.add("show");
8481002

8491003
if (notificationTimeout) clearTimeout(notificationTimeout);
850-
1004+
8511005
notificationTimeout = setTimeout(() => {
8521006
notification.classList.remove("show");
8531007
setTimeout(() => {

public/index.html

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,41 @@
128128
<span class="stat-label">Invalid Signatures</span>
129129
<span class="stat-value" id="diag-invalid-sig">0</span>
130130
</div>
131-
<div class="stat-row">
132-
<span class="stat-label">Bandwidth In</span>
133-
<span class="stat-value" id="diag-bandwidth-in">0 KB</span>
134-
</div>
135-
<div class="stat-row">
136-
<span class="stat-label">Bandwidth Out</span>
137-
<span class="stat-value" id="diag-bandwidth-out">0 KB</span>
138-
</div>
139131
<div class="stat-row">
140132
<span class="stat-label">LEAVE Messages</span>
141133
<span class="stat-value" id="diag-leave">0</span>
142134
</div>
135+
136+
<div id="bandwidthOverlay" class="bandwidth-overlay">
137+
<div class="bandwidth-title">Bandwidth</div>
138+
<div class="bandwidth-graph-container">
139+
<canvas id="bandwidthGraph"></canvas>
140+
</div>
141+
<div class="bandwidth-footer">
142+
<div class="bandwidth-legend">
143+
<div class="legend-item">
144+
<span class="legend-color" style="background: #60a5fa"></span>
145+
<span class="stat-value"
146+
>In: <span id="current-in">0 KB</span></span
147+
>
148+
</div>
149+
<div class="legend-item">
150+
<span class="legend-color" style="background: #f97316"></span>
151+
<span class="stat-value"
152+
>Out: <span id="current-out">0 KB</span></span
153+
>
154+
</div>
155+
</div>
156+
<div class="time-pills">
157+
<button class="time-pill" data-value="300">5m</button>
158+
<button class="time-pill" data-value="1800">30m</button>
159+
<button class="time-pill" data-value="all">All</button>
160+
<button class="close-btn" onclick="toggleBandwidthGraph()">
161+
162+
</button>
163+
</div>
164+
</div>
165+
</div>
143166
<div class="update-time" id="last-update">last 10 seconds</div>
144167
</div>
145168
</div>

public/style.css

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,62 @@ a { color: var(--color-text-anchor-link); text-decoration: none; border-bottom:
156156
margin-top: 1rem;
157157
}
158158

159+
.bandwidth-overlay {
160+
padding-top: .5rem;
161+
}
162+
.bandwidth-title {
163+
font-size: 0.85rem;
164+
color: var(--color-modal-stat-label);
165+
margin-bottom: 0.5rem;
166+
letter-spacing: 0.5px;
167+
}
168+
.bandwidth-graph-container {
169+
padding: 0.5rem 0 0;
170+
transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
171+
max-height: 150px;
172+
opacity: 1;
173+
overflow: hidden;
174+
}
175+
.bandwidth-overlay.collapsed .bandwidth-graph-container { max-height: 0; opacity: 0; padding: 0; }
176+
.bandwidth-overlay .close-btn {
177+
position: static;
178+
font-size: 0.65rem;
179+
font-weight: bold;
180+
padding: 0.2rem 0.5rem;
181+
color: #9ca3af;
182+
background: #1a1a1a;
183+
border: 1px solid #333;
184+
border-radius: 3px;
185+
cursor: pointer;
186+
transition: all 0.2s;
187+
outline: none;
188+
}
189+
.bandwidth-overlay .close-btn:hover { color: #cbd5e1; border-color: #4b5563; }
190+
#bandwidthGraph { width: 100%; height: 100px; display: block; background: transparent; }
191+
.bandwidth-footer {
192+
display: flex;
193+
justify-content: space-between;
194+
align-items: center;
195+
padding: 0.5rem 0 0.75rem;
196+
}
197+
.bandwidth-legend { display: flex; gap: 1.5rem; }
198+
.legend-item { display: flex; align-items: center; gap: 0.4rem; font-size: 0.7rem; }
199+
.legend-color { width: 10px; height: 10px; border-radius: 2px; }
200+
.time-pills { display: flex; gap: 0.3rem; }
201+
.bandwidth-overlay.collapsed .time-pill { display: none; }
202+
.time-pill {
203+
background: transparent;
204+
color: #4b5563;
205+
border: 1px solid #333;
206+
padding: 0.2rem 0.5rem;
207+
font-size: 0.65rem;
208+
border-radius: 3px;
209+
cursor: pointer;
210+
transition: all 0.2s;
211+
outline: none;
212+
}
213+
.time-pill:hover { color: #9ca3af; border-color: #4b5563; }
214+
.time-pill.active { background: #1a1a1a; color: #4ade80; border-color: #4ade80; }
159215
.theme-btn {
160216
position: fixed;
161217
bottom: 1.5rem;

0 commit comments

Comments
 (0)