From 3bfd402a2865565105b97d024e2d2a091a073515 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 15 Feb 2026 14:08:27 +0100 Subject: [PATCH] Support multiple enterprise configurations --- .../Layout/MainLayout.razor.cs | 9 +- .../Pages/Information.razor | 113 ++++++------ .../Pages/Information.razor.cs | 12 +- .../Tools/Rust/EnterpriseConfig.cs | 3 + .../Services/EnterpriseEnvironmentService.cs | 173 +++++++++++------- .../Tools/Services/RustService.Enterprise.cs | 131 ++++++------- .../wwwroot/changelog/v26.2.2.md | 1 + runtime/src/environment.rs | 123 ++++++++++++- runtime/src/runtime_api.rs | 2 + 9 files changed, 366 insertions(+), 201 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Rust/EnterpriseConfig.cs diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index af5f3a5b..08005e68 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -211,9 +211,12 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan // // Check if there is an enterprise configuration plugin to download: // - var enterpriseEnvironment = this.MessageBus.CheckDeferredMessages(Event.STARTUP_ENTERPRISE_ENVIRONMENT).FirstOrDefault(); - if (enterpriseEnvironment != default) - await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseEnvironment.ConfigurationId, enterpriseEnvironment.ConfigurationServerUrl); + var enterpriseEnvironments = this.MessageBus + .CheckDeferredMessages(Event.STARTUP_ENTERPRISE_ENVIRONMENT) + .Where(env => env != default) + .ToList(); + foreach (var env in enterpriseEnvironments) + await PluginFactory.TryDownloadingConfigPluginAsync(env.ConfigurationId, env.ConfigurationServerUrl); // Initialize the enterprise encryption service for decrypting API keys: await PluginFactory.InitializeEnterpriseEncryption(this.RustService); diff --git a/app/MindWork AI Studio/Pages/Information.razor b/app/MindWork AI Studio/Pages/Information.razor index b857f80d..94f333a2 100644 --- a/app/MindWork AI Studio/Pages/Information.razor +++ b/app/MindWork AI Studio/Pages/Information.razor @@ -49,26 +49,29 @@ - @switch (EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.IsActive) + @switch (HasAnyActiveEnvironment) { - case false when this.configPlug is null: + case false when this.configPlugins.Count == 0: @T("This is a private AI Studio installation. It runs without an enterprise configuration.") break; - + case false: - @T("AI Studio runs with an enterprise configuration using a configuration plugin, without central configuration management.") + @T("AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.") - -
- - @T("Configuration plugin ID:") @this.configPlug!.Id - -
-
+ @foreach (var plug in this.configPlugins) + { + +
+ + @T("Configuration plugin ID:") @plug.Id + +
+
+ }
@@ -87,26 +90,28 @@ break; - case true when this.configPlug is null: + case true when this.configPlugins.Count == 0: - @T("AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is not yet available.") + @T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.") - -
- - @T("Enterprise configuration ID:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId - -
-
- - -
- - @T("Configuration server:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl - -
-
+ @foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive)) + { + +
+ + @T("Enterprise configuration ID:") @env.ConfigurationId + +
+
+ +
+ + @T("Configuration server:") @env.ConfigurationServerUrl + +
+
+ }
@@ -127,32 +132,36 @@ case true: - @T("AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is active.") + @T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.") - -
- - @T("Enterprise configuration ID:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId - -
-
- - -
- - @T("Configuration server:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl - -
-
- - -
- - @T("Configuration plugin ID:") @this.configPlug!.Id - -
-
+ @foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive)) + { + +
+ + @T("Enterprise configuration ID:") @env.ConfigurationId + +
+
+ +
+ + @T("Configuration server:") @env.ConfigurationServerUrl + +
+
+ } + @foreach (var plug in this.configPlugins) + { + +
+ + @T("Configuration plugin ID:") @plug.Id + +
+
+ }
diff --git a/app/MindWork AI Studio/Pages/Information.razor.cs b/app/MindWork AI Studio/Pages/Information.razor.cs index aa649a3c..a4eb5123 100644 --- a/app/MindWork AI Studio/Pages/Information.razor.cs +++ b/app/MindWork AI Studio/Pages/Information.razor.cs @@ -69,12 +69,14 @@ public partial class Information : MSGComponentBase private bool showDatabaseDetails; - private IPluginMetadata? configPlug = PluginFactory.AvailablePlugins.FirstOrDefault(x => x.Type is PluginType.CONFIGURATION); + private List configPlugins = PluginFactory.AvailablePlugins.Where(x => x.Type is PluginType.CONFIGURATION).ToList(); private sealed record DatabaseDisplayInfo(string Label, string Value); private readonly List databaseDisplayInfo = new(); + private static bool HasAnyActiveEnvironment => EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Any(e => e.IsActive); + /// /// Determines whether the enterprise configuration has details that can be shown/hidden. /// Returns true if there are details available, false otherwise. @@ -83,16 +85,16 @@ public partial class Information : MSGComponentBase { get { - return EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.IsActive switch + return HasAnyActiveEnvironment switch { // Case 1: No enterprise config and no plugin - no details available - false when this.configPlug is null => false, + false when this.configPlugins.Count == 0 => false, // Case 2: Enterprise config with plugin but no central management - has details false => true, // Case 3: Enterprise config active but no plugin - has details - true when this.configPlug is null => true, + true when this.configPlugins.Count == 0 => true, // Case 4: Enterprise config active with plugin - has details true => true @@ -128,7 +130,7 @@ public partial class Information : MSGComponentBase switch (triggeredEvent) { case Event.PLUGINS_RELOADED: - this.configPlug = PluginFactory.AvailablePlugins.FirstOrDefault(x => x.Type is PluginType.CONFIGURATION); + this.configPlugins = PluginFactory.AvailablePlugins.Where(x => x.Type is PluginType.CONFIGURATION).ToList(); await this.InvokeAsync(this.StateHasChanged); break; } diff --git a/app/MindWork AI Studio/Tools/Rust/EnterpriseConfig.cs b/app/MindWork AI Studio/Tools/Rust/EnterpriseConfig.cs new file mode 100644 index 00000000..bc6fb15e --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/EnterpriseConfig.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools.Rust; + +public sealed record EnterpriseConfig(string Id, string ServerUrl); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs index 44645dc7..ec0ee648 100644 --- a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs +++ b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs @@ -4,7 +4,7 @@ namespace AIStudio.Tools.Services; public sealed class EnterpriseEnvironmentService(ILogger logger, RustService rustService) : BackgroundService { - public static EnterpriseEnvironment CURRENT_ENVIRONMENT; + public static List CURRENT_ENVIRONMENTS = []; #if DEBUG private static readonly TimeSpan CHECK_INTERVAL = TimeSpan.FromMinutes(6); @@ -33,84 +33,125 @@ public sealed class EnterpriseEnvironmentService(ILogger plugin.Id == enterpriseRemoveConfigId); - if (enterpriseRemoveConfigId != Guid.Empty && isPlugin2RemoveInUse) - { - logger.LogWarning("The enterprise environment configuration ID '{EnterpriseRemoveConfigId}' must be removed.", enterpriseRemoveConfigId); - PluginFactory.RemovePluginAsync(enterpriseRemoveConfigId); - } - string? enterpriseConfigServerUrl; + // + // Step 1: Handle deletions first. + // + List deleteConfigIds; try { - enterpriseConfigServerUrl = await rustService.EnterpriseEnvConfigServerUrl(); + deleteConfigIds = await rustService.EnterpriseEnvDeleteConfigIds(); } catch (Exception e) { - logger.LogError(e, "Failed to fetch the enterprise configuration server URL from the Rust service."); - await MessageBus.INSTANCE.SendMessage(null, Event.RUST_SERVICE_UNAVAILABLE, "EnterpriseEnvConfigServerUrl failed"); + logger.LogError(e, "Failed to fetch the enterprise delete configuration IDs from the Rust service."); + await MessageBus.INSTANCE.SendMessage(null, Event.RUST_SERVICE_UNAVAILABLE, "EnterpriseEnvDeleteConfigIds failed"); return; } - Guid enterpriseConfigId; - try + foreach (var deleteId in deleteConfigIds) { - enterpriseConfigId = await rustService.EnterpriseEnvConfigId(); - } - catch (Exception e) - { - logger.LogError(e, "Failed to fetch the enterprise configuration ID from the Rust service."); - await MessageBus.INSTANCE.SendMessage(null, Event.RUST_SERVICE_UNAVAILABLE, "EnterpriseEnvConfigId failed"); - return; - } - - var etag = await PluginFactory.DetermineConfigPluginETagAsync(enterpriseConfigId, enterpriseConfigServerUrl); - var nextEnterpriseEnvironment = new EnterpriseEnvironment(enterpriseConfigServerUrl, enterpriseConfigId, etag); - if (CURRENT_ENVIRONMENT != nextEnterpriseEnvironment) - { - logger.LogInformation("The enterprise environment has changed. Updating the current environment."); - CURRENT_ENVIRONMENT = nextEnterpriseEnvironment; - - switch (enterpriseConfigServerUrl) + var isPluginInUse = PluginFactory.AvailablePlugins.Any(plugin => plugin.Id == deleteId); + if (isPluginInUse) { - case null when enterpriseConfigId == Guid.Empty: - case not null when string.IsNullOrWhiteSpace(enterpriseConfigServerUrl) && enterpriseConfigId == Guid.Empty: - logger.LogInformation("AI Studio runs without an enterprise configuration."); - break; - - case null: - logger.LogWarning("AI Studio runs with an enterprise configuration id ('{EnterpriseConfigId}'), but the configuration server URL is not set.", enterpriseConfigId); - break; - - case not null when !string.IsNullOrWhiteSpace(enterpriseConfigServerUrl) && enterpriseConfigId == Guid.Empty: - logger.LogWarning("AI Studio runs with an enterprise configuration server URL ('{EnterpriseConfigServerUrl}'), but the configuration ID is not set.", enterpriseConfigServerUrl); - break; - - default: - logger.LogInformation("AI Studio runs with an enterprise configuration id ('{EnterpriseConfigId}') and configuration server URL ('{EnterpriseConfigServerUrl}').", enterpriseConfigId, enterpriseConfigServerUrl); - - if(isFirstRun) - MessageBus.INSTANCE.DeferMessage(null, Event.STARTUP_ENTERPRISE_ENVIRONMENT, new EnterpriseEnvironment(enterpriseConfigServerUrl, enterpriseConfigId, etag)); - else - await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseConfigId, enterpriseConfigServerUrl); - break; + logger.LogWarning("The enterprise environment configuration ID '{DeleteConfigId}' must be removed.", deleteId); + PluginFactory.RemovePluginAsync(deleteId); } } - else - logger.LogInformation("The enterprise environment has not changed. No update required."); + + // + // Step 2: Fetch all active configurations. + // + List fetchedConfigs; + try + { + fetchedConfigs = await rustService.EnterpriseEnvConfigs(); + } + catch (Exception e) + { + logger.LogError(e, "Failed to fetch the enterprise configurations from the Rust service."); + await MessageBus.INSTANCE.SendMessage(null, Event.RUST_SERVICE_UNAVAILABLE, "EnterpriseEnvConfigs failed"); + return; + } + + // + // Step 3: Determine ETags and build the next environment list. + // + var nextEnvironments = new List(); + foreach (var config in fetchedConfigs) + { + if (!config.IsActive) + { + logger.LogWarning("Skipping inactive enterprise configuration with ID '{ConfigId}'. There is either no valid server URL or config ID set.", config.ConfigurationId); + continue; + } + + var etag = await PluginFactory.DetermineConfigPluginETagAsync(config.ConfigurationId, config.ConfigurationServerUrl); + nextEnvironments.Add(config with { ETag = etag }); + } + + if (nextEnvironments.Count == 0) + { + if (CURRENT_ENVIRONMENTS.Count > 0) + { + logger.LogWarning("AI Studio no longer has any enterprise configurations. Removing previously active configs."); + + // Remove plugins for configs that were previously active: + foreach (var oldEnv in CURRENT_ENVIRONMENTS) + { + var isPluginInUse = PluginFactory.AvailablePlugins.Any(plugin => plugin.Id == oldEnv.ConfigurationId); + if (isPluginInUse) + PluginFactory.RemovePluginAsync(oldEnv.ConfigurationId); + } + } + else + logger.LogInformation("AI Studio runs without any enterprise configurations."); + + CURRENT_ENVIRONMENTS = []; + return; + } + + // + // Step 4: Compare with current environments and process changes. + // + var currentIds = CURRENT_ENVIRONMENTS.Select(e => e.ConfigurationId).ToHashSet(); + var nextIds = nextEnvironments.Select(e => e.ConfigurationId).ToHashSet(); + + // Remove plugins for configs that are no longer present: + foreach (var oldEnv in CURRENT_ENVIRONMENTS) + { + if (!nextIds.Contains(oldEnv.ConfigurationId)) + { + logger.LogInformation("Enterprise configuration '{ConfigId}' was removed.", oldEnv.ConfigurationId); + var isPluginInUse = PluginFactory.AvailablePlugins.Any(plugin => plugin.Id == oldEnv.ConfigurationId); + if (isPluginInUse) + PluginFactory.RemovePluginAsync(oldEnv.ConfigurationId); + } + } + + // Process new or changed configs: + foreach (var nextEnv in nextEnvironments) + { + var currentEnv = CURRENT_ENVIRONMENTS.FirstOrDefault(e => e.ConfigurationId == nextEnv.ConfigurationId); + if (currentEnv == nextEnv) // Hint: This relies on the record equality to check if anything relevant has changed (e.g. server URL or ETag). + { + logger.LogInformation("Enterprise configuration '{ConfigId}' has not changed. No update required.", nextEnv.ConfigurationId); + continue; + } + + var isNew = !currentIds.Contains(nextEnv.ConfigurationId); + if(isNew) + logger.LogInformation("Detected new enterprise configuration with ID '{ConfigId}' and server URL '{ServerUrl}'.", nextEnv.ConfigurationId, nextEnv.ConfigurationServerUrl); + else + logger.LogInformation("Detected change in enterprise configuration with ID '{ConfigId}'. Server URL or ETag has changed.", nextEnv.ConfigurationId); + + if (isFirstRun) + MessageBus.INSTANCE.DeferMessage(null, Event.STARTUP_ENTERPRISE_ENVIRONMENT, nextEnv); + else + await PluginFactory.TryDownloadingConfigPluginAsync(nextEnv.ConfigurationId, nextEnv.ConfigurationServerUrl); + } + + CURRENT_ENVIRONMENTS = nextEnvironments; } catch (Exception e) { diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs b/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs index 004d445a..cf8fbc26 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs @@ -1,71 +1,9 @@ -namespace AIStudio.Tools.Services; +using AIStudio.Tools.Rust; + +namespace AIStudio.Tools.Services; public sealed partial class RustService { - /// - /// Tries to read the enterprise environment for the current user's configuration ID. - /// - /// - /// Returns the empty Guid when the environment is not set or the request fails. - /// Otherwise, the configuration ID. - /// - public async Task EnterpriseEnvConfigId() - { - var result = await this.http.GetAsync("/system/enterprise/config/id"); - if (!result.IsSuccessStatusCode) - { - this.logger!.LogError($"Failed to query the enterprise configuration ID: '{result.StatusCode}'"); - return Guid.Empty; - } - - Guid.TryParse(await result.Content.ReadAsStringAsync(), out var configurationId); - return configurationId; - } - - /// - /// Tries to read the enterprise environment for a configuration ID, which must be removed. - /// - /// - /// Removing a configuration ID is necessary when the user moved to another department or - /// left the company, or when the configuration ID is no longer valid. - /// - /// - /// Returns the empty Guid when the environment is not set or the request fails. - /// Otherwise, the configuration ID. - /// - public async Task EnterpriseEnvRemoveConfigId() - { - var result = await this.http.DeleteAsync("/system/enterprise/config/id"); - if (!result.IsSuccessStatusCode) - { - this.logger!.LogError($"Failed to query the enterprise configuration ID for removal: '{result.StatusCode}'"); - return Guid.Empty; - } - - Guid.TryParse(await result.Content.ReadAsStringAsync(), out var configurationId); - return configurationId; - } - - /// - /// Tries to read the enterprise environment for the current user's configuration server URL. - /// - /// - /// Returns null when the environment is not set or the request fails. - /// Otherwise, the configuration server URL. - /// - public async Task EnterpriseEnvConfigServerUrl() - { - var result = await this.http.GetAsync("/system/enterprise/config/server"); - if (!result.IsSuccessStatusCode) - { - this.logger!.LogError($"Failed to query the enterprise configuration server URL: '{result.StatusCode}'"); - return string.Empty; - } - - var serverUrl = await result.Content.ReadAsStringAsync(); - return string.IsNullOrWhiteSpace(serverUrl) ? string.Empty : serverUrl; - } - /// /// Tries to read the enterprise environment for the configuration encryption secret. /// @@ -85,4 +23,67 @@ public sealed partial class RustService var encryptionSecret = await result.Content.ReadAsStringAsync(); return string.IsNullOrWhiteSpace(encryptionSecret) ? string.Empty : encryptionSecret; } + + /// + /// Reads all enterprise configurations (multi-config support). + /// + /// + /// Returns a list of enterprise environments parsed from the Rust runtime. + /// The ETag is not yet determined; callers must resolve it separately. + /// + public async Task> EnterpriseEnvConfigs() + { + var result = await this.http.GetAsync("/system/enterprise/configs"); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to query the enterprise configurations: '{result.StatusCode}'"); + return []; + } + + var configs = await result.Content.ReadFromJsonAsync>(this.jsonRustSerializerOptions); + if (configs is null) + return []; + + var environments = new List(); + foreach (var config in configs) + { + if (Guid.TryParse(config.Id, out var id)) + environments.Add(new EnterpriseEnvironment(config.ServerUrl, id, null)); + else + this.logger!.LogWarning($"Skipping enterprise config with invalid ID: '{config.Id}'."); + } + + return environments; + } + + /// + /// Reads all enterprise configuration IDs that should be deleted. + /// + /// + /// Returns a list of GUIDs representing configuration IDs to remove. + /// + public async Task> EnterpriseEnvDeleteConfigIds() + { + var result = await this.http.GetAsync("/system/enterprise/delete-configs"); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to query the enterprise delete configuration IDs: '{result.StatusCode}'"); + return []; + } + + var ids = await result.Content.ReadFromJsonAsync>(this.jsonRustSerializerOptions); + if (ids is null) + return []; + + var guids = new List(); + foreach (var idStr in ids) + { + if (Guid.TryParse(idStr, out var id)) + guids.Add(id); + else + this.logger!.LogWarning($"Skipping invalid GUID in enterprise delete config IDs: '{idStr}'."); + } + + return guids; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.2.2.md b/app/MindWork AI Studio/wwwroot/changelog/v26.2.2.md index d0f5dc9a..7ad7f8c3 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.2.2.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.2.2.md @@ -3,6 +3,7 @@ - Added an app setting to enable administration options for IT staff to configure and maintain organization-wide settings. - Added an option to export all provider types (LLMs, embeddings, transcriptions) so you can use them in a configuration plugin. You'll be asked if you want to export the related API key too. API keys will be encrypted in the export. This feature only shows up when administration options are enabled. - Added an option in the app settings to create an encryption secret, which is required to encrypt values (for example, API keys) in configuration plugins. This feature only shows up when administration options are enabled. +- Added support for using multiple enterprise configurations simultaneously. Enabled organizations to apply configurations based on employee affiliations, such as departments and working groups. See the enterprise configuration documentation for details. - Improved the document analysis assistant (in beta) by hiding the export functionality by default. Enable the administration options in the app settings to show and use the export functionality. This streamlines the usage for regular users. - Improved the workspaces experience by using a different color for the delete button to avoid confusion. - Improved the plugins page by adding an action to open the plugin source link. The action opens website URLs in an external browser, supports `mailto:` links for direct email composition. diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index 6203cac0..2dee6a8c 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -1,7 +1,9 @@ use std::env; use std::sync::OnceLock; -use log::{debug, warn}; +use log::{debug, info, warn}; use rocket::{delete, get}; +use rocket::serde::json::Json; +use serde::Serialize; use sys_locale::get_locale; use crate::api_token::APIToken; @@ -143,23 +145,124 @@ pub fn read_enterprise_env_config_encryption_secret(_token: APIToken) -> String ) } +/// Represents a single enterprise configuration entry with an ID and server URL. +#[derive(Serialize)] +pub struct EnterpriseConfig { + pub id: String, + 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. +#[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 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; + } + + // 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: + let config_id = get_enterprise_configuration( + "config_id", + "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID", + ); + + let config_server_url = get_enterprise_configuration( + "config_server_url", + "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL", + ); + + if !config_id.is_empty() && !config_server_url.is_empty() { + return Json(vec![EnterpriseConfig { + id: config_id, + server_url: config_server_url, + }]); + } + + Json(vec![]) +} + +/// Returns all enterprise configuration IDs that should be deleted. Supports the new +/// multi-delete format (`id1;id2;id3`) as well as the legacy single-delete variable. +#[get("/system/enterprise/delete-configs")] +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 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); + } + + // Fallback: 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]); + } + + Json(vec![]) +} + fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String { cfg_if::cfg_if! { if #[cfg(target_os = "windows")] { - debug!(r"Detected a Windows machine, trying to read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT' or environment variables."); + info!(r"Detected a Windows machine, trying to read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT\{}' or the environment variable '{}'.", _reg_value, env_name); use windows_registry::*; let key_path = r"Software\github\MindWork AI Studio\Enterprise IT"; let key = match CURRENT_USER.open(key_path) { Ok(key) => key, Err(_) => { - debug!(r"Could not read the registry key HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT. Falling back to environment variables."); + info!(r"Could not read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT\{}'. Falling back to the environment variable '{}'.", _reg_value, env_name); return match env::var(env_name) { Ok(val) => { - debug!("Falling back to the environment variable '{}' was successful.", env_name); + info!("Falling back to the environment variable '{}' was successful.", env_name); val }, Err(_) => { - debug!("Falling back to the environment variable '{}' was not successful.", env_name); + info!("Falling back to the environment variable '{}' was not successful. It seems that there is no enterprise environment available.", env_name); "".to_string() }, } @@ -169,14 +272,14 @@ fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String { match key.get_string(_reg_value) { Ok(val) => val, Err(_) => { - debug!(r"We could read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT', but the value '{}' could not be read. Falling back to environment variables.", _reg_value); + info!(r"We could read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT', but the value '{}' could not be read. Falling back to the environment variable '{}'.", _reg_value, env_name); match env::var(env_name) { Ok(val) => { - debug!("Falling back to the environment variable '{}' was successful.", env_name); + info!("Falling back to the environment variable '{}' was successful.", env_name); val }, Err(_) => { - debug!("Falling back to the environment variable '{}' was not successful.", env_name); + info!("Falling back to the environment variable '{}' was not successful. It seems that there is no enterprise environment available.", env_name); "".to_string() } } @@ -184,11 +287,11 @@ fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String { } } else { // In the case of macOS or Linux, we just read the environment variable: - debug!(r"Detected a Unix machine, trying to read the environment variable '{}'.", env_name); + info!(r"Detected a Unix machine, trying to read the environment variable '{}'.", env_name); match env::var(env_name) { Ok(val) => val, Err(_) => { - debug!("The environment variable '{}' was not found.", env_name); + info!("The environment variable '{}' was not found. It seems that there is no enterprise environment available.", env_name); "".to_string() } } diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 647259f3..3a4c1f9c 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -86,6 +86,8 @@ pub fn start_runtime_api() { crate::environment::delete_enterprise_env_config_id, crate::environment::read_enterprise_env_config_server_url, crate::environment::read_enterprise_env_config_encryption_secret, + crate::environment::read_enterprise_configs, + crate::environment::read_enterprise_delete_config_ids, crate::file_data::extract_data, crate::log::get_log_paths, crate::log::log_event,