diff --git a/.standalone.pid b/.standalone.pid index b304e3f4..1b6c76f2 100644 --- a/.standalone.pid +++ b/.standalone.pid @@ -1,3 +1,3 @@ -826698 -826699 -826713 +830554 +830555 +830569 diff --git a/backend/config_manager.py b/backend/config_manager.py index 3bf27119..b9a7194d 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -72,6 +72,74 @@ class ConfigManager: except Exception: return False + @staticmethod + def get_secrets_path(): + """Returns the absolute path to secrets.yaml.""" + base_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(base_dir) + return os.path.join(project_root, "config", "secrets.yaml") + + @staticmethod + def update_secrets(updates: dict) -> dict: + """Update secrets.yaml with new values (flattened structure).""" + path = ConfigManager.get_secrets_path() + + current_secrets = {} + if os.path.exists(path): + try: + with open(path, 'r', encoding='utf-8') as f: + current_secrets = yaml.safe_load(f) or {} + except Exception as e: + log.error(f"❌ Failed to read {path}: {e}") + + # Update flattened secrets + current_secrets.update(updates) + + try: + with open(path, 'w', encoding='utf-8') as f: + # Header for clarity + f.write("# TFM aInventory - Managed Secrets (Updated via Admin UI)\n") + yaml.dump(current_secrets, f, default_flow_style=False, sort_keys=True) + log.info(f"✅ Updated {path} with new secrets.") + + # Reload the global config + load_config() + return get_config() + except Exception as e: + log.error(f"❌ Failed to write {path}: {e}") + raise + + @staticmethod + def update_keys(updates: dict) -> dict: + """ + Intelligent key update: routes sensitive AI keys to secrets.yaml + and others to backend.yaml. + """ + secrets_updates = {} + backend_updates = {} + + sensitive_keys = ["GEMINI_API_KEY", "CLAUDE_API_KEY", "JWT_SECRET_KEY", "LDAP_PASSWORD"] + + for key, val in updates.items(): + if key in sensitive_keys: + secrets_updates[key] = val + else: + # Non-sensitive or complex nested settings + backend_updates[key] = val + + if secrets_updates: + ConfigManager.update_secrets(secrets_updates) + + if backend_updates: + ConfigManager.update_config(backend_updates) + + return get_config() + + @staticmethod + def get_config() -> dict: + """Return the current global configuration.""" + return get_config() + @staticmethod def get_masked_key(key_name: str): """Returns a masked version of a configuration value or env var.""" @@ -113,6 +181,10 @@ def update_config(updates: dict) -> dict: """Update backend.yaml with new values and return updated config.""" return ConfigManager.update_config(updates) +def get_config() -> dict: + """Return the current global configuration.""" + return ConfigManager.get_config() + def validate_config_file() -> bool: """Validate backend.yaml syntax and required fields.""" return ConfigManager.validate_config_file() diff --git a/backend/routers/admin/ai_config.py b/backend/routers/admin/ai_config.py index a32242bc..59978e30 100644 --- a/backend/routers/admin/ai_config.py +++ b/backend/routers/admin/ai_config.py @@ -67,8 +67,11 @@ def get_ai_config( current_admin: auth.TokenData = Depends(auth.get_current_admin) ): """Check AI provider status and active provider.""" - gemini_key = os.environ.get("GEMINI_API_KEY") - claude_key = os.environ.get("CLAUDE_API_KEY") + config = ConfigManager.get_config() # Alias for config_loader.get_config + ai_config = config.get("ai", {}) + + gemini_key = ai_config.get("gemini_api_key") + claude_key = ai_config.get("claude_api_key") provider_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_provider").first() active_provider = provider_setting.value if provider_setting else "gemini" @@ -112,10 +115,14 @@ def update_ai_keys( if updates: ConfigManager.update_keys(updates) + # Re-load config to return accurate status + config = ConfigManager.get_config() + ai_cfg = config.get("ai", {}) + return { "status": "success", - "gemini_configured": bool(os.environ.get("GEMINI_API_KEY")), - "claude_configured": bool(os.environ.get("CLAUDE_API_KEY")) + "gemini_configured": bool(ai_cfg.get("gemini_api_key")), + "claude_configured": bool(ai_cfg.get("claude_api_key")) } diff --git a/backups/ainventory_2026-04-23_14-04-08.tar.gz b/backups/ainventory_2026-04-23_14-04-08.tar.gz new file mode 100644 index 00000000..9b830deb Binary files /dev/null and b/backups/ainventory_2026-04-23_14-04-08.tar.gz differ