From 9eb1b9d726418ffdcff0ef9872ef91fbb0a96933 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 15 Feb 2026 15:30:07 +0100 Subject: [PATCH] Support merging legacy and multi-config enterprise settings --- documentation/Enterprise IT.md | 2 +- runtime/src/environment.rs | 87 ++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/documentation/Enterprise IT.md b/documentation/Enterprise IT.md index 3607105e..6da9c766 100644 --- a/documentation/Enterprise IT.md +++ b/documentation/Enterprise IT.md @@ -39,7 +39,7 @@ MINDWORK_AI_STUDIO_ENTERPRISE_CONFIGS=9072b77d-ca81-40da-be6a-861da525ef7b@https ### Single configuration (legacy) -The following single-configuration keys and variables are still supported for backwards compatibility. If the multi-config variables above are not set, AI Studio falls back to these: +The following single-configuration keys and variables are still supported for backwards compatibility. AI Studio always reads both the multi-config and legacy variables and merges all found configurations into one list. If a configuration ID appears in both, the entry from the multi-config format takes priority (first occurrence wins). This means you can migrate to the new format incrementally without losing existing configurations: - Key `HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT`, value `config_id` or variable `MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID`: This must be a valid [GUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Globally_unique_identifier). It uniquely identifies the configuration. You can use an ID per department, institute, or even per person. diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index 2dee6a8c..f3ccdc60 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -152,45 +152,43 @@ pub struct EnterpriseConfig { pub server_url: String, } -/// Returns all enterprise configurations. Supports the new multi-config format -/// (`id1@url1;id2@url2`) as well as the legacy single-config environment variables. +/// Returns all enterprise configurations. Collects configurations from both the +/// new multi-config format (`id1@url1;id2@url2`) and the legacy single-config +/// environment variables, merging them into one list. Duplicates (by ID) are +/// skipped — the first occurrence wins. #[get("/system/enterprise/configs")] pub fn read_enterprise_configs(_token: APIToken) -> Json> { info!("Trying to read the enterprise environment for all configurations."); - // Try the new combined format first: + let mut configs: Vec = Vec::new(); + let mut seen_ids: std::collections::HashSet = std::collections::HashSet::new(); + + // Read the new combined format: let combined = get_enterprise_configuration( "configs", "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIGS", ); if !combined.is_empty() { - // Parse the new format: id1@url1;id2@url2; ... - let configs: Vec = combined - .split(';') - .filter_map(|entry| { - let entry = entry.trim(); - if entry.is_empty() { - return None; + // Parse the new format: id1@url1;id2@url2;... + for entry in combined.split(';') { + let entry = entry.trim(); + if entry.is_empty() { + continue; + } + + // Split at the first '@' (GUIDs never contain '@'): + if let Some((id, url)) = entry.split_once('@') { + let id = id.trim().to_lowercase(); + let url = url.trim().to_string(); + if !id.is_empty() && !url.is_empty() && seen_ids.insert(id.clone()) { + configs.push(EnterpriseConfig { id, server_url: url }); } - - // Split at the first '@' (GUIDs never contain '@'): - entry.split_once('@').and_then(|(id, url)| { - let id = id.trim().to_string(); - let url = url.trim().to_string(); - if id.is_empty() || url.is_empty() { - None - } else { - Some(EnterpriseConfig { id, server_url: url }) - } - }) - }) - .collect(); - - return Json(configs); + } + } } - // Fallback: read the legacy single-config variables: + // Also read the legacy single-config variables: let config_id = get_enterprise_configuration( "config_id", "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID", @@ -202,13 +200,13 @@ pub fn read_enterprise_configs(_token: APIToken) -> Json> ); if !config_id.is_empty() && !config_server_url.is_empty() { - return Json(vec![EnterpriseConfig { - id: config_id, - server_url: config_server_url, - }]); + let id = config_id.trim().to_lowercase(); + if seen_ids.insert(id.clone()) { + configs.push(EnterpriseConfig { id, server_url: config_server_url }); + } } - Json(vec![]) + Json(configs) } /// Returns all enterprise configuration IDs that should be deleted. Supports the new @@ -217,33 +215,38 @@ pub fn read_enterprise_configs(_token: APIToken) -> Json> pub fn read_enterprise_delete_config_ids(_token: APIToken) -> Json> { info!("Trying to read the enterprise environment for configuration IDs to delete."); - // Try the new combined format first: + let mut ids: Vec = Vec::new(); + let mut seen: std::collections::HashSet = std::collections::HashSet::new(); + + // Read the new combined format: let combined = get_enterprise_configuration( "delete_config_ids", "MINDWORK_AI_STUDIO_ENTERPRISE_DELETE_CONFIG_IDS", ); if !combined.is_empty() { - let ids: Vec = combined - .split(';') - .map(|id| id.trim().to_string()) - .filter(|id| !id.is_empty()) - .collect(); - - return Json(ids); + for id in combined.split(';') { + let id = id.trim().to_lowercase(); + if !id.is_empty() && seen.insert(id.clone()) { + ids.push(id); + } + } } - // Fallback: read the legacy single-delete variable: + // Also read the legacy single-delete variable: let delete_id = get_enterprise_configuration( "delete_config_id", "MINDWORK_AI_STUDIO_ENTERPRISE_DELETE_CONFIG_ID", ); if !delete_id.is_empty() { - return Json(vec![delete_id]); + let id = delete_id.trim().to_lowercase(); + if seen.insert(id.clone()) { + ids.push(id); + } } - Json(vec![]) + Json(ids) } fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String {