diff --git a/public/app.js b/public/app.js index 24e56f3..bdfe1bc 100644 --- a/public/app.js +++ b/public/app.js @@ -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 = () => { @@ -587,7 +593,6 @@ terminalInput.addEventListener("keypress", async (e) => { } } - let scope = "GLOBAL"; let target = null; @@ -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) => { @@ -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 + ); + } } }; @@ -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", @@ -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(() => { diff --git a/public/index.html b/public/index.html index 24ddb76..53d0fec 100644 --- a/public/index.html +++ b/public/index.html @@ -128,18 +128,41 @@ Invalid Signatures 0 -