window.rustProxyStreamDashboard = (() => { const ids = ["stream_overview", "stream_traffic", "stream_sessions", "stream_backend_health"]; const history = { last: null, rx: [], tx: [] }; let wasVisible = false; const card = id => document.querySelector(`[data-dashboard-widget="${id}"]`); const set = (id, value) => { const node = $(id); if (node) node.textContent = value; }; const trafficRates = (rx, tx) => { set("stream-dashboard-rx-rate", rx); set("stream-dashboard-tx-rate", tx); }; function clearTraffic() { history.last = null; history.rx.length = 0; history.tx.length = 0; $("stream-dashboard-rx")?.setAttribute("points", "0,55 300,55"); $("stream-dashboard-tx")?.setAttribute("points", "0,55 300,55"); } function syncVisibility(hiddenOverride = null) { const prefs = window.rustProxyPreferences?.current?.(); const hidden = new Set(hiddenOverride ?? window.rustProxyInlineLayout?.currentDraft?.()?.hidden ?? prefs?.dashboard_hidden_widgets ?? ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets); for (const id of ids) if (card(id)) card(id).hidden = hidden.has(id); if (card("stream_traffic")?.hidden && wasVisible) clearTraffic(); wasVisible = !card("stream_traffic")?.hidden; } function line(points) { const peak = Math.max(1, ...history.rx, ...history.tx); return points.map((value, i) => `${i * 300 / Math.max(1, points.length - 1)},${55 - value / peak * 50}`).join(" "); } function render(status, hiddenOverride = null, recordTrafficSample = true) { syncVisibility(hiddenOverride); if (!status || currentPanel !== "overview" || ids.every(id => card(id)?.hidden)) return; const configured = envelope?.config?.streams || status?.streams?.desired || []; const active = (status?.streams?.active || []).filter(item => item.enabled !== false); const metrics = status?.streams?.metrics || {}; if (!configured.some(item => item.enabled !== false) && !active.length) { for (const name of ["overview", "sessions", "health", "traffic"]) { if (!card(({ overview: "stream_overview", sessions: "stream_sessions", health: "stream_backend_health", traffic: "stream_traffic" })[name])?.hidden) { set(`stream-dashboard-${name}`, "-"); if (name === "traffic") trafficRates("-", "-"); else set(`stream-dashboard-${name}-detail`, t("streamWidgetUnavailable")); } } if (!card("stream_traffic")?.hidden) clearTraffic(); return; } if (!card("stream_overview")?.hidden) { const desired = configured.filter(item => item.enabled !== false); const attention = desired.filter(item => !active.some(a => a.id === item.id)).length + active.filter(item => (metrics[item.id]?.backends || []).length && !(metrics[item.id].backends || []).some(b => b.enabled && !b.drain && b.status !== "unhealthy")).length; set("stream-dashboard-overview", `${active.length} / ${desired.length}`); set("stream-dashboard-overview-detail", `${t("streamWidgetActiveConfigured")} · ${attention} ${t("streamWidgetAttention")}`); } if (!card("stream_sessions")?.hidden) { const sum = protocol => active.filter(item => item.protocol === protocol) .reduce((count, item) => count + (Number(metrics[item.id]?.active) || 0), 0); const tcp = sum("tcp"), udp = sum("udp"); set("stream-dashboard-sessions", fmt(tcp + udp)); set("stream-dashboard-sessions-detail", `TCP ${fmt(tcp)} · UDP ${fmt(udp)}`); } if (!card("stream_backend_health")?.hidden) { const backends = active.flatMap(item => metrics[item.id]?.backends || []); const healthy = backends.filter(b => b.status === "healthy").length; const unhealthy = backends.filter(b => b.status === "unhealthy").length; const unverified = backends.filter(b => b.status === "unverified" || b.status === "checking").length; const emptyPools = active.filter(item => (metrics[item.id]?.backends || []).length > 0 && !(metrics[item.id].backends || []).some(b => b.enabled && !b.drain && b.status !== "unhealthy")).length; set("stream-dashboard-health", `${healthy} / ${unhealthy}`); set("stream-dashboard-health-detail", `${t("streamWidgetHealthyUnhealthy")} · ${unverified} ${t("streamWidgetUnverified")} · ${emptyPools} ${t("streamWidgetEmptyPools")}`); } if (card("stream_traffic")?.hidden) return; const views = active.map(item => metrics[item.id]).filter(Boolean); if (!views.length) { set("stream-dashboard-traffic", t("streamWidgetUnavailable")); trafficRates("-", "-"); return; } if (!recordTrafficSample) { if (history.rx.length) { const rx = history.rx.at(-1), tx = history.tx.at(-1); set("stream-dashboard-traffic", `${bytes(Math.round(rx))}/s`); trafficRates(`${bytes(Math.round(rx))}/s`, `${bytes(Math.round(tx))}/s`); } else { set("stream-dashboard-traffic", "-"); trafficRates("-", "-"); } return; } const rxTotal = views.reduce((sum, view) => sum + (Number(view.bytes_in) || 0), 0); const txTotal = views.reduce((sum, view) => sum + (Number(view.bytes_out) || 0), 0); const now = performance.now(); const sample = rustProxyRateSampling.difference(history.last, [rxTotal, txTotal], now, rustProxyRateSampling.cadence("overview_seconds")); const pausedRate = history.last !== null && sample.deltas === null; history.last = sample.next; const rx = sample.deltas ? sample.deltas[0] / sample.seconds : 0; const tx = sample.deltas ? sample.deltas[1] / sample.seconds : 0; if (!pausedRate) { history.rx.push(rx); history.tx.push(tx); if (history.rx.length > window.rustProxyOverviewSamples.getLimit()) { history.rx.shift(); history.tx.shift(); } } set("stream-dashboard-traffic", pausedRate ? "-" : `${bytes(Math.round(rx))}/s`); trafficRates(pausedRate ? "-" : `${bytes(Math.round(rx))}/s`, pausedRate ? "-" : `${bytes(Math.round(tx))}/s`); $("stream-dashboard-rx")?.setAttribute("points", line(history.rx)); $("stream-dashboard-tx")?.setAttribute("points", line(history.tx)); window.rustProxyChartScale.axes("stream-dashboard-rx", Math.max(1, ...history.rx, ...history.tx), "bytes/s", history.rx.length, { top: 5 / 60, bottom: 55 / 60 }); } function reset() { clearTraffic(); wasVisible = false; } window.rustProxyOverviewSamples.onChange(() => { window.rustProxyOverviewSamples.trim(history.rx); window.rustProxyOverviewSamples.trim(history.tx); if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, null, false); }); return { syncVisibility, render, reset }; })();