This commit is contained in:
Mateusz Gruszczyński committed 2026-10-16 13:02:53 +02:00
1 parent 162e986d37
commit 6b937aaf85
54 files changed
+3527 -1325

No files matched your search

+1 -1
View File
@@ -162,7 +162,7 @@ window.rustProxyInlineLayout = (() => {
draft = original = null;
}
async function begin() {
if (draft || opening || saving || $("overview-layout-dialog")?.open) return;
if (draft || opening || saving || $("overview-presets-dialog")?.open) return;
opening = true;
unlock.disabled = true;
try {
+144
View File
@@ -0,0 +1,144 @@
window.rustProxyDashboardLayout = (() => {
const dialog = $("overview-presets-dialog");
const list = $("overview-presets-list");
const input = $("overview-presets-name");
const message = $("overview-presets-message");
const save = $("overview-presets-save");
const factory = $("overview-presets-factory");
const close = $("overview-presets-close");
let presets = [];
let busy = false;
const presetPath = name => `/api/dashboard-presets/${encodeURIComponent(name)}`;
const nameValid = name => name.length > 0 && new TextEncoder().encode(name).length <= 64
&& !/[\x00-\x1f\x7f/]/.test(name);
const snapshot = () => {
const current = window.rustProxyPreferences.current() || ADMIN_APPEARANCE_DEFAULTS;
return {
dashboard_widgets: [...normalizeDashboardWidgets(current.dashboard_widgets)],
dashboard_hidden_widgets: [...normalizeHiddenDashboardWidgets(current.dashboard_hidden_widgets)],
dashboard_widget_sizes: normalizeDashboardWidgetSizes(current.dashboard_widget_sizes),
dashboard_density: normalizeAdminDensity(current.dashboard_density, "compact"),
overview_samples: window.rustProxyOverviewSamples.getLimit(),
};
};
const translateName = (key, name) => t(key).replace("{name}", name);
function notify(key, isError = false) {
message.textContent = isError ? key : t(key);
message.classList.toggle("error", isError);
}
function lock(value) {
busy = value;
for (const control of [save, factory, close, input, ...list.querySelectorAll("button")])
if (control) control.disabled = value;
}
async function run(fn) {
if (busy) return;
lock(true);
try { await fn(); }
catch (error) {
const message = error.status === 409
? (String(error.code || "").includes("limit (16)")
? t("dashboardPresetsLimit") : t("dashboardPresetsConflict"))
: (error.message || String(error));
notify(message, true);
}
finally { lock(false); }
}
async function reload() {
presets = await api("/api/dashboard-presets");
render();
}
function entryButton(key, className, fn, presetName) {
const button = el("button", t(key), className);
button.type = "button";
button.setAttribute("aria-label", `${t(key)}: ${presetName}`);
button.dataset.presetAction = key;
button.addEventListener("click", () => void run(fn));
return button;
}
function render() {
const elements = presets.map(preset => {
const row = el("div", undefined, "overview-presets-row");
row.setAttribute("role", "listitem");
const actions = el("div", undefined, "overview-presets-actions");
actions.append(
entryButton("dashboardPresetsLoad", "primary", async () => {
await apply(preset);
dialog.close();
toast(t("dashboardPresetsLoaded"));
}, preset.name),
entryButton("dashboardPresetsOverwrite", "outline", async () => {
if (!(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", preset.name)))) return;
await api(presetPath(preset.name), { method: "PUT", body: JSON.stringify(snapshot()) });
await reload(); notify("dashboardPresetsSaved");
}, preset.name),
entryButton("dashboardPresetsRename", "quiet", async () => {
const name = input.value.trim();
if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; }
if (name === preset.name) return;
await api(presetPath(preset.name), { method: "PATCH", body: JSON.stringify({ name }) });
input.value = "";
await reload(); notify("dashboardPresetsRenamed");
}, preset.name),
entryButton("dashboardPresetsDelete", "quiet", async () => {
if (!(await appConfirm(translateName("dashboardPresetsDeleteConfirm", preset.name), { danger: true }))) return;
await api(presetPath(preset.name), { method: "DELETE" });
await reload(); notify("dashboardPresetsDeleted");
}, preset.name),
);
row.append(el("strong", preset.name), actions);
return row;
});
list.replaceChildren(...(elements.length ? elements : [el("p", t("dashboardPresetsEmpty"), "fine")]));
}
async function apply(layout) {
await window.rustProxyPreferences.update({
dashboard_widgets: [...layout.dashboard_widgets],
dashboard_hidden_widgets: [...layout.dashboard_hidden_widgets],
dashboard_widget_sizes: layout.dashboard_widget_sizes === null ? null : { ...layout.dashboard_widget_sizes },
dashboard_density: layout.dashboard_density,
});
window.rustProxyOverviewSamples.setLimit(layout.overview_samples, true);
}
async function open() {
if (dialog?.open || busy || window.rustProxyInlineLayout?.isEditing?.()) return;
await run(async () => {
await window.rustProxyPreferences.load();
await reload();
input.value = "";
notify("");
dialog.showModal();
input.focus();
});
}
save?.addEventListener("click", () => void run(async () => {
const name = input.value.trim();
if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; }
if (presets.length >= 16 && !presets.some(preset => preset.name === name)) {
notify("dashboardPresetsLimit"); return;
}
if (presets.some(preset => preset.name === name)
&& !(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", name)))) return;
await api(presetPath(name), { method: "PUT", body: JSON.stringify(snapshot()) });
input.value = "";
await reload(); notify("dashboardPresetsSaved");
}));
factory?.addEventListener("click", () => void run(async () => {
if (!(await appConfirm(t("dashboardPresetsResetConfirm")))) return;
await apply({
dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS],
dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS],
dashboard_widget_sizes: null,
dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density,
overview_samples: ADMIN_DASHBOARD_DEFAULTS.overview_samples,
});
dialog.close();
toast(t("dashboardPresetsRestored"));
}));
close?.addEventListener("click", () => { if (!busy) dialog.close(); });
dialog?.addEventListener("cancel", event => { if (busy) event.preventDefault(); });
for (const id of ["overview-layout-open", "overview-layout-open-settings"])
$(id)?.addEventListener("click", () => { void open(); });
return { open, refresh: () => { if (dialog?.open) render(); } };
})();
+25 -1055
View File
File diff suppressed because it is too large. Load diff
+373
View File
@@ -0,0 +1,373 @@
<article class="card overview-wide-chart" data-dashboard-widget="wide_protocol_rates" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideProtocolRates">HTTP/1.1 · HTTP/2 · HTTP/3 traffic</h2>
<span class="overview-wide-summary" id="overview-wide-wide_protocol_rates-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="HTTP/1.1 · HTTP/2 · HTTP/3 traffic">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_protocol_rates-h1-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_protocol_rates-h2-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-2" id="overview-wide-wide_protocol_rates-h3-line" d="">
</path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span>H1</span><b
id="overview-wide-wide_protocol_rates-h1-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span>H2</span><b
id="overview-wide-wide_protocol_rates-h2-value">-</b></span>
<span><i class="overview-wide-key-2"></i><span>H3</span><b
id="overview-wide-wide_protocol_rates-h3-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_cpu" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideCpu">System and process CPU</h2>
<span class="overview-wide-summary" id="overview-wide-wide_cpu-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="System and process CPU">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_cpu-system-line" d=""></path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_cpu-process-line" d=""></path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideSystem">System</span><b
id="overview-wide-wide_cpu-system-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideProcess">Process</span><b
id="overview-wide-wide_cpu-process-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_memory" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideMemory">Process RAM and cache memory</h2>
<span class="overview-wide-summary" id="overview-wide-wide_memory-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Process RAM and cache memory">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_memory-process-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_memory-cache-line" d=""></path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideProcess">Process</span><b
id="overview-wide-wide_memory-process-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideCache">Cache</span><b
id="overview-wide-wide_memory-cache-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_cache_activity" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideCacheActivity">Cache hits / misses per second</h2>
<span class="overview-wide-summary" id="overview-wide-wide_cache_activity-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Cache hits / misses per second">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_cache_activity-hits-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_cache_activity-misses-line"
d=""></path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideHits">HIT</span><b
id="overview-wide-wide_cache_activity-hits-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideMisses">MISS</span><b
id="overview-wide-wide_cache_activity-misses-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_connections" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideConnections">Connections and WebSocket tunnels</h2>
<span class="overview-wide-summary" id="overview-wide-wide_connections-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Connections and WebSocket tunnels">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_connections-open-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_connections-websockets-line"
d=""></path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideOpen">Connections</span><b
id="overview-wide-wide_connections-open-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideWebsockets">WebSockets</span><b
id="overview-wide-wide_connections-websockets-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_backend_health" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideBackendHealth">Backend availability over time</h2>
<span class="overview-wide-summary" id="overview-wide-wide_backend_health-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Backend availability over time">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_backend_health-up-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_backend_health-down-line" d="">
</path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideHealthy">Healthy</span><b
id="overview-wide-wide_backend_health-up-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideUnhealthy">Unhealthy</span><b
id="overview-wide-wide_backend_health-down-value">-</b></span>
</div>
</article>
<article class="card overview-wide-chart" data-dashboard-widget="wide_latency" hidden>
<div class="card-heading">
<h2 data-i18n="overviewWideLatency">Response and backend probe latency</h2>
<span class="overview-wide-summary" id="overview-wide-wide_latency-summary">-</span>
</div>
<svg preserveAspectRatio="none" class="overview-wide-svg" viewBox="0 0 600 150" role="img"
aria-label="Response and backend probe latency">
<path class="chart-grid" d="M0 47H600 M0 77H600 M0 107H600"></path>
<path class="overview-wide-series overview-wide-series-0" id="overview-wide-wide_latency-response-line" d="">
</path>
<path class="overview-wide-series overview-wide-series-1" id="overview-wide-wide_latency-probe-line" d="">
</path>
</svg>
<div class="overview-wide-legend">
<span><i class="overview-wide-key-0"></i><span data-i18n="overviewWideResponse">HTTP</span><b
id="overview-wide-wide_latency-response-value">-</b></span>
<span><i class="overview-wide-key-1"></i><span data-i18n="overviewWideProbe">Probes</span><b
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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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>
<!-- RP_CHARTS_SPLIT -->
<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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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 preserveAspectRatio="none" 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 47H600 M0 77H600 M0 107H600"></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">
<h2 data-i18n="requestsPerSecond">Requests per second</h2>
<span id="chart-peak">max 0.0 req/s</span>
</div><svg preserveAspectRatio="none" aria-label="Recent request rate" id="traffic-chart" role="img"
viewbox="0 0 600 130">
<path class="chart-grid" d="M0 25H600 M0 65H600 M0 105H600"></path>
<polyline class="traffic-line traffic-line-total" id="sparkline" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h1" id="sparkline-h1" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h2" id="sparkline-h2" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-h3" id="sparkline-h3" points="0,120 600,120">
</polyline>
<polyline class="traffic-line traffic-line-transform" id="sparkline-transform" points="0,120 600,120">
</polyline>
</svg>
<div class="traffic-protocol-legend" aria-label="Request rate by HTTP protocol and transformed response rate">
<span class="legend-total"><i></i>Total <b id="rate-total">0.0/s</b></span>
<span class="legend-h1"><i></i>HTTP/1.1 <b id="rate-h1">0.0/s</b></span>
<span class="legend-h2"><i></i>HTTP/2 <b id="rate-h2">0.0/s</b></span>
<span class="legend-h3"><i></i>HTTP/3 <b id="rate-h3">0.0/s</b></span>
<span class="legend-transform"><i></i><span data-i18n="liveTransformed">Transformed</span> <b
id="rate-transform">0.0/s</b></span>
</div>
</article>
<article class="card system-card" data-dashboard-widget="system_chart">
<div class="card-heading">
<div>
<h3 class="overview-chart-heading" data-i18n="systemLoad">System load</h3>
<h2><span id="system-cpu">-</span> <small>CPU</small></h2>
</div><span class="meta" id="system-source">-</span>
</div><svg preserveAspectRatio="none" aria-label="System CPU history" data-i18n-aria="systemChart" id="system-chart"
role="img" viewbox="0 0 300 100">
<path class="chart-grid" d="M0 25H300 M0 50H300 M0 75H300"></path>
<polyline id="system-sparkline" points="0,95 300,95"></polyline>
</svg>
<div class="memory-line"><span data-i18n="systemMemory">System RAM</span><b id="system-memory">-</b></div>
<div class="overview-meter system-memory-meter" id="memory-meter" role="progressbar" aria-label="System RAM"
data-i18n-aria="systemMemory" aria-valuemin="0" aria-valuemax="100" aria-valuetext="-">
<span class="overview-meter-fill" id="memory-meter-fill"></span>
</div>
<div class="memory-line"><span data-i18n="systemSwap">System swap</span><b id="system-swap">-</b></div>
<div class="chart-footer"><span data-i18n="loadAverage">Load 1 / 5 / 15 min</span><span id="system-load">-</span>
</div>
<div class="chart-footer"><span data-i18n="systemUptime">System uptime</span><span id="system-uptime">-</span></div>
</article>
<article class="card connection-card" data-dashboard-widget="connection_chart">
<div class="card-heading">
<div>
<h3 class="overview-chart-heading" data-i18n="connectionActivity">Connection activity</h3>
<h2><span id="connection-current">0</span> <small data-i18n="connections">Open
connections</small></h2>
</div>
</div>
<svg preserveAspectRatio="none" aria-label="Connection activity history" data-i18n-aria="connectionActivity"
id="connection-chart" role="img" viewbox="0 0 300 100">
<path class="chart-grid" d="M0 25H300 M0 50H300 M0 75H300"></path>
<polyline class="connection-open-line" id="connection-open-sparkline" points="0,95 300,95"></polyline>
<polyline class="connection-exchange-line" id="connection-exchange-sparkline" points="0,95 300,95"></polyline>
</svg>
<div class="connection-legend"><span class="connection-open-key"><i></i><span data-i18n="connections">Open
connections</span> <b id="connection-open-value">0</b></span><span
class="connection-exchange-key"><i></i><span data-i18n="activeExchanges">Active
exchanges</span> <b id="connection-exchange-value">0</b></span></div>
<div class="chart-footer"><span><span data-i18n="backends">Backends</span>: <b id="connection-backends">0</b></span>
</div>
</article>
@@ -0,0 +1,471 @@
<article class="stat" data-dashboard-widget="requests"><span data-i18n="requests">Requests</span><strong
id="metric-requests">0</strong><small id="request-rate">0 req/s</small></article>
<article class="stat" data-dashboard-widget="active"><span data-i18n="activeExchanges">Active
exchanges</span><strong id="metric-active">0</strong><small data-i18n="bodyLifetime">Full body lifetime</small>
</article>
<article class="stat" data-dashboard-widget="bandwidth"><span data-i18n="bandwidth">Payload
transferred</span><strong id="metric-bytes">0 B</strong><small class="payload-split"
id="metric-bytes-split"><span>↓ <b id="metric-bytes-down">0 B</b></span><span>↑ <b id="metric-bytes-up">0
B</b></span></small>
</article>
<article class="stat" data-dashboard-widget="errors"><span data-i18n="errors">Errors</span><strong
id="metric-errors">0</strong><small data-i18n="errorDescription">5xx / body /
cancelled</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>
<article class="stat stat-with-action" data-dashboard-widget="websocket"><span data-i18n="wsActive">WebSocket
tunnels</span><button class="quiet stat-corner-action" data-i18n="tunnelsOpen" id="tunnels-open"
type="button">Active tunnels</button><strong id="metric-ws">0</strong><small
aria-label="Active WebSocket tunnels by frontend protocol"><span data-i18n="wsH1">H1
Upgrade</span> <span id="metric-ws-h1">0</span> · <span data-i18n="wsH2Rfc8441">H2
RFC 8441</span> <span id="metric-ws-h2">0</span> · <span data-i18n="wsH3Rfc9220">H3
RFC 9220</span> <span id="metric-ws-h3">0</span><br><span data-i18n="wsEstablished">Established</span>:
<span id="metric-ws-total">0</span></small></article>
<article class="stat" data-dashboard-widget="http2"><span data-i18n="h2Requests">HTTP/2
requests</span><strong id="metric-h2">0</strong><small><span data-i18n="h2ActiveConnections">Active H2
connections</span>: <span id="metric-h2-active">0</span></small></article>
<article class="stat" data-dashboard-widget="http3"><span data-i18n="h3Requests">HTTP/3
requests</span><strong id="metric-h3">0</strong></article>
<article class="stat" data-dashboard-widget="tls"><span data-i18n="tlsFailures">TLS handshake
errors</span><strong id="metric-tls-errors">0</strong></article>
<article class="stat stat-chart" data-dashboard-widget="http_protocol"><span data-i18n="httpProtocolMix">HTTP protocol
mix</span><strong id="metric-http-proto-total">0.0/s</strong><svg preserveAspectRatio="none"
aria-label="HTTP protocol activity" data-i18n-aria="httpProtocolMix" id="http-protocol-chart" role="img"
viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="http-protocol-h1" points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h2-line" id="http-protocol-h2" points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h3-line" id="http-protocol-h3" points="0,55 300,55"></polyline>
</svg><small class="stat-chart-legend"><span class="legend-h1"><i></i>H1 <b
id="metric-http-proto-h1">0.0/s</b></span><span class="legend-h2"><i></i>H2 <b
id="metric-http-proto-h2">0.0/s</b></span><span class="legend-h3"><i></i>H3 <b
id="metric-http-proto-h3">0.0/s</b></span></small></article>
<article class="stat stat-chart" data-dashboard-widget="websocket_protocol"><span data-i18n="wsProtocolMix">WebSocket
protocol
mix</span><strong id="metric-ws-proto-total">0</strong><svg preserveAspectRatio="none"
aria-label="WebSocket protocol activity" data-i18n-aria="wsProtocolMix" id="ws-protocol-chart" role="img"
viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="ws-protocol-h1" points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h2-line" id="ws-protocol-h2" points="0,55 300,55"></polyline>
<polyline class="diagnostic-line proto-h3-line" id="ws-protocol-h3" points="0,55 300,55"></polyline>
</svg><small class="stat-chart-legend"><span class="legend-h1"><i></i>H1 <b
id="metric-ws-proto-h1">0</b></span><span class="legend-h2"><i></i>H2 <b
id="metric-ws-proto-h2">0</b></span><span class="legend-h3"><i></i>H3 <b
id="metric-ws-proto-h3">0</b></span></small></article>
<article class="stat" data-dashboard-widget="stream_overview" hidden>
<span data-i18n="streamWidgetOverview">Stream overview</span><strong id="stream-dashboard-overview">-</strong>
<small id="stream-dashboard-overview-detail" data-i18n="streamWidgetUnavailable">No stream
data</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="stream_traffic" hidden>
<span data-i18n="streamWidgetTraffic">Stream traffic</span><strong id="stream-dashboard-traffic">-</strong>
<svg preserveAspectRatio="none" aria-label="Stream traffic RX/TX" data-i18n-aria="streamWidgetTraffic" role="img"
viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line stream-chart-rx" id="stream-dashboard-rx" points="0,55 300,55"></polyline>
<polyline class="diagnostic-line stream-chart-tx" id="stream-dashboard-tx" points="0,55 300,55"></polyline>
</svg><small class="stat-chart-legend stream-traffic-legend" id="stream-dashboard-traffic-detail">
<span class="legend-rx"><i></i>RX <b id="stream-dashboard-rx-rate">-</b></span>
<span class="legend-tx"><i></i>TX <b id="stream-dashboard-tx-rate">-</b></span>
</small>
</article>
<article class="stat" data-dashboard-widget="stream_sessions" hidden>
<span data-i18n="streamWidgetSessions">Stream sessions</span><strong id="stream-dashboard-sessions">-</strong>
<small id="stream-dashboard-sessions-detail" data-i18n="streamWidgetUnavailable">No stream
data</small>
</article>
<article class="stat" data-dashboard-widget="stream_backend_health" hidden>
<span data-i18n="streamWidgetHealth">Stream backend health</span><strong id="stream-dashboard-health">-</strong>
<small id="stream-dashboard-health-detail" data-i18n="streamWidgetUnavailable">No stream
data</small>
</article>
<article class="stat" data-dashboard-widget="backend_health" hidden>
<span data-i18n="backendWidgetHealth">Backend health</span><strong id="backend-dashboard-health">-</strong>
<small id="backend-dashboard-health-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_activity" hidden>
<span data-i18n="backendWidgetActivity">Active backend requests</span><strong
id="backend-dashboard-activity">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetActivity" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="backend-dashboard-active-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-activity-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_errors" hidden>
<span data-i18n="backendWidgetErrors">Backend errors</span><strong id="backend-dashboard-errors">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetErrors" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h3-line" id="backend-dashboard-error-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-errors-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_latency" hidden>
<span data-i18n="backendWidgetLatency">Health probe RTT</span><strong id="backend-dashboard-latency">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetLatency" 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-latency-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-latency-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_traffic" hidden>
<span data-i18n="backendWidgetTraffic">Backend traffic</span><strong id="backend-dashboard-traffic">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetTraffic" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line stream-chart-rx" id="backend-dashboard-in-line" points="0,55 300,55">
</polyline>
<polyline class="diagnostic-line stream-chart-tx" id="backend-dashboard-out-line" points="0,55 300,55">
</polyline>
</svg><small class="stat-chart-legend stream-traffic-legend">
<span class="legend-rx"><i></i>IN <b id="backend-dashboard-in-rate">-</b></span>
<span class="legend-tx"><i></i>OUT <b id="backend-dashboard-out-rate">-</b></span>
</small>
</article>
<article class="stat" data-dashboard-widget="backend_protocols" hidden>
<span data-i18n="backendWidgetProtocols">Configured backend modes</span><strong
id="backend-dashboard-protocols">-</strong>
<div class="backend-mode-bar" role="img" data-i18n-aria="backendWidgetProtocols">
<span class="backend-mode-h1" id="backend-mode-h1"></span>
<span class="backend-mode-auto" id="backend-mode-auto"></span>
<span class="backend-mode-compat" id="backend-mode-compat"></span>
<span class="backend-mode-h2" id="backend-mode-h2"></span>
</div><small id="backend-dashboard-protocols-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_requests" hidden>
<span data-i18n="backendWidgetRequests">Backend request rate</span><strong
id="backend-dashboard-requests">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetRequests" role="img" viewBox="0 0 300 60">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line proto-h1-line" id="backend-dashboard-request-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-requests-detail">-</small>
</article>
<article class="stat stat-meter" data-dashboard-widget="backend_capacity" hidden>
<span data-i18n="backendWidgetCapacity">Backend capacity</span><strong id="backend-dashboard-capacity">-</strong>
<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 id="backend-dashboard-http5xx">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetHttp5xx" 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-5xx-line" points="0,55 300,55"></polyline>
</svg><small id="backend-dashboard-http5xx-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_timeouts" hidden>
<span data-i18n="backendWidgetTimeouts">backendWidgetTimeouts</span><strong
id="backend-dashboard-timeouts">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetTimeouts" 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-timeout-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-timeouts-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_network" hidden>
<span data-i18n="backendWidgetNetwork">backendWidgetNetwork</span><strong id="backend-dashboard-network">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetNetwork" 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-network-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-network-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_retries" hidden>
<span data-i18n="backendWidgetRetries">backendWidgetRetries</span><strong id="backend-dashboard-retries">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetRetries" 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-retry-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-retries-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="backend_queue" hidden>
<span data-i18n="backendWidgetQueue">backendWidgetQueue</span><strong id="backend-dashboard-queue">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="backendWidgetQueue" 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-queue-line" points="0,55 300,55">
</polyline>
</svg><small id="backend-dashboard-queue-detail">-</small>
</article>
<article class="stat" data-dashboard-widget="backend_rotations" hidden>
<span data-i18n="backendWidgetRotations">Backend client pool rotations</span><strong
id="backend-dashboard-rotations">-</strong>
<small id="backend-dashboard-rotations-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_process_cpu" hidden>
<span data-i18n="overviewExtraProcessCpu">overviewExtraProcessCpu</span><strong
id="overview-extra-extra_process_cpu">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraProcessCpu" 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_cpu-line" points="0,55 300,55"></polyline>
</svg><small id="overview-extra-extra_process_cpu-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_process_rss" hidden>
<span data-i18n="overviewExtraProcessRss">overviewExtraProcessRss</span><strong
id="overview-extra-extra_process_rss">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraProcessRss" 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_rss-line" points="0,55 300,55"></polyline>
</svg><small id="overview-extra-extra_process_rss-detail">-</small>
</article>
<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>
<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
id="overview-extra-extra_open_connections">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraOpenConnections" 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_open_connections-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_open_connections-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_websocket_active" hidden>
<span data-i18n="overviewExtraWebsocketActive">overviewExtraWebsocketActive</span><strong
id="overview-extra-extra_websocket_active">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraWebsocketActive" 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_websocket_active-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_websocket_active-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_tls_error_rate" hidden>
<span data-i18n="overviewExtraTlsErrorRate">overviewExtraTlsErrorRate</span><strong
id="overview-extra-extra_tls_error_rate">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraTlsErrorRate" 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_tls_error_rate-line" points="0,55 300,55"></polyline>
</svg><small id="overview-extra-extra_tls_error_rate-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_request_error_rate" hidden>
<span data-i18n="overviewExtraRequestErrorRate">overviewExtraRequestErrorRate</span><strong
id="overview-extra-extra_request_error_rate">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraRequestErrorRate" 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_request_error_rate-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_request_error_rate-detail">-</small>
</article>
<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-meter" data-dashboard-widget="extra_cache_memory">
<span data-i18n="overviewExtraCacheMemory">overviewExtraCacheMemory</span><strong
id="overview-extra-extra_cache_memory">-</strong>
<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-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>
<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
id="overview-extra-extra_response_latency">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraResponseLatency" 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_response_latency-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_response_latency-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_backend_unhealthy" hidden>
<span data-i18n="overviewExtraBackendUnhealthy">overviewExtraBackendUnhealthy</span><strong
id="overview-extra-extra_backend_unhealthy">-</strong>
<svg preserveAspectRatio="none" data-i18n-aria="overviewExtraBackendUnhealthy" 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_backend_unhealthy-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_backend_unhealthy-detail">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_total_rps" hidden>
<span data-i18n="overviewExtraTotalRps">HTTP requests / s</span><strong
id="overview-extra-extra_total_rps">-</strong>
<svg preserveAspectRatio="none" viewBox="0 0 300 60" role="img" aria-label="HTTP requests / s">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line" id="overview-extra-extra_total_rps-line" points="0,55 300,55"></polyline>
</svg><small id="overview-extra-extra_total_rps-detail" data-i18n="overviewExtraRecentSamples">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_active_exchanges" hidden>
<span data-i18n="overviewExtraActiveExchanges">Active HTTP exchanges</span><strong
id="overview-extra-extra_active_exchanges">-</strong>
<svg preserveAspectRatio="none" viewBox="0 0 300 60" role="img" aria-label="Active HTTP exchanges">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line" id="overview-extra-extra_active_exchanges-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_active_exchanges-detail" data-i18n="overviewExtraRecentSamples">-</small>
</article>
<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>
<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-meter" data-dashboard-widget="extra_h3_share" hidden>
<span data-i18n="overviewExtraH3Share">HTTP/3 share</span><strong id="overview-extra-extra_h3_share">-</strong>
<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>
<span data-i18n="overviewExtraCacheMissRate">Cache misses / s</span><strong
id="overview-extra-extra_cache_miss_rate">-</strong>
<svg preserveAspectRatio="none" viewBox="0 0 300 60" role="img" aria-label="Cache misses / s">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line" id="overview-extra-extra_cache_miss_rate-line" points="0,55 300,55">
</polyline>
</svg><small id="overview-extra-extra_cache_miss_rate-detail" data-i18n="overviewExtraRecentSamples">-</small>
</article>
<article class="stat stat-chart" data-dashboard-widget="extra_backend_active" hidden>
<span data-i18n="overviewExtraBackendActive">In-flight backend requests</span><strong
id="overview-extra-extra_backend_active">-</strong>
<svg preserveAspectRatio="none" viewBox="0 0 300 60" role="img" aria-label="In-flight backend requests">
<path class="chart-grid" d="M0 15H300 M0 30H300 M0 45H300"></path>
<polyline class="diagnostic-line" id="overview-extra-extra_backend_active-line" points="0,55 300,55"></polyline>
</svg><small id="overview-extra-extra_backend_active-detail" data-i18n="overviewExtraRecentSamples">-</small>
</article>
<!-- RP_METRICS_SPLIT -->
<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 preserveAspectRatio="none" 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>
+347 -2
View File
@@ -3508,6 +3508,10 @@ body[data-density=dense] .stat strong {
margin: 6px 0
}
#panel-cache>article.card+article.card {
margin-top: 10px
}
.cache-table {
font-size: 12px
}
@@ -11210,7 +11214,7 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
border-radius: var(--rp-radius-surface) !important;
}
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(button:not(.nav):not(.file-log-tab), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .badge, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-hidden-toggle, .overview-inline-restore) {
html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-style=rounded], [data-corner-style=extra]) :is(button:not(.nav):not(.file-log-tab):not(.vhost-backend-trigger), input:not([type=checkbox]):not([type=radio]):not([type=range]), select, textarea, .pill, .vhost-backend-protocol-badge, .overview-widget-type, .badge, .quiet, .outline, .primary, .code, .command, .dashboard-widget-toggle, .settings-tabs button, .overview-inline-hidden-toggle, .overview-inline-restore) {
border-radius: var(--rp-radius-control) !important;
}
@@ -11237,6 +11241,16 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
padding: 3px 7px;
font: 10px var(--mono);
overflow-wrap: anywhere;
text-align: left;
}
.vhost-backend-protocol-badge:is(:hover, [aria-expanded=true]) {
background: var(--bg);
}
.vhost-backend-protocol-badge:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.vhost-backend-protocol-badge[data-observed=true] {
@@ -11596,7 +11610,6 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
color: var(--text);
}
/* Web Services reuse the same full-width live tiles and chart as Vhosts. */
#panel-web-server .www-service-panel {
min-width: 0;
margin-top: 10px;
@@ -11813,4 +11826,336 @@ html:is([data-corner-style=square], [data-corner-style=subtle], [data-corner-sty
#panel-overview .dashboard-overview-grid.is-unlocked>[data-dashboard-widget] {
padding-top: 135px;
}
}
#panel-overview :is(.system-card, .connection-card) :is(.card-heading h2, .card-heading .meta, .memory-line, .chart-footer, .connection-legend) {
font-family: var(--mono);
font-variant-numeric: tabular-nums;
}
#panel-overview :is(.system-card, .connection-card) .card-heading h2 {
letter-spacing: normal;
}
.overview-presets-dialog {
width: calc(min(730px, 100vw - 24px));
max-height: calc(100dvh - 24px);
overflow-y: auto;
}
.overview-presets-content {
display: grid;
gap: 14px;
padding: 14px;
}
.overview-presets-content>p {
margin: 0;
}
.overview-presets-create {
display: grid;
gap: 7px;
}
.overview-presets-create-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.overview-presets-create-controls input {
min-width: 150px;
flex: 1 1 240px;
}
.overview-presets-list {
display: grid;
gap: 7px;
}
.overview-presets-row {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
padding: 9px;
border: 1px solid var(--line);
border-radius: var(--radius, 8px);
background: var(--panel);
}
.overview-presets-row strong {
font-size: 13px;
overflow-wrap: anywhere;
}
.overview-presets-actions {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.overview-presets-dialog .overview-layout-footer {
flex-wrap: wrap;
}
@media(max-width:480px) {
.overview-presets-create-controls>button {
flex: 1 1 auto;
}
.overview-presets-row {
align-items: flex-start;
flex-direction: column;
}
.overview-presets-actions {
width: 100%;
}
}
#panel-overview .activity-card .card-heading h2,
#panel-overview .overview-wide-chart .card-heading h2,
#panel-overview :is(.stat-chart, .stat-donut)>span:first-child,
#panel-overview :is(.system-card, .connection-card) .overview-chart-heading,
.function-live-chart-heading>strong,
.mini-live-chart>.mini-live-chart-head>span:first-child,
.traffic-chart-card>h3.subheading,
.vhost-chart-head>span:first-child {
font-family: var(--sans);
font-size: 12px;
font-weight: 650;
line-height: 1.35;
letter-spacing: normal;
text-transform: uppercase;
}
html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.activity-card .card-heading h2,
html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.overview-wide-chart .card-heading h2,
html[data-dashboard-density] #panel-overview .stat:is(.stat-chart, .stat-donut)>span:first-child {
font-size: 12px;
}
#panel-overview :is(.system-card, .connection-card) .overview-chart-heading {
color: var(--text);
margin: 0 0 5px;
}
:is(.overview-wide-summary, .overview-wide-legend, .overview-donut-legend, .www-method-legend, .stat-chart-legend, .mini-chart-legend, .connection-legend, .traffic-protocol-legend, .traffic-chart-legend, .function-live-chart-legend, .stream-chart-legend, .chart-footer, .vhost-chart-head, .network-interface-rate) {
font-family: var(--mono);
font-variant-numeric: tabular-nums;
}
#panel-overview .overview-wide-chart>.fine,
#panel-overview .stat-chart>small,
#panel-overview .www-top-row,
.traffic-bar-row {
font-family: var(--mono);
font-variant-numeric: tabular-nums;
}
#panel-overview :is(.overview-wide-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stat-chart-legend, .mini-chart-legend, .connection-legend, .traffic-protocol-legend, .stream-traffic-legend),
:is(.overview-wide-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stat-chart-legend, .mini-chart-legend, .connection-legend, .traffic-protocol-legend, .traffic-chart-legend, .function-live-chart-legend, .stream-chart-legend, .stream-traffic-legend) {
font-family: var(--mono);
font-size: 9px;
font-variant-numeric: tabular-nums;
}
#panel-overview :is(.overview-wide-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stat-chart-legend, .mini-chart-legend, .connection-legend, .traffic-protocol-legend, .stream-traffic-legend) :is(span, b, strong, small, li, em),
:is(.overview-wide-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stat-chart-legend, .mini-chart-legend, .connection-legend, .traffic-protocol-legend, .traffic-chart-legend, .function-live-chart-legend, .stream-chart-legend, .stream-traffic-legend) :is(span, b, strong, small, li, em) {
font-size: inherit;
font-family: inherit;
}
html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stream-traffic-legend),
html[data-dashboard-density] #panel-overview .stat :is(.stat-chart-legend, .overview-donut-legend, .www-ring-legend, .www-method-legend, .stream-traffic-legend) :is(span, b, small, strong, li) {
font-size: 9px;
font-family: var(--mono);
}
.vhost-detail.vhost-backend-trigger,
.vhost-live-metric.vhost-backend-trigger {
appearance: none;
display: block;
width: 100%;
min-width: 0;
margin: 0;
border-radius: 0;
color: var(--text);
text-align: left;
font: inherit;
cursor: pointer;
}
.vhost-detail.vhost-backend-trigger {
border: 0;
background: var(--bg);
}
.vhost-live-metric.vhost-backend-trigger {
border: 1px solid var(--line);
background: transparent;
}
.vhost-backend-trigger:is(:hover, [aria-expanded=true]) {
background: var(--panel2);
}
.vhost-backend-trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.vhost-backend-popup {
position: fixed;
z-index: 1600;
max-height: calc(min(360px, 100dvh - 24px));
overflow: auto;
padding: 12px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: var(--rp-radius-surface, 0);
box-shadow: 0 12px 35px var(--shadow-strong);
color: var(--text);
font-size: 11px;
}
.vhost-backend-popup-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
margin: 0 0 10px;
}
.vhost-backend-popup h4 {
margin: 0;
font-size: 12px;
font-weight: 650;
overflow-wrap: anywhere;
}
.vhost-backend-popup-close {
flex: 0 0 auto;
display: grid;
place-items: center;
width: 25px;
height: 25px;
padding: 0;
border: 1px solid var(--line);
background: var(--bg);
color: var(--text);
cursor: pointer;
font: 18px/1 var(--sans);
border-radius: var(--rp-radius-control, 0);
}
.vhost-backend-popup-close:hover {
background: var(--panel2);
}
.vhost-backend-popup-list {
display: grid;
gap: 6px;
padding: 0;
margin: 0;
list-style: none;
}
.vhost-backend-popup-row {
padding: 8px;
border: 1px solid var(--line);
border-radius: var(--rp-radius-surface, 0);
background: var(--bg);
min-width: 0;
}
.vhost-backend-popup-name {
display: block;
margin-bottom: 6px;
overflow-wrap: anywhere;
font: 650 11px var(--mono);
}
.vhost-backend-popup-columns {
display: grid;
grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
gap: 6px 10px;
}
.vhost-backend-popup-field {
min-width: 0;
}
.vhost-backend-popup-field>span {
display: block;
}
.vhost-backend-popup-field>span:first-child {
font-size: 9px;
color: var(--muted);
}
.vhost-backend-popup-field>span:last-child {
font: 10px/1.35 var(--mono);
overflow-wrap: anywhere;
}
.vhost-backend-popup-field.has-success>span:last-child {
color: var(--green);
}
.vhost-backend-popup-field.has-error>span:last-child {
color: var(--red);
}
.vhost-backend-popup-hint {
margin: 10px 0 0;
line-height: 1.4;
}
#panel-overview .dashboard-overview-grid>.card .mini-heading>h3,
#panel-overview .dashboard-overview-grid>.card .overview-chart-heading,
#panel-overview .dashboard-overview-grid>.card:is(.overview-wide-chart, .activity-card, .pool-card) .card-heading h2,
#panel-overview .dashboard-overview-grid>.stat>span:first-child {
font-family: var(--sans);
font-size: 12px;
font-weight: 600;
line-height: 1.35;
letter-spacing: normal;
text-transform: none;
}
#panel-overview .dashboard-overview-grid>[data-dashboard-widget] :is(small, .mini-metrics dt, .mini-metrics dd, .mini-live-chart-head, .memory-line, .chart-footer, .small-label, .meta, .fine) {
font-family: var(--mono);
font-variant-numeric: tabular-nums;
text-transform: none;
}
#panel-overview .dashboard-overview-grid .mini-live-chart>.mini-live-chart-head>span:first-child {
font-family: var(--mono);
font-size: 10px;
font-weight: 400;
line-height: 1.35;
letter-spacing: normal;
text-transform: none;
}
html[data-dashboard-density] #panel-overview .dashboard-overview-grid>.card.pool-card .card-heading h2 {
font-size: 12px;
}
#panel-cache>.card .card-heading h2,
#panel-overview .dashboard-overview-grid .mini-live-chart-head>span:first-child,
#panel-overview .dashboard-overview-grid>.card .mini-heading>h3,
#panel-overview .dashboard-overview-grid>.card .overview-chart-heading,
#panel-overview .dashboard-overview-grid>.card:not(.system-card):not(.connection-card) .card-heading h2,
#panel-overview .dashboard-overview-grid>.stat>span:first-child,
#panel-transform>.card .card-heading h2,
.function-live-chart-heading>strong,
.traffic-chart-card>h3.subheading,
.vhost-chart-head>span:first-child {
color: var(--muted);
}
+177 -9
View File
@@ -1,3 +1,158 @@
// Shared, anchored Vhost popover for backend quick-view and upstream HTTP
// protocol observations. Both consume existing live status without extra probes.
let activeVhostBackendPopup = null;
function closeVhostBackendPopup(returnFocus = false) {
const current = activeVhostBackendPopup;
if (!current) return;
activeVhostBackendPopup = null;
current.trigger.setAttribute("aria-expanded", "false");
current.node.remove();
if (returnFocus && current.trigger.isConnected) current.trigger.focus();
}
function positionVhostBackendPopup() {
const current = activeVhostBackendPopup;
if (!current) return;
const rect = current.trigger.getBoundingClientRect(), edge = 12, gap = 6;
const width = Math.min(430, Math.max(0, window.innerWidth - edge * 2));
const height = Math.min(current.node.scrollHeight, 360, Math.max(100, window.innerHeight - edge * 2));
const below = window.innerHeight - rect.bottom - edge - gap;
const above = rect.top - edge - gap;
const placeAbove = below < height && above > below;
const desiredTop = placeAbove ? rect.top - height - gap : rect.bottom + gap;
current.node.style.width = `${width}px`;
current.node.style.left = `${Math.max(edge, Math.min(rect.left, window.innerWidth - width - edge))}px`;
current.node.style.top = `${Math.max(edge, Math.min(desiredTop, window.innerHeight - height - edge))}px`;
}
function vhostPopupHeading(label) {
const heading = el("h4", label);
heading.id = "vhost-backend-popup-heading";
const headingRow = el("div", undefined, "vhost-backend-popup-heading");
const closeButton = el("button", "×", "vhost-backend-popup-close");
closeButton.type = "button";
closeButton.setAttribute("aria-label", t("close"));
closeButton.addEventListener("click", () => closeVhostBackendPopup(true));
headingRow.append(heading, closeButton);
return { heading, headingRow };
}
function refreshVhostProtocolPopup(current, vhost, members) {
const backend = (vhost.backends || []).find(item => item.id === current.backendId);
if (!backend) { closeVhostBackendPopup(); return; }
const member = members.get(backend.id);
const observed = upstreamProtocolBadgeText(backend, member);
if (!current.protocolView) {
const { heading, headingRow } = vhostPopupHeading(observed.label);
const info = el("p", "", "fine vhost-backend-popup-hint");
const list = el("ul", undefined, "vhost-backend-popup-list");
const row = el("li", undefined, "vhost-backend-popup-row");
const columns = el("div", undefined, "vhost-backend-popup-columns");
const counters = [];
for (const protocol of ["H1", "H2"]) {
const field = el("div", undefined, "vhost-backend-popup-field");
const value = el("span", "0");
field.append(el("span", protocol), value);
columns.append(field); counters.push(value);
}
row.append(columns); list.append(row);
current.node.append(headingRow, info, list);
current.node.setAttribute("aria-labelledby", heading.id);
current.protocolView = { heading, info, list, counters };
}
const view = current.protocolView;
const explanation = t(observed.recent ? "vhBackendProtocolRecent" : "vhBackendProtocolUnobserved");
if (view.heading.textContent !== observed.label) view.heading.textContent = observed.label;
if (view.info.textContent !== explanation) view.info.textContent = explanation;
view.list.hidden = !observed.recent;
if (observed.recent) {
const totals = [member.upstream_protocol_http1_total || 0, member.upstream_protocol_http2_total || 0];
totals.forEach((total, index) => {
const label = String(total);
if (view.counters[index].textContent !== label) view.counters[index].textContent = label;
});
}
positionVhostBackendPopup();
}
function refreshVhostBackendPopup(status = latestStatus) {
const current = activeVhostBackendPopup;
if (!current) return;
const vhost = (envelope?.config.vhosts || []).find(item => item.id === current.id);
if (!vhost || !current.trigger.isConnected) { closeVhostBackendPopup(); return; }
const pool = (status?.backend_pools || []).find(item => item.id === vhost.id);
const members = new Map((pool?.members || []).map(member => [member.id, member]));
if (current.backendId !== null) {
refreshVhostProtocolPopup(current, vhost, members);
return;
}
const { heading, headingRow } = vhostPopupHeading(`${t("vhBackendPopupTitle")} · ${vhost.id}`);
const list = el("ul", undefined, "vhost-backend-popup-list");
for (const backend of vhost.backends || []) {
const member = members.get(backend.id);
const row = el("li", undefined, "vhost-backend-popup-row");
row.append(el("strong", backend.id, "vhost-backend-popup-name"));
let address = "-", scheme = "-";
try {
const url = new URL(backend.url);
scheme = url.protocol.replace(":", "").toUpperCase();
const port = url.port || (scheme === "HTTPS" ? "443" : scheme === "HTTP" ? "80" : "");
address = `${url.hostname}${port ? `:${port}` : ""}`;
} catch { /* Configuration may have changed while status was refreshing. */ }
const upstream = upstreamProtocolBadgeText(backend, member);
const protocol = `${scheme} · ${upstream.mode}${upstream.observed ? ` → ${upstream.observed}` : ""}`;
const statusCode = Number(member?.last_status);
const hasStatus = member && Number.isInteger(statusCode) && statusCode >= 100 && statusCode <= 599;
const httpResponse = hasStatus ? `HTTP ${statusCode}` : t("vhBackendPopupNoResponse");
const statusLabel = !member ? t("vhHealthUnknown") : t(({
up: "vhUp", down: "vhDown", disabled: "vhDisabled", checking: "vhChecking", draining: "vhDraining"
})[member.status] || "vhHealthUnknown");
const columns = el("div", undefined, "vhost-backend-popup-columns");
for (const [key, value] of [
["vhBackendPopupAddress", address], ["vhBackendPopupProtocol", protocol],
["vhBackendPopupLastHttp", httpResponse], ["status", statusLabel]
]) {
const field = el("div", undefined, "vhost-backend-popup-field");
field.append(text("span", key), el("span", value));
if (key === "vhBackendPopupLastHttp" && hasStatus) field.classList.add(statusCode >= 500 ? "has-error" : statusCode < 400 ? "has-success" : "");
columns.append(field);
}
row.append(columns); list.append(row);
}
if (!list.children.length) list.append(el("li", t("vhBackendPopupEmpty"), "fine"));
const foot = el("p", t("vhBackendPopupHint"), "fine vhost-backend-popup-hint");
current.node.replaceChildren(headingRow, list, foot);
current.node.setAttribute("aria-labelledby", heading.id);
positionVhostBackendPopup();
}
function toggleVhostBackendPopup(id, trigger, backendId = null) {
if (activeVhostBackendPopup?.trigger === trigger) { closeVhostBackendPopup(); return; }
closeVhostBackendPopup();
const node = el("section", undefined, "vhost-backend-popup");
node.id = "vhost-backend-popup";
node.setAttribute("role", "dialog");
node.setAttribute("aria-modal", "false");
trigger.setAttribute("aria-expanded", "true");
activeVhostBackendPopup = { id, trigger, node, backendId, protocolView: null };
document.body.append(node);
refreshVhostBackendPopup();
}
function enableVhostBackendPopup(trigger, vhost, backendId = null) {
trigger.dataset.vhostBackendTrigger = vhost.id;
trigger.setAttribute("aria-haspopup", "dialog");
trigger.setAttribute("aria-expanded", "false");
trigger.setAttribute("aria-controls", "vhost-backend-popup");
if (backendId === null) trigger.setAttribute("aria-label", `${t("vhBackendPopupOpen")}: ${vhost.id}`);
trigger.addEventListener("click", () => toggleVhostBackendPopup(vhost.id, trigger, backendId));
}
document.addEventListener("pointerdown", event => {
const current = activeVhostBackendPopup;
if (current && !current.node.contains(event.target) && !current.trigger.contains(event.target)) closeVhostBackendPopup();
});
document.addEventListener("keydown", event => {
if (event.key === "Escape" && activeVhostBackendPopup) { event.preventDefault(); event.stopPropagation(); closeVhostBackendPopup(true); }
});
window.addEventListener("resize", positionVhostBackendPopup);
document.addEventListener("scroll", event => {
if (activeVhostBackendPopup && !activeVhostBackendPopup.node.contains(event.target)) positionVhostBackendPopup();
}, true);
const poolColumns = ["vhost", "backend", "status", "active", "requests", "errors", "latency", "traffic", "generation", "generationRequests", "generationAge", "rotations", "lastFailure", "lastRecovery"];
const vhostSortColumns = ["id", "hostname", "status", "listeners", "certificate", "backends"];
const defaultPoolTablePrefs = () => ({ sort: "vhost", descending: false, columns: [...poolColumns] });
@@ -107,7 +262,8 @@ function upstreamProtocolBadgeText(backend, member, now = Date.now()) {
const seen = Number(member?.upstream_protocol_last_seen_ms);
const recent = member && (member.upstream_protocol_observed === "h1" || member.upstream_protocol_observed === "h2") &&
Number.isFinite(seen) && seen > 0 && now >= seen && now - seen <= VHOST_PROTOCOL_FRESH_MS;
return { label: `${backend.id} · ${mode} → ${recent ? member.upstream_protocol_observed.toUpperCase() : "?"}`, recent };
const observed = recent ? member.upstream_protocol_observed.toUpperCase() : null;
return { label: `${backend.id} · ${mode} → ${observed || "?"}`, recent, mode, observed };
}
function renderUpstreamProtocolBadges(status) {
const members = new Map((status.backend_pools || []).flatMap(pool => (pool.members || []).map(member =>
@@ -119,13 +275,12 @@ function renderUpstreamProtocolBadges(status) {
if (!backend) return;
const member = members.get(`${vhost}\u0000${id}`);
const observed = upstreamProtocolBadgeText(backend, member, now);
node.textContent = observed.label;
if (node.textContent !== observed.label) node.textContent = observed.label;
node.dataset.observed = String(!!observed.recent);
const counts = member && observed.recent ? ` H1: ${member.upstream_protocol_http1_total || 0}; H2: ${member.upstream_protocol_http2_total || 0}.` : "";
node.title = `${t(observed.recent ? "vhBackendProtocolRecent" : "vhBackendProtocolUnobserved")}${counts}`;
});
}
function render() {
closeVhostBackendPopup();
if (!envelope) return;
if (!initialized) load(defaults(), [], false);
const config = envelope.config;
@@ -209,10 +364,21 @@ function render() {
: t("webServer"))
: `${(v.backends || []).length}`],
];
for (const [key, value] of detailItems) { const item = el("div", undefined, "vhost-detail"); item.append(text("span", key, "small-label"), el("b", value)); details.append(item); }
for (const [key, value] of detailItems) {
const isBackend = key === "vhBackendPool";
const item = el(isBackend ? "button" : "div", undefined, `vhost-detail${isBackend ? " vhost-backend-trigger" : ""}`);
if (isBackend) { item.type = "button"; enableVhostBackendPopup(item, v); }
item.append(text("span", key, "small-label"), el("b", value)); details.append(item);
}
box.append(details);
const live = el("div", undefined, "vhost-live-grid");
const metric = (key, suffix) => { const item = el("div", undefined, "vhost-live-metric"); item.append(text("span", key), el("strong", "-")); item.querySelector("strong").dataset[suffix] = v.id; live.append(item); };
const metric = (key, suffix) => {
const isHealth = key === "status";
const item = el(isHealth ? "button" : "div", undefined, `vhost-live-metric${isHealth ? " vhost-backend-trigger" : ""}`);
if (isHealth) { item.type = "button"; enableVhostBackendPopup(item, v); }
item.append(text("span", key), el("strong", "-"));
item.querySelector("strong").dataset[suffix] = v.id; live.append(item);
};
metric("status", "vhostHealth"); metric("vhRequests", "vhostRequests"); metric("vhActive", "vhostActive"); metric("vhErrors", "vhostErrors"); metric("vhLatency", "vhostLatency"); metric("vhTraffic", "vhostTraffic");
box.append(live);
const chart = el("div", undefined, "vhost-chart"); const chartHead = el("div", undefined, "vhost-chart-head"); chartHead.append(text("span", "vhLifetimeChart", "small-label")); const chartValue = el("span", "-", "fine"); chartValue.dataset.chartValueFor = v.id; chartHead.append(chartValue); chart.append(chartHead);
@@ -243,11 +409,12 @@ function render() {
// Retain their own badge class so observed H1/H2 keeps its green styling.
if (hasProxySource) {
for (const backend of v.backends || []) {
const badge = el("span", undefined, "vhost-backend-protocol-badge");
const badge = el("button", undefined, "vhost-backend-protocol-badge");
badge.type = "button";
badge.dataset.upstreamProtocolVhost = v.id;
badge.dataset.upstreamProtocolBackend = backend.id;
badge.textContent = upstreamProtocolBadgeText(backend, null).label;
badge.title = t("vhBackendProtocolUnobserved");
enableVhostBackendPopup(badge, v, backend.id);
policy.append(badge);
}
}
@@ -282,6 +449,7 @@ function vhostTrafficRate(id, metrics, snapshot, now, seen) {
}
function renderStatus(status) {
renderUpstreamProtocolBadges(status);
refreshVhostBackendPopup(status);
form.querySelectorAll(".vh-member").forEach(row => row.syncLocalRisk?.());
const rows = [], summaries = new Map(), aggregate = new Map();
const trafficSnapshot = status, trafficNow = performance.now(), seenTraffic = new Set(), seenVhostTraffic = new Set();
@@ -415,5 +583,5 @@ $("pool-open").addEventListener("click", () => setPanel("vhosts"));
initPoolTableTools();
$("location-help-close")?.addEventListener("click", () => $("location-help-dialog")?.close());
window.rustProxyDrafts.register("vhost", { labelKey: "vhosts", isOpen: () => dialog.open, isDirty: () => dirty, suspend: suspendDraft, restore: restoreDraft, close: () => { dirty = false; if (dialog.open) dialog.close(); } });
window.rustProxyVhosts = { render, renderStatus, applyPreferences, reset() { dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; backendHealthEditorControl = null; poolTablePrefs = defaultPoolTablePrefs(); vhostListPrefs = defaultVhostListPrefs(); poolPreferenceSaveRunning = false; poolPreferenceSaveQueued = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; if ($("pool-search")) $("pool-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort; const vhostSort = $("vhost-sort"); if (vhostSort) vhostSort.value = vhostListPrefs.sort; syncVhostSortDirection(); applyPoolTableView(); } };
window.rustProxyVhosts = { render, renderStatus, applyPreferences, reset() { closeVhostBackendPopup(); dialog.close(); draftEpoch++; stagedCertificates = []; uploading = false; editing = null; revision = null; dirty = false; initialized = false; backendHealthEditorControl = null; poolTablePrefs = defaultPoolTablePrefs(); vhostListPrefs = defaultVhostListPrefs(); poolPreferenceSaveRunning = false; poolPreferenceSaveQueued = false; chartHistory.clear(); backendTrafficHistory.clear(); vhostTrafficHistory.clear(); container.replaceChildren(); $("vhost-search").value = ""; if ($("pool-search")) $("pool-search").value = ""; $("vhost-list").replaceChildren(); $("pool-rows").replaceChildren(); const sort = $("pool-sort"); if (sort) sort.value = poolTablePrefs.sort; const vhostSort = $("vhost-sort"); if (vhostSort) vhostSort.value = vhostListPrefs.sort; syncVhostSortDirection(); applyPoolTableView(); } };
if (envelope) render(); if (latestStatus) renderStatus(latestStatus);