This commit is contained in:
Mateusz Gruszczyński committed 2026-10-08 09:42:10 +02:00
1 parent c24bc4279b
commit 3b3f0db377
84 files changed
+2686 -392

No files matched your search

+1 -1
View File
@@ -116,7 +116,7 @@ function appConfirm(message, options = {}) {
}
function apiArea(path) {
const section = path.match(/^\/api\/config\/([^/?]+)/)?.[1];
if (section) return ({ "listeners": "settings", "http2": "settings", "docker-agents": "dockerVhosts", "response": "settings", "pages": "settings", "cache": "cache", "transform": "settings", "logging": "settings", "forwarding": "settings", "headers": "settings", "prometheus": "settings", "administration": "administration", "tls": "tls", "vhosts": "vhosts", "redirects": "redirects", "not_found_hosts": "notfound", "streams": "streams", "acl-profiles": "aclProfiles", "host-policies": "security" })[section] || "configuration";
if (section) return ({ "listeners": "settings", "http2": "settings", "docker-agents": "dockerVhosts", "response": "settings", "pages": "settings", "cache": "cache", "transform": "settings", "logging": "settings", "geoip": "settings", "forwarding": "settings", "headers": "settings", "prometheus": "settings", "administration": "administration", "tls": "tls", "vhosts": "vhosts", "redirects": "redirects", "not_found_hosts": "notfound", "streams": "streams", "acl-profiles": "aclProfiles", "host-policies": "security" })[section] || "configuration";
if (path === "/api/config") return "configuration";
if (path.startsWith("/api/traffic")) return "traffic";
if (path.startsWith("/api/cache")) return "cache";
+22 -1
View File
@@ -359,7 +359,28 @@ function renderDiagnostics(status, appendSample = true) {
const l = status.listeners || {};
$("listener-pending").hidden = !l.restart_required;
$("listener-pending").textContent = t("restartPending");
$("listener-active").textContent = `${t("activeListeners")}: ${l.active_endpoints?.map(e => `${e.tls ? "HTTPS" : "HTTP"} ${e.address}${e.http3 ? " + H3/UDP" : ""}`).join(" | ") || `HTTP ${status.proxy_listen} / HTTPS ${status.tls_listen || t("notEnabled")}`}`;
const activeListeners = $("listener-active"), listenerItems = el("div", undefined, "listener-active-items");
let endpoints = Array.isArray(l.active_endpoints) ? l.active_endpoints : [];
if (!endpoints.length) {
endpoints = [];
if (status.proxy_listen) endpoints.push({ tls: false, address: status.proxy_listen, http3: false });
if (status.tls_listen) endpoints.push({ tls: true, address: status.tls_listen, http3: false });
}
for (const endpoint of endpoints) {
const item = el("div", undefined, "listener-active-item");
item.append(el("strong", endpoint.tls ? "HTTPS" : "HTTP", "listener-active-protocol"), el("code", endpoint.address || "-", "listener-active-address"));
const protocols = ["H1"];
if (endpoint.tls) {
if (endpoint.http2) protocols.push("H2");
if (endpoint.http3) protocols.push("H3/UDP");
} else if (endpoint.h2c) {
protocols.push("H2C");
}
for (const protocol of protocols) item.append(el("span", protocol, "listener-active-feature"));
listenerItems.append(item);
}
if (!endpoints.length) listenerItems.append(el("span", t("notEnabled"), "fine"));
activeListeners.replaceChildren(el("strong", t("activeListeners"), "listener-active-title"), listenerItems);
}
function renderHeaderRules(direction, rules) {
const target = $(`${direction}-header-rules`); target.replaceChildren();
+8 -1
View File
@@ -30,6 +30,8 @@ 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;
const viaHeader = c.via_header === undefined ? "RustProxy" : c.via_header;
$("cache-via-enabled").checked = viaHeader !== null; $("cache-via-value").value = viaHeader || "RustProxy"; syncCacheVia();
setCacheMemoryPresetSelection(["conservative", "recommended", "performance"].includes(c.memory_preset) ? c.memory_preset : null);
setCacheColdPresetSelection(c.cold_preset === "recommended" ? "recommended" : null);
syncColdCachePolicy();
@@ -170,6 +172,10 @@ function applyCacheMemoryPreset(kind) {
setCacheMemoryPresetSelection(kind);
setSettingsDirty("cache-form"); updateCacheMemorySizing();
}
function syncCacheVia() {
const enabled = $("cache-via-enabled")?.checked;
if ($("cache-via-value")) $("cache-via-value").disabled = !enabled;
}
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;
@@ -279,7 +285,7 @@ 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, 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), hot_auto_trim_enabled: $("cache-auto-trim-enabled").checked, hot_auto_trim_after_seconds: Number($("cache-auto-trim-after").value), hot_auto_trim_target_percent: Number($("cache-auto-trim-target").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 };
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), hot_auto_trim_enabled: $("cache-auto-trim-enabled").checked, hot_auto_trim_after_seconds: Number($("cache-auto-trim-after").value), hot_auto_trim_target_percent: Number($("cache-auto-trim-target").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, via_header: $("cache-via-enabled").checked ? $("cache-via-value").value.trim() : null };
}, "cache");
bindSettingsForm("transform-form", next => {
next.transform = {
@@ -384,6 +390,7 @@ $("server-mode").addEventListener("change", syncServerMode);
$("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);
$("cache-via-enabled").addEventListener("change", syncCacheVia);
document.querySelectorAll("[data-cache-memory-preset]").forEach(button => button.addEventListener("click", () => applyCacheMemoryPreset(button.dataset.cacheMemoryPreset)));
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)));
+61 -6
View File
@@ -1749,9 +1749,12 @@
data-settings-tab="pages" id="settings-tab-pages" role="tab" tabindex="-1"
type="button"></button><button aria-controls="settings-page-logging" aria-selected="false"
data-i18n="logging" data-settings-tab="logging" id="settings-tab-logging" role="tab"
tabindex="-1" type="button"></button><button aria-controls="settings-page-prometheus"
aria-selected="false" data-i18n="prometheusTitle" data-settings-tab="prometheus"
id="settings-tab-prometheus" role="tab" tabindex="-1" type="button"></button><button
tabindex="-1" type="button"></button><button aria-controls="settings-page-geoip"
aria-selected="false" data-i18n="geoipUpdatesTitle" data-settings-tab="geoip"
id="settings-tab-geoip" role="tab" tabindex="-1" type="button"></button><button
aria-controls="settings-page-prometheus" aria-selected="false" data-i18n="prometheusTitle"
data-settings-tab="prometheus" id="settings-tab-prometheus" role="tab" tabindex="-1"
type="button"></button><button
aria-controls="settings-page-administration" aria-selected="false"
data-i18n="administration" data-settings-tab="administration"
id="settings-tab-administration" role="tab" tabindex="-1" type="button"></button><button
@@ -1915,7 +1918,7 @@
<p class="fine" data-i18n="lowPortsHelp">Privileged ports may need CAP_NET_BIND_SERVICE.
Do not run the whole application as root.</p>
<p class="pending-note" hidden="" id="listener-pending"></p>
<p class="fine" id="listener-active"></p>
<div class="listener-active-summary" id="listener-active" role="status"></div>
<div class="form-actions"><button class="primary" data-i18n="saveListeners"
type="submit">Save listeners</button></div>
</form>
@@ -2264,6 +2267,15 @@
type="checkbox" /><span>Cache-Status (RFC 9211)</span></label><label
class="check"><input id="cache-resident-age-header"
type="checkbox" /><span>X-Cache-Resident-Age</span></label></div>
<div class="cache-via-setting">
<label class="check"><input id="cache-via-enabled" type="checkbox" /><span
data-i18n="cacheViaEnable">Add Via response header</span></label>
<label><span data-i18n="cacheViaValue">Via header value</span><input
id="cache-via-value" maxlength="256" placeholder="RustProxy"
required="" type="text" /></label>
</div>
<p class="fine" data-i18n="cacheViaHelp">Defaults to Via: RustProxy. Changing this
response-only header does not purge cached objects.</p>
<p class="fine" data-i18n="cacheAgeHelp">Age is sent on cache hits unless an
explicit response-header rule removes or replaces it. Such Age rules are
wire-only and do not change cache freshness.</p>
@@ -2548,6 +2560,49 @@
</form>
</div>
</div>
<div aria-labelledby="settings-tab-geoip" class="settings-page"
data-settings-page="geoip" hidden="" id="settings-page-geoip" role="tabpanel">
<form class="card edit-form" id="geoip-form">
<div class="card-heading">
<h2 data-i18n="geoipUpdatesTitle"></h2><span class="pill">AUTO</span>
</div>
<p class="fine" data-i18n="geoipUpdatesHelp"></p>
<label class="check"><input id="geoip-enabled" type="checkbox" /><span
data-i18n="geoipUpdatesEnable"></span></label>
<div class="field-grid">
<label><span data-i18n="geoipFormat"></span><select id="geoip-format">
<option data-i18n="geoipFormatCsvPair" value="csv_pair"></option>
<option data-i18n="geoipFormatMmdb" value="mmdb"></option>
</select></label>
<label><span data-i18n="geoipMaxFileMiB"></span><input id="geoip-max-mib" min="1"
max="256" required="" type="number" /></label>
<label><span data-i18n="geoipIntervalHours"></span><input id="geoip-interval-hours"
min="0.0166667" max="744" step="any" required="" type="number" /></label>
</div>
<div class="field-grid" id="geoip-csv-fields">
<label><span data-i18n="geoipIpv4Url"></span><input id="geoip-ipv4-url"
maxlength="2048" required="" type="url" /></label>
<label><span data-i18n="geoipIpv4ChecksumUrl"></span><input id="geoip-ipv4-checksum-url"
maxlength="2048" type="url" /></label>
<label><span data-i18n="geoipIpv6Url"></span><input id="geoip-ipv6-url"
maxlength="2048" required="" type="url" /></label>
<label><span data-i18n="geoipIpv6ChecksumUrl"></span><input id="geoip-ipv6-checksum-url"
maxlength="2048" type="url" /></label>
</div>
<div class="field-grid" id="geoip-mmdb-fields" hidden="">
<label><span data-i18n="geoipMmdbUrl"></span><input id="geoip-mmdb-url"
maxlength="2048" type="url" /></label>
<label><span data-i18n="geoipMmdbChecksumUrl"></span><input id="geoip-mmdb-checksum-url"
maxlength="2048" type="url" /></label>
</div>
<p class="fine" data-i18n="geoipSourceHelp"></p>
<p class="fine" id="geoip-status" role="status"></p>
<div class="form-actions"><button class="primary" data-i18n="apply"
type="submit"></button><button class="outline" data-i18n="geoipUpdateNow"
id="geoip-update-now" type="button"></button></div>
<p class="fine" data-i18n="geoipUpdateNowHelp"></p>
</form>
</div>
<div aria-labelledby="settings-tab-prometheus" class="settings-page"
data-settings-page="prometheus" hidden="" id="settings-page-prometheus" role="tabpanel">
<form class="card edit-form" id="prometheus-form">
@@ -2823,7 +2878,7 @@
</section>
</div>
</section>
<footer class="page-footer"><span>RustProxy <b>0.27.3</b></span><span id="footer-status">API - /
<footer class="page-footer"><span>RustProxy <b>0.28.0</b></span><span id="footer-status">API - /
dropped: 0</span></footer>
</div>
</main>
@@ -3567,7 +3622,7 @@ max_execution_time = 30</code></pre>
<article class="card about-card">
<div class="about-product">
<span aria-hidden="true" class="brand-mark">r<span>/</span>p</span>
<div><span class="small-label">RustProxy</span><strong>0.27.3</strong></div>
<div><span class="small-label">RustProxy</span><strong>0.28.0</strong></div>
</div>
<dl class="about-meta">
<div>
+1 -1
View File
@@ -5,7 +5,7 @@
const themes = new Set(["dark", "light"]);
try { const cached = localStorage.getItem("rp-theme"); document.documentElement.dataset.theme = themes.has(cached) ? cached : "dark"; } catch { document.documentElement.dataset.theme = "dark"; }
const panelPaths = new Set(["/overview", "/vhosts", "/docker-vhosts", "/redirects", "/404-hosts", "/streams", "/acl-profiles", "/configuration", "/tls", "/traffic", "/cache", "/transform", "/logging", "/logs", "/settings"]);
const settingsTabs = new Set(["appearance", "listeners", "connections", "docker-agents", "response", "cache", "transform", "headers", "forwarding", "pages", "logging", "prometheus", "administration", "tls-policy", "manual-config", "security"]);
const settingsTabs = new Set(["appearance", "listeners", "connections", "docker-agents", "response", "cache", "transform", "headers", "forwarding", "pages", "logging", "geoip", "prometheus", "administration", "tls-policy", "manual-config", "security"]);
let packs = new Map(), fallback = null, language = null, setupRequired = false, ready = false;
try { language = localStorage.getItem("rp-language"); } catch { }
const t = key => packs.get(language)?.translations?.[key] ?? packs.get(fallback)?.translations?.[key] ?? key;
+63
View File
@@ -142,6 +142,29 @@ curl --fail --show-error --request POST \
finally { certificateApiLoading = false; }
}
const defaults = { enabled: false, listen: "127.0.0.1:9191", path: "/metrics", allowed_cidrs: ["127.0.0.0/8", "::1/128"], bearer_token_env: null, allow_trusted_network_without_token: false, include_system: true, include_vhosts: true, include_traffic: true, include_backends: true, include_docker_vhosts: true, include_cache_domains: true, include_streams: true, include_logging: true };
const geoipDefaults = {
enabled: true, format: "csv_pair",
ipv4_url: "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv4.csv",
ipv6_url: "https://github.com/sapics/ip-location-db/releases/download/latest/user-country-ipv6.csv",
mmdb_url: "https://github.com/sapics/ip-location-db/releases/download/latest/user-country.mmdb", ipv4_checksum_url: null, ipv6_checksum_url: null, mmdb_checksum_url: null,
max_bytes: 268435456, interval_seconds: 86400
};
const legacyGeoipRelease = "https://github.com/sapics/ip-location-db/releases/download/latest/";
const legacyGeoipChecksum = "https://github.com/sapics/ip-location-db/releases/download/checksum/";
function normalizedGeoip(raw = {}) {
const geoip = { ...geoipDefaults, ...raw };
if (!raw.ipv4_url && raw.release_url && raw.release_url !== legacyGeoipRelease) geoip.ipv4_url = `${raw.release_url.replace(/\/?$/, "/")}user-country-ipv4.csv`;
if (!raw.ipv6_url && raw.release_url && raw.release_url !== legacyGeoipRelease) geoip.ipv6_url = `${raw.release_url.replace(/\/?$/, "/")}user-country-ipv6.csv`;
if (!raw.ipv4_checksum_url && raw.checksum_url && raw.checksum_url !== legacyGeoipChecksum) geoip.ipv4_checksum_url = `${raw.checksum_url.replace(/\/?$/, "/")}user-country-ipv4.csv.sha256`;
if (!raw.ipv6_checksum_url && raw.checksum_url && raw.checksum_url !== legacyGeoipChecksum) geoip.ipv6_checksum_url = `${raw.checksum_url.replace(/\/?$/, "/")}user-country-ipv6.csv.sha256`;
return geoip;
}
function syncGeoipFormat() {
const mmdb = $("geoip-format").value === "mmdb";
$("geoip-csv-fields").hidden = mmdb; $("geoip-mmdb-fields").hidden = !mmdb;
$("geoip-ipv4-url").required = !mmdb; $("geoip-ipv6-url").required = !mmdb;
$("geoip-mmdb-url").required = mmdb && $("geoip-enabled").checked;
}
function example() {
const addr = $("prom-listen").value.trim(), path = $("prom-path").value.trim(), env = $("prom-token-env").value.trim();
$("prom-scrape-example").textContent = `scrape_configs:\n - job_name: rustproxy\n metrics_path: ${JSON.stringify(path)}\n static_configs:\n - targets: [${JSON.stringify(addr)}]${env ? "\n authorization:\n credentials_file: /etc/prometheus/rustproxy.token" : ""}`;
@@ -156,6 +179,22 @@ curl --fail --show-error --request POST \
const status = latestStatus?.prometheus;
$("prom-status").textContent = status?.restart_required ? t("restartPending") : status?.active?.enabled ? `${t("prometheusActive")}: ${status.active.listen}${p.path}` : t("prometheusDisabled"); example();
const geoip = normalizedGeoip(envelope.config.geoip_updates || {});
if (!settingsDirty.has("geoip-form")) {
$("geoip-enabled").checked = geoip.enabled !== false; $("geoip-format").value = geoip.format || "csv_pair";
$("geoip-ipv4-url").value = geoip.ipv4_url || ""; $("geoip-ipv6-url").value = geoip.ipv6_url || "";
$("geoip-mmdb-url").value = geoip.mmdb_url || "";
$("geoip-ipv4-checksum-url").value = geoip.ipv4_checksum_url || ""; $("geoip-ipv6-checksum-url").value = geoip.ipv6_checksum_url || "";
$("geoip-mmdb-checksum-url").value = geoip.mmdb_checksum_url || "";
$("geoip-max-mib").value = Number(geoip.max_bytes || geoipDefaults.max_bytes) / 1048576;
$("geoip-interval-hours").value = Number(geoip.interval_seconds || geoipDefaults.interval_seconds) / 3600;
syncGeoipFormat();
}
const geoStatus = latestStatus?.geoip;
$("geoip-status").textContent = geoStatus?.loaded
? geoStatus.format === "mmdb" ? `${t("geoipStatusLoaded")}: ${geoStatus.source}` : `${t("geoipStatusLoaded")}: IPv4 ${fmt(geoStatus.ipv4_ranges)} · IPv6 ${fmt(geoStatus.ipv6_ranges)}`
: geoip.enabled ? t("geoipStatusMissing") : t("geoipStatusDisabled");
const admin = envelope.config.administration || {}, adminStatus = latestStatus?.administration;
if (!settingsDirty.has("administration-form")) {
$("admin-listen").value = adminStatus?.locked_listen || admin.listen || adminStatus?.desired?.listen || latestStatus?.admin_listen || "127.0.0.1:9090";
@@ -186,6 +225,30 @@ curl --fail --show-error --request POST \
$("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : "";
renderCertificateApi(); loadCertificateApi();
}
$("geoip-format").addEventListener("change", syncGeoipFormat); $("geoip-enabled").addEventListener("change", syncGeoipFormat);
bindSettingsForm("geoip-form", next => {
next.geoip_updates = {
enabled: $("geoip-enabled").checked, format: $("geoip-format").value,
ipv4_url: $("geoip-ipv4-url").value.trim(), ipv6_url: $("geoip-ipv6-url").value.trim(), mmdb_url: $("geoip-mmdb-url").value.trim(),
ipv4_checksum_url: $("geoip-ipv4-checksum-url").value.trim() || null,
ipv6_checksum_url: $("geoip-ipv6-checksum-url").value.trim() || null,
mmdb_checksum_url: $("geoip-mmdb-checksum-url").value.trim() || null,
max_bytes: Math.round(Number($("geoip-max-mib").value) * 1048576),
interval_seconds: Math.round(Number($("geoip-interval-hours").value) * 3600)
};
}, "geoip", next => next.geoip_updates);
$("geoip-update-now").addEventListener("click", async () => {
if (settingsDirty.has("geoip-form")) { toast(t("geoipUpdateSaveFirst"), true); return; }
const button = $("geoip-update-now"); button.disabled = true;
$("geoip-status").textContent = t("geoipUpdateWorking");
try {
const result = await api("/api/geoip/update", { method: "POST" });
if (result.database) latestStatus = { ...(latestStatus || {}), geoip: result.database };
render();
toast(t(result.outcome === "updated" ? "geoipUpdateUpdated" : result.outcome === "busy" ? "geoipUpdateBusy" : "geoipUpdateCurrent"));
} catch (error) { toast(error.message, true); render(); }
finally { button.disabled = false; }
});
bindSettingsForm("prometheus-form", next => {
next.prometheus = {
enabled: $("prom-enabled").checked, listen: $("prom-listen").value.trim(), path: $("prom-path").value.trim(), allowed_cidrs: $("prom-cidrs").value.split(/[\n,]/).map(v => v.trim()).filter(Boolean), bearer_token_env: $("prom-token-env").value.trim() || null, allow_trusted_network_without_token: $("prom-trusted-no-token").checked,
+69 -3
View File
@@ -247,13 +247,13 @@ h3 {
.shell {
display: grid;
grid-template-columns: 218px minmax(0, 1fr);
min-height: 100vh
min-height: calc(100vh / var(--ui-scale, 1))
}
.sidebar {
position: sticky;
top: 0;
height: 100vh;
height: calc(100vh / var(--ui-scale, 1));
background: var(--sidebar-bg);
border-right: 1px solid var(--line);
padding: 34px 22px;
@@ -9646,4 +9646,70 @@ html[data-chart-style] :is(#workspace, dialog) svg polyline {
.acl-policy-step-head>button {
width: 100%
}
}
}
.listener-active-summary {
margin-top: 10px;
padding: 10px 12px;
border: 1px solid var(--line);
background: var(--panel2);
display: grid;
gap: 8px
}
.listener-active-title {
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .45px
}
.listener-active-items {
display: flex;
flex-wrap: wrap;
gap: 8px
}
.listener-active-item {
display: inline-flex;
align-items: center;
gap: 7px;
min-width: 0;
padding: 7px 9px;
border: 1px solid var(--line);
background: var(--panel)
}
.listener-active-protocol {
font: 600 10px var(--mono);
color: var(--accent)
}
.listener-active-address {
overflow-wrap: anywhere
}
.listener-active-feature {
padding: 2px 5px;
border: 1px solid var(--line);
font: 600 9px var(--mono);
color: var(--muted)
}
.cache-via-setting {
display: grid;
grid-template-columns: minmax(180px, .8fr) minmax(220px, 1.2fr);
gap: 8px 14px;
align-items: end;
margin-top: 10px
}
.location-header-editor .vh-header-rule-head > button {
margin-left: auto;
flex: 0 0 auto
}
@media(max-width:650px) {
.cache-via-setting {
grid-template-columns: 1fr
}
}
+1 -1
View File
@@ -97,7 +97,7 @@
target.value = JSON.stringify(result, null, 2); target.setCustomValidity(""); onChange();
};
for (const direction of ["request", "response"]) {
const block = el("section", undefined, "vh-wide location-header-direction"); block.dataset.headerDirection = direction;
const block = el("section", undefined, "vh-wide location-header-direction location-header-editor"); block.dataset.headerDirection = direction;
const heading = el("div", undefined, "location-header-direction-head");
heading.append(text("h3", direction === "response" ? "locationResponseHeaders" : "locationProxyHeaders"), text("p", "headerRuleHelp", "fine"));
block.append(heading);