v0.32.0
This commit is contained in:
1 parent
d23fa5e154
commit
81ab39e70a
51 files changed
+5352
-1489
No files matched your search
@@ -0,0 +1,114 @@
|
||||
// Four opt-in cards in the EXISTING dashboard widget order and status refresh.
|
||||
// No timers, fetches, or independent persistence are created here.
|
||||
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 ?? prefs?.dashboard_hidden_widgets ?? ADMIN_APPEARANCE_DEFAULTS.dashboard_hidden_widgets);
|
||||
// Visibility belongs exclusively to Appearance. An absent, disabled or
|
||||
// restart-pending Stream must not silently override an explicit opt-in.
|
||||
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) {
|
||||
// A deliberately shown tile has a neutral empty state, not a
|
||||
// hidden DOM node. No history is sampled without running streams.
|
||||
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;
|
||||
}
|
||||
// Switching tabs or appearance preferences paints cached numbers without
|
||||
// generating an extra rate sample from the same status response.
|
||||
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();
|
||||
let rx = 0, tx = 0;
|
||||
if (history.last) {
|
||||
const seconds = Math.max(.001, (now - history.last.when) / 1000);
|
||||
if (rxTotal >= history.last.rx) rx = (rxTotal - history.last.rx) / seconds;
|
||||
if (txTotal >= history.last.tx) tx = (txTotal - history.last.tx) / seconds;
|
||||
}
|
||||
history.last = { when: now, rx: rxTotal, tx: txTotal };
|
||||
history.rx.push(rx); history.tx.push(tx);
|
||||
if (history.rx.length > 60) { history.rx.shift(); history.tx.shift(); }
|
||||
set("stream-dashboard-traffic", `${bytes(Math.round(rx))}/s`);
|
||||
trafficRates(`${bytes(Math.round(rx))}/s`, `${bytes(Math.round(tx))}/s`);
|
||||
$("stream-dashboard-rx")?.setAttribute("points", line(history.rx));
|
||||
$("stream-dashboard-tx")?.setAttribute("points", line(history.tx));
|
||||
}
|
||||
function reset() { clearTraffic(); wasVisible = false; }
|
||||
return { syncVisibility, render, reset };
|
||||
})();
|
||||
Reference in new issue
Block a user