This commit is contained in:
Mateusz Gruszczyński committed 2026-10-03 10:05:46 +02:00
1 parent 5e23e9af02
commit 3883a21bc4
56 files changed
+1640 -403

No files matched your search

+116 -4
View File
@@ -70,6 +70,7 @@ async function loadLanguages() {
$("language").value = language;
}
let latestStatus = null, certificateInventory = new Map();
let selectedCacheMemoryPreset = null, selectedCacheColdPreset = null, selectedTransformPreset = null;
function translate() {
clearTimeout(toastTimer); hideToast();
document.documentElement.lang = locale(); document.documentElement.dir = activePack()?.meta.direction || "ltr";
@@ -633,6 +634,11 @@ function renderDiagnostics(status, appendSample = true) {
$("cache-objects").textContent = `${fmt(c.entries)} / ${fmtBytes(c.resident_bytes)}`;
$("cache-memory-breakdown").textContent = `${fmtBytes(c.resident_bytes)} / ${fmtBytes(c.transient_bytes)}`;
$("cache-transient-breakdown").textContent = `${fmtBytes(c.fill_bytes_active)} / ${fmtBytes(c.cold_queue_bytes)} / ${fmtBytes((Number(c.cold_write_bytes_active) || 0) + (Number(c.cold_read_bytes_active) || 0))}`;
const coldStorage = knownPair(c.cold_bytes, c.cold_limit_bytes, value => fmtBytes(value));
const coldObjects = knownPair(c.cold_entries, c.cold_limit_entries, value => fmt(value));
$("cache-cold-storage").textContent = envelope?.config.cache?.cold_enabled === true
? `${coldStorage ?? "-"}${coldObjects == null ? "" : ` · ${coldObjects} ${t("cacheColdStorageObjects")}`}`
: t("notEnabled");
$("cache-evictions").textContent = `${fmt(c.evictions)} / ${fmt(c.expired)}`;
if (appendSample) {
if (c.resident_bytes != null) { cacheMemorySamples.push(Number(c.resident_bytes)); cacheMemorySamples = cacheMemorySamples.slice(-60); }
@@ -664,6 +670,16 @@ function renderDiagnostics(status, appendSample = true) {
setOptionalMetric("process-start-time", d.process_start_time_seconds != null, d.process_start_time_seconds == null ? "" : new Date(Number(d.process_start_time_seconds) * 1000).toLocaleString(locale()));
setOptionalMetric("process-uptime", status.uptime_seconds != null, status.uptime_seconds == null ? "" : uptime(status.uptime_seconds));
setOptionalMetric("process-rss-total", d.process_rss_bytes != null, fmtBytes(d.process_rss_bytes));
const memory = status.memory_accounting || {};
const hotMemory = knownPair(memory.cache_hot_bytes, memory.cache_hot_limit_bytes, value => fmtBytes(value));
const transientMemory = knownPair(memory.cache_transient_bytes, memory.cache_transient_limit_bytes, value => fmtBytes(value));
const transformMemory = knownPair(memory.transform_reserved_bytes, memory.transform_budget_bytes, value => fmtBytes(value));
const transformInflight = knownPair(memory.transform_inflight, memory.transform_max_inflight, value => fmt(value));
setOptionalMetric("memory-cache-hot", hotMemory != null, hotMemory);
setOptionalMetric("memory-cache-transient", transientMemory != null, transientMemory);
setOptionalMetric("memory-transform", transformMemory != null, transformMemory);
setOptionalMetric("memory-transform-inflight", transformInflight != null, transformInflight);
setOptionalMetric("memory-non-cache-rss", memory.non_cache_rss_bytes != null, fmtBytes(memory.non_cache_rss_bytes));
setOptionalMetric("process-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("process-rss-file", rssFile != null, rssFile);
@@ -736,7 +752,7 @@ function renderSettings() {
if (!settingsDirty.has("response-form")) {
const server = config.server_header || {}, compression = config.compression || {}, diagnostics = config.diagnostic_headers || {};
$("expose-request-id").checked = !!diagnostics.request_id;
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.24.5";
$("server-mode").value = server.mode || "preserve"; $("server-value").value = server.value || "RustProxy/v0.24.7";
for (const name of ["gzip", "br", "zstd", "deflate"]) $(`compress-${name}`).checked = !!compression[name];
$("compress-min").value = compression.min_bytes ?? 1024; syncServerMode();
}
@@ -757,12 +773,15 @@ function renderSettings() {
$("cache-query").checked = !!c.bypass_query; $("cache-prefixes").value = (c.bypass_prefixes || []).join("\n");
$("cache-static-cookie").checked = c.static_ignore_cookies !== false; $("cache-static-extensions").value = (c.static_extensions || []).join("\n");
$("cache-status-header").checked = !!c.expose_status; $("cache-hits-header").checked = !!c.expose_hits; $("cache-rfc-header").checked = !!c.expose_cache_status; $("cache-resident-age-header").checked = !!c.expose_resident_age;
setCacheMemoryPresetSelection(["conservative", "recommended", "performance"].includes(c.memory_preset) ? c.memory_preset : null);
setCacheColdPresetSelection(c.cold_preset === "recommended" ? "recommended" : null);
syncColdCachePolicy();
syncAdvancedCachePolicy();
updateCacheMemorySizing();
}
if (!settingsDirty.has("transform-form")) {
const tr = config.transform || {};
setTransformPresetSelection(["conservative", "recommended", "performance", "uber"].includes(tr.preset) ? tr.preset : null);
$("transform-enabled").checked = !!tr.enabled;
$("transform-javascript").checked = tr.javascript !== false;
$("transform-css").checked = tr.css !== false;
@@ -771,6 +790,9 @@ function renderSettings() {
$("transform-json").checked = !!tr.json;
$("transform-extensions").value = (tr.extensions || ["js", "mjs", "css", "html", "htm", "svg", "json"]).join("\n");
$("transform-max-body").value = (tr.max_body_bytes ?? 2097152) / 1024;
$("transform-memory-budget").value = (tr.memory_budget_bytes ?? 134217728) / 1048576;
$("transform-max-inflight").value = tr.max_inflight ?? 32;
updateTransformResourceSizing();
}
if (!settingsDirty.has("logging-form")) {
const l = config.logging || {}, file = l.file || {}, logstash = l.logstash || {}, loki = l.loki || {}, format = l.format || {};
@@ -814,6 +836,29 @@ function cachePresetValues(kind, bytes) {
const entries = Math.max(1024, Math.min(16384, Math.round(hot * 16)));
return { hot, transient, queue, objectKiB: objectMiB * 1024, entries, eviction: profile.eviction };
}
function cacheMemoryPresetName(kind) {
return kind ? t({ conservative: "cachePresetConservative", recommended: "cachePresetRecommended", performance: "cachePresetPerformance" }[kind]) : t("cachePresetCustom");
}
function setCacheMemoryPresetSelection(kind) {
selectedCacheMemoryPreset = ["conservative", "recommended", "performance"].includes(kind) ? kind : null;
document.querySelectorAll("[data-cache-memory-preset]").forEach(button => {
const active = button.dataset.cacheMemoryPreset === selectedCacheMemoryPreset;
button.classList.toggle("primary", active); button.classList.toggle("outline", !active);
button.setAttribute("aria-pressed", String(active));
});
const badge = $("cache-memory-preset-selected");
if (badge) badge.textContent = `${t("cachePresetSelected")}: ${cacheMemoryPresetName(selectedCacheMemoryPreset)}`;
}
function setCacheColdPresetSelection(kind) {
selectedCacheColdPreset = kind === "recommended" ? "recommended" : null;
document.querySelectorAll("[data-cache-cold-preset]").forEach(button => {
const active = button.dataset.cacheColdPreset === selectedCacheColdPreset;
button.classList.toggle("primary", active); button.classList.toggle("outline", !active);
button.setAttribute("aria-pressed", String(active));
});
const badge = $("cache-cold-preset-selected");
if (badge) badge.textContent = `${t("cachePresetSelected")}: ${selectedCacheColdPreset ? t("cachePresetRecommended") : t("cachePresetCustom")}`;
}
function updateCacheMemorySizing() {
const detected = effectiveCacheMemory(), badge = $("cache-memory-detected"), warning = $("cache-memory-warning");
if (!badge || !warning) return;
@@ -854,8 +899,65 @@ function applyCacheMemoryPreset(kind) {
$("cache-memory").value = preset.hot; $("cache-transient-memory").value = preset.transient;
$("cache-object").value = preset.objectKiB; $("cache-entries").value = preset.entries;
$("cache-eviction-limit").value = preset.eviction; $("cache-cold-queue-bytes").value = preset.queue;
setCacheMemoryPresetSelection(kind);
setSettingsDirty("cache-form"); updateCacheMemorySizing();
}
function applyRecommendedColdCachePreset() {
const transient = Number($("cache-transient-memory").value);
const queue = Number.isFinite(transient) && transient > 0 ? Math.max(0.0625, Math.floor((transient * .5) / 8) * 8 || Math.min(transient, 8)) : 32;
$("cache-cold-enabled").checked = true;
if (!$("cache-cold-path").value.trim()) $("cache-cold-path").value = "cache/cold";
$("cache-cold-on-idle").checked = true; $("cache-cold-on-age").checked = false;
$("cache-cold-on-lru").checked = true; $("cache-cold-on-shutdown").checked = true;
$("cache-cold-after").value = 300; $("cache-cold-max-hot").value = 3600;
$("cache-cold-gc-interval").value = 60; $("cache-cold-gc-max-delete").value = 256;
$("cache-cold-queue-bytes").value = Math.min(queue, transient || queue);
setCacheColdPresetSelection("recommended");
syncColdCachePolicy(); setSettingsDirty("cache-form"); updateCacheMemorySizing();
}
function transformPresetValues(kind) {
return {
conservative: { memoryMiB: 32, bodyKiB: 1024, inflight: 8 },
recommended: { memoryMiB: 128, bodyKiB: 2048, inflight: 32 },
performance: { memoryMiB: 512, bodyKiB: 8192, inflight: 64 },
uber: { memoryMiB: 1024, bodyKiB: 32768, inflight: 128 }
}[kind] || { memoryMiB: 128, bodyKiB: 2048, inflight: 32 };
}
function transformPresetName(kind) {
return kind ? t({ conservative: "transformPresetConservative", recommended: "transformPresetRecommended", performance: "transformPresetPerformance", uber: "transformPresetUber" }[kind]) : t("cachePresetCustom");
}
function setTransformPresetSelection(kind) {
selectedTransformPreset = ["conservative", "recommended", "performance", "uber"].includes(kind) ? kind : null;
document.querySelectorAll("[data-transform-preset]").forEach(button => {
const active = button.dataset.transformPreset === selectedTransformPreset;
button.classList.toggle("primary", active); button.classList.toggle("outline", !active);
button.setAttribute("aria-pressed", String(active));
});
const badge = $("transform-preset-selected");
if (badge) badge.textContent = `${t("cachePresetSelected")}: ${transformPresetName(selectedTransformPreset)}`;
}
function updateTransformResourceSizing() {
const warning = $("transform-memory-warning"); if (!warning) return;
const memoryMiB = Number($("transform-memory-budget")?.value), bodyKiB = Number($("transform-max-body")?.value), inflight = Number($("transform-max-inflight")?.value);
const detected = effectiveCacheMemory();
const budgetBytes = memoryMiB * 1048576, bodyBytes = bodyKiB * 1024;
const worstEncodedEstimate = bodyBytes * 3;
const budgetCannotFitMax = Number.isFinite(budgetBytes) && Number.isFinite(worstEncodedEstimate) && budgetBytes < worstEncodedEstimate;
const highHostShare = detected && Number.isFinite(budgetBytes) && budgetBytes > detected.bytes * .5;
warning.hidden = !(budgetCannotFitMax || highHostShare);
if (budgetCannotFitMax) warning.textContent = t("transformBudgetTooSmallWarning");
else if (highHostShare) warning.textContent = `${t("transformBudgetHighWarning")} ${fmtBytes(budgetBytes)} / ${fmtBytes(detected.bytes)}.`;
const summary = $("transform-preset-summary");
if (summary) summary.textContent = `${t("transformResourceCurrent")}: ${Number.isFinite(memoryMiB) ? memoryMiB : "-"} MiB · ${Number.isFinite(bodyKiB) ? bodyKiB : "-"} KiB · ${t("transformMaxInflightShort")} ${Number.isFinite(inflight) ? inflight : "-"}`;
}
function applyTransformPreset(kind) {
const preset = transformPresetValues(kind);
$("transform-memory-budget").value = preset.memoryMiB;
$("transform-max-body").value = preset.bodyKiB;
$("transform-max-inflight").value = preset.inflight;
setTransformPresetSelection(kind);
setSettingsDirty("transform-form"); updateTransformResourceSizing();
}
function syncServerMode() { $("server-value").disabled = $("server-mode").value !== "custom"; $("server-value").required = !$("server-value").disabled; }
function syncLoggingFileMode() {
const enabled = $("log-file-enabled").checked, perVhost = $("log-file-per-vhost").checked;
@@ -903,18 +1005,21 @@ bindSettingsForm("response-form", next => {
next.compression = { min_bytes: Number($("compress-min").value) }; for (const name of ["gzip", "br", "zstd", "deflate"]) next.compression[name] = $(`compress-${name}`).checked;
}, "response", next => ({ server_header: next.server_header, diagnostic_headers: next.diagnostic_headers, compression: next.compression }));
bindSettingsForm("cache-form", next => {
next.cache = { ...(next.cache || {}), enabled: $("cache-enabled").checked, max_bytes: Math.round(Number($("cache-memory").value) * 1048576), transient_max_bytes: Math.round(Number($("cache-transient-memory").value) * 1048576), max_object_bytes: Number($("cache-object").value) * 1024, max_entries: Number($("cache-entries").value), hot_eviction_max_objects: Number($("cache-eviction-limit").value), cold_enabled: $("cache-cold-enabled").checked, cold_path: $("cache-cold-path").value.trim(), cold_max_bytes: Math.round(Number($("cache-cold-bytes").value) * 1048576), cold_max_entries: Number($("cache-cold-entries").value), cold_queue_max_bytes: Math.round(Number($("cache-cold-queue-bytes").value) * 1048576), cold_gc_interval_seconds: Number($("cache-cold-gc-interval").value), cold_gc_max_delete: Number($("cache-cold-gc-max-delete").value), cold_after_seconds: Number($("cache-cold-after").value), cold_max_hot_seconds: Number($("cache-cold-max-hot").value), cold_on_idle: $("cache-cold-on-idle").checked, cold_on_age: $("cache-cold-on-age").checked, cold_on_lru: $("cache-cold-on-lru").checked, cold_on_shutdown: $("cache-cold-on-shutdown").checked, max_ttl_seconds: Number($("cache-ttl").value), default_ttl_seconds: Number($("cache-default-ttl").value), collapse_enabled: $("cache-collapse-enabled").checked, collapse_wait_timeout_ms: Number($("cache-collapse-timeout").value), collapse_timeout_fallback: $("cache-collapse-fallback").value, stale_if_error_timeout: $("cache-sie-timeout").checked, stale_if_error_connection: $("cache-sie-connection").checked, stale_if_error_dns: $("cache-sie-dns").checked, stale_if_error_tls: $("cache-sie-tls").checked, stale_if_error_statuses: $("cache-sie-statuses").value.split(/[\s,]+/).filter(Boolean).map(Number), proactive_refresh_enabled: $("cache-proactive-enabled").checked, proactive_refresh_before_expiry_seconds: Number($("cache-proactive-before").value), proactive_refresh_percent: Number($("cache-proactive-percent").value), proactive_refresh_max_inflight: Number($("cache-proactive-inflight").value), bypass_query: $("cache-query").checked, bypass_prefixes: $("cache-prefixes").value.split(/\r?\n/).map(v => v.trim()).filter(Boolean), static_ignore_cookies: $("cache-static-cookie").checked, static_extensions: $("cache-static-extensions").value.split(/[\n,]+/).map(v => v.trim().replace(/^\./, "").toLowerCase()).filter(Boolean), expose_status: $("cache-status-header").checked, expose_hits: $("cache-hits-header").checked, expose_cache_status: $("cache-rfc-header").checked, expose_resident_age: $("cache-resident-age-header").checked };
next.cache = { ...(next.cache || {}), enabled: $("cache-enabled").checked, memory_preset: selectedCacheMemoryPreset, max_bytes: Math.round(Number($("cache-memory").value) * 1048576), transient_max_bytes: Math.round(Number($("cache-transient-memory").value) * 1048576), max_object_bytes: Number($("cache-object").value) * 1024, max_entries: Number($("cache-entries").value), hot_eviction_max_objects: Number($("cache-eviction-limit").value), cold_enabled: $("cache-cold-enabled").checked, cold_preset: selectedCacheColdPreset, cold_path: $("cache-cold-path").value.trim(), cold_max_bytes: Math.round(Number($("cache-cold-bytes").value) * 1048576), cold_max_entries: Number($("cache-cold-entries").value), cold_queue_max_bytes: Math.round(Number($("cache-cold-queue-bytes").value) * 1048576), cold_gc_interval_seconds: Number($("cache-cold-gc-interval").value), cold_gc_max_delete: Number($("cache-cold-gc-max-delete").value), cold_after_seconds: Number($("cache-cold-after").value), cold_max_hot_seconds: Number($("cache-cold-max-hot").value), cold_on_idle: $("cache-cold-on-idle").checked, cold_on_age: $("cache-cold-on-age").checked, cold_on_lru: $("cache-cold-on-lru").checked, cold_on_shutdown: $("cache-cold-on-shutdown").checked, max_ttl_seconds: Number($("cache-ttl").value), default_ttl_seconds: Number($("cache-default-ttl").value), collapse_enabled: $("cache-collapse-enabled").checked, collapse_wait_timeout_ms: Number($("cache-collapse-timeout").value), collapse_timeout_fallback: $("cache-collapse-fallback").value, stale_if_error_timeout: $("cache-sie-timeout").checked, stale_if_error_connection: $("cache-sie-connection").checked, stale_if_error_dns: $("cache-sie-dns").checked, stale_if_error_tls: $("cache-sie-tls").checked, stale_if_error_statuses: $("cache-sie-statuses").value.split(/[\s,]+/).filter(Boolean).map(Number), proactive_refresh_enabled: $("cache-proactive-enabled").checked, proactive_refresh_before_expiry_seconds: Number($("cache-proactive-before").value), proactive_refresh_percent: Number($("cache-proactive-percent").value), proactive_refresh_max_inflight: Number($("cache-proactive-inflight").value), bypass_query: $("cache-query").checked, bypass_prefixes: $("cache-prefixes").value.split(/\r?\n/).map(v => v.trim()).filter(Boolean), static_ignore_cookies: $("cache-static-cookie").checked, static_extensions: $("cache-static-extensions").value.split(/[\n,]+/).map(v => v.trim().replace(/^\./, "").toLowerCase()).filter(Boolean), expose_status: $("cache-status-header").checked, expose_hits: $("cache-hits-header").checked, expose_cache_status: $("cache-rfc-header").checked, expose_resident_age: $("cache-resident-age-header").checked };
}, "cache");
bindSettingsForm("transform-form", next => {
next.transform = {
enabled: $("transform-enabled").checked,
preset: selectedTransformPreset,
javascript: $("transform-javascript").checked,
css: $("transform-css").checked,
html: $("transform-html").checked,
svg: $("transform-svg").checked,
json: $("transform-json").checked,
extensions: $("transform-extensions").value.split(/[\n,]+/).map(v => v.trim().replace(/^\./, "").toLowerCase()).filter(Boolean),
max_body_bytes: Math.round(Number($("transform-max-body").value) * 1024)
max_body_bytes: Math.round(Number($("transform-max-body").value) * 1024),
memory_budget_bytes: Math.round(Number($("transform-memory-budget").value) * 1048576),
max_inflight: Number($("transform-max-inflight").value)
};
}, "transform");
const LOGGING_SOURCE_FIELDS = ["vhost", "route", "cache_effective_enabled", "transform_effective_enabled", "backend", "id", "time_ms", "method", "host", "path", "http_version", "transport", "websocket_bootstrap", "websocket_phase", "websocket_error", "h3_reset_code", "upstream_status", "upstream_error", "client_ip", "status", "duration_ms", "request_bytes", "response_bytes", "outcome", "cache", "cache_reason"];
@@ -995,7 +1100,14 @@ $("log-file-enabled").addEventListener("change", syncLoggingFileMode);
$("log-file-per-vhost").addEventListener("change", syncLoggingFileMode);
for (const id of ["cache-cold-enabled", "cache-cold-on-idle", "cache-cold-on-age"]) $(id).addEventListener("change", syncColdCachePolicy);
document.querySelectorAll("[data-cache-memory-preset]").forEach(button => button.addEventListener("click", () => applyCacheMemoryPreset(button.dataset.cacheMemoryPreset)));
for (const id of ["cache-memory", "cache-transient-memory", "cache-object", "cache-cold-queue-bytes"]) $(id).addEventListener("input", updateCacheMemorySizing);
document.querySelectorAll("[data-cache-cold-preset]").forEach(button => button.addEventListener("click", applyRecommendedColdCachePreset));
document.querySelectorAll("[data-transform-preset]").forEach(button => button.addEventListener("click", () => applyTransformPreset(button.dataset.transformPreset)));
for (const id of ["cache-memory", "cache-transient-memory", "cache-object", "cache-entries", "cache-eviction-limit"]) $(id).addEventListener("input", () => { setCacheMemoryPresetSelection(null); updateCacheMemorySizing(); });
$("cache-cold-queue-bytes").addEventListener("input", () => { setCacheMemoryPresetSelection(null); setCacheColdPresetSelection(null); updateCacheMemorySizing(); });
for (const id of ["cache-cold-enabled", "cache-cold-on-idle", "cache-cold-on-age", "cache-cold-on-lru", "cache-cold-on-shutdown", "cache-cold-after", "cache-cold-max-hot", "cache-cold-gc-interval", "cache-cold-gc-max-delete"]) {
for (const event of ["input", "change"]) $(id).addEventListener(event, () => setCacheColdPresetSelection(null));
}
for (const id of ["transform-memory-budget", "transform-max-body", "transform-max-inflight"]) $(id).addEventListener("input", () => { setTransformPresetSelection(null); updateTransformResourceSizing(); });
for (const id of ["cache-collapse-enabled", "cache-proactive-enabled"]) $(id).addEventListener("change", syncAdvancedCachePolicy);
syncColdCachePolicy();
syncAdvancedCachePolicy();