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();
+78 -10
View File
@@ -265,6 +265,10 @@
<dt data-i18n="cacheTransientBreakdown">Fill / cold queue / cold I/O</dt>
<dd id="cache-transient-breakdown">-</dd>
</div>
<div>
<dt data-i18n="cacheColdStorageUsage">COLD storage / budget</dt>
<dd id="cache-cold-storage">-</dd>
</div>
<div>
<dt data-i18n="cacheEvictions">Evicted / expired</dt>
<dd id="cache-evictions">-</dd>
@@ -339,6 +343,26 @@
<dt data-i18n="processRssTotal">RSS total</dt>
<dd id="process-rss-total">-</dd>
</div>
<div>
<dt data-i18n="memoryCacheHot">HOT cache RAM / limit</dt>
<dd id="memory-cache-hot">-</dd>
</div>
<div>
<dt data-i18n="memoryCacheTransient">Cache transient RAM / limit</dt>
<dd id="memory-cache-transient">-</dd>
</div>
<div>
<dt data-i18n="memoryTransform">Transform reserved RAM / budget</dt>
<dd id="memory-transform">-</dd>
</div>
<div>
<dt data-i18n="memoryTransformInflight">Active transforms / limit</dt>
<dd id="memory-transform-inflight">-</dd>
</div>
<div>
<dt data-i18n="memoryNonCacheRss">Non-cache process RSS</dt>
<dd id="memory-non-cache-rss">-</dd>
</div>
<div>
<dt data-i18n="processRssAnon">Anonymous RSS</dt>
<dd id="process-rss-anon">-</dd>
@@ -356,8 +380,7 @@
<dd id="cgroup-memory">-</dd>
</div>
</dl>
<p class="fine" data-i18n="memoryAccountingHint">OS RSS categories and RustProxy cache
accounting are overlapping views; do not add them together.</p>
<p class="fine" data-i18n="memoryAccountingHint">RSS is the real process footprint. HOT/transient cache values are RustProxy-accounted allocations; transform memory is a conservative active reservation. Non-cache RSS is RSS minus accounted cache and includes transforms, connections, TLS, logging, Tokio/runtime state, allocator overhead and other buffers, so these rows are not all additive.</p>
<div class="mini-live-chart-stack">
<div class="mini-live-chart">
<div class="mini-live-chart-head"><span data-i18n="processCpuHistory">Process CPU
@@ -1099,6 +1122,10 @@
id="tp-skipped">-</strong><small id="tp-skip-top">-</small></article>
<article class="stat"><span data-i18n="transformErrors">Body/transport errors</span><strong
id="tp-errors">-</strong><small id="tp-duration">-</small></article>
<article class="stat"><span data-i18n="transformMemoryUsage">Transform RAM reservation</span><strong
id="tp-memory">-</strong><small id="tp-memory-detail">-</small></article>
<article class="stat"><span data-i18n="transformConcurrencyUsage">Active transform slots</span><strong
id="tp-inflight">-</strong><small id="tp-inflight-detail">-</small></article>
</div>
<div class="diagnostics-grid transform-diagnostics">
<article class="card">
@@ -1326,7 +1353,7 @@
<option data-i18n="serverCustom" value="custom">Replace / add custom value</option>
</select>
<label data-i18n="serverValue" for="server-value">Custom Server value</label><input
id="server-value" maxlength="256" placeholder="RustProxy/v0.24.5"
id="server-value" maxlength="256" placeholder="RustProxy/v0.24.7"
spellcheck="false" />
<h3 class="subheading" data-i18n="diagnosticHeaders">Diagnostic headers</h3>
<div class="switch-grid">
@@ -1377,8 +1404,9 @@
multipart/unsupported ranges pass through.</p>
<div class="card soft-card cache-memory-sizing">
<div class="card-heading">
<h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3><span class="pill"
id="cache-memory-detected">-</span>
<h3 data-i18n="cacheMemorySizingTitle">Memory sizing</h3>
<span class="pill" id="cache-memory-preset-selected">-</span>
<span class="pill" id="cache-memory-detected">-</span>
</div>
<p class="fine" data-i18n="cacheMemorySizingHelp">Presets use the effective
memory available to RustProxy (host RAM capped by cgroup when present). They
@@ -1387,7 +1415,7 @@
<div class="form-actions">
<button class="outline" data-cache-memory-preset="conservative"
data-i18n="cachePresetConservative" type="button">Conservative</button>
<button class="primary" data-cache-memory-preset="recommended"
<button class="outline" data-cache-memory-preset="recommended"
data-i18n="cachePresetRecommended" type="button">Recommended</button>
<button class="outline" data-cache-memory-preset="performance"
data-i18n="cachePresetPerformance" type="button">Performance</button>
@@ -1480,6 +1508,17 @@
deterministic sampling and a global concurrency cap to avoid refresh storms.</p>
</div>
<div data-cache-settings-page="cold" hidden>
<div class="card soft-card cache-memory-sizing">
<div class="card-heading">
<h3 data-i18n="cacheColdPresetTitle">COLD policy preset</h3>
<span class="pill" id="cache-cold-preset-selected">-</span>
</div>
<p class="fine" data-i18n="cacheColdPresetHelp">The recommended policy enables idle/LRU/shutdown demotion, uses a queue sized from transient RAM and conservative GC timings. Disk path, disk budget and object limit are left unchanged.</p>
<div class="form-actions">
<button class="outline" data-cache-cold-preset="recommended"
data-i18n="cachePresetRecommended" type="button">Recommended</button>
</div>
</div>
<label class="check"><input id="cache-cold-enabled" type="checkbox" /><span
data-i18n="cacheColdEnable">Enable persistent disk tier</span></label>
<p class="fine" data-i18n="cacheColdHelp">Idle and LRU hot objects are demoted to
@@ -1569,6 +1608,38 @@
<p class="fine" data-i18n="transformHelp">Transformation happens after upstream decoding
and before cache storage and client compression. Each vhost must opt in separately.
</p>
<div class="card soft-card cache-memory-sizing">
<div class="card-heading">
<h3 data-i18n="transformResourcePresetTitle">Resource preset</h3>
<span class="pill" id="transform-preset-selected">-</span>
</div>
<p class="fine" data-i18n="transformResourcePresetHelp">Presets tune the temporary
memory ceiling, maximum transformable response size and global concurrent
transformation limit. Content types and extension allow-lists are left unchanged.</p>
<div class="form-actions">
<button class="outline" data-i18n="transformPresetConservative"
data-transform-preset="conservative" type="button">RAM saver</button>
<button class="outline" data-i18n="transformPresetRecommended"
data-transform-preset="recommended" type="button">Recommended</button>
<button class="outline" data-i18n="transformPresetPerformance"
data-transform-preset="performance" type="button">Performance</button>
<button class="outline" data-i18n="transformPresetUber"
data-transform-preset="uber" type="button">Uber performance</button>
</div>
<p class="fine" id="transform-preset-summary">-</p>
</div>
<p class="fine" id="transform-memory-warning" hidden></p>
<div class="field-grid">
<div><label data-i18n="transformMemoryBudget" for="transform-memory-budget">Temporary
transform memory budget (MiB)</label><input id="transform-memory-budget"
max="2048" min="1" required="" step="1" type="number" /></div>
<div><label data-i18n="transformMaxBody" for="transform-max-body">Maximum transformable
response (KiB)</label><input id="transform-max-body" min="1" max="32768"
required="" type="number" /></div>
<div><label data-i18n="transformMaxInflight" for="transform-max-inflight">Maximum
concurrent transformations</label><input id="transform-max-inflight" min="1"
max="1024" required="" type="number" /></div>
</div>
<div class="switch-grid">
<label class="check"><input id="transform-javascript" type="checkbox" /><span
data-i18n="transformJavascript">JavaScript minification</span></label>
@@ -1584,9 +1655,6 @@
<label data-i18n="transformExtensions" for="transform-extensions">Allowed file
extensions, one per line</label>
<textarea id="transform-extensions" rows="6" spellcheck="false"></textarea>
<label data-i18n="transformMaxBody" for="transform-max-body">Maximum transformable
response (KiB)</label>
<input id="transform-max-body" min="1" max="65536" required="" type="number" />
<p class="fine" data-i18n="transformSafety">Cache-Control: no-transform is always
respected. Transformed representations receive a new ETag and are cached before
gzip, Brotli or zstd encoding.</p>
@@ -1992,7 +2060,7 @@
</section>
</div>
</section>
<footer class="page-footer"><span>RustProxy <b>0.24.5</b></span><span id="footer-status">API - /
<footer class="page-footer"><span>RustProxy <b>0.24.7</b></span><span id="footer-status">API - /
dropped: 0</span></footer>
</div>
</main>
+7
View File
@@ -134,6 +134,13 @@
$("tp-skipped").textContent = number(counters.skipped);
$("tp-errors").textContent = number(counters.errors);
$("tp-duration").textContent = `${t("transformAvgTime")}: ${averageMs(counters)}`;
const resources = data?.resources || {};
const reserved = Number(resources.memory_reserved_bytes), budget = Number(resources.memory_budget_bytes);
const inflight = Number(resources.inflight), maxInflight = Number(resources.max_inflight);
$("tp-memory").textContent = Number.isFinite(reserved) ? fmtBytes(reserved) : "-";
$("tp-memory-detail").textContent = Number.isFinite(budget) ? `${t("transformBudgetShort")}: ${fmtBytes(budget)}${budget > 0 && Number.isFinite(reserved) ? ` · ${fmt(reserved / budget * 100, 1)}%` : ""}` : "-";
$("tp-inflight").textContent = Number.isFinite(inflight) ? number(inflight) : "-";
$("tp-inflight-detail").textContent = Number.isFinite(maxInflight) ? `${t("cacheLimitShort")}: ${number(maxInflight)}` : "-";
renderTypes(counters); renderReasons(counters); renderVhosts(data);
$("transform-panel-state").textContent = `${t("cacheUpdated")}: ${new Date().toLocaleTimeString(locale())}`;
}
+1 -1
View File
@@ -600,7 +600,7 @@
authMode.addEventListener("change", syncAuth); syncAuth();
g = group("vhResponsePolicy"); const server = value.server_header, diagnostics = value.diagnostic_headers;
const serverMode = field(g, "server-mode", "serverHeader", server?.mode || "inherit", "select", { values: [["inherit", "vhInherit"], ["preserve", "serverPreserve"], ["hide", "serverHide"], ["custom", "serverCustom"]] });
const serverValue = field(g, "server-value", "serverValue", server?.value || "RustProxy/v0.24.5", "text", { maxLength: 256 });
const serverValue = field(g, "server-value", "serverValue", server?.value || "RustProxy/v0.24.7", "text", { maxLength: 256 });
const syncServer = () => { serverValue.disabled = serverMode.value !== "custom"; serverValue.required = !serverValue.disabled; }; serverMode.addEventListener("change", syncServer); syncServer();
const inheritDiagnostics = field(g, "diag-inherit", "diagInherit", !diagnostics, "checkbox"), diagInputs = [];
for (const [name, key] of [["request_id", "diagRequestId"]]) diagInputs.push(field(g, `diag-${name}`, key, diagnostics?.[name] ?? envelope?.config.diagnostic_headers?.[name], "checkbox"));