313 lines
27 KiB
JavaScript
313 lines
27 KiB
JavaScript
"use strict";
|
|
(() => {
|
|
const buttons = [...document.querySelectorAll("[data-settings-tab]")];
|
|
const search = $("settings-search"), searchStatus = $("settings-search-status");
|
|
const legacyTab = (typeof location !== "undefined" && location.pathname === "/configuration") ? "manual-config" : "appearance";
|
|
// Explicit URL tabs win. Otherwise reopen the last chosen Settings tab,
|
|
// falling back to Appearance for new browsers and invalid stored values.
|
|
let rememberedTab = null;
|
|
try { rememberedTab = localStorage.getItem("rp-settings-last-tab"); } catch { }
|
|
const defaultTab = legacyTab === "manual-config" ? legacyTab : (rememberedTab || "appearance");
|
|
const requestedTab = typeof location !== "undefined" && location.pathname === "/logging"
|
|
? "logging"
|
|
: (typeof location !== "undefined" && location.pathname === "/settings" ? new URLSearchParams(location.search).get("tab") : null);
|
|
const tabAliases = { http2: "connections", headers: "policies", response: "policies" };
|
|
function pinnedAdminHostname(value) {
|
|
try {
|
|
const url = new URL(value);
|
|
return url.protocol === "https:" ? url.hostname.toLowerCase().replace(/\.$/, "") : "";
|
|
} catch { return ""; }
|
|
}
|
|
function additionalAdminHosts(hosts, origin) {
|
|
const pinned = pinnedAdminHostname(origin);
|
|
return hosts.map(host => host.trim()).filter(host =>
|
|
host && (!pinned || host.replace(/\.$/, "").toLowerCase() !== pinned));
|
|
}
|
|
const normalizedRequestedTab = tabAliases[requestedTab] || requestedTab;
|
|
const validTab = tab => buttons.some(button => button.dataset.settingsTab === tab);
|
|
let active = validTab(normalizedRequestedTab) ? normalizedRequestedTab : (validTab(defaultTab) ? defaultTab : "appearance");
|
|
function applySettingsSearch() {
|
|
const query = String(search?.value || "").trim().toLocaleLowerCase();
|
|
const matches = [];
|
|
for (const button of buttons) {
|
|
const page = document.querySelector(`[data-settings-page="${CSS.escape(button.dataset.settingsTab)}"]`);
|
|
const haystack = `${button.textContent || ""} ${page?.textContent || ""}`.toLocaleLowerCase();
|
|
const match = !query || haystack.includes(query); button.hidden = !match; if (match) matches.push(button);
|
|
}
|
|
if (searchStatus) searchStatus.textContent = query ? `${t("settingsSearchResults")}: ${matches.length}` : "";
|
|
if (query && !matches.some(button => button.dataset.settingsTab === active) && matches[0]) select(matches[0].dataset.settingsTab, false, false);
|
|
}
|
|
function syncLocation() {
|
|
if (typeof location === "undefined" || !["/settings", "/configuration", "/logging"].includes(location.pathname) || !["http:", "https:"].includes(location.protocol)) return;
|
|
const url = new URL(location.href); const alreadyCanonical = url.pathname === "/settings" && url.searchParams.get("tab") === active; if (alreadyCanonical) return;
|
|
url.pathname = "/settings"; url.search = ""; url.searchParams.set("tab", active);
|
|
history.replaceState({ ...(history.state || {}), panel: "settings", settingsTab: active }, "", `${url.pathname}${url.search}${url.hash}`);
|
|
}
|
|
function select(name, focus = false, persist = true) {
|
|
const target = buttons.find(button => button.dataset.settingsTab === name); if (!target) return;
|
|
if (target.hidden && search?.value) { search.value = ""; applySettingsSearch(); }
|
|
active = name;
|
|
if (persist) { rememberedTab = name; try { localStorage.setItem("rp-settings-last-tab", name); } catch { } }
|
|
if (typeof window !== "undefined") {
|
|
window.rustProxyRefresh?.setSettingsTab(name);
|
|
if (authenticated && name === "security") void window.rustProxyApiTokens?.refresh?.();
|
|
if (authenticated && name === "prometheus") void window.rustProxyPrometheusToken?.refresh?.();
|
|
if (name !== "prometheus") window.rustProxyPrometheusToken?.hideSecret?.();
|
|
if (name !== "security") window.rustProxyApiTokens?.hideSecret?.();
|
|
}
|
|
for (const b of buttons) { const chosen = b.dataset.settingsTab === name; b.setAttribute("aria-selected", String(chosen)); b.tabIndex = chosen ? 0 : -1; if (chosen && focus) b.focus(); }
|
|
document.querySelectorAll("[data-settings-page]").forEach(p => p.hidden = p.dataset.settingsPage !== name);
|
|
if (persist) syncLocation();
|
|
}
|
|
for (const b of buttons) {
|
|
b.addEventListener("click", () => select(b.dataset.settingsTab)); b.addEventListener("keydown", event => {
|
|
const visible = buttons.filter(button => !button.hidden), index = visible.indexOf(b); let next;
|
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % visible.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + visible.length - 1) % visible.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = visible.length - 1; else return;
|
|
if (!visible.length) return; event.preventDefault(); select(visible[next].dataset.settingsTab, true);
|
|
});
|
|
}
|
|
search?.addEventListener("input", applySettingsSearch);
|
|
function scrollToApiTokens() {
|
|
if (active !== "security" || typeof location === "undefined" || location.hash !== "#api-tokens-card") return;
|
|
const target = $("api-tokens-card");
|
|
if (target) { target.scrollIntoView({ block: "start" }); target.focus({ preventScroll: true }); }
|
|
}
|
|
// Preserve a real deep link (including opening in a new tab), while switching
|
|
// tabs without reloading the panel for ordinary left-click navigation.
|
|
$("api-tokens-security-link")?.addEventListener("click", event => {
|
|
if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
|
|
event.preventDefault();
|
|
select("security");
|
|
if (typeof location !== "undefined" && ["http:", "https:"].includes(location.protocol)) {
|
|
const url = new URL($("api-tokens-security-link").href);
|
|
history.replaceState({ ...(history.state || {}), panel: "settings", settingsTab: "security" }, "", `${url.pathname}${url.search}${url.hash}`);
|
|
}
|
|
scrollToApiTokens();
|
|
});
|
|
const policyTabs = [...document.querySelectorAll("[data-policy-settings-tab]")].filter(button => typeof button.addEventListener === "function" && typeof button.setAttribute === "function");
|
|
function selectPolicyTab(name, focus = false) {
|
|
if (!policyTabs.some(button => button.dataset.policySettingsTab === name)) return;
|
|
for (const button of policyTabs) {
|
|
const chosen = button.dataset.policySettingsTab === name;
|
|
button.setAttribute("aria-selected", String(chosen)); button.tabIndex = chosen ? 0 : -1;
|
|
if (chosen && focus) button.focus();
|
|
}
|
|
document.querySelectorAll("[data-policy-settings-page]").forEach(page => page.hidden = page.dataset.policySettingsPage !== name);
|
|
}
|
|
for (const button of policyTabs) {
|
|
button.addEventListener("click", () => selectPolicyTab(button.dataset.policySettingsTab));
|
|
button.addEventListener("keydown", event => {
|
|
const index = policyTabs.indexOf(button); let next;
|
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % policyTabs.length;
|
|
else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + policyTabs.length - 1) % policyTabs.length;
|
|
else if (event.key === "Home") next = 0; else if (event.key === "End") next = policyTabs.length - 1; else return;
|
|
event.preventDefault(); selectPolicyTab(policyTabs[next].dataset.policySettingsTab, true);
|
|
});
|
|
}
|
|
selectPolicyTab(["headers", "response"].includes(requestedTab) ? requestedTab : "user-agent");
|
|
const cacheTabs = [...document.querySelectorAll("[data-cache-settings-tab]")];
|
|
function selectCacheTab(name, focus = false) {
|
|
if (!cacheTabs.some(button => button.dataset.cacheSettingsTab === name)) return;
|
|
for (const button of cacheTabs) { const chosen = button.dataset.cacheSettingsTab === name; button.setAttribute("aria-selected", String(chosen)); button.tabIndex = chosen ? 0 : -1; if (chosen && focus) button.focus(); }
|
|
document.querySelectorAll("[data-cache-settings-page]").forEach(page => page.hidden = page.dataset.cacheSettingsPage !== name);
|
|
}
|
|
for (const button of cacheTabs) {
|
|
button.addEventListener("click", () => selectCacheTab(button.dataset.cacheSettingsTab));
|
|
button.addEventListener("keydown", event => { const index = cacheTabs.indexOf(button); let next; if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % cacheTabs.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + cacheTabs.length - 1) % cacheTabs.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = cacheTabs.length - 1; else return; event.preventDefault(); selectCacheTab(cacheTabs[next].dataset.cacheSettingsTab, true); });
|
|
}
|
|
selectCacheTab("general");
|
|
const loggingTabs = [...document.querySelectorAll("[data-logging-settings-tab]")];
|
|
function selectLoggingTab(name, focus = false) {
|
|
if (!loggingTabs.some(button => button.dataset.loggingSettingsTab === name)) return;
|
|
for (const button of loggingTabs) { const chosen = button.dataset.loggingSettingsTab === name; button.setAttribute("aria-selected", String(chosen)); button.tabIndex = chosen ? 0 : -1; if (chosen && focus) button.focus(); }
|
|
document.querySelectorAll("[data-logging-settings-page]").forEach(page => page.hidden = page.dataset.loggingSettingsPage !== name);
|
|
const loggingForm = $("logging-form"), errorLogForm = $("error-log-form");
|
|
if (loggingForm) loggingForm.hidden = name === "errors";
|
|
if (errorLogForm) errorLogForm.hidden = name !== "errors";
|
|
}
|
|
for (const button of loggingTabs) {
|
|
button.addEventListener("click", () => selectLoggingTab(button.dataset.loggingSettingsTab));
|
|
button.addEventListener("keydown", event => { const index = loggingTabs.indexOf(button); let next; if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % loggingTabs.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + loggingTabs.length - 1) % loggingTabs.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = loggingTabs.length - 1; else return; event.preventDefault(); selectLoggingTab(loggingTabs[next].dataset.loggingSettingsTab, true); });
|
|
}
|
|
selectLoggingTab("outputs");
|
|
const httpTabs = [...document.querySelectorAll("[data-http-settings-tab]")];
|
|
function selectHttpTab(name, focus = false) {
|
|
if (!httpTabs.some(button => button.dataset.httpSettingsTab === name)) return;
|
|
for (const button of httpTabs) { const chosen = button.dataset.httpSettingsTab === name; button.setAttribute("aria-selected", String(chosen)); button.tabIndex = chosen ? 0 : -1; if (chosen && focus) button.focus(); }
|
|
document.querySelectorAll("[data-http-settings-page]").forEach(page => page.hidden = page.dataset.httpSettingsPage !== name);
|
|
}
|
|
for (const button of httpTabs) {
|
|
button.addEventListener("click", () => selectHttpTab(button.dataset.httpSettingsTab));
|
|
button.addEventListener("keydown", event => { const index = httpTabs.indexOf(button); let next; if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % httpTabs.length; else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (index + httpTabs.length - 1) % httpTabs.length; else if (event.key === "Home") next = 0; else if (event.key === "End") next = httpTabs.length - 1; else return; event.preventDefault(); selectHttpTab(httpTabs[next].dataset.httpSettingsTab, true); });
|
|
}
|
|
selectHttpTab("http2");
|
|
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(), configured = window.rustProxyPrometheusToken?.configured?.() || Boolean(envelope?.config?.prometheus?.bearer_token_env);
|
|
$("prom-scrape-example").textContent = `scrape_configs:\n - job_name: rustproxy\n metrics_path: ${JSON.stringify(path)}\n static_configs:\n - targets: [${JSON.stringify(addr)}]${configured ? "\n authorization:\n credentials_file: /etc/prometheus/rustproxy.token" : ""}`;
|
|
}
|
|
function render() {
|
|
if (!envelope) return; const p = { ...defaults, ...envelope.config.prometheus };
|
|
if (!settingsDirty.has("prometheus-form")) {
|
|
$("prom-enabled").checked = p.enabled; $("prom-listen").value = p.listen; $("prom-path").value = p.path;
|
|
$("prom-cidrs").value = p.allowed_cidrs.join("\n"); $("prom-trusted-no-token").checked = !!p.allow_trusted_network_without_token;
|
|
for (const [id, key] of [["system", "include_system"], ["vhosts", "include_vhosts"], ["traffic", "include_traffic"], ["backends", "include_backends"], ["docker-vhosts", "include_docker_vhosts"], ["domains", "include_cache_domains"], ["streams", "include_streams"], ["logging", "include_logging"]]) $(`prom-${id}`).checked = p[key];
|
|
}
|
|
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";
|
|
$("admin-tls").checked = !!admin.tls;
|
|
const current = admin.certificate_id || "", generated = document.createElement("option"); generated.value = ""; generated.textContent = t("administrationGeneratedCertificate");
|
|
$("admin-certificate").replaceChildren(generated, ...(envelope.config.tls?.certificates || []).map(cert => { const o = document.createElement("option"); o.value = cert.id; o.textContent = cert.id; return o; })); $("admin-certificate").value = current;
|
|
const extraHosts = additionalAdminHosts(admin.allowed_hosts || [], admin.public_origin);
|
|
$("admin-hosts").value = extraHosts.join("\n");
|
|
$("admin-extra-hosts-details").open = extraHosts.length > 0;
|
|
$("admin-public-origin").value = admin.public_origin || "";
|
|
$("admin-session-days").value = Number(admin.session_days || 30);
|
|
for (const codec of ["zstd", "br", "gzip"]) $(`admin-compression-${codec}`).checked = admin.panel_compression?.[codec] !== false;
|
|
}
|
|
const listenLocked = !!adminStatus?.listen_locked;
|
|
$("admin-listen").disabled = listenLocked; $("admin-listen-lock-note").hidden = !listenLocked;
|
|
const syncAdminTls = () => { $("admin-certificate").disabled = !$("admin-tls").checked; }; syncAdminTls();
|
|
$("admin-status").textContent = adminStatus?.restart_required ? t("restartPending") : adminStatus?.active?.tls ? `${t("administrationActiveHttps")}: ${adminStatus.active.listen}` : `${t("administrationActiveHttp")}: ${adminStatus?.active?.listen || latestStatus?.admin_listen || "-"}`;
|
|
const runtime = latestStatus?.runtime || {};
|
|
const system = latestStatus?.system || {};
|
|
const hasMetric = value => value !== null && value !== undefined && Number.isFinite(Number(value));
|
|
const humanCount = value => hasMetric(value) ? Number(value).toLocaleString(locale()) : t("unavailable");
|
|
const humanBytes = value => hasMetric(value) ? bytes(Number(value)) : t("unavailable");
|
|
const humanUptime = value => hasMetric(value) ? uptime(Number(value)) : t("unavailable");
|
|
const percent = value => hasMetric(value) ? `${Number(value).toLocaleString(locale(), { maximumFractionDigits: 1 })}%` : t("unavailable");
|
|
const ram = hasMetric(system.memory_used_bytes) || hasMetric(system.memory_total_bytes)
|
|
? `${humanBytes(system.memory_used_bytes)} / ${humanBytes(system.memory_total_bytes)}` : t("unavailable");
|
|
for (const [id, value] of [
|
|
["runtime-version", latestStatus?.version],
|
|
["runtime-platform", runtime.platform],
|
|
["runtime-operating-system", runtime.operating_system],
|
|
["runtime-kernel-version", runtime.kernel_version],
|
|
["runtime-architecture", runtime.architecture],
|
|
["runtime-logical-cpus", humanCount(system.logical_cpus)],
|
|
["runtime-cpu-usage", percent(system.cpu_percent)],
|
|
["runtime-system-memory", ram],
|
|
["runtime-system-uptime", humanUptime(system.system_uptime_seconds)],
|
|
["runtime-process-uptime", humanUptime(latestStatus?.uptime_seconds)],
|
|
["runtime-process-memory", humanBytes(system.process_rss_bytes)],
|
|
["runtime-process-threads", humanCount(system.process_threads)],
|
|
["runtime-open-fds", humanCount(system.open_fds)],
|
|
["runtime-statistics-source", runtime.statistics_source],
|
|
["runtime-data-directory", runtime.data_directory],
|
|
["runtime-database-path", runtime.database_path]
|
|
]) { const node = $(id); if (node) node.textContent = value ?? t("unavailable"); }
|
|
const certificateRuntime = runtime.certificate_runtime || {};
|
|
const availability = flag => flag === true ? t("runtimeAvailable") : flag === false ? t("runtimeUnavailable") : t("unavailable");
|
|
const modeLabel = certificateRuntime.status === "unavailable" ? t("certificateRuntimeUnavailable") : certificateRuntime.status === "ready" && certificateRuntime.mode === "managed" ? t("certificateRuntimeManaged") : certificateRuntime.status === "ready" && certificateRuntime.mode === "system" ? t("certificateRuntimeSystem") : t("certificateRuntimePreparing");
|
|
const versioned = (flag, version) => version && flag === true ? `${availability(flag)} · ${version}` : availability(flag);
|
|
for (const [id, value] of [
|
|
["runtime-acme-mode", modeLabel],
|
|
["runtime-acme-status", certificateRuntime.message ? t(certificateRuntime.message) : t("unavailable")],
|
|
["runtime-python", versioned(certificateRuntime.python, certificateRuntime.python_version)],
|
|
["runtime-pip", versioned(certificateRuntime.pip, certificateRuntime.pip_version)],
|
|
["runtime-certbot", versioned(certificateRuntime.certbot, certificateRuntime.certbot_version)],
|
|
["runtime-acme-directory", certificateRuntime.managed_directory]
|
|
]) { const node = $(id); if (node) node.textContent = value || t("unavailable"); }
|
|
|
|
const policy = envelope.config.tls?.policy || { tls12: true, tls13: true, cipher_suites: [] };
|
|
if (!settingsDirty.has("tls-policy-form")) { $("tls-policy-12").checked = policy.tls12 !== false; $("tls-policy-13").checked = policy.tls13 !== false; $("tls-policy-ciphers").value = (policy.cipher_suites || []).join("\n"); }
|
|
const available = latestStatus?.tls?.cipher_suites || [];
|
|
const effective = latestStatus?.tls?.effective_cipher_suites || [];
|
|
$("tls-policy-effective").textContent = effective.length ? `${t("tlsEffectiveCiphers")}: ${effective.join(", ")}` : "";
|
|
$("tls-policy-available").textContent = available.length ? `${t("tlsAvailableCiphers")}: ${available.join(", ")}` : "";
|
|
window.rustProxyCertificateApi?.render(); window.rustProxyCertificateApi?.load();
|
|
}
|
|
$("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: envelope?.config?.prometheus?.bearer_token_env || null, allow_trusted_network_without_token: $("prom-trusted-no-token").checked,
|
|
include_system: $("prom-system").checked, include_vhosts: $("prom-vhosts").checked, include_traffic: $("prom-traffic").checked, include_backends: $("prom-backends").checked, include_docker_vhosts: $("prom-docker-vhosts").checked, include_cache_domains: $("prom-domains").checked, include_streams: $("prom-streams").checked, include_logging: $("prom-logging").checked
|
|
};
|
|
}, "prometheus");
|
|
bindSettingsForm("administration-form", next => {
|
|
const locked = !!latestStatus?.administration?.listen_locked;
|
|
next.administration = { listen: locked ? null : $("admin-listen").value.trim(), tls: $("admin-tls").checked, certificate_id: $("admin-tls").checked ? ($("admin-certificate").value || null) : null, allowed_hosts: additionalAdminHosts($("admin-hosts").value.split(/[\n,]+/), $("admin-public-origin").value.trim()), public_origin: $("admin-public-origin").value.trim() || null, session_days: Number($("admin-session-days").value), panel_compression: Object.fromEntries(["zstd", "br", "gzip"].map(codec => [codec, $(`admin-compression-${codec}`).checked])) };
|
|
}, "administration");
|
|
bindSettingsForm("tls-policy-form", next => {
|
|
if (!$("tls-policy-12").checked && !$("tls-policy-13").checked) throw fieldError($("tls-policy-12"), t("tlsPolicyVersionRequired"));
|
|
next.tls ||= {}; next.tls.policy = { tls12: $("tls-policy-12").checked, tls13: $("tls-policy-13").checked, cipher_suites: $("tls-policy-ciphers").value.split(/[\n,]+/).map(v => v.trim()).filter(Boolean) };
|
|
}, "tls");
|
|
$("admin-tls").addEventListener("change", () => { $("admin-certificate").disabled = !$("admin-tls").checked; });
|
|
$("prometheus-form").addEventListener("input", example);
|
|
$("prom-preview").addEventListener("click", async () => {
|
|
const epoch = authEpoch, button = $("prom-preview"); button.disabled = true;
|
|
try {
|
|
const response = await fetch("/api/metrics", { credentials: "same-origin", headers: { "Accept": "text/plain" } }); if (!response.ok) throw new Error(`${t("prometheusPreviewFailed")} (${response.status})`);
|
|
const text = await response.text(); if (epoch !== authEpoch || !authenticated) return; $("prom-preview-output").textContent = text.slice(0, 262144); $("prom-preview-output").hidden = false;
|
|
} catch (error) { if (epoch === authEpoch) toast(error.message, true); } finally { button.disabled = false; }
|
|
});
|
|
window.rustProxySettings = { select, selectHttpTab, syncLocation, render, refreshSearch: applySettingsSearch, current: () => active, scrollToApiTokens, reset() { if (search) search.value = ""; applySettingsSearch(); select(validTab(rememberedTab) ? rememberedTab : "appearance", false, false); selectPolicyTab("user-agent"); selectCacheTab("general"); selectLoggingTab("outputs"); selectHttpTab("http2"); window.rustProxyCertificateApi?.reset(); window.rustProxyApiTokens?.reset?.(); window.rustProxyPrometheusToken?.reset?.(); $("prom-preview-output").textContent = ""; $("prom-preview-output").hidden = true; } };
|
|
select(active); render();
|
|
})();
|