mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-03-30 11:11:38 +00:00
Some checks are pending
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
409 lines
14 KiB
Rust
409 lines
14 KiB
Rust
use std::env;
|
|
use std::sync::OnceLock;
|
|
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;
|
|
|
|
const DEFAULT_LANGUAGE: &str = "en-US";
|
|
|
|
/// The data directory where the application stores its data.
|
|
pub static DATA_DIRECTORY: OnceLock<String> = OnceLock::new();
|
|
|
|
/// The config directory where the application stores its configuration.
|
|
pub static CONFIG_DIRECTORY: OnceLock<String> = OnceLock::new();
|
|
|
|
/// Returns the config directory.
|
|
#[get("/system/directories/config")]
|
|
pub fn get_config_directory(_token: APIToken) -> String {
|
|
match CONFIG_DIRECTORY.get() {
|
|
Some(config_directory) => config_directory.clone(),
|
|
None => String::from(""),
|
|
}
|
|
}
|
|
|
|
/// Returns the data directory.
|
|
#[get("/system/directories/data")]
|
|
pub fn get_data_directory(_token: APIToken) -> String {
|
|
match DATA_DIRECTORY.get() {
|
|
Some(data_directory) => data_directory.clone(),
|
|
None => String::from(""),
|
|
}
|
|
}
|
|
|
|
/// Returns true if the application is running in development mode.
|
|
pub fn is_dev() -> bool {
|
|
cfg!(debug_assertions)
|
|
}
|
|
|
|
/// Returns true if the application is running in production mode.
|
|
pub fn is_prod() -> bool {
|
|
!is_dev()
|
|
}
|
|
|
|
fn normalize_locale_tag(locale: &str) -> Option<String> {
|
|
let trimmed = locale.trim();
|
|
if trimmed.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let without_encoding = trimmed
|
|
.split('.')
|
|
.next()
|
|
.unwrap_or(trimmed)
|
|
.split('@')
|
|
.next()
|
|
.unwrap_or(trimmed)
|
|
.trim();
|
|
|
|
if without_encoding.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let normalized_delimiters = without_encoding.replace('_', "-");
|
|
let mut segments = normalized_delimiters
|
|
.split('-')
|
|
.filter(|segment| !segment.is_empty());
|
|
|
|
let language = segments.next()?;
|
|
if language.eq_ignore_ascii_case("c") || language.eq_ignore_ascii_case("posix") {
|
|
return None;
|
|
}
|
|
|
|
let language = language.to_ascii_lowercase();
|
|
if language.len() < 2 || !language.chars().all(|c| c.is_ascii_alphabetic()) {
|
|
return None;
|
|
}
|
|
|
|
if let Some(region) = segments.next() {
|
|
if region.len() == 2 && region.chars().all(|c| c.is_ascii_alphabetic()) {
|
|
return Some(format!("{}-{}", language, region.to_ascii_uppercase()));
|
|
}
|
|
}
|
|
|
|
Some(language)
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn read_locale_from_environment() -> Option<String> {
|
|
if let Ok(language) = env::var("LANGUAGE") {
|
|
for candidate in language.split(':') {
|
|
if let Some(locale) = normalize_locale_tag(candidate) {
|
|
info!("Detected user language from Linux environment variable 'LANGUAGE': '{}'.", locale);
|
|
return Some(locale);
|
|
}
|
|
}
|
|
}
|
|
|
|
for key in ["LC_ALL", "LC_MESSAGES", "LANG"] {
|
|
if let Ok(value) = env::var(key) {
|
|
if let Some(locale) = normalize_locale_tag(&value) {
|
|
info!("Detected user language from Linux environment variable '{}': '{}'.", key, locale);
|
|
return Some(locale);
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
fn read_locale_from_environment() -> Option<String> {
|
|
None
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::normalize_locale_tag;
|
|
|
|
#[test]
|
|
fn normalize_locale_tag_supports_common_linux_formats() {
|
|
assert_eq!(normalize_locale_tag("de_DE.UTF-8"), Some(String::from("de-DE")));
|
|
assert_eq!(normalize_locale_tag("de_DE@euro"), Some(String::from("de-DE")));
|
|
assert_eq!(normalize_locale_tag("de"), Some(String::from("de")));
|
|
assert_eq!(normalize_locale_tag("en-US"), Some(String::from("en-US")));
|
|
}
|
|
|
|
#[test]
|
|
fn normalize_locale_tag_rejects_non_language_locales() {
|
|
assert_eq!(normalize_locale_tag("C"), None);
|
|
assert_eq!(normalize_locale_tag("C.UTF-8"), None);
|
|
assert_eq!(normalize_locale_tag("POSIX"), None);
|
|
assert_eq!(normalize_locale_tag(""), None);
|
|
}
|
|
}
|
|
|
|
#[get("/system/language")]
|
|
pub fn read_user_language(_token: APIToken) -> String {
|
|
if let Some(locale) = get_locale() {
|
|
if let Some(normalized_locale) = normalize_locale_tag(&locale) {
|
|
info!("Detected user language from sys-locale: '{}'.", normalized_locale);
|
|
return normalized_locale;
|
|
}
|
|
|
|
warn!("sys-locale returned an unusable locale value: '{}'.", locale);
|
|
}
|
|
|
|
if let Some(locale) = read_locale_from_environment() {
|
|
return locale;
|
|
}
|
|
|
|
warn!("Could not determine the system language. Use default '{}'.", DEFAULT_LANGUAGE);
|
|
String::from(DEFAULT_LANGUAGE)
|
|
}
|
|
|
|
#[get("/system/enterprise/config/id")]
|
|
pub fn read_enterprise_env_config_id(_token: APIToken) -> String {
|
|
//
|
|
// When we are on a Windows machine, we try to read the enterprise config from
|
|
// the Windows registry. In case we can't find the registry key, or we are on a
|
|
// macOS or Linux machine, we try to read the enterprise config from the
|
|
// environment variables.
|
|
//
|
|
// The registry key is:
|
|
// HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT
|
|
//
|
|
// In this registry key, we expect the following values:
|
|
// - config_id
|
|
//
|
|
// The environment variable is:
|
|
// MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID
|
|
//
|
|
debug!("Trying to read the enterprise environment for some config ID.");
|
|
get_enterprise_configuration(
|
|
"config_id",
|
|
"MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID",
|
|
)
|
|
}
|
|
|
|
#[delete("/system/enterprise/config/id")]
|
|
pub fn delete_enterprise_env_config_id(_token: APIToken) -> String {
|
|
//
|
|
// When we are on a Windows machine, we try to read the enterprise config from
|
|
// the Windows registry. In case we can't find the registry key, or we are on a
|
|
// macOS or Linux machine, we try to read the enterprise config from the
|
|
// environment variables.
|
|
//
|
|
// The registry key is:
|
|
// HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT
|
|
//
|
|
// In this registry key, we expect the following values:
|
|
// - delete_config_id
|
|
//
|
|
// The environment variable is:
|
|
// MINDWORK_AI_STUDIO_ENTERPRISE_DELETE_CONFIG_ID
|
|
//
|
|
debug!("Trying to read the enterprise environment for some config ID, which should be deleted.");
|
|
get_enterprise_configuration(
|
|
"delete_config_id",
|
|
"MINDWORK_AI_STUDIO_ENTERPRISE_DELETE_CONFIG_ID",
|
|
)
|
|
}
|
|
|
|
#[get("/system/enterprise/config/server")]
|
|
pub fn read_enterprise_env_config_server_url(_token: APIToken) -> String {
|
|
//
|
|
// When we are on a Windows machine, we try to read the enterprise config from
|
|
// the Windows registry. In case we can't find the registry key, or we are on a
|
|
// macOS or Linux machine, we try to read the enterprise config from the
|
|
// environment variables.
|
|
//
|
|
// The registry key is:
|
|
// HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT
|
|
//
|
|
// In this registry key, we expect the following values:
|
|
// - config_server_url
|
|
//
|
|
// The environment variable is:
|
|
// MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL
|
|
//
|
|
debug!("Trying to read the enterprise environment for the config server URL.");
|
|
get_enterprise_configuration(
|
|
"config_server_url",
|
|
"MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL",
|
|
)
|
|
}
|
|
|
|
#[get("/system/enterprise/config/encryption_secret")]
|
|
pub fn read_enterprise_env_config_encryption_secret(_token: APIToken) -> String {
|
|
//
|
|
// When we are on a Windows machine, we try to read the enterprise config from
|
|
// the Windows registry. In case we can't find the registry key, or we are on a
|
|
// macOS or Linux machine, we try to read the enterprise config from the
|
|
// environment variables.
|
|
//
|
|
// The registry key is:
|
|
// HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT
|
|
//
|
|
// In this registry key, we expect the following values:
|
|
// - config_encryption_secret
|
|
//
|
|
// The environment variable is:
|
|
// MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET
|
|
//
|
|
debug!("Trying to read the enterprise environment for the config encryption secret.");
|
|
get_enterprise_configuration(
|
|
"config_encryption_secret",
|
|
"MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET",
|
|
)
|
|
}
|
|
|
|
/// 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. 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<Vec<EnterpriseConfig>> {
|
|
info!("Trying to read the enterprise environment for all configurations.");
|
|
|
|
let mut configs: Vec<EnterpriseConfig> = Vec::new();
|
|
let mut seen_ids: std::collections::HashSet<String> = 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;...
|
|
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 });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Also 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() {
|
|
let id = config_id.trim().to_lowercase();
|
|
if seen_ids.insert(id.clone()) {
|
|
configs.push(EnterpriseConfig { id, server_url: config_server_url });
|
|
}
|
|
}
|
|
|
|
Json(configs)
|
|
}
|
|
|
|
/// 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<Vec<String>> {
|
|
info!("Trying to read the enterprise environment for configuration IDs to delete.");
|
|
|
|
let mut ids: Vec<String> = Vec::new();
|
|
let mut seen: std::collections::HashSet<String> = 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() {
|
|
for id in combined.split(';') {
|
|
let id = id.trim().to_lowercase();
|
|
if !id.is_empty() && seen.insert(id.clone()) {
|
|
ids.push(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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() {
|
|
let id = delete_id.trim().to_lowercase();
|
|
if seen.insert(id.clone()) {
|
|
ids.push(id);
|
|
}
|
|
}
|
|
|
|
Json(ids)
|
|
}
|
|
|
|
fn get_enterprise_configuration(_reg_value: &str, env_name: &str) -> String {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(target_os = "windows")] {
|
|
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(_) => {
|
|
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) => {
|
|
info!("Falling back to the environment variable '{}' was successful.", env_name);
|
|
val
|
|
},
|
|
Err(_) => {
|
|
info!("Falling back to the environment variable '{}' was not successful. It seems that there is no enterprise environment available.", env_name);
|
|
"".to_string()
|
|
},
|
|
}
|
|
},
|
|
};
|
|
|
|
match key.get_string(_reg_value) {
|
|
Ok(val) => val,
|
|
Err(_) => {
|
|
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) => {
|
|
info!("Falling back to the environment variable '{}' was successful.", env_name);
|
|
val
|
|
},
|
|
Err(_) => {
|
|
info!("Falling back to the environment variable '{}' was not successful. It seems that there is no enterprise environment available.", env_name);
|
|
"".to_string()
|
|
}
|
|
}
|
|
},
|
|
}
|
|
} else {
|
|
// In the case of macOS or Linux, we just read the environment variable:
|
|
info!(r"Detected a Unix machine, trying to read the environment variable '{}'.", env_name);
|
|
match env::var(env_name) {
|
|
Ok(val) => val,
|
|
Err(_) => {
|
|
info!("The environment variable '{}' was not found. It seems that there is no enterprise environment available.", env_name);
|
|
"".to_string()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|