0.34.11
This commit is contained in:
1 parent
63cdecea6c
commit
37c197c095
17 files changed
+96
-35
No files matched your search
@@ -2,7 +2,7 @@
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "RustProxy Administration API",
|
||||
"version": "0.34.10",
|
||||
"version": "0.34.11",
|
||||
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust. The API also permits explicitly scoped GeneralApiBearer tokens on a narrow route allowlist, with RO/RW and source CIDR checks; API-token management and Prometheus-token management are browser-session-only."
|
||||
},
|
||||
"servers": [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Stable plot ranges: percentages are absolute 0-100; unbounded metrics have
|
||||
// unit-appropriate zero-based floors, expanding only for real higher readings.
|
||||
// Overview charts retain stable, expanding ranges (and fixed 0-100% axes).
|
||||
// Vhost request-rate sparklines use a separate, adaptive plotting policy.
|
||||
window.rustProxyChartScale = (() => {
|
||||
const floors = { rate: 100, count: 10, ms: 100, bytes: 64 * 1024 * 1024, "bytes/s": 64 * 1024 };
|
||||
const ceilings = new Map();
|
||||
@@ -17,8 +17,28 @@ window.rustProxyChartScale = (() => {
|
||||
ceilings.set(key, result);
|
||||
return result;
|
||||
}
|
||||
// Vhost live charts show the entire session history, but their Y range
|
||||
// follows the recent typical rate. A one-off surge must not flatten the
|
||||
// subsequent request activity; the real (unclipped) peak stays in the legend.
|
||||
// The recent quantile also prevents a range that only grows over time.
|
||||
function liveRate(values) {
|
||||
const samples = (Array.isArray(values) ? values : []).map(value =>
|
||||
typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0);
|
||||
const maximum = Math.max(0, ...samples);
|
||||
const recent = samples.slice(-24).sort((a, b) => a - b);
|
||||
// Interpolation would allow a single extreme point to dominate small
|
||||
// histories. Nearest lower rank excludes that isolated maximum.
|
||||
const typical = recent.length ? recent[Math.floor((recent.length - 1) * 0.85)] : 0;
|
||||
const scale = Math.max(1, typical * 1.25);
|
||||
const points = samples.map((value, index) => {
|
||||
const x = index * 320 / Math.max(1, samples.length - 1);
|
||||
const y = 50 - Math.min(value / scale, 1) * 44;
|
||||
return `${x},${y}`;
|
||||
}).join(" ");
|
||||
return { points, peak: maximum, scale, clipped: maximum > scale };
|
||||
}
|
||||
function reset(prefix) {
|
||||
for (const key of ceilings.keys()) if (key.startsWith(prefix)) ceilings.delete(key);
|
||||
}
|
||||
return { peak, reset };
|
||||
return { peak, liveRate, reset };
|
||||
})();
|
||||
+11
-5
@@ -278,12 +278,18 @@
|
||||
view.fields.vhErrors.textContent = metrics ? fmt(metrics.errors) : "-";
|
||||
view.fields.vhLatency.textContent = Number(metrics?.ewma_latency_ms) > 0 ? `${fmt(metrics.ewma_latency_ms, 1)} ms` : "-";
|
||||
view.fields.vhTraffic.textContent = !history || history.paused ? "-" : `${rateBytes(history.traffic)}/s`;
|
||||
if (!history) { view.chartValue.textContent = "-"; view.line.setAttribute("points", ""); continue; }
|
||||
if (!history) {
|
||||
view.chartValue.textContent = "-"; view.chartValue.removeAttribute("title");
|
||||
view.line.setAttribute("points", ""); view.line.parentNode?.setAttribute("aria-label", t("vhLifetimeChart"));
|
||||
continue;
|
||||
}
|
||||
const data = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points;
|
||||
const peak = Math.max(0, ...data), scale = Math.max(1, peak);
|
||||
const points = data.map((value, index) => `${index * 320 / Math.max(1, data.length - 1)},${50 - value / scale * 44}`).join(" ");
|
||||
view.line.setAttribute("points", points);
|
||||
view.chartValue.textContent = `${history.paused ? "-" : history.rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${peak.toFixed(1)}`;
|
||||
const plot = window.rustProxyChartScale.liveRate(data);
|
||||
view.line.setAttribute("points", plot.points);
|
||||
const scaleHint = `${t("vhChartAdaptiveScale")}: 0–${plot.scale.toFixed(1)} ${t("vhReqPerSecond")}${plot.clipped ? ` · ${t("vhChartClippedPeak")}` : ""}`;
|
||||
view.line.parentNode?.setAttribute("aria-label", `${t("vhLifetimeChart")} · ${scaleHint}`);
|
||||
view.chartValue.textContent = `${history.paused ? "-" : history.rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${plot.peak.toFixed(1)}`;
|
||||
view.chartValue.title = scaleHint;
|
||||
}
|
||||
}
|
||||
function selectHealthMode(value) { const control = el("select", undefined, "docker-health-mode"); for (const [name, key] of [["http", "dockerHealthHttp"], ["tcp", "dockerHealthTcp"], ["off", "dockerHealthOff"]]) { const option = el("option", t(key)); option.value = name; control.append(option); } control.value = value; return control; }
|
||||
|
||||
@@ -536,8 +536,17 @@ function renderStatus(status) {
|
||||
}
|
||||
if (history.points.length >= 120) { const compacted = []; for (let i = 0; i < history.points.length; i += 2) compacted.push((history.points[i] + history.points[i + 1]) / 2); history.points = compacted; history.bucketSize *= 2; }
|
||||
chartHistory.set(id, history);
|
||||
const displayPoints = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points; const actualPeak = Math.max(0, ...displayPoints), scalePeak = Math.max(1, actualPeak); const points = displayPoints.map((value, index) => `${index * 320 / Math.max(1, displayPoints.length - 1)},${50 - value / scalePeak * 44}`).join(" ");
|
||||
const svg = document.querySelector(`[data-chart-for="${CSS.escape(id)}"]`); svg?.querySelector("polyline")?.setAttribute("points", points); const label = document.querySelector(`[data-chart-value-for="${CSS.escape(id)}"]`); if (label) label.textContent = `${pausedRate ? "-" : rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${actualPeak.toFixed(1)}`;
|
||||
const displayPoints = history.pendingCount ? [...history.points, history.pendingSum / history.pendingCount] : history.points;
|
||||
const plot = window.rustProxyChartScale.liveRate(displayPoints);
|
||||
const svg = document.querySelector(`[data-chart-for="${CSS.escape(id)}"]`);
|
||||
svg?.querySelector("polyline")?.setAttribute("points", plot.points);
|
||||
const scaleHint = `${t("vhChartAdaptiveScale")}: 0–${plot.scale.toFixed(1)} ${t("vhReqPerSecond")}${plot.clipped ? ` · ${t("vhChartClippedPeak")}` : ""}`;
|
||||
svg?.setAttribute("aria-label", `${t("vhLifetimeChart")} · ${scaleHint}`);
|
||||
const label = document.querySelector(`[data-chart-value-for="${CSS.escape(id)}"]`);
|
||||
if (label) {
|
||||
label.textContent = `${pausedRate ? "-" : rate.toFixed(1)} ${t("vhReqPerSecond")} · ${t("vhPeak")} ${plot.peak.toFixed(1)}`;
|
||||
label.title = scaleHint;
|
||||
}
|
||||
}
|
||||
const editingVhost = editing ? configuredVhosts.get(editing) : null;
|
||||
const editingHasProxy = editingVhost && (editingVhost.source?.type !== "web_server" || (editingVhost.routes || []).some(route => !route.response && (route.source?.type || "reverse_proxy") === "reverse_proxy"));
|
||||
|
||||
Reference in new issue
Block a user