v0.35.1: performance, AGPL licensing and documentation overhaul

- Web Server reads static files inline; proxy middleware chain flattened
- About window with license summary and project links; AGPL-3.0-or-later
- Rewritten documentation: feature list, screenshots, performance guide
- Fix false spikes in per-Vhost request-rate charts
This commit is contained in:
gru committed 2026-11-05 18:29:56 +01:00
1 parent d3b492874b
commit eba9f89283
135 files changed
+1425 -1064

No files matched your search

+4 -11
View File
@@ -24,7 +24,6 @@ const group = (key, advanced = false) => {
const body = el("div", undefined, "vh-grid"); box.append(body); container.append(box); return body;
};
const note = (parent, key) => { const node = text("p", key, "fine vh-wide"); parent.append(node); return node; };
// Presentation-only subgroup: all existing inputs remain inside their original form.
const subsection = (parent, key) => {
const box = el("section", undefined, "vh-wide vh-setting-section");
box.append(text("h3", key));
@@ -215,8 +214,7 @@ function memberRow(parent, value = { id: `origin-${parent.children.length + 1}`,
const connection = el("div", undefined, "vh-wide vh-backend-switches vh-backend-connection"); main.append(connection);
controls["member-private"] = field(connection, "member-private", "vhPrivate", value.allow_private, "checkbox");
const preserve = field(connection, "member-host", "vhPreserveHost", value.preserve_host === true, "checkbox");
// Preserve Host is a request-authority choice, not an HTTP/1.1-only option.
// The Rust forwarder keeps HTTP/2 :authority consistent with this value.
// Preserve Host also applies to HTTP/2 :authority.
const advanced = el("details", undefined, "vh-wide vh-backend-advanced"), extra = el("div", undefined, "vh-grid"); advanced.append(text("summary", "advanced"), extra);
field(extra, "member-allowlist", "vhAddressAllowlist", (value.address_allowlist || []).join("\n"), "textarea", { rows: 2, maxLength: 8192 });
@@ -230,8 +228,7 @@ function memberRow(parent, value = { id: `origin-${parent.children.length + 1}`,
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");
// The per-member settings live in the Health tab. Keep one persistent form
// per backend, so switching the selection never discards unsaved edits.
// One persistent form per backend keeps unsaved edits when switching.
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 || {};
@@ -305,9 +302,7 @@ function debugOriginIssues(members) {
let parsed; try { parsed = new URL(member.url); } catch { return false; }
const host = parsed.hostname.replace(/^\[|\]$/g, ""), ipHost = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host) || host.includes(":");
if (parsed.protocol !== "https:" || !ipHost) return false;
// The troubleshooting preset is OPTIONAL, not a protocol compatibility rule.
// Warn only when IP-host TLS verification may need an explicit identity;
// HTTP/1.1, Auto and H2 plus preserve_host are all valid configurations.
// Warn only when TLS verification of an IP host needs an explicit server name.
return member.tls_verify === true && !String(member.tls_server_name || "").trim();
});
}
@@ -498,9 +493,7 @@ function locationsEditor(parent, values) {
row.addEventListener("input", () => { syncUi(); sync(); }); row.addEventListener("change", () => { syncUi(); sync(); });
rows.append(row); syncUi();
}
// The main vhost backend is an internal fallback, not a Custom location.
// An empty location list must stay empty so the editor cannot accidentally
// delete or recreate the main route as user configuration.
// The main backend is an internal fallback, never an editable location.
for (const value of values) add(value);
const bottom = el("div", undefined, "vh-wide location-toolbar location-toolbar-bottom"); bottom.append(button("locationAdd", () => { add(); sync(); }, "primary")); parent.append(bottom);
form.querySelector(".vh-members").addEventListener("input", () => { for (const select of rows.querySelectorAll('[name="location-backend"]')) memberOptions(select); });