v0.27.0-hotfix

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-07 09:15:21 +02:00
1 parent 762eb9e28b
commit 39238fdf53
17 files changed
+74 -67

No files matched your search

+10 -6
View File
@@ -2424,31 +2424,35 @@
"type": "boolean",
"nullable": true,
"default": null,
"description": "Per-published-vhost dynamic compression override. null inherits the enclosing/global policy; true/false enables or disables the scope."
"description": "Single per-Docker-vhost dynamic compression switch. null inherits the global policy; true/false enables or disables compression for this publication. Codec availability and priority come from global Response settings."
},
"compression_zstd": {
"type": "boolean",
"nullable": true,
"default": null,
"description": "Optional per-Docker-vhost Zstandard override. null inherits; globally disabled codecs remain unavailable."
"description": "Legacy compatibility field accepted when loading older 0.27.0 pre-release configuration. Ignored for Docker vhosts; Zstandard availability is controlled by global Response settings.",
"deprecated": true
},
"compression_br": {
"type": "boolean",
"nullable": true,
"default": null,
"description": "Optional per-Docker-vhost Brotli override. null inherits; globally disabled codecs remain unavailable."
"description": "Legacy compatibility field accepted when loading older 0.27.0 pre-release configuration. Ignored for Docker vhosts; Brotli availability is controlled by global Response settings.",
"deprecated": true
},
"compression_gzip": {
"type": "boolean",
"nullable": true,
"default": null,
"description": "Optional per-Docker-vhost gzip override. null inherits; globally disabled codecs remain unavailable."
"description": "Legacy compatibility field accepted when loading older 0.27.0 pre-release configuration. Ignored for Docker vhosts; gzip availability is controlled by global Response settings.",
"deprecated": true
},
"compression_deflate": {
"type": "boolean",
"nullable": true,
"default": null,
"description": "Optional per-Docker-vhost deflate override. null inherits; globally disabled codecs remain unavailable."
"description": "Legacy compatibility field accepted when loading older 0.27.0 pre-release configuration. Ignored for Docker vhosts; deflate availability is controlled by global Response settings.",
"deprecated": true
}
},
"required": [
@@ -5524,7 +5528,7 @@
}
}
},
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7–30 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
"description": "Sets an HttpOnly SameSite=Strict session cookie. Browser sessions use the configured 7\u201330 day absolute lifetime. Use the panel sign-in page. No bearer token or URL token is accepted.",
"security": [],
"parameters": [
{
+6 -10
View File
@@ -251,8 +251,9 @@
const inline = row.querySelector(".docker-acl-inline")?.checked;
return {
service_key: row.dataset.key, hostname: row.querySelector(".docker-hostname").value.trim().toLowerCase(), aliases: row._readAliases?.() || [], target_ip: row.querySelector(".docker-target-ip").value || null,
enabled: true, scheme: row.querySelector(".docker-scheme").value, preserve_host: row.querySelector(".docker-preserve-host").checked, force_ssl: row.querySelector(".docker-force-ssl").checked, websockets: row.querySelector(".docker-websockets").checked, cache_enabled: row.querySelector(".docker-cache-enabled").checked, transform_enabled: row.querySelector(".docker-transform-enabled").checked, compression_enabled: (() => { const value = row.querySelector(".docker-compression-enabled").value; return value === "" ? null : value === "on"; })(),
compression_zstd: (() => { const value = row.querySelector(".docker-compression-zstd").value; return value === "" ? null : value === "on"; })(), compression_br: (() => { const value = row.querySelector(".docker-compression-br").value; return value === "" ? null : value === "on"; })(), compression_gzip: (() => { const value = row.querySelector(".docker-compression-gzip").value; return value === "" ? null : value === "on"; })(), compression_deflate: (() => { const value = row.querySelector(".docker-compression-deflate").value; return value === "" ? null : value === "on"; })(),
enabled: true, scheme: row.querySelector(".docker-scheme").value, preserve_host: row.querySelector(".docker-preserve-host").checked, force_ssl: row.querySelector(".docker-force-ssl").checked, websockets: row.querySelector(".docker-websockets").checked, cache_enabled: row.querySelector(".docker-cache-enabled").checked, transform_enabled: row.querySelector(".docker-transform-enabled").checked, compression_enabled: row.querySelector(".docker-compression-enabled").checked,
// Docker publications intentionally expose one compression switch only. Codec selection stays global.
compression_zstd: null, compression_br: null, compression_gzip: null, compression_deflate: null,
acl_profile_id: row.querySelector(".docker-acl-profile")?.value || null, acl: inline ? { default: row.querySelector(".docker-acl-default").value, rules: parseRules(row.querySelector(".docker-acl-rules").value) } : null,
proxy_timeouts: { connect_timeout_seconds: Number(row.querySelector(".docker-proxy-connect").value), response_header_timeout_seconds: Number(row.querySelector(".docker-proxy-header").value), read_timeout_seconds: Number(row.querySelector(".docker-proxy-read").value), send_timeout_seconds: Number(row.querySelector(".docker-proxy-send").value) },
health_check: collectHealth(row), passive_health: collectPassiveHealth(row)
@@ -289,14 +290,9 @@
const websockets = input("", "checkbox", "docker-websockets"); websockets.checked = assignment?.websockets !== false; const websocketField = checkboxField("dockerWebsockets", websockets);
const cacheEnabled = input("", "checkbox", "docker-cache-enabled"); cacheEnabled.checked = assignment?.cache_enabled === true; const cacheField = checkboxField("dockerCache", cacheEnabled);
const transformEnabled = input("", "checkbox", "docker-transform-enabled"); transformEnabled.checked = assignment?.transform_enabled === true; const transformField = checkboxField("dockerTransform", transformEnabled);
const compressionSelect = (className, current) => { const select = el("select", undefined, className); for (const [value, keyName] of [["", "inherit"], ["on", "enabled"], ["off", "notEnabled"]]) { const option = el("option", t(keyName)); option.value = value; select.append(option); } select.value = current === true ? "on" : current === false ? "off" : ""; return select; };
const compressionEnabled = compressionSelect("docker-compression-enabled", assignment?.compression_enabled);
const compressionZstd = compressionSelect("docker-compression-zstd", assignment?.compression_zstd), compressionBr = compressionSelect("docker-compression-br", assignment?.compression_br), compressionGzip = compressionSelect("docker-compression-gzip", assignment?.compression_gzip), compressionDeflate = compressionSelect("docker-compression-deflate", assignment?.compression_deflate);
const compressionFields = [[labeled("compressionZstd", compressionZstd), compressionZstd], [labeled("compressionBr", compressionBr), compressionBr], [labeled("compressionGzip", compressionGzip), compressionGzip], [labeled("compressionDeflate", compressionDeflate), compressionDeflate]];
const syncCompressionVisibility = () => { const disabled = compressionEnabled.value === "off"; for (const [field, control] of compressionFields) { field.hidden = disabled; control.disabled = disabled; } };
compressionEnabled.addEventListener("change", syncCompressionVisibility); syncCompressionVisibility();
const switches = el("div", undefined, "docker-switch-row"); switches.append(preserveLabel, forceSslField, websocketField, cacheField, transformField);
route.append(labeled("dockerHostname", host, t("dockerHostnameHelp").replace("{domain}", agent.base_domain)), aliases.node, labeled("dockerBackendIp", target), labeled("dockerBackendScheme", scheme), labeled("dockerCompression", compressionEnabled, t("dockerCompressionHelp")), ...compressionFields.map(([field]) => field), switches);
const compressionEnabled = input("", "checkbox", "docker-compression-enabled"); compressionEnabled.checked = assignment?.compression_enabled !== false; const compressionField = checkboxField("dockerCompression", compressionEnabled); compressionField.title = t("dockerCompressionHelp");
const switches = el("div", undefined, "docker-switch-row"); switches.append(preserveLabel, forceSslField, websocketField, cacheField, compressionField, transformField);
route.append(labeled("dockerHostname", host, t("dockerHostnameHelp").replace("{domain}", agent.base_domain)), aliases.node, labeled("dockerBackendIp", target), labeled("dockerBackendScheme", scheme), switches);
editor.append(route, aclEditor(assignment, agent), timeoutEditor(assignment), healthEditor(assignment), passiveHealthEditor(assignment));
const actions = el("div", undefined, "docker-service-actions"), apply = el("button", t("dockerApplyService"), "outline docker-apply"); apply.type = "button"; apply.disabled = !publish.checked; apply.addEventListener("click", async () => { apply.disabled = true; try { await saveAssignment(agent.id, row, true); } catch (error) { toast(error.message, true); } finally { if (row.isConnected) apply.disabled = !publish.checked; } }); actions.append(apply); editor.append(actions); row.append(editor);
function updateHint() { const targetLabel = target.selectedOptions?.[0]?.textContent || t("dockerBackendIpAuto"), aliasCount = aliases.count(); editorHint.textContent = `${host.value.trim() || technical} → ${scheme.value.toUpperCase()} · ${targetLabel}${aliasCount ? ` · +${aliasCount} ${t("dockerAliasesShort")}` : ""}`; } updateHint();
+3
View File
@@ -110,7 +110,10 @@ pub struct Assignment {
/// the per-app redirect switch existed. New UI assignments persist true.
pub force_ssl: Option<bool>,
pub websockets: bool,
/// Single Docker-vhost dynamic-compression switch. None inherits the global policy.
pub compression_enabled: Option<bool>,
/// Legacy pre-release 0.27.0 fields kept for saved-config compatibility. Docker
/// reconciliation ignores per-codec overrides; codecs are selected globally.
pub compression_gzip: Option<bool>,
pub compression_br: Option<bool>,
pub compression_zstd: Option<bool>,
+3 -7
View File
@@ -118,13 +118,9 @@ pub fn effective_config(
"force_ssl": assignment.force_ssl.unwrap_or(false),
"websockets": assignment.websockets,
"backends": backends,
"compression": {
"enabled": assignment.compression_enabled,
"gzip": assignment.compression_gzip,
"br": assignment.compression_br,
"zstd": assignment.compression_zstd,
"deflate": assignment.compression_deflate
},
// Docker publications intentionally use one compression switch. The enabled
// codec set is inherited from global Response settings.
"compression": {"enabled": assignment.compression_enabled},
"cache": {"enabled": assignment.cache_enabled},
"transform": {"enabled": assignment.transform_enabled},
"acl_profile_id": assignment.acl_profile_id.clone().or_else(|| agent.acl_profile_id.clone()),
+4 -3
View File
@@ -233,7 +233,7 @@ mod tests {
assert!(effective.vhosts[0].backends.is_empty());
assert!(effective.vhosts[0].transient_unavailable);
assert_eq!(effective.vhosts[0].compression.enabled, Some(false));
assert_eq!(effective.vhosts[0].compression.zstd, Some(false));
assert_eq!(effective.vhosts[0].compression.zstd, None);
let policy = crate::config::ValidationPolicy {
proxy_addr: "127.0.0.1:8080".parse().unwrap(),
@@ -271,6 +271,7 @@ mod tests {
service_key: "container:app:8080/tcp".into(),
hostname: "app".into(),
compression_enabled: Some(true),
// Legacy per-codec values must not affect Docker vhosts.
compression_gzip: Some(false),
compression_br: Some(true),
..Default::default()
@@ -294,8 +295,8 @@ mod tests {
assert_eq!(effective.vhosts[0].backends.len(), 1);
assert!(!effective.vhosts[0].transient_unavailable);
assert_eq!(effective.vhosts[0].compression.enabled, Some(true));
assert_eq!(effective.vhosts[0].compression.gzip, Some(false));
assert_eq!(effective.vhosts[0].compression.br, Some(true));
assert_eq!(effective.vhosts[0].compression.gzip, None);
assert_eq!(effective.vhosts[0].compression.br, None);
}
#[test]