# [DEF:SettingsRouter:Module] # # @SEMANTICS: settings, api, router, fastapi # @PURPOSE: Provides API endpoints for managing application settings and Superset environments. # @LAYER: UI (API) # @RELATION: DEPENDS_ON -> ConfigManager # @RELATION: DEPENDS_ON -> ConfigModels # # @INVARIANT: All settings changes must be persisted via ConfigManager. # @PUBLIC_API: router # [SECTION: IMPORTS] from fastapi import APIRouter, Depends, HTTPException from typing import List from ...core.config_models import AppConfig, Environment, GlobalSettings from ...dependencies import get_config_manager from ...core.config_manager import ConfigManager from ...core.logger import logger from superset_tool.client import SupersetClient from superset_tool.models import SupersetConfig import os # [/SECTION] router = APIRouter() # [DEF:get_settings:Function] # @PURPOSE: Retrieves all application settings. # @RETURN: AppConfig - The current configuration. @router.get("/", response_model=AppConfig) async def get_settings(config_manager: ConfigManager = Depends(get_config_manager)): logger.info("[get_settings][Entry] Fetching all settings") config = config_manager.get_config().copy(deep=True) # Mask passwords for env in config.environments: if env.password: env.password = "********" return config # [/DEF:get_settings] # [DEF:update_global_settings:Function] # @PURPOSE: Updates global application settings. # @PARAM: settings (GlobalSettings) - The new global settings. # @RETURN: GlobalSettings - The updated settings. @router.patch("/global", response_model=GlobalSettings) async def update_global_settings( settings: GlobalSettings, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info("[update_global_settings][Entry] Updating global settings") config_manager.update_global_settings(settings) return settings # [/DEF:update_global_settings] # [DEF:get_environments:Function] # @PURPOSE: Lists all configured Superset environments. # @RETURN: List[Environment] - List of environments. @router.get("/environments", response_model=List[Environment]) async def get_environments(config_manager: ConfigManager = Depends(get_config_manager)): logger.info("[get_environments][Entry] Fetching environments") return config_manager.get_environments() # [/DEF:get_environments] # [DEF:add_environment:Function] # @PURPOSE: Adds a new Superset environment. # @PARAM: env (Environment) - The environment to add. # @RETURN: Environment - The added environment. @router.post("/environments", response_model=Environment) async def add_environment( env: Environment, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[add_environment][Entry] Adding environment {env.id}") # Validate connection before adding try: superset_config = SupersetConfig( env=env.name, base_url=env.url, auth={ "provider": "db", "username": env.username, "password": env.password, "refresh": "true" } ) client = SupersetClient(config=superset_config) client.get_dashboards(query={"page_size": 1}) except Exception as e: logger.error(f"[add_environment][Coherence:Failed] Connection validation failed: {e}") raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}") config_manager.add_environment(env) return env # [/DEF:add_environment] # [DEF:update_environment:Function] # @PURPOSE: Updates an existing Superset environment. # @PARAM: id (str) - The ID of the environment to update. # @PARAM: env (Environment) - The updated environment data. # @RETURN: Environment - The updated environment. @router.put("/environments/{id}", response_model=Environment) async def update_environment( id: str, env: Environment, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[update_environment][Entry] Updating environment {id}") # If password is masked, we need the real one for validation env_to_validate = env.copy(deep=True) if env_to_validate.password == "********": old_env = next((e for e in config_manager.get_environments() if e.id == id), None) if old_env: env_to_validate.password = old_env.password # Validate connection before updating try: superset_config = SupersetConfig( env=env_to_validate.name, base_url=env_to_validate.url, auth={ "provider": "db", "username": env_to_validate.username, "password": env_to_validate.password, "refresh": "true" } ) client = SupersetClient(config=superset_config) client.get_dashboards(query={"page_size": 1}) except Exception as e: logger.error(f"[update_environment][Coherence:Failed] Connection validation failed: {e}") raise HTTPException(status_code=400, detail=f"Connection validation failed: {e}") if config_manager.update_environment(id, env): return env raise HTTPException(status_code=404, detail=f"Environment {id} not found") # [/DEF:update_environment] # [DEF:delete_environment:Function] # @PURPOSE: Deletes a Superset environment. # @PARAM: id (str) - The ID of the environment to delete. @router.delete("/environments/{id}") async def delete_environment( id: str, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[delete_environment][Entry] Deleting environment {id}") config_manager.delete_environment(id) return {"message": f"Environment {id} deleted"} # [/DEF:delete_environment] # [DEF:test_environment_connection:Function] # @PURPOSE: Tests the connection to a Superset environment. # @PARAM: id (str) - The ID of the environment to test. # @RETURN: dict - Success message or error. @router.post("/environments/{id}/test") async def test_environment_connection( id: str, config_manager: ConfigManager = Depends(get_config_manager) ): logger.info(f"[test_environment_connection][Entry] Testing environment {id}") # Find environment env = next((e for e in config_manager.get_environments() if e.id == id), None) if not env: raise HTTPException(status_code=404, detail=f"Environment {id} not found") try: # Create SupersetConfig # Note: SupersetConfig expects 'auth' dict with specific keys superset_config = SupersetConfig( env=env.name, base_url=env.url, auth={ "provider": "db", # Defaulting to db for now "username": env.username, "password": env.password, "refresh": "true" } ) # Initialize client (this will trigger authentication) client = SupersetClient(config=superset_config) # Try a simple request to verify client.get_dashboards(query={"page_size": 1}) logger.info(f"[test_environment_connection][Coherence:OK] Connection successful for {id}") return {"status": "success", "message": "Connection successful"} except Exception as e: logger.error(f"[test_environment_connection][Coherence:Failed] Connection failed for {id}: {e}") return {"status": "error", "message": str(e)} # [/DEF:test_environment_connection] # [DEF:validate_backup_path:Function] # @PURPOSE: Validates if a backup path exists and is writable. # @PARAM: path (str) - The path to validate. # @RETURN: dict - Validation result. @router.post("/validate-path") async def validate_backup_path( path_data: dict, config_manager: ConfigManager = Depends(get_config_manager) ): path = path_data.get("path") if not path: raise HTTPException(status_code=400, detail="Path is required") logger.info(f"[validate_backup_path][Entry] Validating path: {path}") valid, message = config_manager.validate_path(path) if not valid: return {"status": "error", "message": message} return {"status": "success", "message": message} # [/DEF:validate_backup_path] # [/DEF:SettingsRouter]