From 6fdbe0e15916756cfad6efd0a7e206870068c88c Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Tue, 14 Jul 2026 10:30:39 +0300 Subject: [PATCH] fix(settings): replacing a managed local model no longer breaks save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - put_settings now heals [routing] after the managed-servers list changes: a removed server whose port is taken over by a new one is treated as a replacement (old id → new id in default + every tier); truly vanished ids are stripped; an emptied default falls back to the first managed server / pool model. Previously the stale id failed validation and the save errored. - /admin/api/cmf/port now returns managed:true when the port is held by this gateway's own managed server; the Settings port check shows that as info ("freed on restart, replacing is fine") instead of a red "port busy". --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/admin/mod.rs | 66 ++++++++++++++++++++++++++++++++++++++++ web/js/i18n/en.js | 1 + web/js/i18n/ru.js | 1 + web/js/views/settings.js | 6 +++- 6 files changed, 75 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82ddbef..3f3a94b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "cortiq-gateway" -version = "0.2.15" +version = "0.2.16" dependencies = [ "anyhow", "arc-swap", diff --git a/Cargo.toml b/Cargo.toml index 9f6fde0..1ba04c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cortiq-gateway" -version = "0.2.15" +version = "0.2.16" edition = "2021" rust-version = "1.75" license = "Apache-2.0" diff --git a/src/admin/mod.rs b/src/admin/mod.rs index 955d2fc..d72364c 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -905,12 +905,75 @@ async fn put_settings(State(state): State, Json(b): Json = cfg + .models + .iter() + .map(|m| m.id.clone()) + .chain(servers.iter().map(|s| s.id.clone())) + .collect(); + let new_by_port: HashMap = + servers.iter().map(|s| (s.port, s.id.clone())).collect(); + let mut rename: HashMap = HashMap::new(); + for old in old_servers { + if !known.contains(&old.id) { + if let Some(new_id) = new_by_port.get(&old.port) { + rename.insert(old.id.clone(), new_id.clone()); + } + } + } + let fix = |id: &str| -> Option { + if known.contains(id) { + Some(id.to_string()) + } else { + rename.get(id).cloned() + } + }; + if !cfg.routing.default.is_empty() { + cfg.routing.default = fix(&cfg.routing.default).unwrap_or_default(); + } + if cfg.routing.default.is_empty() { + cfg.routing.default = servers + .first() + .map(|s| s.id.clone()) + .or_else(|| cfg.models.first().map(|m| m.id.clone())) + .unwrap_or_default(); + } + for tt in cfg.routing.tiers.values_mut() { + let list = match tt { + TierTargets::List(v) => v.clone(), + TierTargets::One(s) => vec![s.clone()], + }; + let mut healed: Vec = Vec::new(); + for id in &list { + if let Some(new_id) = fix(id) { + if !healed.contains(&new_id) { + healed.push(new_id); + } + } + } + *tt = TierTargets::List(healed); + } +} + // ───────────────────────────── cmf runtime ───────────────────────────────── /// Status of the `cortiq` CLI (the CMF format runtime): is it installed, which /// version, and whether a managed server is running. Drives the "Install cortiq" @@ -1028,6 +1091,9 @@ async fn cmf_port_check(State(state): State, Query(q): Query { portIn.value = r.suggested; mount(st, ""); }); kids.push(b); } mount(st, h("span", {}, ...kids));