v0.33.0
This commit is contained in:
1 parent
bbfa8cbc3f
commit
2ccc598b12
49 files changed
+4771
-639
No files matched your search
@@ -1,5 +1,3 @@
|
||||
// Optional HTTP origin diagnostics. Reuse status.backend_pools already present
|
||||
// in live SSE/polling; never issue one request per tile or per member.
|
||||
window.rustProxyBackendDashboard = (() => {
|
||||
const ids = ["backend_health", "backend_activity", "backend_errors", "backend_latency", "backend_traffic", "backend_protocols", "backend_requests", "backend_capacity", "backend_http5xx", "backend_timeouts", "backend_network", "backend_retries", "backend_queue", "backend_rotations"];
|
||||
const charts = new Set(["backend_activity", "backend_errors", "backend_latency", "backend_traffic", "backend_requests", "backend_capacity", "backend_http5xx", "backend_timeouts", "backend_network", "backend_retries", "backend_queue"]);
|
||||
@@ -34,7 +32,7 @@ window.rustProxyBackendDashboard = (() => {
|
||||
}
|
||||
function push(name, value) {
|
||||
samples[name].push(Math.max(0, value));
|
||||
if (samples[name].length > 30) samples[name].shift();
|
||||
if (samples[name].length > window.rustProxyOverviewSamples.getLimit()) samples[name].shift();
|
||||
}
|
||||
function unavailable() {
|
||||
for (const id of ids) {
|
||||
@@ -47,6 +45,7 @@ window.rustProxyBackendDashboard = (() => {
|
||||
$(id)?.setAttribute("points", "0,55 300,55");
|
||||
const bar = $("backend-mode-h1");
|
||||
if (bar) for (const item of bar.parentElement.children) item.style.flexGrow = "0";
|
||||
window.rustProxyExtraOverview?.meter?.("backend-dashboard-capacity", null);
|
||||
reset();
|
||||
}
|
||||
// Counter deltas are matched to stable (pool, member) IDs, not aggregate
|
||||
@@ -177,7 +176,7 @@ window.rustProxyBackendDashboard = (() => {
|
||||
set("backend-dashboard-capacity", capacityPercent === null ? "-" : `${fmt(capacityPercent, 1)}%`);
|
||||
set("backend-dashboard-capacity-detail", capacityPercent === null ? t("backendWidgetCapacityNone") :
|
||||
`${t("backendWidgetCapacityLimits")}: ${limited.length} · ${capacityUsed}/${capacityLimit} · ${t("backendWidgetCapacityRejects")}: ${fmt(capacityRejects)}`);
|
||||
paintSeries("capacity", "backend-dashboard-capacity-line", Math.max(100, ...samples.capacity));
|
||||
window.rustProxyExtraOverview?.meter?.("backend-dashboard-capacity", capacityPercent);
|
||||
}
|
||||
if (visible("backend_errors")) {
|
||||
set("backend-dashboard-errors", fmt(totalErrors));
|
||||
@@ -208,7 +207,7 @@ window.rustProxyBackendDashboard = (() => {
|
||||
if (!visible(`backend_${name}`)) continue;
|
||||
const total = members.reduce((n, { member }) => n + (counterName ? counter(member[counterName]) :
|
||||
name === "network" ? ["connect_errors_total", "dns_errors_total", "tls_errors_total", "io_errors_total"].reduce((v, key) => v + counter(member[key]), 0) :
|
||||
counter(member.queue_timeouts_total) + counter(member.capacity_rejections_total)), 0);
|
||||
counter(member.queue_timeouts_total) + counter(member.capacity_rejections_total)), 0);
|
||||
set(field, fmt(total));
|
||||
let detail = `${t("backendWidgetRateDetail")}: ${displayRate(additional[name])}`;
|
||||
if (name === "retries") detail += ` · ${t("backendWidgetRetriesSuccess")}: ${fmt(members.reduce((n, { member }) => n + counter(member.retry_successes_total), 0))}`;
|
||||
@@ -218,5 +217,9 @@ window.rustProxyBackendDashboard = (() => {
|
||||
paintSeries(name, lineId, Math.max(1, ...samples[name]));
|
||||
}
|
||||
}
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const values of Object.values(samples)) window.rustProxyOverviewSamples.trim(values);
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, null, false);
|
||||
});
|
||||
return { syncVisibility, render, reset };
|
||||
})();
|
||||
+45
-50
@@ -27,8 +27,6 @@ let samples = [], protocolSamples = { h1: [], h2: [], h3: [] }, websocketProtoco
|
||||
let liveEvents = null, liveConnected = false, liveFallback = false, liveInterval = null, authEpoch = 0, pendingAuthDestination = null;
|
||||
let lastApiLatencyMs = null, sessionCheckPromise = null;
|
||||
let adminAuthChannel = null; try { adminAuthChannel = new BroadcastChannel("rustproxy-admin-session"); adminAuthChannel.addEventListener("message", event => { if (event.data === "locked" && authenticated) lock(false); }); } catch { }
|
||||
// Language content is discovered by build.rs from langs/*.json, never compiled
|
||||
// into a two-language switch here. Locale metadata also drives Intl and RTL.
|
||||
let languagePacks = new Map(), defaultLanguage = null;
|
||||
function activePack() { return languagePacks.get(language) || languagePacks.get(defaultLanguage); }
|
||||
function locale() { try { return Intl.getCanonicalLocales(activePack()?.meta.locale || "und")[0]; } catch { return "und"; } }
|
||||
@@ -59,15 +57,13 @@ function translate() {
|
||||
window.rustProxyDrafts?.refresh();
|
||||
window.rustProxyPreferences?.refreshAppearanceUi?.();
|
||||
$("connection-label").textContent = t(connected ? "connected" : "disconnected");
|
||||
if (envelope) renderConfig(); if (latestStatus) { renderFooter(latestStatus); renderDiagnostics(latestStatus, false); window.rustProxyVhosts?.renderStatus(latestStatus); window.rustProxyStreams?.renderStatus(latestStatus); window.rustProxyStreamDashboard?.render(latestStatus); window.rustProxyBackendDashboard?.render(latestStatus, null, false); window.rustProxyExtraOverview?.render(latestStatus, false); window.rustProxyWideOverview?.render(latestStatus, false); } if (lastLogs) renderLogs(lastLogs);
|
||||
if (envelope) renderConfig(); if (latestStatus) { renderFooter(latestStatus); renderDiagnostics(latestStatus, false); window.rustProxyVhosts?.renderStatus(latestStatus); window.rustProxyStreams?.renderStatus(latestStatus); window.rustProxyStreamDashboard?.render(latestStatus); window.rustProxyBackendDashboard?.render(latestStatus, null, false); window.rustProxyExtraOverview?.render(latestStatus, false); window.rustProxyWideOverview?.render(latestStatus, false); window.rustProxyWebOverview?.render(latestStatus, false); } if (lastLogs) renderLogs(lastLogs);
|
||||
syncSettingsDirtyIndicators();
|
||||
}
|
||||
function hideToast() { const node = $("toast"); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
|
||||
function raiseToast() {
|
||||
const node = $("toast"); if (node.hidden) return;
|
||||
const modal = [...document.querySelectorAll("dialog[open]")].at(-1), parent = modal || document.body;
|
||||
// A popover outside a modal is still inert. Keep it inside the active dialog,
|
||||
// then lift it to the top layer so its backdrop cannot blur or cover it.
|
||||
if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover();
|
||||
if (node.parentElement !== parent) parent.append(node);
|
||||
if (typeof node.showPopover === "function") node.showPopover();
|
||||
@@ -154,19 +150,16 @@ function setPanel(name, navigate = true) {
|
||||
const previousPanel = currentPanel; if (name !== "overview" && previousPanel === "overview") window.rustProxyInlineLayout?.cancel?.();
|
||||
currentPanel = name;
|
||||
$("overview-refresh").hidden = name !== "overview";
|
||||
$("overview-samples-toolbar").hidden = name !== "overview";
|
||||
for (const id of ["overview-layout-open", "overview-layout-unlock"]) if ($(id)) $(id).hidden = name !== "overview";
|
||||
document.querySelectorAll(".nav").forEach(e => e.classList.toggle("active", e.dataset.panel === name));
|
||||
document.querySelectorAll(".panel").forEach(e => e.hidden = e.id !== `panel-${name}`);
|
||||
$("page-label").textContent = t(name); $("page-title").textContent = t(`${name}Title`); $("page-description").textContent = t(`${name}Description`);
|
||||
if (navigate && typeof location !== "undefined" && ["http:", "https:"].includes(location.protocol) && location.pathname !== PANEL_PATHS[name]) history.pushState({ panel: name }, "", PANEL_PATHS[name]);
|
||||
if (name === "web-server" && latestStatus) window.rustProxyWebServer?.renderLive?.(latestStatus, false);
|
||||
if (name === "settings") window.rustProxySettings?.syncLocation?.();
|
||||
window.rustProxyRefresh?.setPanel(name);
|
||||
// Paint Stream tiles immediately on return from Appearance using cached status.
|
||||
// Do not create an extra sample/poll on navigation.
|
||||
if (name === "overview" && previousPanel !== name && typeof latestStatus !== "undefined" && latestStatus)
|
||||
{ window.rustProxyStreamDashboard?.render?.(latestStatus, null, false); window.rustProxyBackendDashboard?.render?.(latestStatus, null, false); window.rustProxyExtraOverview?.render?.(latestStatus, false); window.rustProxyWideOverview?.render?.(latestStatus, false); }
|
||||
// Read the cached Docker status on each visit, independent of the Live-view toggle.
|
||||
// This is GET /api/docker, not the POST that triggers an agent poll.
|
||||
if (name === "overview" && previousPanel !== name && typeof latestStatus !== "undefined" && latestStatus) { window.rustProxyStreamDashboard?.render?.(latestStatus, null, false); window.rustProxyBackendDashboard?.render?.(latestStatus, null, false); window.rustProxyExtraOverview?.render?.(latestStatus, false); window.rustProxyWideOverview?.render?.(latestStatus, false); window.rustProxyWebOverview?.render?.(latestStatus, false); }
|
||||
if (name === "docker-vhosts" && previousPanel !== name && authenticated && envelope) void window.rustProxyRefresh?.manual("docker");
|
||||
if (name === "tls") window.rustProxyAcme?.syncLocation?.();
|
||||
if (name === "filelogs") void window.rustProxyFileLogs?.refresh();
|
||||
@@ -187,7 +180,7 @@ function uptime(value) {
|
||||
function rateBytes(value) { value = Math.max(0, Number(value) || 0); if (value === 0) return "0.0 KiB"; value /= 1024; for (const unit of ["KiB", "MiB", "GiB", "TiB"]) { if (value < 1024 || unit === "TiB") { if (unit === "KiB" && value < 0.1) return "<0.1 KiB"; const digits = value < 10 ? 1 : 0; return `${Number(value.toFixed(digits))} ${unit}`; } value /= 1024; } }
|
||||
function el(tag, text, cls) { const e = document.createElement(tag); if (text !== undefined) e.textContent = text; if (cls) e.className = cls; return e; }
|
||||
function enhanceEnabledControl(input, hintKey = "entityEnableHint") {
|
||||
const label = input?.closest("label"); if (!label) return () => {};
|
||||
const label = input?.closest("label"); if (!label) return () => { };
|
||||
label.classList.add("vh-enable-control", "vh-wide");
|
||||
let hint = label.querySelector(".vh-enable-hint");
|
||||
if (!hint) { hint = el("small", undefined, "vh-enable-hint"); label.append(hint); }
|
||||
@@ -203,9 +196,35 @@ function enhanceEnabledControl(input, hintKey = "entityEnableHint") {
|
||||
}
|
||||
window.rustProxyEnableControl = enhanceEnabledControl;
|
||||
function action(label, fn) { const b = el("button", label, "quiet"); b.type = "button"; b.addEventListener("click", fn); return b; }
|
||||
function redrawOverviewHistoryCharts() {
|
||||
const connectionPeak = Math.max(1, ...connectionOpenSamples, ...connectionExchangeSamples);
|
||||
const connectionPoints = values => values.map((value, index) => `${index * 300 / Math.max(1, values.length - 1)},${95 - value / connectionPeak * 85}`).join(" ");
|
||||
$("connection-open-sparkline").setAttribute("points", connectionPoints(connectionOpenSamples));
|
||||
$("connection-exchange-sparkline").setAttribute("points", connectionPoints(connectionExchangeSamples));
|
||||
const peak = Math.max(1, ...samples, ...transformSamples);
|
||||
const points = values => values.map((n, i) => `${i * 600 / Math.max(1, values.length - 1)},${120 - n / peak * 105}`).join(" ");
|
||||
$("sparkline").setAttribute("points", points(samples));
|
||||
$("sparkline-h1").setAttribute("points", points(protocolSamples.h1));
|
||||
$("sparkline-h2").setAttribute("points", points(protocolSamples.h2));
|
||||
$("sparkline-h3").setAttribute("points", points(protocolSamples.h3));
|
||||
$("sparkline-transform").setAttribute("points", points(transformSamples));
|
||||
for (const key of ["h1", "h2", "h3"]) {
|
||||
$("http-protocol-" + key).setAttribute("points", sparklinePoints(protocolSamples[key], 300, 60, 5));
|
||||
$("ws-protocol-" + key).setAttribute("points", sparklinePoints(websocketProtocolSamples[key], 300, 60, 5));
|
||||
}
|
||||
$("chart-peak").textContent = `max ${Math.max(0, ...samples).toFixed(1)} req/s`;
|
||||
}
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const history of [samples, transformSamples, connectionOpenSamples, connectionExchangeSamples,
|
||||
...Object.values(protocolSamples), ...Object.values(websocketProtocolSamples)]) {
|
||||
window.rustProxyOverviewSamples.trim(history);
|
||||
}
|
||||
redrawOverviewHistoryCharts();
|
||||
});
|
||||
function renderStatus(s, systemSample = false) {
|
||||
const firstStatus = latestStatus === null;
|
||||
latestStatus = { ...(latestStatus || {}), ...s,
|
||||
latestStatus = {
|
||||
...(latestStatus || {}), ...s,
|
||||
streams: s.streams ? { ...(latestStatus?.streams || {}), ...s.streams } : latestStatus?.streams,
|
||||
listeners: s.listeners ? { ...(latestStatus?.listeners || {}), ...s.listeners } : latestStatus?.listeners,
|
||||
logging: s.logging ? { ...(latestStatus?.logging || {}), ...s.logging } : latestStatus?.logging
|
||||
@@ -220,12 +239,8 @@ function renderStatus(s, systemSample = false) {
|
||||
$("metric-h2").textContent = m.http2_requests || 0; $("metric-h2-active").textContent = m.http2_active_connections || 0; $("metric-tls-errors").textContent = m.tls_handshake_errors || 0;
|
||||
$("metric-requests").textContent = m.requests.toLocaleString(locale()); $("metric-active").textContent = m.active; $("metric-bytes").textContent = bytes(m.bytes_in + m.bytes_out); $("metric-bytes-down").textContent = bytes(m.bytes_out); $("metric-bytes-up").textContent = bytes(m.bytes_in); $("metric-errors").textContent = m.errors;
|
||||
const openConnections = Math.max(0, Number(m.active_connections) || 0), activeExchanges = Math.max(0, Number(m.active) || 0);
|
||||
connectionOpenSamples.push(openConnections); connectionOpenSamples = connectionOpenSamples.slice(-30);
|
||||
connectionExchangeSamples.push(activeExchanges); connectionExchangeSamples = connectionExchangeSamples.slice(-30);
|
||||
const connectionPeak = Math.max(1, ...connectionOpenSamples, ...connectionExchangeSamples);
|
||||
const connectionPoints = values => values.map((value, index) => `${index * 300 / Math.max(1, values.length - 1)},${95 - value / connectionPeak * 85}`).join(" ");
|
||||
$("connection-open-sparkline").setAttribute("points", connectionPoints(connectionOpenSamples));
|
||||
$("connection-exchange-sparkline").setAttribute("points", connectionPoints(connectionExchangeSamples));
|
||||
connectionOpenSamples.push(openConnections); connectionOpenSamples = connectionOpenSamples.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
connectionExchangeSamples.push(activeExchanges); connectionExchangeSamples = connectionExchangeSamples.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
$("connection-current").textContent = fmt(openConnections); $("connection-open-value").textContent = fmt(openConnections); $("connection-exchange-value").textContent = fmt(activeExchanges); $("connection-backends").textContent = fmt(s.backends);
|
||||
const now = performance.now();
|
||||
const transformed = s.transform_metrics || {};
|
||||
@@ -234,38 +249,25 @@ function renderStatus(s, systemSample = false) {
|
||||
const pausedRate = previous !== null && rateSample.deltas === null; previous = rateSample.next;
|
||||
const rates = Object.fromEntries(keys.map((key, i) => [key, rateSample.deltas ? rateSample.deltas[i] / rateSample.seconds : 0]));
|
||||
if (!pausedRate) {
|
||||
samples.push(rates.total); samples = samples.slice(-30);
|
||||
for (const key of ["h1", "h2", "h3"]) { protocolSamples[key].push(rates[key]); protocolSamples[key] = protocolSamples[key].slice(-30); }
|
||||
samples.push(rates.total); samples = samples.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
for (const key of ["h1", "h2", "h3"]) { protocolSamples[key].push(rates[key]); protocolSamples[key] = protocolSamples[key].slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
}
|
||||
websocketProtocolSamples.h1.push(Number(m.websocket_h1_active) || 0); websocketProtocolSamples.h1 = websocketProtocolSamples.h1.slice(-30);
|
||||
websocketProtocolSamples.h2.push(Number(m.websocket_h2_active) || 0); websocketProtocolSamples.h2 = websocketProtocolSamples.h2.slice(-30);
|
||||
websocketProtocolSamples.h3.push(Number(m.websocket_h3_active) || 0); websocketProtocolSamples.h3 = websocketProtocolSamples.h3.slice(-30);
|
||||
if (!pausedRate) { transformSamples.push(rates.transform); transformSamples = transformSamples.slice(-30); }
|
||||
const peak = Math.max(1, ...samples, ...transformSamples);
|
||||
const points = values => values.map((n, i) => `${i * 600 / Math.max(1, values.length - 1)},${120 - n / peak * 105}`).join(" ");
|
||||
$("sparkline").setAttribute("points", points(samples));
|
||||
$("sparkline-h1").setAttribute("points", points(protocolSamples.h1));
|
||||
$("sparkline-h2").setAttribute("points", points(protocolSamples.h2));
|
||||
$("sparkline-h3").setAttribute("points", points(protocolSamples.h3));
|
||||
$("sparkline-transform").setAttribute("points", points(transformSamples));
|
||||
websocketProtocolSamples.h1.push(Number(m.websocket_h1_active) || 0); websocketProtocolSamples.h1 = websocketProtocolSamples.h1.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
websocketProtocolSamples.h2.push(Number(m.websocket_h2_active) || 0); websocketProtocolSamples.h2 = websocketProtocolSamples.h2.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
websocketProtocolSamples.h3.push(Number(m.websocket_h3_active) || 0); websocketProtocolSamples.h3 = websocketProtocolSamples.h3.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
if (!pausedRate) { transformSamples.push(rates.transform); transformSamples = transformSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
redrawOverviewHistoryCharts();
|
||||
const rateText = key => pausedRate ? "-" : `${rates[key].toFixed(1)}/s`;
|
||||
$("request-rate").textContent = pausedRate ? "- req/s" : `${rates.total.toFixed(1)} req/s`;
|
||||
$("rate-total").textContent = rateText("total"); $("rate-h1").textContent = rateText("h1"); $("rate-h2").textContent = rateText("h2"); $("rate-h3").textContent = rateText("h3"); $("rate-transform").textContent = rateText("transform");
|
||||
$("metric-http-proto-total").textContent = rateText("total"); $("metric-http-proto-h1").textContent = rateText("h1");
|
||||
$("metric-http-proto-h2").textContent = rateText("h2"); $("metric-http-proto-h3").textContent = rateText("h3");
|
||||
$("http-protocol-h1").setAttribute("points", sparklinePoints(protocolSamples.h1, 300, 60, 5));
|
||||
$("http-protocol-h2").setAttribute("points", sparklinePoints(protocolSamples.h2, 300, 60, 5));
|
||||
$("http-protocol-h3").setAttribute("points", sparklinePoints(protocolSamples.h3, 300, 60, 5));
|
||||
const wsTotalActive = (Number(m.websocket_h1_active) || 0) + (Number(m.websocket_h2_active) || 0) + (Number(m.websocket_h3_active) || 0);
|
||||
$("metric-ws-proto-total").textContent = wsTotalActive.toLocaleString(locale());
|
||||
$("metric-ws-proto-h1").textContent = (Number(m.websocket_h1_active) || 0).toLocaleString(locale());
|
||||
$("metric-ws-proto-h2").textContent = (Number(m.websocket_h2_active) || 0).toLocaleString(locale());
|
||||
$("metric-ws-proto-h3").textContent = (Number(m.websocket_h3_active) || 0).toLocaleString(locale());
|
||||
$("ws-protocol-h1").setAttribute("points", sparklinePoints(websocketProtocolSamples.h1, 300, 60, 5));
|
||||
$("ws-protocol-h2").setAttribute("points", sparklinePoints(websocketProtocolSamples.h2, 300, 60, 5));
|
||||
$("ws-protocol-h3").setAttribute("points", sparklinePoints(websocketProtocolSamples.h3, 300, 60, 5));
|
||||
$("chart-peak").textContent = `max ${Math.max(0, ...samples).toFixed(1)} req/s`;
|
||||
renderFooter(s); renderDiagnostics(s, true, systemSample); window.rustProxyVhosts?.renderStatus(s); window.rustProxyStreams?.renderStatus(s); window.rustProxyStreamDashboard?.render(s); window.rustProxyBackendDashboard?.render(s); window.rustProxyExtraOverview?.render(s); window.rustProxyWideOverview?.render(s);
|
||||
renderFooter(s); renderDiagnostics(s, true, systemSample); window.rustProxyVhosts?.renderStatus(s); window.rustProxyStreams?.renderStatus(s); window.rustProxyStreamDashboard?.render(s); window.rustProxyBackendDashboard?.render(s); window.rustProxyExtraOverview?.render(s); window.rustProxyWideOverview?.render(s); window.rustProxyWebOverview?.render(s); window.rustProxyWebServer?.renderLive?.(s);
|
||||
}
|
||||
function renderFooter(s) {
|
||||
const apiLatency = lastApiLatencyMs == null ? "API -" : `API ${lastApiLatencyMs.toFixed(lastApiLatencyMs < 10 ? 1 : 0)} ms`;
|
||||
@@ -370,17 +372,16 @@ function authReturnDestination() {
|
||||
const tab = window.rustProxySettings?.current?.() || new URLSearchParams(location.search).get("tab");
|
||||
return tab ? `/settings?tab=${encodeURIComponent(tab)}` : "/settings";
|
||||
}
|
||||
function lock(notify = true) { window.rustProxyInlineLayout?.cancel?.();
|
||||
function lock(notify = true) {
|
||||
window.rustProxyInlineLayout?.cancel?.();
|
||||
window.rustProxyRefresh?.stop();
|
||||
const destination = pendingAuthDestination || authReturnDestination(), navigating = typeof location !== "undefined" && (location.protocol === "http:" || location.protocol === "https:");
|
||||
pendingAuthDestination = destination; if (notify) adminAuthChannel?.postMessage("locked"); $("api-docs").hidden = true; $("logs-menu").hidden = true; resetAdminPreferences(); window.rustProxyCertificates?.reset(); window.rustProxySettings?.reset(); window.rustProxyTrafficPanel?.reset(); window.rustProxyCachePanel?.reset(); window.rustProxyTransformPanel?.reset(); window.rustProxyAcme?.reset(); window.rustProxyIntegrations?.reset(); window.rustProxyLogs?.reset(); window.rustProxyFileLogs?.reset(); window.rustProxyDrafts?.reset(); $("logs-open").hidden = true; $("file-logs-open").hidden = true; window.rustProxyAclProfiles?.reset(); window.rustProxyVhosts?.reset(); window.rustProxyWebServer?.reset(); window.rustProxyDocker?.reset(); window.rustProxyRedirects?.reset(); window.rustProxyNotFound?.reset(); window.rustProxyStreams?.reset(); window.rustProxyStreamDashboard?.reset(); window.rustProxyBackendDashboard?.reset(); window.rustProxyExtraOverview?.reset(); window.rustProxyWideOverview?.reset(); authEpoch++; stopLive(); csrf = ""; authenticated = false; sessionInfo = null; envelope = null; certificateInventory = new Map(); previous = null; samples = []; protocolSamples = { h1: [], h2: [], h3: [] }; transformSamples = []; systemSamples = []; connectionOpenSamples = []; connectionExchangeSamples = []; networkInterfaceSamples.clear(); lastLogs = null; latestStatus = null; clearSettingsDirty(); connected = false; busy = false; $("workspace").hidden = true; $("login").hidden = navigating; $("logout").hidden = true; $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("password-form").reset(); $("config-editor").value = ""; $("logs").replaceChildren(); $("audit-rows").replaceChildren(); authMessage("");
|
||||
pendingAuthDestination = destination; if (notify) adminAuthChannel?.postMessage("locked"); $("api-docs").hidden = true; $("logs-menu").hidden = true; resetAdminPreferences(); window.rustProxyCertificates?.reset(); window.rustProxySettings?.reset(); window.rustProxyTrafficPanel?.reset(); window.rustProxyCachePanel?.reset(); window.rustProxyTransformPanel?.reset(); window.rustProxyAcme?.reset(); window.rustProxyIntegrations?.reset(); window.rustProxyLogs?.reset(); window.rustProxyFileLogs?.reset(); window.rustProxyDrafts?.reset(); $("logs-open").hidden = true; $("file-logs-open").hidden = true; window.rustProxyAclProfiles?.reset(); window.rustProxyVhosts?.reset(); window.rustProxyWebServer?.reset(); window.rustProxyDocker?.reset(); window.rustProxyRedirects?.reset(); window.rustProxyNotFound?.reset(); window.rustProxyStreams?.reset(); window.rustProxyStreamDashboard?.reset(); window.rustProxyBackendDashboard?.reset(); window.rustProxyExtraOverview?.reset(); window.rustProxyWideOverview?.reset(); window.rustProxyWebOverview?.reset(); authEpoch++; stopLive(); csrf = ""; authenticated = false; sessionInfo = null; envelope = null; certificateInventory = new Map(); previous = null; samples = []; protocolSamples = { h1: [], h2: [], h3: [] }; websocketProtocolSamples = { h1: [], h2: [], h3: [] }; transformSamples = []; systemSamples = []; connectionOpenSamples = []; connectionExchangeSamples = []; networkInterfaceSamples.clear(); lastLogs = null; latestStatus = null; clearSettingsDirty(); connected = false; busy = false; $("workspace").hidden = true; $("login").hidden = navigating; $("logout").hidden = true; $("password").value = ""; $("password-confirm").value = ""; $("setup-token").value = ""; $("password-form").reset(); $("config-editor").value = ""; $("logs").replaceChildren(); $("audit-rows").replaceChildren(); authMessage("");
|
||||
for (const id of ["certificate-list", "host-policy-list"]) { $(id)?.replaceChildren(); }
|
||||
$("workspace").querySelectorAll("form").forEach(form => form.reset());
|
||||
$("workspace").querySelectorAll("input:not([type=checkbox]):not([type=radio]),textarea").forEach(input => { input.value = ""; });
|
||||
document.querySelectorAll("dialog").forEach(dialog => { dialog.close(); dialog.querySelectorAll("input:not([type=checkbox]):not([type=radio]),textarea").forEach(input => input.value = ""); });
|
||||
renderSession(); syncAuthForm();
|
||||
// Replace the authenticated document with the minimal public login document.
|
||||
// Keep the exact panel/settings tab so re-authentication resumes in place.
|
||||
if (navigating) { document.documentElement.classList.add("app-loading"); location.replace(destination); }
|
||||
}
|
||||
|
||||
@@ -392,8 +393,6 @@ async function activate(info) {
|
||||
for (const id of ["password", "password-confirm", "setup-token"]) $(id).value = "";
|
||||
$("login").hidden = true; $("workspace").hidden = false; $("logout").hidden = false;
|
||||
renderSession();
|
||||
// Paint a usable shell immediately. Preferences, configuration and initial
|
||||
// telemetry are network requests and must not block the first content.
|
||||
document.documentElement.classList.remove("app-loading");
|
||||
document.documentElement.classList.remove("app-startup-failed");
|
||||
rustProxyRefresh.start();
|
||||
@@ -415,8 +414,6 @@ async function activate(info) {
|
||||
}
|
||||
}
|
||||
if (!authenticated || epoch !== authEpoch) return false;
|
||||
// A single full snapshot seeds non-live diagnostics; subsequent updates
|
||||
// continue over SSE rather than polling full status on a timer.
|
||||
void refresh(true).catch(error => {
|
||||
if (authenticated && epoch === authEpoch && error?.status !== 401) toast(error.message, true);
|
||||
});
|
||||
@@ -470,8 +467,6 @@ function setMobileMenu(open) {
|
||||
}
|
||||
mobileMenuToggle?.addEventListener("click", event => { event.stopPropagation(); setMobileMenu(!dashboardSidebar.classList.contains("mobile-nav-open")); });
|
||||
document.querySelectorAll(".nav").forEach(button => button.addEventListener("click", () => { setPanel(button.dataset.panel); setMobileMenu(false); }));
|
||||
// The brand is a Home navigation action, not a hard reload of the HTTP/3
|
||||
// document. Preserve href="/" as the no-JS and modified-click fallback.
|
||||
document.querySelector('.brand[href="/"]')?.addEventListener("click", event => {
|
||||
if (!authenticated || event.defaultPrevented || event.button !== 0 ||
|
||||
event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
|
||||
|
||||
@@ -1,25 +1,69 @@
|
||||
// Opt-in compact metrics and bounded visualizations. All readings reuse the
|
||||
// existing Overview live-status payload (no additional requests or timers).
|
||||
window.rustProxyExtraOverview = (() => {
|
||||
const limit = 30;
|
||||
const limit = () => window.rustProxyOverviewSamples?.getLimit?.() ?? 60;
|
||||
const names = [
|
||||
"extra_process_cpu", "extra_process_rss", "extra_process_fds", "extra_open_connections",
|
||||
"extra_websocket_active", "extra_tls_error_rate", "extra_request_error_rate",
|
||||
"extra_cache_hit_rate", "extra_cache_memory", "extra_h2_share",
|
||||
"extra_cache_hit_rate", "extra_cache_memory", "extra_cold_queue_memory", "extra_cold_storage", "extra_h2_share",
|
||||
"extra_response_latency", "extra_backend_unhealthy",
|
||||
"extra_total_rps", "extra_active_exchanges", "extra_h1_share", "extra_h3_share",
|
||||
"extra_cache_miss_rate", "extra_backend_active",
|
||||
];
|
||||
const histories = new Map();
|
||||
const lastCounter = new Map();
|
||||
const lastValue = new Map();
|
||||
const lastUnits = new Map();
|
||||
const bounded = new Set([
|
||||
"extra_process_fds", "extra_cache_memory", "extra_cold_queue_memory", "extra_cold_storage",
|
||||
"extra_h1_share", "extra_h2_share", "extra_h3_share",
|
||||
]);
|
||||
const histories = new Map(), lastCounter = new Map(), lastValue = new Map();
|
||||
const lastUnits = new Map(), lastSegments = new Map();
|
||||
const positive = value => value != null && Number.isFinite(Number(value)) && Number(value) >= 0 ? Number(value) : null;
|
||||
const node = id => document.querySelector(`[data-dashboard-widget="${id}"]`);
|
||||
function meter(id, percent) {
|
||||
const track = $(`${id}-meter`), fill = $(`${id}-meter-fill`);
|
||||
if (!track || !fill) return;
|
||||
const valid = positive(percent), boundedPercent = valid === null ? 0 : Math.min(100, valid);
|
||||
fill.style.width = `${boundedPercent}%`;
|
||||
if (valid === null) {
|
||||
track.removeAttribute("aria-valuenow");
|
||||
track.setAttribute("aria-valuetext", "-");
|
||||
} else {
|
||||
track.setAttribute("aria-valuenow", String(boundedPercent));
|
||||
track.setAttribute("aria-valuetext", `${fmt(valid, 1)}%`);
|
||||
}
|
||||
}
|
||||
function donut(prefix, hits, misses) {
|
||||
const hit = positive(hits), miss = positive(misses);
|
||||
const total = hit !== null && miss !== null ? hit + miss : 0;
|
||||
const share = total > 0 ? hit / total * 100 : 0;
|
||||
const hitArc = $(`${prefix}-hit-arc`), missArc = $(`${prefix}-miss-arc`);
|
||||
if (hitArc) hitArc.setAttribute("stroke-dasharray", `${share} ${100 - share}`);
|
||||
if (missArc) {
|
||||
missArc.setAttribute("stroke-dasharray", `${100 - share} ${share}`);
|
||||
missArc.setAttribute("stroke-dashoffset", String(-share));
|
||||
}
|
||||
const hitCount = $(`${prefix}-hit-count`), missCount = $(`${prefix}-miss-count`);
|
||||
if (hitCount) hitCount.textContent = hit === null ? "-" : fmt(hit);
|
||||
if (missCount) missCount.textContent = miss === null ? "-" : fmt(miss);
|
||||
const graphic = $(`${prefix}-chart`);
|
||||
if (graphic) graphic.setAttribute("aria-label", t("overviewCacheHitsMisses")
|
||||
.replace("{hits}", hit === null ? "-" : fmt(hit))
|
||||
.replace("{misses}", miss === null ? "-" : fmt(miss)));
|
||||
}
|
||||
function elapsedDelta(id, counters, now) {
|
||||
const old = lastCounter.get(id);
|
||||
const result = rustProxyRateSampling.difference(old, counters, now, rustProxyRateSampling.cadence("overview_seconds"));
|
||||
const result = rustProxyRateSampling.difference(lastCounter.get(id), counters, now,
|
||||
rustProxyRateSampling.cadence("overview_seconds"));
|
||||
lastCounter.set(id, result.next);
|
||||
return result.deltas ? result : null;
|
||||
}
|
||||
function ramBudget(bytes, budget) {
|
||||
const used = positive(bytes), maximum = positive(budget);
|
||||
const percent = maximum > 0 && used !== null ? used / maximum * 100 : null;
|
||||
return {
|
||||
value: percent === null ? used : percent,
|
||||
unit: percent === null ? "bytes" : "percent", meterPercent: percent,
|
||||
detail: used === null ? "" : `${fmtBytes(used)} / ${maximum > 0 ? fmtBytes(maximum) : t("overviewMeterNoLimit")}`,
|
||||
};
|
||||
}
|
||||
function read(id, status, now, record) {
|
||||
const m = status.metrics || {}, d = status.system || {}, live = status.live_system || d, c = status.cache || {};
|
||||
const direct = {
|
||||
@@ -32,33 +76,28 @@ window.rustProxyExtraOverview = (() => {
|
||||
extra_active_exchanges: m.active,
|
||||
};
|
||||
if (Object.hasOwn(direct, id)) {
|
||||
const numeric = positive(direct[id]);
|
||||
const numeric = positive(direct[id]), fdLimit = positive(d.max_fds);
|
||||
return {
|
||||
value: numeric, unit: id === "extra_process_cpu" ? "percent" : id === "extra_process_rss" ? "bytes" : id === "extra_response_latency" ? "ms" : "count",
|
||||
detail: id === "extra_process_fds" && positive(d.max_fds) ? `${t("overviewExtraFdLimit")}: ${fmt(d.max_fds)}` : ""
|
||||
value: numeric, unit: id === "extra_process_cpu" ? "percent" : id === "extra_process_rss" ? "bytes"
|
||||
: id === "extra_response_latency" ? "ms" : "count",
|
||||
meterPercent: id === "extra_process_fds" && fdLimit > 0 && numeric !== null ? numeric / fdLimit * 100 : null,
|
||||
detail: id === "extra_process_fds"
|
||||
? (fdLimit > 0 ? `${t("overviewExtraFdLimit")}: ${fmt(fdLimit)}` : t("overviewMeterNoLimit")) : "",
|
||||
};
|
||||
}
|
||||
if (id === "extra_backend_active") {
|
||||
const members = Array.isArray(status.backend_pools) ? status.backend_pools.flatMap(pool => pool.members || []) : null;
|
||||
return {
|
||||
value: members ? members.reduce((sum, member) => sum + (positive(member.active) ?? 0), 0) : null,
|
||||
unit: "count", detail: ""
|
||||
};
|
||||
return { value: members ? members.reduce((sum, member) => sum + (positive(member.active) ?? 0), 0) : null,
|
||||
unit: "count", detail: "" };
|
||||
}
|
||||
if (id === "extra_backend_unhealthy") {
|
||||
return {
|
||||
value: Array.isArray(status.backend_pools) ? status.backend_pools.reduce((sum, pool) =>
|
||||
sum + (pool.members || []).filter(member => member.status === "down").length, 0) : null, unit: "count", detail: ""
|
||||
};
|
||||
}
|
||||
if (id === "extra_cache_memory") {
|
||||
const bytes = positive(c.resident_bytes), budget = positive(c.hot_limit_bytes);
|
||||
const percent = budget && bytes !== null ? bytes / budget * 100 : null;
|
||||
return {
|
||||
value: percent === null ? bytes : percent, unit: percent === null ? "bytes" : "percent",
|
||||
detail: bytes === null ? "" : `${fmtBytes(bytes)}${budget ? ` / ${fmtBytes(budget)}` : ""}`
|
||||
};
|
||||
return { value: Array.isArray(status.backend_pools) ? status.backend_pools.reduce((sum, pool) =>
|
||||
sum + (pool.members || []).filter(member => member.status === "down").length, 0) : null,
|
||||
unit: "count", detail: "" };
|
||||
}
|
||||
if (id === "extra_cache_memory") return ramBudget(c.resident_bytes, c.hot_limit_bytes);
|
||||
if (id === "extra_cold_queue_memory") return ramBudget(c.cold_queue_bytes, c.cold_queue_limit_bytes);
|
||||
if (id === "extra_cold_storage") return ramBudget(c.cold_bytes, c.cold_limit_bytes);
|
||||
if (!["extra_tls_error_rate", "extra_request_error_rate", "extra_cache_hit_rate", "extra_h2_share",
|
||||
"extra_total_rps", "extra_h1_share", "extra_h3_share", "extra_cache_miss_rate"].includes(id))
|
||||
return { value: null, unit: "count", detail: "" };
|
||||
@@ -74,14 +113,18 @@ window.rustProxyExtraOverview = (() => {
|
||||
})[id].map(positive);
|
||||
const unit = ["extra_cache_hit_rate", "extra_h2_share", "extra_h1_share", "extra_h3_share"].includes(id)
|
||||
? "percent" : "rate";
|
||||
if (fields.some(v => v === null)) { lastCounter.delete(id); return { value: null, unit, detail: "" }; }
|
||||
if (!record) return { value: lastValue.get(id) ?? null, unit, detail: "" };
|
||||
if (fields.some(v => v === null)) {
|
||||
lastCounter.delete(id);
|
||||
return { value: null, unit, detail: "", slices: null };
|
||||
}
|
||||
if (!record) return { value: lastValue.get(id) ?? null, unit, detail: "", slices: lastSegments.get(id) || null };
|
||||
const result = elapsedDelta(id, fields, now);
|
||||
if (!result) return { value: null, unit, detail: "" };
|
||||
if (!result) return { value: null, unit, detail: "", slices: null };
|
||||
const { deltas, seconds } = result;
|
||||
if (unit === "rate") return { value: deltas[0] / seconds, unit, detail: "" };
|
||||
const sum = id === "extra_cache_hit_rate" ? deltas[0] + deltas[1] : deltas[1];
|
||||
return { value: sum ? Math.min(100, deltas[0] / sum * 100) : null, unit, detail: "" };
|
||||
return { value: sum > 0 ? Math.min(100, deltas[0] / sum * 100) : null, unit, detail: "",
|
||||
slices: id === "extra_cache_hit_rate" ? [deltas[0], deltas[1]] : null };
|
||||
}
|
||||
function format(value, unit) {
|
||||
if (value == null) return "-";
|
||||
@@ -108,20 +151,35 @@ window.rustProxyExtraOverview = (() => {
|
||||
histories.delete(id);
|
||||
lastUnits.set(id, metric.unit);
|
||||
}
|
||||
const history = histories.get(id) || [];
|
||||
if (record && value !== null) {
|
||||
history.push(value);
|
||||
if (history.length > limit) history.shift();
|
||||
histories.set(id, history);
|
||||
if (!bounded.has(id) && id !== "extra_cache_hit_rate") {
|
||||
const history = histories.get(id) || [];
|
||||
if (record && value !== null) {
|
||||
history.push(value);
|
||||
if (history.length > limit()) history.shift();
|
||||
histories.set(id, history);
|
||||
}
|
||||
$(`overview-extra-${id}-line`)?.setAttribute("points", points(history, metric.unit));
|
||||
}
|
||||
if (record) {
|
||||
lastValue.set(id, value);
|
||||
if (id === "extra_cache_hit_rate") lastSegments.set(id, metric.slices || null);
|
||||
}
|
||||
if (record) lastValue.set(id, value);
|
||||
const field = $(`overview-extra-${id}`);
|
||||
if (field) field.textContent = format(value, metric.unit);
|
||||
if (bounded.has(id)) {
|
||||
meter(`overview-extra-${id}`, metric.meterPercent ?? (metric.unit === "percent" ? value : null));
|
||||
} else if (id === "extra_cache_hit_rate") {
|
||||
const slices = metric.slices || (record ? null : lastSegments.get(id));
|
||||
donut("extra-cache-ratio", slices?.[0], slices?.[1]);
|
||||
}
|
||||
const detail = $(`overview-extra-${id}-detail`);
|
||||
if (detail) detail.textContent = metric.detail || t("overviewExtraRecentSamples");
|
||||
$(`overview-extra-${id}-line`)?.setAttribute("points", points(history, metric.unit));
|
||||
if (detail && id !== "extra_cache_hit_rate") detail.textContent = metric.detail || t("overviewExtraRecentSamples");
|
||||
}
|
||||
}
|
||||
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); }
|
||||
return { render, reset };
|
||||
function reset() { histories.clear(); lastCounter.clear(); lastValue.clear(); lastUnits.clear(); lastSegments.clear(); }
|
||||
window.rustProxyOverviewSamples?.onChange?.(() => {
|
||||
for (const history of histories.values()) window.rustProxyOverviewSamples.trim(history);
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
|
||||
});
|
||||
return { render, reset, meter, donut };
|
||||
})();
|
||||
@@ -0,0 +1,58 @@
|
||||
window.rustProxyOverviewSamples = (() => {
|
||||
const allowed = new Set([60, 120, 300, 600]);
|
||||
const storageKey = "rustproxy.overview.liveChartSamples";
|
||||
let saved = 60;
|
||||
try {
|
||||
const stored = Number(localStorage.getItem(storageKey));
|
||||
if (allowed.has(stored)) saved = stored;
|
||||
} catch { /* Private browsing or storage policy: temporary selection still works. */ }
|
||||
let limit = saved;
|
||||
const callbacks = new Set();
|
||||
const getLimit = () => limit;
|
||||
const trim = array => {
|
||||
if (Array.isArray(array) && array.length > limit) array.splice(0, array.length - limit);
|
||||
return array;
|
||||
};
|
||||
function sync(messageKey = limit === saved ? "overviewSamplesSaved" : "overviewSamplesTemporary") {
|
||||
const select = document.getElementById("overview-live-samples");
|
||||
if (select) select.value = String(limit);
|
||||
const state = document.getElementById("overview-samples-state");
|
||||
if (state) {
|
||||
state.dataset.i18n = messageKey;
|
||||
state.textContent = t(messageKey);
|
||||
}
|
||||
const save = document.getElementById("overview-samples-save");
|
||||
if (save) save.disabled = limit === saved;
|
||||
}
|
||||
function setLimit(value, persist = false) {
|
||||
const count = Number(value);
|
||||
if (!allowed.has(count)) return false;
|
||||
if (persist) {
|
||||
try {
|
||||
localStorage.setItem(storageKey, String(count));
|
||||
saved = count;
|
||||
} catch {
|
||||
const changed = count !== limit;
|
||||
limit = count;
|
||||
if (changed) for (const callback of callbacks) callback(limit);
|
||||
sync("overviewSamplesStorageUnavailable");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const changed = count !== limit;
|
||||
limit = count;
|
||||
if (changed) for (const callback of callbacks) callback(limit);
|
||||
sync();
|
||||
return true;
|
||||
}
|
||||
function init() {
|
||||
const select = document.getElementById("overview-live-samples");
|
||||
const save = document.getElementById("overview-samples-save");
|
||||
select?.addEventListener("change", () => setLimit(select.value));
|
||||
save?.addEventListener("click", () => setLimit(select.value, true));
|
||||
sync();
|
||||
}
|
||||
if (document.readyState !== "complete") document.addEventListener("DOMContentLoaded", init, { once: true });
|
||||
else queueMicrotask(init);
|
||||
return { getLimit, trim, setLimit, onChange: callback => callbacks.add(callback) };
|
||||
})();
|
||||
@@ -1,5 +1,5 @@
|
||||
window.rustProxyWideOverview = (() => {
|
||||
const limit = 60;
|
||||
const limit = () => window.rustProxyOverviewSamples?.getLimit?.() ?? 60;
|
||||
const configurations = {
|
||||
wide_protocol_rates: {
|
||||
unit: "rate", names: ["h1", "h2", "h3"], counters: s =>
|
||||
@@ -39,6 +39,16 @@ window.rustProxyWideOverview = (() => {
|
||||
probes.length ? probes.reduce((total, m) => total + Number(m.latency_ms), 0) / probes.length : null];
|
||||
}
|
||||
},
|
||||
wide_transform_activity: {
|
||||
unit: "rate", names: ["applied", "cache_hits", "skipped", "errors"], total: true,
|
||||
counters: s => [s.transform_metrics?.applied, s.transform_metrics?.cache_hits,
|
||||
s.transform_metrics?.skipped, s.transform_metrics?.errors]
|
||||
},
|
||||
wide_compression_activity: {
|
||||
unit: "rate", names: ["gzip", "br", "zstd", "deflate"], total: true,
|
||||
counters: s => [s.metrics?.compression_gzip, s.metrics?.compression_br,
|
||||
s.metrics?.compression_zstd, s.metrics?.compression_deflate]
|
||||
},
|
||||
};
|
||||
const history = new Map(), baselines = new Map(), last = new Map();
|
||||
let revision = null;
|
||||
@@ -81,7 +91,7 @@ window.rustProxyWideOverview = (() => {
|
||||
if (record) {
|
||||
values.forEach((value, i) => {
|
||||
series[i].push(value);
|
||||
if (series[i].length > limit) series[i].shift();
|
||||
if (series[i].length > limit()) series[i].shift();
|
||||
});
|
||||
history.set(id, series);
|
||||
last.set(id, values);
|
||||
@@ -95,9 +105,15 @@ window.rustProxyWideOverview = (() => {
|
||||
$(`overview-wide-${id}-${key}-line`)?.setAttribute("d", path(series[i], peak));
|
||||
});
|
||||
const summary = $(`overview-wide-${id}-summary`);
|
||||
if (summary) summary.textContent = format(values[0], config.unit);
|
||||
if (summary) summary.textContent = config.total
|
||||
? format(values.every(value => value !== null) ? values.reduce((sum, value) => sum + value, 0) : null, config.unit)
|
||||
: format(values[0], config.unit);
|
||||
}
|
||||
}
|
||||
function reset() { history.clear(); baselines.clear(); last.clear(); revision = null; }
|
||||
window.rustProxyOverviewSamples?.onChange?.(() => {
|
||||
for (const series of history.values()) for (const values of series) window.rustProxyOverviewSamples.trim(values);
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") render(latestStatus, false);
|
||||
});
|
||||
return { render, reset };
|
||||
})();
|
||||
@@ -103,7 +103,7 @@ function applyDashboardLayout(order, hidden) {
|
||||
window.rustProxyStreamDashboard?.syncVisibility?.([...hiddenSet]);
|
||||
window.rustProxyBackendDashboard?.syncVisibility?.([...hiddenSet]);
|
||||
window.rustProxyMasonry?.refresh?.();
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") { window.rustProxyStreamDashboard?.render?.(latestStatus, [...hiddenSet], false); window.rustProxyBackendDashboard?.render?.(latestStatus, [...hiddenSet], false); window.rustProxyExtraOverview?.render?.(latestStatus, false); window.rustProxyWideOverview?.render?.(latestStatus, false); }
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") { window.rustProxyStreamDashboard?.render?.(latestStatus, [...hiddenSet], false); window.rustProxyBackendDashboard?.render?.(latestStatus, [...hiddenSet], false); window.rustProxyExtraOverview?.render?.(latestStatus, false); window.rustProxyWideOverview?.render?.(latestStatus, false); window.rustProxyWebOverview?.render?.(latestStatus, false); }
|
||||
return { order: normalizedOrder, hidden: [...hiddenSet] };
|
||||
}
|
||||
function dashboardWidgetLabel(id) {
|
||||
|
||||
@@ -222,8 +222,8 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
|
||||
if (!item?.name) continue;
|
||||
const history = networkInterfaceSamples.get(item.name) || { rx: [], tx: [] };
|
||||
const rx = Number(item.rx_bytes_per_second), tx = Number(item.tx_bytes_per_second);
|
||||
if (Number.isFinite(rx)) { history.rx.push(rx); history.rx = history.rx.slice(-60); }
|
||||
if (Number.isFinite(tx)) { history.tx.push(tx); history.tx = history.tx.slice(-60); }
|
||||
if (Number.isFinite(rx)) { history.rx.push(rx); history.rx = history.rx.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (Number.isFinite(tx)) { history.tx.push(tx); history.tx = history.tx.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
networkInterfaceSamples.set(item.name, history);
|
||||
}
|
||||
}
|
||||
@@ -306,8 +306,10 @@ function renderNetworkInterfaceCharts(interfaces, appendSample) {
|
||||
function renderDiagnostics(status, appendSample = true, systemSample = appendSample) {
|
||||
const d = status.system || {}, live = status.live_system || d, c = status.cache || {}, m = status.metrics || {};
|
||||
$("metric-h3").textContent = fmt(m.http3_requests);
|
||||
$("metric-cache-ratio").textContent = c.hit_ratio == null ? "-" : `${fmt(c.hit_ratio * 100, 1)}%`;
|
||||
$("cache-summary").textContent = `${fmt(c.hits)} HIT / ${fmt(c.misses)} MISS`;
|
||||
const cacheHits = c.hits == null ? null : Number(c.hits), cacheMisses = c.misses == null ? null : Number(c.misses);
|
||||
const cacheEvents = cacheHits !== null && cacheMisses !== null ? cacheHits + cacheMisses : 0;
|
||||
$("metric-cache-ratio").textContent = cacheEvents > 0 ? `${fmt(cacheHits / cacheEvents * 100, 1)}%` : "-";
|
||||
window.rustProxyExtraOverview?.donut?.("cache-ratio", cacheHits, cacheMisses);
|
||||
if (systemSample) {
|
||||
if (d.process_rss_bytes != null) $("metric-rss").textContent = fmtBytes(d.process_rss_bytes);
|
||||
$("process-summary").hidden = d.process_threads == null;
|
||||
@@ -325,7 +327,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
setOptionalMetric("system-load", loadSupported, loadSupported ? d.load_average.map(n => fmt(n, 2)).join(" / ") : "");
|
||||
setOptionalMetric("system-uptime", d.system_uptime_seconds != null, d.system_uptime_seconds == null ? "" : uptime(d.system_uptime_seconds));
|
||||
}
|
||||
$("system-chart").hidden = live.cpu_percent == null; if (appendSample && live.cpu_percent != null) { systemSamples.push(Number(live.cpu_percent)); systemSamples = systemSamples.slice(-30); }
|
||||
$("system-chart").hidden = live.cpu_percent == null; if (appendSample && live.cpu_percent != null) { systemSamples.push(Number(live.cpu_percent)); systemSamples = systemSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
$("system-sparkline").setAttribute("points", systemSamples.map((n, i) => `${i * 300 / Math.max(1, systemSamples.length - 1)},${95 - Math.max(0, Math.min(100, n)) * .9}`).join(" "));
|
||||
$("cache-state").textContent = t(envelope?.config.cache?.enabled ? "enabled" : "notEnabled");
|
||||
$("cache-counters").textContent = [c.hits, c.misses, c.bypasses].map(n => fmt(n)).join(" / ");
|
||||
@@ -339,28 +341,28 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
: t("notEnabled");
|
||||
$("cache-evictions").textContent = `${fmt(c.evictions)} / ${fmt(c.expired)}`;
|
||||
if (appendSample) {
|
||||
if (c.resident_bytes != null) { cacheMemorySamples.push(Number(c.resident_bytes)); cacheMemorySamples = cacheMemorySamples.slice(-60); }
|
||||
if (c.transient_bytes != null) { cacheTransientSamples.push(Number(c.transient_bytes)); cacheTransientSamples = cacheTransientSamples.slice(-60); }
|
||||
if (c.fill_bytes_active != null) { cacheFillSamples.push(Number(c.fill_bytes_active)); cacheFillSamples = cacheFillSamples.slice(-60); }
|
||||
if (c.cold_queue_bytes != null) { cacheColdQueueSamples.push(Number(c.cold_queue_bytes)); cacheColdQueueSamples = cacheColdQueueSamples.slice(-60); }
|
||||
if (c.resident_bytes != null) { cacheMemorySamples.push(Number(c.resident_bytes)); cacheMemorySamples = cacheMemorySamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (c.transient_bytes != null) { cacheTransientSamples.push(Number(c.transient_bytes)); cacheTransientSamples = cacheTransientSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (c.fill_bytes_active != null) { cacheFillSamples.push(Number(c.fill_bytes_active)); cacheFillSamples = cacheFillSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (c.cold_queue_bytes != null) { cacheColdQueueSamples.push(Number(c.cold_queue_bytes)); cacheColdQueueSamples = cacheColdQueueSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
const coldIoBytes = (Number(c.cold_write_bytes_active) || 0) + (Number(c.cold_read_bytes_active) || 0);
|
||||
if (c.cold_write_bytes_active != null || c.cold_read_bytes_active != null) { cacheColdIoSamples.push(coldIoBytes); cacheColdIoSamples = cacheColdIoSamples.slice(-60); }
|
||||
if (c.cold_bytes != null) { cacheColdStorageSamples.push(Number(c.cold_bytes)); cacheColdStorageSamples = cacheColdStorageSamples.slice(-60); }
|
||||
if (c.cold_write_bytes_active != null || c.cold_read_bytes_active != null) { cacheColdIoSamples.push(coldIoBytes); cacheColdIoSamples = cacheColdIoSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (c.cold_bytes != null) { cacheColdStorageSamples.push(Number(c.cold_bytes)); cacheColdStorageSamples = cacheColdStorageSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
const currentCounters = [c.hits, c.misses, c.bypasses].map(Number);
|
||||
if (currentCounters.every(Number.isFinite)) {
|
||||
const delta = rustProxyRateSampling.difference(lastCacheCounters, currentCounters, performance.now(), rustProxyRateSampling.cadence("overview_seconds"));
|
||||
const values = delta.deltas || lastCacheOutcomeRates || (!lastCacheCounters ? [0, 0, 0] : null);
|
||||
if (values) {
|
||||
cacheHitSamples.push(values[0]); cacheMissSamples.push(values[1]); cacheBypassSamples.push(values[2]);
|
||||
cacheHitSamples = cacheHitSamples.slice(-60); cacheMissSamples = cacheMissSamples.slice(-60); cacheBypassSamples = cacheBypassSamples.slice(-60);
|
||||
cacheHitSamples = cacheHitSamples.slice(-window.rustProxyOverviewSamples.getLimit()); cacheMissSamples = cacheMissSamples.slice(-window.rustProxyOverviewSamples.getLimit()); cacheBypassSamples = cacheBypassSamples.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
lastCacheOutcomeRates = [...values];
|
||||
}
|
||||
lastCacheCounters = delta.next;
|
||||
}
|
||||
}
|
||||
if (appendSample) {
|
||||
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-60); }
|
||||
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-60); }
|
||||
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
}
|
||||
const cacheOutcomePeak = Math.max(1, ...cacheHitSamples, ...cacheMissSamples, ...cacheBypassSamples);
|
||||
$("cache-hit-sparkline").setAttribute("points", sparklinePoints(cacheHitSamples, 300, 60, 5, cacheOutcomePeak));
|
||||
@@ -427,16 +429,15 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
|
||||
if (appendSample) {
|
||||
if (networkRateSupported) {
|
||||
networkRxSamples.push(Number(live.network_rx_bytes_per_second)); networkTxSamples.push(Number(live.network_tx_bytes_per_second));
|
||||
networkRxSamples = networkRxSamples.slice(-60); networkTxSamples = networkTxSamples.slice(-60);
|
||||
networkRxSamples = networkRxSamples.slice(-window.rustProxyOverviewSamples.getLimit()); networkTxSamples = networkTxSamples.slice(-window.rustProxyOverviewSamples.getLimit());
|
||||
}
|
||||
if (m.mean_duration_ms != null) { responseLatencySamples.push(Number(m.mean_duration_ms)); responseLatencySamples = responseLatencySamples.slice(-60); }
|
||||
if (m.mean_duration_ms != null) { responseLatencySamples.push(Number(m.mean_duration_ms)); responseLatencySamples = responseLatencySamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
|
||||
}
|
||||
const networkPeak = Math.max(1, ...networkRxSamples, ...networkTxSamples);
|
||||
$("network-rx-sparkline").setAttribute("points", sparklinePoints(networkRxSamples, 300, 60, 5, networkPeak));
|
||||
$("network-tx-sparkline").setAttribute("points", sparklinePoints(networkTxSamples, 300, 60, 5, networkPeak));
|
||||
setChartText($("network-chart-value"), networkRateSupported ? `${fmtBytes(live.network_rx_bytes_per_second)} / ${fmtBytes(live.network_tx_bytes_per_second)}` : "");
|
||||
$("network-rx-sparkline").closest(".mini-live-chart").hidden = !networkRateSupported;
|
||||
// Cache-sampled OS chart rates are independent from slow System details.
|
||||
renderNetworkInterfaceCharts(live.network_interfaces || [], appendSample);
|
||||
$("latency-sparkline").setAttribute("points", sparklinePoints(responseLatencySamples));
|
||||
setChartText($("latency-chart-value"), m.mean_duration_ms == null ? "" : `${fmt(m.mean_duration_ms, 2)} ms`);
|
||||
@@ -493,3 +494,19 @@ function collectHeaderRules(direction) {
|
||||
return op === "remove" ? { op, name } : { op, name, value };
|
||||
});
|
||||
}
|
||||
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const history of [systemSamples, cacheMemorySamples, cacheTransientSamples,
|
||||
cacheFillSamples, cacheColdQueueSamples, cacheColdIoSamples, cacheColdStorageSamples,
|
||||
cacheHitSamples, cacheMissSamples, cacheBypassSamples, processCpuSamples,
|
||||
processRssSamples, networkRxSamples, networkTxSamples, responseLatencySamples]) {
|
||||
window.rustProxyOverviewSamples.trim(history);
|
||||
}
|
||||
for (const history of networkInterfaceSamples.values()) {
|
||||
window.rustProxyOverviewSamples.trim(history.rx);
|
||||
window.rustProxyOverviewSamples.trim(history.tx);
|
||||
}
|
||||
if (typeof latestStatus !== "undefined" && latestStatus && currentPanel === "overview") {
|
||||
renderDiagnostics(latestStatus, false, false);
|
||||
}
|
||||
});
|
||||
@@ -95,7 +95,7 @@ window.rustProxyStreamDashboard = (() => {
|
||||
const tx = sample.deltas ? sample.deltas[1] / sample.seconds : 0;
|
||||
if (!pausedRate) {
|
||||
history.rx.push(rx); history.tx.push(tx);
|
||||
if (history.rx.length > 60) { history.rx.shift(); history.tx.shift(); }
|
||||
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`);
|
||||
@@ -103,5 +103,10 @@ window.rustProxyStreamDashboard = (() => {
|
||||
$("stream-dashboard-tx")?.setAttribute("points", line(history.tx));
|
||||
}
|
||||
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 };
|
||||
})();
|
||||
@@ -0,0 +1,225 @@
|
||||
window.rustProxyWebOverview = (() => {
|
||||
const valid = n => n != null && Number.isFinite(Number(n)) && Number(n) >= 0 ? Number(n) : null;
|
||||
const show = (id, text) => { const el = $(id); if (el) el.textContent = text; };
|
||||
const fmtRate = n => n == null ? "-" : `${fmt(n, 1)}/s`;
|
||||
const tile = id => document.querySelector(`[data-dashboard-widget="${id}"]`);
|
||||
const seriesNames = {
|
||||
www_requests: ["all", "php"], www_latency: ["web", "php"],
|
||||
www_failures: ["timeout", "connection", "protocol", "capacity"],
|
||||
www_traffic: ["nonphp", "php"],
|
||||
www_status_trend: ["2xx", "3xx", "4xx", "5xx"]
|
||||
};
|
||||
const histories = new Map(), serviceBaselines = new Map();
|
||||
let baseline = null, revision = null, last = null;
|
||||
function aggregate(s) {
|
||||
const services = s?.web_server_metrics;
|
||||
if (!services || typeof services !== "object") return null;
|
||||
const web = {
|
||||
requests: 0, response_headers_us: 0, response_bytes: 0,
|
||||
status: [0, 0, 0, 0], kinds: [0, 0, 0], methods: [0, 0, 0, 0, 0],
|
||||
selected: Array(8).fill(0), active: 0
|
||||
};
|
||||
const php = {
|
||||
requests: 0, response_headers_us: 0, response_bytes: 0,
|
||||
failures: Array(9).fill(0), capacity_limit: 0, capacity_used: 0, count: 0
|
||||
};
|
||||
const perService = {};
|
||||
for (const [id, entry] of Object.entries(services)) {
|
||||
const w = entry?.web;
|
||||
if (!w || valid(w.requests) === null) continue;
|
||||
for (const key of ["requests", "response_headers_us", "response_bytes"])
|
||||
web[key] += valid(w[key]) || 0;
|
||||
for (const [i, key] of ["status_2xx", "status_3xx", "status_4xx", "status_5xx"].entries())
|
||||
web.status[i] += valid(w[key]) || 0;
|
||||
for (const [i, key] of ["static_requests", "php_requests", "other_requests"].entries())
|
||||
web.kinds[i] += valid(w[key]) || 0;
|
||||
["methods", "selected_status"].forEach((field, index) => {
|
||||
const output = index ? web.selected : web.methods;
|
||||
output.forEach((_, i) => { output[i] += valid(w[field]?.[i]) || 0; });
|
||||
});
|
||||
web.active += valid(w.active_headers) || 0;
|
||||
perService[id] = valid(w.requests);
|
||||
if (entry.php?.metrics) {
|
||||
php.count++;
|
||||
const pm = entry.php.metrics;
|
||||
for (const key of ["requests", "response_headers_us", "response_bytes"])
|
||||
php[key] += valid(pm[key]) || 0;
|
||||
php.failures.forEach((_, i) => { php.failures[i] += valid(pm.failures?.[i]) || 0; });
|
||||
php.capacity_limit += valid(entry.php.capacity_limit) || 0;
|
||||
php.capacity_used += valid(entry.php.capacity_used) || 0;
|
||||
}
|
||||
}
|
||||
return Object.keys(perService).length ? { web, php, perService } : null;
|
||||
}
|
||||
function compact(a) {
|
||||
return [a.web.requests, ...a.web.status, ...a.web.kinds, ...a.web.methods,
|
||||
a.web.response_headers_us, a.web.response_bytes, a.php.requests,
|
||||
a.php.response_headers_us, a.php.response_bytes, ...a.php.failures];
|
||||
}
|
||||
function rates(a, record) {
|
||||
if (!record) return last;
|
||||
const sample = rustProxyRateSampling.difference(baseline, compact(a), performance.now(),
|
||||
rustProxyRateSampling.cadence("overview_seconds"));
|
||||
baseline = sample.next;
|
||||
if (!sample.deltas) { last = null; return null; }
|
||||
const d = sample.deltas, seconds = sample.seconds;
|
||||
const perSec = value => value / seconds;
|
||||
last = {
|
||||
rps: [perSec(d[0]), perSec(d[15])],
|
||||
status: d.slice(1, 5).map(perSec),
|
||||
latency: [d[0] > 0 ? d[13] / d[0] / 1000 : null,
|
||||
d[15] > 0 ? d[16] / d[15] / 1000 : null],
|
||||
traffic: [perSec(Math.max(0, d[14] - d[17])), perSec(d[17])],
|
||||
failures: [perSec(d[19] + d[21] + d[23]),
|
||||
perSec(d[20] + d[22] + d[24]), perSec(d[25] + d[26]), perSec(d[18])],
|
||||
errorRate: d[0] > 0 ? d[4] / d[0] * 100 : null,
|
||||
phpRps: perSec(d[15]),
|
||||
};
|
||||
return last;
|
||||
}
|
||||
function chartPath(values, peak) {
|
||||
let output = "", painting = false;
|
||||
values.forEach((value, i) => {
|
||||
if (value == null) { painting = false; return; }
|
||||
output += `${painting ? " L" : "M"}${Math.round(i * 600 / Math.max(1, values.length - 1))} ${Math.round(137 - 120 * value / peak)}`;
|
||||
painting = true;
|
||||
});
|
||||
return output;
|
||||
}
|
||||
function smallPath(values) {
|
||||
const peak = Math.max(1, ...values.filter(v => v != null));
|
||||
return values.map((value, i) => `${i * 300 / Math.max(1, values.length - 1)},${55 - (value || 0) / peak * 50}`).join(" ");
|
||||
}
|
||||
function ring(id, amounts) {
|
||||
const total = amounts.reduce((n, value) => n + value, 0);
|
||||
let offset = 0;
|
||||
amounts.forEach((amount, i) => {
|
||||
const share = total ? amount / total * 100 : 0;
|
||||
const arc = $(`${id}-arc-${i}`);
|
||||
arc?.setAttribute("stroke-dasharray", `${share} ${100 - share}`);
|
||||
arc?.setAttribute("stroke-dashoffset", String(-offset));
|
||||
offset += share;
|
||||
show(`${id}-count-${i}`, fmt(amount));
|
||||
});
|
||||
show(`${id}-value`, total ? `${fmt(amounts[0] / total * 100, 1)}%` : "-");
|
||||
$(`${id}-chart`)?.setAttribute("aria-label", `${t(id === "www_status" ? "webMetricStatus" : "webMetricKind")}: ${amounts.join(" / ")}`);
|
||||
}
|
||||
function meter(id, percent) {
|
||||
window.rustProxyExtraOverview?.meter?.(id, percent);
|
||||
}
|
||||
function clearUnavailable() {
|
||||
for (const id of ["www_requests", "www_latency", "www_failures", "www_traffic",
|
||||
"www_status_trend", "www_php_requests", "www_capacity", "www_error_rate",
|
||||
"www_methods", "www_top_services"]) show(`${id}-value`, "-");
|
||||
for (const [id, names] of Object.entries(seriesNames)) {
|
||||
for (const name of names) {
|
||||
show(`${id}-${name}-value`, "-");
|
||||
$(`${id}-${name}-line`)?.setAttribute("d", "");
|
||||
}
|
||||
}
|
||||
$("www_php_requests-line")?.setAttribute("points", "");
|
||||
$("www_top_services-bars")?.replaceChildren();
|
||||
show("www_methods-detail", "-");
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const segment = $(`www_methods-segment-${i}`);
|
||||
if (segment) { segment.style.width = "0%"; segment.title = ""; }
|
||||
}
|
||||
show("www_capacity-detail", t("webLiveNoPhp"));
|
||||
show("www_error_rate-detail", t("webMetricErrorRateHelp"));
|
||||
meter("www_capacity", null);
|
||||
meter("www_error_rate", null);
|
||||
ring("www_status", [0, 0, 0, 0]);
|
||||
ring("www_kind", [0, 0, 0]);
|
||||
}
|
||||
function render(s, record = true) {
|
||||
const a = aggregate(s);
|
||||
if (revision !== s?.revision) { reset(); revision = s?.revision; }
|
||||
if (!a) { reset(); revision = s?.revision; clearUnavailable(); return; }
|
||||
if (currentPanel !== "overview") { baseline = null; return; }
|
||||
const r = rates(a, record);
|
||||
ring("www_status", a.web.status);
|
||||
ring("www_kind", a.web.kinds);
|
||||
const totalMethods = a.web.methods.reduce((n, v) => n + v, 0);
|
||||
show("www_methods-value", totalMethods ? fmt(totalMethods) : "-");
|
||||
a.web.methods.forEach((value, i) => {
|
||||
const segment = $(`www_methods-segment-${i}`);
|
||||
if (segment) { segment.style.width = `${totalMethods ? value / totalMethods * 100 : 0}%`; segment.title = `${["GET", "POST", "PUT", "DELETE", "Other"][i]}: ${fmt(value)}`; }
|
||||
});
|
||||
show("www_methods-detail", ["GET", "POST", "PUT", "DELETE", "Other"].map((name, i) => `${name} ${fmt(a.web.methods[i])}`).join(" · "));
|
||||
const { capacity_limit: max, capacity_used: used } = a.php;
|
||||
show("www_capacity-value", max ? `${fmt(used)} / ${fmt(max)}` : "-");
|
||||
meter("www_capacity", max ? used / max * 100 : null);
|
||||
show("www_capacity-detail", max ? `${fmt(used / max * 100, 1)}%` : t("webLiveNoPhp"));
|
||||
show("www_error_rate-value", r?.errorRate == null ? "-" : `${fmt(r.errorRate, 1)}%`);
|
||||
meter("www_error_rate", r?.errorRate);
|
||||
show("www_error_rate-detail", t("webMetricErrorRateHelp"));
|
||||
show("www_php_requests-value", fmtRate(r?.phpRps));
|
||||
if (record && !tile("www_php_requests")?.hidden) {
|
||||
const values = histories.get("www_php_requests") || [];
|
||||
values.push(r?.phpRps ?? null); window.rustProxyOverviewSamples.trim(values);
|
||||
histories.set("www_php_requests", values);
|
||||
}
|
||||
$(`www_php_requests-line`)?.setAttribute("points", smallPath(histories.get("www_php_requests") || []));
|
||||
const set = {
|
||||
www_requests: r?.rps, www_latency: r?.latency, www_failures: r?.failures,
|
||||
www_traffic: r?.traffic, www_status_trend: r?.status,
|
||||
};
|
||||
for (const [id, names] of Object.entries(seriesNames)) {
|
||||
const series = histories.get(id) || names.map(() => []);
|
||||
if (record && !tile(id)?.hidden) {
|
||||
names.forEach((_, i) => { series[i].push(set[id]?.[i] ?? null); window.rustProxyOverviewSamples.trim(series[i]); });
|
||||
histories.set(id, series);
|
||||
}
|
||||
const peak = Math.max(1, ...series.flat().filter(v => v !== null));
|
||||
names.forEach((name, i) => {
|
||||
const value = set[id]?.[i];
|
||||
const label = id === "www_latency" ? (value == null ? "-" : `${fmt(value, 1)} ms`)
|
||||
: id === "www_traffic" ? (value == null ? "-" : `${fmtBytes(value)}/s`) : fmtRate(value);
|
||||
show(`${id}-${name}-value`, label);
|
||||
$(`${id}-${name}-line`)?.setAttribute("d", chartPath(series[i], peak));
|
||||
});
|
||||
const summary = id === "www_latency" ? (set[id]?.[0] == null ? "-" : `${fmt(set[id][0], 1)} ms`)
|
||||
: id === "www_traffic" ? (set[id] ? `${fmtBytes(set[id][0] + set[id][1])}/s` : "-")
|
||||
: fmtRate(set[id]?.[0]);
|
||||
show(`${id}-value`, summary);
|
||||
}
|
||||
if (record) {
|
||||
const currentIds = new Set(Object.keys(a.perService));
|
||||
for (const id of serviceBaselines.keys()) if (!currentIds.has(id)) serviceBaselines.delete(id);
|
||||
const rows = Object.entries(a.perService).map(([id, total]) => {
|
||||
const old = serviceBaselines.get(id);
|
||||
const delta = rustProxyRateSampling.difference(old, [total], performance.now(),
|
||||
rustProxyRateSampling.cadence("overview_seconds"));
|
||||
serviceBaselines.set(id, delta.next);
|
||||
return { id, value: delta.deltas ? delta.deltas[0] / delta.seconds : null };
|
||||
});
|
||||
if (rows.some(item => item.value !== null)) {
|
||||
rows.sort((x, y) => (y.value ?? -1) - (x.value ?? -1));
|
||||
const top = rows.filter(item => item.value !== null).slice(0, 6);
|
||||
const container = $("www_top_services-bars");
|
||||
if (container && !tile("www_top_services")?.hidden) {
|
||||
const peak = Math.max(1, ...top.map(item => item.value));
|
||||
container.replaceChildren(...top.map(item => {
|
||||
const row = document.createElement("div"); row.className = "www-top-row";
|
||||
const name = document.createElement("span"); name.textContent = item.id;
|
||||
const meter = document.createElement("div"); meter.className = "www-top-meter";
|
||||
const fill = document.createElement("span"); fill.style.width = `${item.value / peak * 100}%`;
|
||||
meter.append(fill);
|
||||
const value = document.createElement("b"); value.textContent = fmtRate(item.value);
|
||||
row.append(name, meter, value); return row;
|
||||
}));
|
||||
}
|
||||
show("www_top_services-value", fmtRate(top.reduce((n, item) => n + item.value, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
function reset() { histories.clear(); serviceBaselines.clear(); baseline = null; last = null; revision = null; }
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const history of histories.values()) {
|
||||
if (Array.isArray(history[0])) history.forEach(window.rustProxyOverviewSamples.trim);
|
||||
else window.rustProxyOverviewSamples.trim(history);
|
||||
}
|
||||
if (latestStatus && currentPanel === "overview") render(latestStatus, false);
|
||||
});
|
||||
return { render, reset, aggregate };
|
||||
})();
|
||||
@@ -425,7 +425,29 @@
|
||||
"data_source": "status_metrics",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "performance",
|
||||
"default_hidden": true,
|
||||
"default_hidden": false,
|
||||
"display_type": "value_chart"
|
||||
},
|
||||
{
|
||||
"id": "extra_cold_queue_memory",
|
||||
"label_key": "overviewExtraColdQueueMemory",
|
||||
"description_key": "overviewExtraColdQueueMemoryDescription",
|
||||
"element_selector": "[data-dashboard-widget=\"extra_cold_queue_memory\"]",
|
||||
"data_source": "cache_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "performance",
|
||||
"default_hidden": false,
|
||||
"display_type": "value_chart"
|
||||
},
|
||||
{
|
||||
"id": "extra_cold_storage",
|
||||
"label_key": "overviewExtraColdStorage",
|
||||
"description_key": "overviewExtraColdStorageDescription",
|
||||
"element_selector": "[data-dashboard-widget=\"extra_cold_storage\"]",
|
||||
"data_source": "cache_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "performance",
|
||||
"default_hidden": false,
|
||||
"display_type": "value_chart"
|
||||
},
|
||||
{
|
||||
@@ -604,6 +626,28 @@
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "wide_transform_activity",
|
||||
"label_key": "overviewWideTransformActivity",
|
||||
"description_key": "overviewWideTransformActivityDescription",
|
||||
"element_selector": "[data-dashboard-widget=\"wide_transform_activity\"]",
|
||||
"data_source": "status_metrics",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "panels",
|
||||
"default_hidden": false,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "wide_compression_activity",
|
||||
"label_key": "overviewWideCompressionActivity",
|
||||
"description_key": "overviewWideCompressionActivityDescription",
|
||||
"element_selector": "[data-dashboard-widget=\"wide_compression_activity\"]",
|
||||
"data_source": "status_metrics",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "panels",
|
||||
"default_hidden": false,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "traffic_chart",
|
||||
"label_key": "requestsPerSecond",
|
||||
@@ -680,5 +724,137 @@
|
||||
"category": "panels",
|
||||
"default_hidden": false,
|
||||
"display_type": "table"
|
||||
},
|
||||
{
|
||||
"id": "www_status",
|
||||
"label_key": "webMetricStatus",
|
||||
"description_key": "webMetricStatusHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_status\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value"
|
||||
},
|
||||
{
|
||||
"id": "www_kind",
|
||||
"label_key": "webMetricKind",
|
||||
"description_key": "webMetricKindHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_kind\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value"
|
||||
},
|
||||
{
|
||||
"id": "www_capacity",
|
||||
"label_key": "webMetricCapacity",
|
||||
"description_key": "webMetricCapacityHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_capacity\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value"
|
||||
},
|
||||
{
|
||||
"id": "www_methods",
|
||||
"label_key": "webMetricMethods",
|
||||
"description_key": "webMetricMethodsHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_methods\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value"
|
||||
},
|
||||
{
|
||||
"id": "www_error_rate",
|
||||
"label_key": "webMetricErrorRate",
|
||||
"description_key": "webMetricErrorRateHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_error_rate\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value"
|
||||
},
|
||||
{
|
||||
"id": "www_php_requests",
|
||||
"label_key": "webMetricPhpRequests",
|
||||
"description_key": "webMetricPhpRequestsHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_php_requests\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "value_chart"
|
||||
},
|
||||
{
|
||||
"id": "www_requests",
|
||||
"label_key": "webMetricRequests",
|
||||
"description_key": "webMetricRequestsHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_requests\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "www_latency",
|
||||
"label_key": "webMetricLatency",
|
||||
"description_key": "webMetricLatencyHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_latency\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "www_failures",
|
||||
"label_key": "webMetricFailures",
|
||||
"description_key": "webMetricFailuresHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_failures\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "www_traffic",
|
||||
"label_key": "webMetricTraffic",
|
||||
"description_key": "webMetricTrafficHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_traffic\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "www_top_services",
|
||||
"label_key": "webMetricTopServices",
|
||||
"description_key": "webMetricTopServicesHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_top_services\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
},
|
||||
{
|
||||
"id": "www_status_trend",
|
||||
"label_key": "webMetricStatusTrend",
|
||||
"description_key": "webMetricStatusTrendHelp",
|
||||
"element_selector": "[data-dashboard-widget=\"www_status_trend\"]",
|
||||
"data_source": "web_server_status",
|
||||
"no_data": "neutral_dash",
|
||||
"category": "web_server",
|
||||
"default_hidden": true,
|
||||
"display_type": "chart"
|
||||
}
|
||||
]
|
||||
]
|
||||
+406
-49
@@ -113,8 +113,27 @@
|
||||
<div hidden="" id="workspace">
|
||||
<section class="page-intro">
|
||||
<div class="page-intro-copy">
|
||||
<div class="eyebrow"><i class="signal" id="signal"></i><span id="connection-label">CONNECTED TO
|
||||
LOCAL CORE</span></div>
|
||||
<div class="page-intro-meta-row">
|
||||
<div class="eyebrow"><i class="signal" id="signal"></i><span id="connection-label">CONNECTED
|
||||
TO
|
||||
LOCAL CORE</span></div>
|
||||
<div class="overview-samples-toolbar" id="overview-samples-toolbar" hidden role="group"
|
||||
aria-label="Overview chart samples" data-i18n-aria="overviewSamplesTitle">
|
||||
<label class="function-live-chart-samples" for="overview-live-samples"><span
|
||||
data-i18n="liveChartSamples">Samples</span>
|
||||
<select id="overview-live-samples" aria-describedby="overview-samples-state">
|
||||
<option value="60">60</option>
|
||||
<option value="120">120</option>
|
||||
<option value="300">300</option>
|
||||
<option value="600">600</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="quiet" id="overview-samples-save" type="button"
|
||||
data-i18n="overviewSamplesRemember">Remember choice</button>
|
||||
<small class="fine" id="overview-samples-state" role="status" aria-live="polite"
|
||||
data-i18n="overviewSamplesSaved">Saved default</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-intro-title-row">
|
||||
<h1 id="page-title">Traffic, in focus.</h1>
|
||||
<button class="quiet" data-i18n="refreshNow" id="overview-refresh" type="button"
|
||||
@@ -159,9 +178,29 @@
|
||||
data-i18n="errors">Errors</span><strong id="metric-errors">0</strong><small
|
||||
data-i18n="errorDescription">5xx / body /
|
||||
cancelled</small></article>
|
||||
<article class="stat" data-dashboard-widget="cache"><span data-i18n="cacheHitRatio">Cache hit
|
||||
ratio</span><strong id="metric-cache-ratio">-</strong><small
|
||||
id="cache-summary">-</small></article>
|
||||
<article class="stat stat-donut" data-dashboard-widget="cache"><span
|
||||
data-i18n="cacheHitRatio">Cache hit
|
||||
ratio</span>
|
||||
<div class="overview-donut-row">
|
||||
<svg class="overview-donut-svg" id="cache-ratio-chart" role="img"
|
||||
aria-label="Cache HIT and MISS distribution" viewBox="0 0 64 64">
|
||||
<circle class="overview-donut-track" cx="32" cy="32" r="24"></circle>
|
||||
<circle class="overview-donut-hit" id="cache-ratio-hit-arc" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100"></circle>
|
||||
<circle class="overview-donut-miss" id="cache-ratio-miss-arc" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100"></circle>
|
||||
</svg>
|
||||
<div class="overview-donut-info">
|
||||
<strong id="metric-cache-ratio">-</strong>
|
||||
<small class="overview-donut-legend" id="cache-summary">
|
||||
<span class="overview-donut-hit-key"><i></i>HIT <b
|
||||
id="cache-ratio-hit-count">0</b></span>
|
||||
<span class="overview-donut-miss-key"><i></i>MISS <b
|
||||
id="cache-ratio-miss-count">0</b></span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat" data-dashboard-widget="memory"><span data-i18n="processMemory">Process
|
||||
memory</span><strong id="metric-rss">-</strong><small id="process-summary">-</small>
|
||||
</article>
|
||||
@@ -315,14 +354,14 @@
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="backend-dashboard-requests-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="backend_capacity" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="backend_capacity" hidden>
|
||||
<span data-i18n="backendWidgetCapacity">Backend capacity</span><strong
|
||||
id="backend-dashboard-capacity">-</strong>
|
||||
<svg data-i18n-aria="backendWidgetCapacity" role="img" viewBox="0 0 300 60">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line proto-h2-line" id="backend-dashboard-capacity-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="backend-dashboard-capacity-detail">-</small>
|
||||
<div class="overview-meter" id="backend-dashboard-capacity-meter" role="progressbar"
|
||||
data-i18n-aria="backendWidgetCapacity" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="backend-dashboard-capacity-meter-fill"></span>
|
||||
</div><small id="backend-dashboard-capacity-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="backend_http5xx" hidden>
|
||||
<span data-i18n="backendWidgetHttp5xx">backendWidgetHttp5xx</span><strong
|
||||
@@ -392,14 +431,15 @@
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_process_rss-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_process_fds" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_process_fds" hidden>
|
||||
<span data-i18n="overviewExtraProcessFds">overviewExtraProcessFds</span><strong
|
||||
id="overview-extra-extra_process_fds">-</strong>
|
||||
<svg data-i18n-aria="overviewExtraProcessFds" role="img" viewBox="0 0 300 60">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_process_fds-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_process_fds-detail">-</small>
|
||||
<div class="overview-meter" id="overview-extra-extra_process_fds-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraProcessFds" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill"
|
||||
id="overview-extra-extra_process_fds-meter-fill"></span>
|
||||
</div><small id="overview-extra-extra_process_fds-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_open_connections" hidden>
|
||||
<span data-i18n="overviewExtraOpenConnections">overviewExtraOpenConnections</span><strong
|
||||
@@ -437,32 +477,69 @@
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_request_error_rate-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_cache_hit_rate" hidden>
|
||||
<span data-i18n="overviewExtraCacheHitRate">overviewExtraCacheHitRate</span><strong
|
||||
id="overview-extra-extra_cache_hit_rate">-</strong>
|
||||
<svg data-i18n-aria="overviewExtraCacheHitRate" role="img" viewBox="0 0 300 60">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_cache_hit_rate-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_cache_hit_rate-detail">-</small>
|
||||
<article class="stat stat-donut" data-dashboard-widget="extra_cache_hit_rate" hidden>
|
||||
<span data-i18n="overviewExtraCacheHitRate">Cache hits share (recent)</span>
|
||||
<div class="overview-donut-row">
|
||||
<svg class="overview-donut-svg" id="extra-cache-ratio-chart" role="img"
|
||||
aria-label="Recent cache HIT and MISS distribution" viewBox="0 0 64 64">
|
||||
<circle class="overview-donut-track" cx="32" cy="32" r="24"></circle>
|
||||
<circle class="overview-donut-hit" id="extra-cache-ratio-hit-arc" cx="32" cy="32"
|
||||
r="24" pathLength="100" stroke-dasharray="0 100"></circle>
|
||||
<circle class="overview-donut-miss" id="extra-cache-ratio-miss-arc" cx="32" cy="32"
|
||||
r="24" pathLength="100" stroke-dasharray="0 100"></circle>
|
||||
</svg>
|
||||
<div class="overview-donut-info">
|
||||
<strong id="overview-extra-extra_cache_hit_rate">-</strong>
|
||||
<small class="overview-donut-legend"
|
||||
id="overview-extra-extra_cache_hit_rate-detail">
|
||||
<span class="overview-donut-hit-key"><i></i>HIT <b
|
||||
id="extra-cache-ratio-hit-count">0</b></span>
|
||||
<span class="overview-donut-miss-key"><i></i>MISS <b
|
||||
id="extra-cache-ratio-miss-count">0</b></span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_cache_memory" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_cache_memory">
|
||||
<span data-i18n="overviewExtraCacheMemory">overviewExtraCacheMemory</span><strong
|
||||
id="overview-extra-extra_cache_memory">-</strong>
|
||||
<svg data-i18n-aria="overviewExtraCacheMemory" role="img" viewBox="0 0 300 60">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_cache_memory-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_cache_memory-detail">-</small>
|
||||
<div class="overview-meter" id="overview-extra-extra_cache_memory-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraCacheMemory" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill"
|
||||
id="overview-extra-extra_cache_memory-meter-fill"></span>
|
||||
</div><small id="overview-extra-extra_cache_memory-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_h2_share" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_cold_queue_memory">
|
||||
<span data-i18n="overviewExtraColdQueueMemory">COLD queue RAM utilization</span>
|
||||
<strong id="overview-extra-extra_cold_queue_memory">-</strong>
|
||||
<div class="overview-meter" id="overview-extra-extra_cold_queue_memory-meter"
|
||||
role="progressbar" data-i18n-aria="overviewExtraColdQueueMemory" aria-valuemin="0"
|
||||
aria-valuemax="100" aria-valuetext="-">
|
||||
<span class="overview-meter-fill"
|
||||
id="overview-extra-extra_cold_queue_memory-meter-fill"></span>
|
||||
</div>
|
||||
<small id="overview-extra-extra_cold_queue_memory-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_cold_storage">
|
||||
<span data-i18n="overviewExtraColdStorage">COLD disk utilization</span>
|
||||
<strong id="overview-extra-extra_cold_storage">-</strong>
|
||||
<div class="overview-meter" id="overview-extra-extra_cold_storage-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraColdStorage" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill"
|
||||
id="overview-extra-extra_cold_storage-meter-fill"></span>
|
||||
</div>
|
||||
<small id="overview-extra-extra_cold_storage-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_h2_share" hidden>
|
||||
<span data-i18n="overviewExtraH2Share">overviewExtraH2Share</span><strong
|
||||
id="overview-extra-extra_h2_share">-</strong>
|
||||
<svg data-i18n-aria="overviewExtraH2Share" role="img" viewBox="0 0 300 60">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_h2_share-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_h2_share-detail">-</small>
|
||||
<div class="overview-meter" id="overview-extra-extra_h2_share-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraH2Share" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="overview-extra-extra_h2_share-meter-fill"></span>
|
||||
</div><small id="overview-extra-extra_h2_share-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_response_latency" hidden>
|
||||
<span data-i18n="overviewExtraResponseLatency">overviewExtraResponseLatency</span><strong
|
||||
@@ -502,24 +579,24 @@
|
||||
</svg><small id="overview-extra-extra_active_exchanges-detail"
|
||||
data-i18n="overviewExtraRecentSamples">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_h1_share" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_h1_share" hidden>
|
||||
<span data-i18n="overviewExtraH1Share">HTTP/1.1 share</span><strong
|
||||
id="overview-extra-extra_h1_share">-</strong>
|
||||
<svg viewBox="0 0 300 60" role="img" aria-label="HTTP/1.1 share">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_h1_share-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_h1_share-detail"
|
||||
<div class="overview-meter" id="overview-extra-extra_h1_share-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraH1Share" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="overview-extra-extra_h1_share-meter-fill"></span>
|
||||
</div><small id="overview-extra-extra_h1_share-detail"
|
||||
data-i18n="overviewExtraRecentSamples">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_h3_share" hidden>
|
||||
<article class="stat stat-meter" data-dashboard-widget="extra_h3_share" hidden>
|
||||
<span data-i18n="overviewExtraH3Share">HTTP/3 share</span><strong
|
||||
id="overview-extra-extra_h3_share">-</strong>
|
||||
<svg viewBox="0 0 300 60" role="img" aria-label="HTTP/3 share">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line" id="overview-extra-extra_h3_share-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg><small id="overview-extra-extra_h3_share-detail"
|
||||
<div class="overview-meter" id="overview-extra-extra_h3_share-meter" role="progressbar"
|
||||
data-i18n-aria="overviewExtraH3Share" aria-valuemin="0" aria-valuemax="100"
|
||||
aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="overview-extra-extra_h3_share-meter-fill"></span>
|
||||
</div><small id="overview-extra-extra_h3_share-detail"
|
||||
data-i18n="overviewExtraRecentSamples">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="extra_cache_miss_rate" hidden>
|
||||
@@ -700,6 +777,286 @@
|
||||
id="overview-wide-wide_latency-probe-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="wide_transform_activity">
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="overviewWideTransformActivity">Transformation activity</h2>
|
||||
<span class="overview-wide-summary"
|
||||
id="overview-wide-wide_transform_activity-summary">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
data-i18n-aria="overviewWideTransformActivity" aria-label="Transformation activity">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_transform_activity-applied-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
id="overview-wide-wide_transform_activity-cache_hits-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-2"
|
||||
id="overview-wide-wide_transform_activity-skipped-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-3"
|
||||
id="overview-wide-wide_transform_activity-errors-line" d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend">
|
||||
<span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="overviewTransformApplied">Applied</span><b
|
||||
id="overview-wide-wide_transform_activity-applied-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="overviewTransformCacheHits">Cache HIT</span><b
|
||||
id="overview-wide-wide_transform_activity-cache_hits-value">-</b></span>
|
||||
<span><i class="overview-wide-key-2"></i><span
|
||||
data-i18n="overviewTransformSkipped">Skipped</span><b
|
||||
id="overview-wide-wide_transform_activity-skipped-value">-</b></span>
|
||||
<span><i class="overview-wide-key-3"></i><span
|
||||
data-i18n="overviewTransformErrors">Errors</span><b
|
||||
id="overview-wide-wide_transform_activity-errors-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="wide_compression_activity">
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="overviewWideCompressionActivity">Compression activity</h2>
|
||||
<span class="overview-wide-summary"
|
||||
id="overview-wide-wide_compression_activity-summary">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
data-i18n-aria="overviewWideCompressionActivity" aria-label="Compression activity">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0"
|
||||
id="overview-wide-wide_compression_activity-gzip-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
id="overview-wide-wide_compression_activity-br-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-2"
|
||||
id="overview-wide-wide_compression_activity-zstd-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-3"
|
||||
id="overview-wide-wide_compression_activity-deflate-line" d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend">
|
||||
<span><i class="overview-wide-key-0"></i><span>gzip</span><b
|
||||
id="overview-wide-wide_compression_activity-gzip-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span>Brotli</span><b
|
||||
id="overview-wide-wide_compression_activity-br-value">-</b></span>
|
||||
<span><i class="overview-wide-key-2"></i><span>Zstd</span><b
|
||||
id="overview-wide-wide_compression_activity-zstd-value">-</b></span>
|
||||
<span><i class="overview-wide-key-3"></i><span>deflate</span><b
|
||||
id="overview-wide-wide_compression_activity-deflate-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat stat-donut" data-dashboard-widget="www_status" hidden>
|
||||
<span data-i18n="webMetricStatus">webMetricStatus</span>
|
||||
<div class="overview-donut-row">
|
||||
<svg class="overview-donut-svg" id="www_status-chart" role="img" viewBox="0 0 64 64"
|
||||
aria-label="WWW status" data-i18n-aria="webMetricStatus">
|
||||
<circle class="overview-donut-track" cx="32" cy="32" r="24"></circle>
|
||||
<circle class="www-ring-arc www-ring-0" id="www_status-arc-0" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
<circle class="www-ring-arc www-ring-1" id="www_status-arc-1" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
<circle class="www-ring-arc www-ring-2" id="www_status-arc-2" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
<circle class="www-ring-arc www-ring-3" id="www_status-arc-3" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
</svg>
|
||||
<div class="overview-donut-info"><strong id="www_status-value">-</strong><small
|
||||
class="overview-donut-legend www-ring-legend"><span><i class="www-dot-0"></i>2xx
|
||||
<b id="www_status-count-0">0</b></span>
|
||||
<span><i class="www-dot-1"></i>3xx <b id="www_status-count-1">0</b></span>
|
||||
<span><i class="www-dot-2"></i>4xx <b id="www_status-count-2">0</b></span>
|
||||
<span><i class="www-dot-3"></i>5xx <b
|
||||
id="www_status-count-3">0</b></span></small></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat stat-donut" data-dashboard-widget="www_kind" hidden>
|
||||
<span data-i18n="webMetricKind">webMetricKind</span>
|
||||
<div class="overview-donut-row">
|
||||
<svg class="overview-donut-svg" id="www_kind-chart" role="img" viewBox="0 0 64 64"
|
||||
aria-label="Static / PHP" data-i18n-aria="webMetricKind">
|
||||
<circle class="overview-donut-track" cx="32" cy="32" r="24"></circle>
|
||||
<circle class="www-ring-arc www-ring-0" id="www_kind-arc-0" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
<circle class="www-ring-arc www-ring-1" id="www_kind-arc-1" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
<circle class="www-ring-arc www-ring-2" id="www_kind-arc-2" cx="32" cy="32" r="24"
|
||||
pathLength="100" stroke-dasharray="0 100" />
|
||||
</svg>
|
||||
<div class="overview-donut-info"><strong id="www_kind-value">-</strong><small
|
||||
class="overview-donut-legend www-ring-legend"><span><i
|
||||
class="www-dot-0"></i>Static <b id="www_kind-count-0">0</b></span>
|
||||
<span><i class="www-dot-1"></i>PHP <b id="www_kind-count-1">0</b></span>
|
||||
<span><i class="www-dot-2"></i>Other <b
|
||||
id="www_kind-count-2">0</b></span></small></div>
|
||||
</div>
|
||||
</article>
|
||||
<article class="stat stat-meter" data-dashboard-widget="www_capacity" hidden>
|
||||
<span data-i18n="webMetricCapacity">webMetricCapacity</span><strong
|
||||
id="www_capacity-value">-</strong>
|
||||
<div class="overview-meter" id="www_capacity-meter" role="progressbar" aria-valuemin="0"
|
||||
aria-valuemax="100" aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="www_capacity-meter-fill"></span>
|
||||
</div><small id="www_capacity-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-meter" data-dashboard-widget="www_error_rate" hidden>
|
||||
<span data-i18n="webMetricErrorRate">webMetricErrorRate</span><strong
|
||||
id="www_error_rate-value">-</strong>
|
||||
<div class="overview-meter" id="www_error_rate-meter" role="progressbar" aria-valuemin="0"
|
||||
aria-valuemax="100" aria-valuetext="-">
|
||||
<span class="overview-meter-fill" id="www_error_rate-meter-fill"></span>
|
||||
</div><small id="www_error_rate-detail">-</small>
|
||||
</article>
|
||||
<article class="stat" data-dashboard-widget="www_methods" hidden>
|
||||
<span data-i18n="webMetricMethods">HTTP Methods</span><strong
|
||||
id="www_methods-value">-</strong>
|
||||
<div class="www-segmented" id="www_methods-bar" role="img" aria-label="HTTP method share"
|
||||
data-i18n-aria="webMetricMethods"><span class="www-segment-0"
|
||||
id="www_methods-segment-0"></span><span class="www-segment-1"
|
||||
id="www_methods-segment-1"></span><span class="www-segment-2"
|
||||
id="www_methods-segment-2"></span><span class="www-segment-3"
|
||||
id="www_methods-segment-3"></span><span class="www-segment-4"
|
||||
id="www_methods-segment-4"></span></div>
|
||||
<small class="www-method-legend" id="www_methods-detail">-</small>
|
||||
</article>
|
||||
<article class="stat stat-chart" data-dashboard-widget="www_php_requests" hidden>
|
||||
<span data-i18n="webMetricPhpRequests">PHP requests/s</span><strong
|
||||
id="www_php_requests-value">-</strong>
|
||||
<svg role="img" viewBox="0 0 300 60" aria-label="PHP requests per second"
|
||||
data-i18n-aria="webMetricPhpRequests">
|
||||
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
|
||||
<polyline class="diagnostic-line proto-h2-line" id="www_php_requests-line"
|
||||
points="0,55 300,55"></polyline>
|
||||
</svg>
|
||||
<small data-i18n="webMetricPerSecond">Requests per second</small>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_requests" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricRequests">webMetricRequests</h2><span
|
||||
class="overview-wide-summary" id="www_requests-value">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server request rate" data-i18n-aria="webMetricRequests">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_requests-all-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_requests-php-line"
|
||||
d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend"><span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="webMetricSeriesAll">all</span><b
|
||||
id="www_requests-all-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="webMetricSeriesPhp">php</span><b
|
||||
id="www_requests-php-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_latency" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricLatency">webMetricLatency</h2><span
|
||||
class="overview-wide-summary" id="www_latency-value">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server header latency" data-i18n-aria="webMetricLatency">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_latency-web-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_latency-php-line"
|
||||
d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend"><span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="webMetricSeriesWeb">web</span><b
|
||||
id="www_latency-web-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="webMetricSeriesPhp">php</span><b
|
||||
id="www_latency-php-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_failures" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricFailures">webMetricFailures</h2><span
|
||||
class="overview-wide-summary" id="www_failures-value">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="PHP FastCGI failures" data-i18n-aria="webMetricFailures">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_failures-timeout-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1"
|
||||
id="www_failures-connection-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-2"
|
||||
id="www_failures-protocol-line" d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-3"
|
||||
id="www_failures-capacity-line" d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend"><span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="webMetricSeriesTimeout">timeout</span><b
|
||||
id="www_failures-timeout-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="webMetricSeriesConnection">connection</span><b
|
||||
id="www_failures-connection-value">-</b></span>
|
||||
<span><i class="overview-wide-key-2"></i><span
|
||||
data-i18n="webMetricSeriesProtocol">protocol</span><b
|
||||
id="www_failures-protocol-value">-</b></span>
|
||||
<span><i class="overview-wide-key-3"></i><span
|
||||
data-i18n="webMetricSeriesCapacity">capacity</span><b
|
||||
id="www_failures-capacity-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_traffic" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricTraffic">webMetricTraffic</h2><span
|
||||
class="overview-wide-summary" id="www_traffic-value">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server body byte rate" data-i18n-aria="webMetricTraffic">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_traffic-nonphp-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_traffic-php-line"
|
||||
d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend"><span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="webMetricSeriesNonphp">nonphp</span><b
|
||||
id="www_traffic-nonphp-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="webMetricSeriesPhp">php</span><b
|
||||
id="www_traffic-php-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_status_trend" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricStatusTrend">webMetricStatusTrend</h2><span
|
||||
class="overview-wide-summary" id="www_status_trend-value">-</span>
|
||||
</div>
|
||||
<svg class="overview-wide-svg" viewBox="0 0 600 150" role="img"
|
||||
aria-label="Web Server HTTP statuses" data-i18n-aria="webMetricStatusTrend">
|
||||
<path class="chart-grid" d="M0 37H600 M0 75H600 M0 113H600"></path>
|
||||
<path class="overview-wide-series overview-wide-series-0" id="www_status_trend-2xx-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-1" id="www_status_trend-3xx-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-2" id="www_status_trend-4xx-line"
|
||||
d=""></path>
|
||||
<path class="overview-wide-series overview-wide-series-3" id="www_status_trend-5xx-line"
|
||||
d=""></path>
|
||||
</svg>
|
||||
<div class="overview-wide-legend"><span><i class="overview-wide-key-0"></i><span
|
||||
data-i18n="webMetricSeriesTwoxx">2xx</span><b
|
||||
id="www_status_trend-2xx-value">-</b></span>
|
||||
<span><i class="overview-wide-key-1"></i><span
|
||||
data-i18n="webMetricSeriesThreexx">3xx</span><b
|
||||
id="www_status_trend-3xx-value">-</b></span>
|
||||
<span><i class="overview-wide-key-2"></i><span
|
||||
data-i18n="webMetricSeriesFourxx">4xx</span><b
|
||||
id="www_status_trend-4xx-value">-</b></span>
|
||||
<span><i class="overview-wide-key-3"></i><span
|
||||
data-i18n="webMetricSeriesFivexx">5xx</span><b
|
||||
id="www_status_trend-5xx-value">-</b></span>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card overview-wide-chart" data-dashboard-widget="www_top_services" hidden>
|
||||
<div class="card-heading">
|
||||
<h2 data-i18n="webMetricTopServices">Top Web Services</h2>
|
||||
<span class="overview-wide-summary" id="www_top_services-value">-</span>
|
||||
</div>
|
||||
<div id="www_top_services-bars" class="www-top-bars"></div>
|
||||
<small class="fine" data-i18n="webMetricTopServicesHelp">Requests per second by Web
|
||||
Service.</small>
|
||||
</article>
|
||||
<article class="card activity-card" data-dashboard-widget="traffic_chart">
|
||||
<div class="card-heading">
|
||||
<div><span class="small-label" data-i18n="liveTraffic">LIVE TRAFFIC</span>
|
||||
|
||||
@@ -11235,4 +11235,393 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
|
||||
overflow: visible;
|
||||
overscroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#panel-overview .stat-meter>.overview-meter {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
margin: 9px 0 8px;
|
||||
overflow: hidden;
|
||||
border-radius: var(--rp-radius-control, 5px);
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
#panel-overview .overview-meter-fill {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--chart-accent);
|
||||
transition: width 180ms ease;
|
||||
}
|
||||
|
||||
#panel-overview .stat-meter>small {
|
||||
display: block;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin: 4px 0 2px;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-svg {
|
||||
display: block;
|
||||
width: 62px;
|
||||
height: 62px;
|
||||
flex: 0 0 62px;
|
||||
overflow: visible;
|
||||
filter: var(--chart-svg-filter);
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-svg circle {
|
||||
fill: none;
|
||||
stroke-width: 9;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: 32px 32px;
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-track {
|
||||
stroke: var(--line);
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-hit {
|
||||
stroke: var(--chart-accent);
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-miss {
|
||||
stroke: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-legend>span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-legend i {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
flex: 0 0 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--chart-accent);
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-miss-key i {
|
||||
background: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .overview-donut-legend b {
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#panel-overview .overview-wide-series-3 {
|
||||
stroke: var(--chart-quaternary);
|
||||
}
|
||||
|
||||
#panel-overview .overview-wide-legend .overview-wide-key-3 {
|
||||
background: var(--chart-quaternary);
|
||||
}
|
||||
|
||||
@container overview-canvas (max-width: 700px) {
|
||||
#panel-overview .stat-donut .overview-donut-row {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-basis: 48px;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-legend {
|
||||
gap: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#panel-overview .overview-meter-fill {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.page-intro .page-intro-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 16px;
|
||||
min-width: 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.page-intro .page-intro-meta-row .eyebrow {
|
||||
margin-bottom: 0;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.page-intro .overview-samples-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 6px 12px;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
|
||||
.page-intro .overview-samples-toolbar .function-live-chart-samples select {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.page-intro .overview-samples-toolbar button {
|
||||
width: auto;
|
||||
flex: none;
|
||||
min-height: 28px;
|
||||
padding: 5px 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.page-intro .overview-samples-toolbar .fine {
|
||||
font-size: 10px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.page-intro .overview-samples-toolbar {
|
||||
margin-left: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-info>strong {
|
||||
display: block;
|
||||
line-height: 1.1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#panel-overview .stat-donut .overview-donut-info>.overview-donut-legend {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 7px;
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend span {
|
||||
white-space: nowrap;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-arc {
|
||||
stroke-width: 9;
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-0,
|
||||
.www-dot-0 {
|
||||
stroke: var(--chart-accent);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-1,
|
||||
.www-dot-1 {
|
||||
stroke: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-2,
|
||||
.www-dot-2 {
|
||||
stroke: var(--chart-tertiary);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-3,
|
||||
.www-dot-3 {
|
||||
stroke: var(--chart-quaternary);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend i {
|
||||
background: var(--chart-accent);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend .www-dot-1 {
|
||||
background: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend .www-dot-2 {
|
||||
background: var(--chart-tertiary);
|
||||
}
|
||||
|
||||
#panel-overview .www-ring-legend .www-dot-3 {
|
||||
background: var(--chart-quaternary);
|
||||
}
|
||||
|
||||
#panel-overview .www-segmented {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
margin: 9px 0 8px;
|
||||
background: var(--line);
|
||||
border-radius: var(--rp-radius-control, 5px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#panel-overview .www-segmented span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
transition: width 180ms ease;
|
||||
}
|
||||
|
||||
#panel-overview .www-segment-0 {
|
||||
background: var(--chart-accent);
|
||||
}
|
||||
|
||||
#panel-overview .www-segment-1 {
|
||||
background: var(--chart-secondary);
|
||||
}
|
||||
|
||||
#panel-overview .www-segment-2 {
|
||||
background: var(--chart-tertiary);
|
||||
}
|
||||
|
||||
#panel-overview .www-segment-3 {
|
||||
background: var(--chart-quaternary);
|
||||
}
|
||||
|
||||
#panel-overview .www-segment-4 {
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
#panel-overview .www-method-legend {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#panel-overview .www-top-bars {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
min-height: 90px;
|
||||
}
|
||||
|
||||
#panel-overview .www-top-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(60px, 1fr) minmax(30px, 2fr) auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
font: 11px var(--mono);
|
||||
}
|
||||
|
||||
#panel-overview .www-top-row>span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#panel-overview .www-top-meter {
|
||||
height: 7px;
|
||||
background: var(--line);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#panel-overview .www-top-meter span {
|
||||
display: block;
|
||||
background: var(--chart-accent);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#panel-overview .www-top-row b {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-panel {
|
||||
border-top: 1px solid var(--line);
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-panel summary {
|
||||
cursor: pointer;
|
||||
color: var(--chart-accent);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-live {
|
||||
padding: 12px 0 3px;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-values {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-values>div {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-values small {
|
||||
color: var(--muted);
|
||||
font: 10px var(--mono);
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-values strong {
|
||||
font: 500 14px var(--mono);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-live svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-height: 86px;
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-live polyline {
|
||||
fill: none;
|
||||
stroke: var(--chart-accent);
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
#panel-web-server .www-service-split {
|
||||
color: var(--muted);
|
||||
font: 11px var(--mono);
|
||||
overflow-wrap: anywhere;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#panel-overview .www-segmented span {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@@ -392,11 +392,70 @@
|
||||
item.append(el("span", t(key), "small-label"), el("b", value));
|
||||
details.append(item);
|
||||
}
|
||||
box.append(details); return box;
|
||||
const live = el("details", undefined, "www-service-panel");
|
||||
const summary = el("summary", t("webLiveMetrics")); live.append(summary);
|
||||
const panel = el("div", undefined, "www-service-live"); panel.dataset.webLiveId = service.id;
|
||||
const figures = el("div", undefined, "www-service-values");
|
||||
for (const [field, key] of [
|
||||
["requests", "webLiveRequests"], ["rate", "webLiveRate"],
|
||||
["errors", "webLiveErrors"], ["latency", "webLiveLatency"],
|
||||
["php", "webLivePhpCapacity"], ["bytes", "webLiveBytes"]]) {
|
||||
const item = el("div");
|
||||
const name = el("small", t(key));
|
||||
name.dataset.i18n = key;
|
||||
const value = el("strong", "-"); value.dataset.webLiveField = field;
|
||||
item.append(name, value); figures.append(item);
|
||||
}
|
||||
const chart = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
chart.setAttribute("viewBox", "0 0 300 60"); chart.setAttribute("role", "img");
|
||||
chart.setAttribute("aria-label", t("webLiveRate"));
|
||||
const line = document.createElementNS("http://www.w3.org/2000/svg", "polyline");
|
||||
line.setAttribute("points", "0,55 300,55"); chart.append(line);
|
||||
const split = el("p", "-", "www-service-split"); split.dataset.webLiveField = "split";
|
||||
panel.append(figures, chart, split); live.append(panel);
|
||||
live.addEventListener("toggle", () => { if (live.open && latestStatus) renderLive(latestStatus, false); });
|
||||
box.append(details, live); return box;
|
||||
});
|
||||
$("web-service-list").replaceChildren(...(cards.length ? cards : [el("p", t(query ? "webServiceSearchEmpty" : "webServiceEmpty"), "fine")]));
|
||||
}
|
||||
|
||||
const serviceHistory = new Map();
|
||||
const serviceLast = new Map();
|
||||
function renderLive(status, record = true) {
|
||||
if (currentPanel !== "web-server") return;
|
||||
const services = status?.web_server_metrics || {};
|
||||
for (const panel of document.querySelectorAll("#web-service-list [data-web-live-id]")) {
|
||||
if (!panel.closest("details")?.open) continue;
|
||||
const id = panel.dataset.webLiveId, values = services[id], web = values?.web;
|
||||
const lookup = key => panel.querySelector(`[data-web-live-field="${key}"]`);
|
||||
const set = (key, value) => { const node = lookup(key); if (node) node.textContent = value; };
|
||||
if (!web) { for (const key of ["requests", "rate", "errors", "latency", "php", "bytes", "split"]) set(key, "-"); continue; }
|
||||
const n = value => value == null || !Number.isFinite(Number(value)) ? 0 : Number(value);
|
||||
const total = n(web.requests), php = values.php, last = serviceLast.get(id);
|
||||
const sample = rustProxyRateSampling.difference(record ? last : null,
|
||||
[total, n(web.response_headers_us), n(web.status_5xx)], performance.now(),
|
||||
rustProxyRateSampling.cadence("overview_seconds"));
|
||||
if (record) serviceLast.set(id, sample.next);
|
||||
const deltas = record ? sample.deltas : null;
|
||||
const rate = deltas ? deltas[0] / sample.seconds : null;
|
||||
const previous = serviceHistory.get(id) || [];
|
||||
if (record) { previous.push(rate); window.rustProxyOverviewSamples.trim(previous); serviceHistory.set(id, previous); }
|
||||
const peak = Math.max(1, ...previous.filter(x => x != null));
|
||||
const points = previous.map((value, i) => `${300 * i / Math.max(1, previous.length - 1)},${55 - (value || 0) / peak * 50}`).join(" ");
|
||||
panel.querySelector("polyline")?.setAttribute("points", points || "0,55 300,55");
|
||||
set("requests", fmt(total)); set("rate", rate == null ? "-" : `${fmt(rate, 1)}/s`);
|
||||
set("errors", fmt(n(web.status_5xx)));
|
||||
set("latency", deltas?.[0] > 0 ? `${fmt(deltas[1] / deltas[0] / 1000, 1)} ms` : "-");
|
||||
set("php", php ? `${fmt(n(php.capacity_used))} / ${fmt(n(php.capacity_limit))}` : t("webLiveNoPhp"));
|
||||
set("bytes", fmtBytes(n(web.response_bytes)));
|
||||
set("split", `${t("webLiveKinds")}: ${fmt(n(web.static_requests))} / ${fmt(n(web.php_requests))} / ${fmt(n(web.other_requests))}`);
|
||||
}
|
||||
}
|
||||
window.rustProxyOverviewSamples.onChange(() => {
|
||||
for (const history of serviceHistory.values()) window.rustProxyOverviewSamples.trim(history);
|
||||
if (latestStatus && currentPanel === "web-server") renderLive(latestStatus, false);
|
||||
});
|
||||
|
||||
form.addEventListener("submit", async event => {
|
||||
event.preventDefault(); if (!envelope || busy) return;
|
||||
const invalid = [...form.elements].find(input => input.willValidate && !input.checkValidity()); if (invalid) { activateTab(invalid.closest("[data-web-service-page]")?.dataset.webServicePage || "general"); invalid.reportValidity(); return; }
|
||||
@@ -491,6 +550,6 @@
|
||||
$("web-service-search").addEventListener("input", render);
|
||||
dialog.addEventListener("cancel", event => { event.preventDefault(); closeEditor(); });
|
||||
window.rustProxyDrafts.register("web-server", { labelKey: "webServer", isOpen: () => dialog.open, isDirty: () => dirty, suspend: suspendDraft, restore: restoreDraft, close: clearEditor });
|
||||
window.rustProxyWebServer = { render, reset: clearEditor };
|
||||
window.rustProxyWebServer = { render, renderLive, reset() { clearEditor(); serviceHistory.clear(); serviceLast.clear(); } };
|
||||
if (envelope) render();
|
||||
})();
|
||||
Reference in new issue
Block a user