This commit is contained in:
Mateusz Gruszczyński committed 2026-10-10 08:53:55 +02:00
1 parent 1431dd47f0
commit f3dd34dae4
42 files changed
+950 -133

No files matched your search

+23 -11
View File
@@ -1,7 +1,7 @@
"use strict";
// Vhost UI: language content comes from langs/*.json, never a locale switch.
// Drafts are isolated from SSE/config rendering; writes share revision+CSRF CAS.
let basicEditor = null, locationsReader = () => [], aclControl = null, editing = null, revision = null, dirty = false, initialized = false, readBindings = () => [], readHosts = () => [], countryRatesReader = () => [], pagesEditor = null, stagedCertificates = [], uploading = false, draftEpoch = 0;
let basicEditor = null, locationsReader = () => [], aclControl = null, editing = null, revision = null, dirty = false, initialized = false, readBindings = () => [], readHosts = () => [], countryRatesReader = () => [], pagesEditor = null, stagedCertificates = [], uploading = false, draftEpoch = 0, backendHealthEditorControl = null;
const chartHistory = new Map();
const backendTrafficHistory = new Map();
const vhostTrafficHistory = new Map();
@@ -137,7 +137,7 @@
const state = {
editing, revision, label: $("vhost-editing").textContent,
fields: window.rustProxyDrafts.takeChildren(container), tabs: window.rustProxyDrafts.takeChildren($("vhost-tabs")),
basicEditor, locationsReader, aclControl, readBindings, readHosts, countryRatesReader, pagesEditor,
basicEditor, locationsReader, aclControl, readBindings, readHosts, countryRatesReader, pagesEditor, backendHealthEditorControl,
stagedCertificates, uploading
};
const title = read("id")?.value.trim() || editing || t("draftUntitled");
@@ -147,7 +147,7 @@
function restoreDraft(state) {
draftEpoch++; window.rustProxyDrafts.restoreChildren(container, state.fields); window.rustProxyDrafts.restoreChildren($("vhost-tabs"), state.tabs);
editing = state.editing; revision = state.revision; basicEditor = state.basicEditor; locationsReader = state.locationsReader; aclControl = state.aclControl;
readBindings = state.readBindings; readHosts = state.readHosts; countryRatesReader = state.countryRatesReader; pagesEditor = state.pagesEditor;
readBindings = state.readBindings; readHosts = state.readHosts; countryRatesReader = state.countryRatesReader; pagesEditor = state.pagesEditor; backendHealthEditorControl = state.backendHealthEditorControl || null;
stagedCertificates = state.stagedCertificates || []; uploading = state.uploading === true; initialized = true; dirty = true; $("vhost-editing").textContent = state.label;
if (!dialog.open) dialog.showModal(); if (latestStatus) renderStatus(latestStatus);
}
@@ -194,7 +194,7 @@
const row = el("article", undefined, "vh-member vh-row vh-backend-card");
const head = el("div", undefined, "vh-backend-head"), identity = el("div", undefined, "vh-backend-identity");
const title = el("strong", value.id || t("backend"), "vh-backend-title"), endpoint = el("span", value.url || "-", "fine vh-backend-endpoint");
identity.append(title, endpoint); head.append(identity); const remove = button("remove", () => { row.remove(); changed(); }); head.append(remove); row.append(head);
identity.append(title, endpoint); head.append(identity); const remove = button("remove", () => { row.remove(); backendHealthEditorControl?.refresh(); changed(); }); head.append(remove); row.append(head);
const main = el("div", undefined, "vh-grid vh-backend-main"); row.append(main);
const id = field(main, "member-id", "vhMemberId", value.id, "text", { pattern: "[A-Za-z0-9_-]{1,64}", maxLength: 64 });
@@ -222,9 +222,12 @@
field(extra, "member-max-concurrent", "vhMaxConcurrentRequests", value.max_concurrent_requests ?? 0, "number", { required: true, min: 0, max: 1000000 });
field(extra, "member-queue-timeout", "vhBackendQueueTimeout", value.queue_timeout_ms ?? 0, "number", { required: true, min: 0, max: 60000 });
note(extra, "vhBackendCapacityHelp");
const customHealth = field(extra, "member-health-custom", "backendHealthOverride", value.health_check != null, "checkbox");
// The per-member settings live in the Health tab. Keep one persistent form
// per backend, so switching the selection never discards unsaved edits.
const healthPanel = el("section", undefined, "vh-grid vh-backend-health-panel");
const customHealth = field(healthPanel, "member-health-custom", "backendHealthOverride", value.health_check != null, "checkbox");
const health = value.health_check || {};
const healthFields = el("div", undefined, "vh-wide vh-grid vh-backend-health-fields"); extra.append(healthFields);
const healthFields = el("div", undefined, "vh-wide vh-grid vh-backend-health-fields"); healthPanel.append(healthFields);
field(healthFields, "member-health-mode", "vhHealthMode", health.mode || "tcp", "select", { values: [["off", "vhOff"], ["tcp", "vhTcp"], ["http", "vhHttp"]] });
field(healthFields, "member-health-path", "vhHealthPath", health.path ?? "/", "text", { maxLength: 1024 });
field(healthFields, "member-health-host", "vhHealthHost", health.host ?? "", "text", { maxLength: 253 });
@@ -236,11 +239,19 @@
field(healthFields, "member-health-min", "vhStatusMin", health.expected_status_min ?? 200, "number", { min: 100, max: 599, required: true });
field(healthFields, "member-health-max", "vhStatusMax", health.expected_status_max ?? 399, "number", { min: 100, max: 599, required: true });
const syncHealth = () => {
const editable = controls["member-health-enabled"].checked && customHealth.checked;
healthFields.hidden = !customHealth.checked;
healthFields.querySelectorAll("input,select").forEach(input => { input.disabled = !customHealth.checked; });
healthFields.querySelectorAll("input,select").forEach(input => { input.disabled = !editable; });
};
customHealth.addEventListener("change", syncHealth); syncHealth();
note(extra, "backendHealthOverridesHelp");
let customInitialized = value.health_check != null;
customHealth.addEventListener("change", () => {
if (customHealth.checked && !customInitialized) { inheritBackendHealthInputs(healthPanel); customInitialized = true; }
syncHealth();
});
controls["member-health-enabled"].addEventListener("change", () => { syncHealth(); backendHealthEditorControl?.refresh(); });
row.memberHealthPanel = healthPanel;
row.memberHealthEnabled = controls["member-health-enabled"];
syncHealth();
row.append(advanced);
const actions = el("div", undefined, "vh-backend-actions");
@@ -258,10 +269,11 @@
riskGate.hidden = show !== true;
if (clearWhenHidden && show === false) localRisk.checked = false;
};
id.addEventListener("input", syncIdentity); url.addEventListener("input", () => { syncIdentity(); syncRisk(true); }); url.addEventListener("change", () => syncRisk(true));
id.addEventListener("input", () => { syncIdentity(); backendHealthEditorControl?.refresh(); }); url.addEventListener("input", () => { syncIdentity(); syncRisk(true); backendHealthEditorControl?.refresh(); }); url.addEventListener("change", () => syncRisk(true));
row.syncLocalRisk = () => syncRisk(false);
syncIdentity(); syncRisk(false); parent.append(row);
syncIdentity(); syncRisk(false); parent.append(row); backendHealthEditorControl?.refresh();
}
function countryRateEditor(parent, values = []) {
const rows = el("div", undefined, "vh-wide vh-country-rates"); parent.append(rows);
const add = (value = {}) => {