This commit is contained in:
Mateusz Gruszczyński committed 2026-11-08 23:23:21 +01:00
1 parent 1ac94213e0
commit 0119b11005
28 files changed
+698 -153

No files matched your search

+12 -4
View File
@@ -61,15 +61,23 @@ function translate() {
syncSettingsDirtyIndicators();
window.rustProxyIcons?.refresh?.();
}
function hideToast() { const node = $("toast"); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
function toastNode() {
let node = document.getElementById("toast");
if (!node) { // the node lived inside a dialog that was rebuilt; recreate it instead of failing every later toast
node = document.createElement("div"); node.id = "toast"; node.className = "toast"; node.hidden = true;
node.setAttribute("role", "status"); node.setAttribute("aria-live", "polite"); node.setAttribute("popover", "manual"); document.body.append(node);
}
return node;
}
function hideToast() { const node = toastNode(); if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover(); node.hidden = true; }
function raiseToast() {
const node = $("toast"); if (node.hidden) return;
const node = toastNode(); if (node.hidden) return;
const modal = [...document.querySelectorAll("dialog[open]")].at(-1), parent = modal || document.body;
if (typeof node.hidePopover === "function" && node.matches(":popover-open")) node.hidePopover();
if (node.parentElement !== parent) parent.append(node);
if (typeof node.showPopover === "function") node.showPopover();
}
function toast(message, error = false) { clearTimeout(toastTimer); const node = $("toast"); node.textContent = message; node.classList.toggle("error", error); node.hidden = false; raiseToast(); toastTimer = setTimeout(hideToast, error ? 10000 : 4000); }
function toast(message, error = false) { clearTimeout(toastTimer); const node = toastNode(); node.textContent = message; node.classList.toggle("error", error); node.hidden = false; raiseToast(); toastTimer = setTimeout(hideToast, error ? 10000 : 4000); }
function authMessage(message = "") { const node = $("auth-message"); if (!node) return; node.textContent = message; node.classList.toggle("error", Boolean(message)); }
function fieldError(target, message) { const error = new Error(message); error.fieldTarget = target; return error; }
function clearFieldError(target) {
@@ -134,7 +142,7 @@ function apiErrorMessage(path, response, value) {
const area = apiArea(path);
return area ? t("errorContext").replace("{area}", t(area)).replace("{message}", detail) : detail;
}
new MutationObserver(records => { if (!records.some(r => r.target.tagName === "DIALOG")) return; const node = $("toast"); if (!node.hidden) raiseToast(); }).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
new MutationObserver(records => { if (!records.some(r => r.target.tagName === "DIALOG")) return; const node = toastNode(); if (!node.hidden) raiseToast(); }).observe(document.body, { subtree: true, attributes: true, attributeFilter: ["open"] });
async function api(path, options = {}) {
const started = performance.now(), trackLatency = path.startsWith("/api/");
try {
+1 -1
View File
@@ -81,7 +81,7 @@ window.rustProxyDashboardLayout = (() => {
if (ADMIN_DASHBOARD_BY_ID.get(id)?.category === "panels") {
const card = el("article", undefined, "overview-layout-panel-preview");
card.append(el("strong", label(id)));
const chart = id === "backend_table" ? null : original.querySelector?.("svg");
const chart = id === "backend_tiny" || id === "backend_table" ? null : original.querySelector?.("svg");
if (chart) {
const graphic = chart.cloneNode(true);
for (const node of [graphic, ...graphic.querySelectorAll("[id]")]) node.removeAttribute("id");
+68 -1
View File
@@ -226,7 +226,7 @@ $("reset-certificate").addEventListener("click", () => { $("certificate-form").r
syncCertificateFileSource();
let systemSamples = [], connectionOpenSamples = [], connectionExchangeSamples = [], cacheMemorySamples = [], cacheTransientSamples = [], cacheFillSamples = [], cacheColdQueueSamples = [], cacheColdIoSamples = [], cacheColdStorageSamples = [], cacheHitSamples = [], cacheMissSamples = [], cacheBypassSamples = [], lastCacheCounters = null, lastCacheOutcomeRates = null,
processCpuSamples = [], processRssSamples = [], networkRxSamples = [], networkTxSamples = [], responseLatencySamples = [], networkInterfaceSamples = new Map(), settingsDirty = new Set();
processCpuSamples = [], processRssSamples = [], processFdSamples = [], processThreadSamples = [], networkRxSamples = [], networkTxSamples = [], responseLatencySamples = [], networkInterfaceSamples = new Map(), settingsDirty = new Set();
const hiddenNetworkInterfaces = (() => {
try {
const parsed = JSON.parse(localStorage.getItem("rp-hidden-network-interfaces") || "[]");
@@ -257,6 +257,68 @@ function setOptionalMetric(id, supported, text) {
const row = node.closest("div"); if (row) row.hidden = !supported;
if (supported) node.textContent = text;
}
function setProcessMeter(id, used, limit) {
const meter = $(id)?.closest("div")?.querySelector(".pr-meter"); if (!meter) return;
const usedNumber = Number(used), limitNumber = Number(limit);
const ok = used != null && limit != null && Number.isFinite(usedNumber) && Number.isFinite(limitNumber) && limitNumber > 0;
meter.hidden = !ok; if (!ok) return;
const percent = Math.max(0, Math.min(100, usedNumber / limitNumber * 100));
const bar = meter.firstElementChild; bar.style.width = `${percent}%`; bar.classList.toggle("is-high", percent >= 85);
}
function renderProcessOverview(d, status, live, systemSample) {
if (!$("po-cpu")) return;
if (systemSample) {
const memory = status.memory_accounting || {};
const platform = [d.platform, d.environment].filter(Boolean).join(" / ");
setOptionalMetric("po-platform", !!d.platform && d.platform !== "Unsupported", platform);
setOptionalMetric("po-threads", d.process_threads != null, fmt(d.process_threads));
setOptionalMetric("po-start", d.process_start_time_seconds != null, d.process_start_time_seconds == null ? "" : formatDateTime(Number(d.process_start_time_seconds) * 1000));
setOptionalMetric("po-uptime", status.uptime_seconds != null, status.uptime_seconds == null ? "" : uptime(status.uptime_seconds));
setOptionalMetric("po-cpu", d.process_cpu_percent != null, `${fmt(d.process_cpu_percent, 1)}%`);
setProcessMeter("po-cpu", d.process_cpu_percent, 100);
setOptionalMetric("po-rss-total", d.process_rss_bytes != null, fmtBytes(d.process_rss_bytes));
setProcessMeter("po-rss-total", d.process_rss_bytes, d.cgroup_memory_max_bytes);
const fds = knownPair(d.open_fds, d.max_fds, value => fmt(value), t("unlimited"));
setOptionalMetric("po-fds", fds != null, fds);
setProcessMeter("po-fds", d.open_fds, d.max_fds);
const cgroupMemory = knownPair(d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("po-cgroup", cgroupMemory != null, cgroupMemory);
setProcessMeter("po-cgroup", d.cgroup_memory_current_bytes, d.cgroup_memory_max_bytes);
setOptionalMetric("po-rss-anon", d.process_rss_anon_bytes != null, fmtBytes(d.process_rss_anon_bytes));
const rssFile = knownPair(d.process_rss_file_bytes, d.process_rss_shmem_bytes, value => fmtBytes(value));
setOptionalMetric("po-rss-file", rssFile != null, rssFile);
setOptionalMetric("po-non-cache", memory.non_cache_rss_bytes != null, fmtBytes(memory.non_cache_rss_bytes));
const virtualMemory = knownPair(d.process_virtual_memory_bytes, d.process_virtual_memory_max_bytes, value => fmtBytes(value), t("unlimited"));
setOptionalMetric("po-virtual", virtualMemory != null, virtualMemory);
const dataSwap = knownPair(d.process_data_bytes, d.process_swap_bytes, value => fmtBytes(value));
setOptionalMetric("po-data-swap", dataSwap != null, dataSwap);
const split = $("po-rss-split"), anon = Number(d.process_rss_anon_bytes), file = Number(d.process_rss_file_bytes);
const known = d.process_rss_anon_bytes != null && d.process_rss_file_bytes != null && anon + file > 0;
split.hidden = !known;
if (known) { split.firstElementChild.style.width = `${anon / (anon + file) * 100}%`; split.lastElementChild.style.width = `${file / (anon + file) * 100}%`; }
}
$("po-cpu-sparkline").setAttribute("points", sparklinePoints(processCpuSamples));
window.rustProxyChartScale.axes("po-cpu-sparkline", Math.max(1, ...processCpuSamples), "percent",
processCpuSamples.length, { top: 5 / 60, bottom: 55 / 60 });
setChartText($("po-cpu-chart-value"), live.process_cpu_percent == null ? "" : `${fmt(live.process_cpu_percent, 1)}%`);
$("po-rss-sparkline").setAttribute("points", sparklinePoints(processRssSamples));
window.rustProxyChartScale.axes("po-rss-sparkline", Math.max(1, ...processRssSamples), "bytes",
processRssSamples.length, { top: 5 / 60, bottom: 55 / 60 });
setChartText($("po-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
$("po-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
$("po-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
const openFdsNow = live.open_fds ?? d.open_fds, threadsNow = live.process_threads ?? d.process_threads;
$("po-fd-sparkline").setAttribute("points", sparklinePoints(processFdSamples));
window.rustProxyChartScale.axes("po-fd-sparkline", Math.max(1, ...processFdSamples), "count",
processFdSamples.length, { top: 5 / 60, bottom: 55 / 60 });
setChartText($("po-fd-chart-value"), openFdsNow == null ? "" : fmt(openFdsNow));
$("po-fd-sparkline").closest(".mini-live-chart").hidden = openFdsNow == null;
$("po-thread-sparkline").setAttribute("points", sparklinePoints(processThreadSamples));
window.rustProxyChartScale.axes("po-thread-sparkline", Math.max(1, ...processThreadSamples), "count",
processThreadSamples.length, { top: 5 / 60, bottom: 55 / 60 });
setChartText($("po-thread-chart-value"), threadsNow == null ? "" : fmt(threadsNow));
$("po-thread-sparkline").closest(".mini-live-chart").hidden = threadsNow == null;
}
function knownPair(left, right, format, zeroRight = null) {
if (left == null && right == null) return null;
if (left == null) return format(right);
@@ -528,6 +590,9 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
if (appendSample) {
if (live.process_cpu_percent != null) { processCpuSamples.push(Number(live.process_cpu_percent)); processCpuSamples = processCpuSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
if (live.process_rss_bytes != null) { processRssSamples.push(Number(live.process_rss_bytes)); processRssSamples = processRssSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
const openFds = live.open_fds ?? d.open_fds, threadCount = live.process_threads ?? d.process_threads;
if (openFds != null) { processFdSamples.push(Number(openFds)); processFdSamples = processFdSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
if (threadCount != null) { processThreadSamples.push(Number(threadCount)); processThreadSamples = processThreadSamples.slice(-window.rustProxyOverviewSamples.getLimit()); }
}
const cacheOutcomePeak = Math.max(1, ...cacheHitSamples, ...cacheMissSamples, ...cacheBypassSamples);
$("cache-hit-sparkline").setAttribute("points", sparklinePoints(cacheHitSamples, 300, 60, 5, cacheOutcomePeak));
@@ -599,6 +664,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
setChartText($("process-rss-chart-value"), live.process_rss_bytes == null ? "" : fmtBytes(live.process_rss_bytes));
$("process-cpu-sparkline").closest(".mini-live-chart").hidden = live.process_cpu_percent == null;
$("process-rss-sparkline").closest(".mini-live-chart").hidden = live.process_rss_bytes == null;
renderProcessOverview(d, status, live, systemSample);
updateCacheMemorySizing();
const networkRateSupported = live.network_rx_bytes_per_second != null && live.network_tx_bytes_per_second != null;
if (systemSample) setOptionalMetric("system-network", d.network_rx_bytes_per_second != null && d.network_tx_bytes_per_second != null, `${fmtBytes(d.network_rx_bytes_per_second)} / ${fmtBytes(d.network_tx_bytes_per_second)}`);
@@ -628,6 +694,7 @@ function renderDiagnostics(status, appendSample = true, systemSample = appendSam
$("system-sample-age").hidden = age == null;
if (age != null) $("system-sample-age").textContent = `${fmt(age)} s`;
$("system-sample-age").classList.toggle("status-error", age != null && age > 10);
if ($("po-sample-age")) { $("po-sample-age").hidden = age == null; if (age != null) $("po-sample-age").textContent = `${fmt(age)} s`; $("po-sample-age").classList.toggle("status-error", age != null && age > 10); }
const l = status.listeners || {};
$("listener-pending").hidden = !l.restart_required;
$("listener-pending").textContent = t("restartPending");