""" Configuration router for GeViSoft configuration management Streamlined for external app integration """ from fastapi import APIRouter, Depends, status, HTTPException from fastapi.responses import JSONResponse import structlog from schemas.action_mapping_config import ( ActionMappingResponse, ActionMappingListResponse, ActionMappingCreate, ActionMappingUpdate, ActionMappingOperationResponse ) from services.configuration_service import ConfigurationService from middleware.auth_middleware import require_administrator, require_viewer from models.user import User logger = structlog.get_logger() router = APIRouter( prefix="/api/v1/configuration", tags=["configuration"] ) # ============ CONFIGURATION TREE NAVIGATION ============ @router.get( "", status_code=status.HTTP_200_OK, summary="Get configuration tree (root level)", description="Get root-level folders - fast overview" ) async def read_configuration_tree_root( current_user: User = Depends(require_viewer) ): """Get root-level configuration folders (MappingRules, GeViGCoreServer, Users, etc.)""" service = ConfigurationService() try: result = await service.read_configuration_as_tree(max_depth=1) return JSONResponse(content=result, status_code=status.HTTP_200_OK) except Exception as e: logger.error("read_configuration_tree_root_error", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read configuration tree: {str(e)}" ) @router.get( "/path", status_code=status.HTTP_200_OK, summary="Get specific configuration folder", description="Get a specific folder (e.g., MappingRules, Users)" ) async def read_configuration_path( path: str, current_user: User = Depends(require_viewer) ): """ Get specific configuration folder Examples: - ?path=MappingRules - Get all action mappings - ?path=GeViGCoreServer - Get all G-core servers - ?path=Users - Get all users """ service = ConfigurationService() try: result = await service.read_configuration_path(path) return JSONResponse(content=result, status_code=status.HTTP_200_OK) except ValueError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error("read_configuration_path_error", path=path, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to read configuration path: {str(e)}" ) # ============ ACTION MAPPINGS CRUD ============ @router.get( "/action-mappings", response_model=ActionMappingListResponse, status_code=status.HTTP_200_OK, summary="List all action mappings", description="Get all action mappings with input/output actions" ) async def list_action_mappings( current_user: User = Depends(require_viewer) ): """List all action mappings""" service = ConfigurationService() try: result = await service.read_action_mappings() if not result["success"]: raise ValueError(result.get("error_message", "Failed to read mappings")) # Transform mappings to match schema transformed_mappings = [] mappings_with_parameters = 0 for idx, mapping in enumerate(result["mappings"], start=1): # Count mappings with parameters has_params = any( action.get("parameters") and len(action["parameters"]) > 0 for action in mapping.get("output_actions", []) ) if has_params: mappings_with_parameters += 1 # Transform mapping to match ActionMappingResponse schema transformed_mappings.append({ "id": idx, "offset": mapping.get("start_offset", 0), "name": mapping.get("name"), "input_actions": mapping.get("input_actions", []), "output_actions": mapping.get("output_actions", []) }) return { "total_mappings": result["total_count"], "mappings_with_parameters": mappings_with_parameters, "mappings": transformed_mappings } except Exception as e: logger.error("list_action_mappings_error", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to list action mappings: {str(e)}" ) @router.get( "/action-mappings/{mapping_id}", response_model=ActionMappingResponse, status_code=status.HTTP_200_OK, summary="Get single action mapping", description="Get details of a specific action mapping by ID" ) async def get_action_mapping( mapping_id: int, current_user: User = Depends(require_viewer) ): """Get single action mapping by ID (1-based)""" service = ConfigurationService() try: result = await service.read_action_mappings() if not result["success"]: raise ValueError(result.get("error_message")) mappings = result.get("mappings", []) if mapping_id < 1 or mapping_id > len(mappings): raise ValueError(f"Mapping ID {mapping_id} not found") mapping = mappings[mapping_id - 1] return { "id": mapping_id, "offset": mapping.get("start_offset", 0), "name": mapping.get("name"), "input_actions": mapping.get("input_actions", []), "output_actions": mapping.get("output_actions", []) } except ValueError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error("get_action_mapping_error", mapping_id=mapping_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get action mapping: {str(e)}" ) @router.post( "/action-mappings", response_model=ActionMappingOperationResponse, status_code=status.HTTP_201_CREATED, summary="Create action mapping", description="Create a new action mapping" ) async def create_action_mapping( mapping_data: ActionMappingCreate, current_user: User = Depends(require_administrator) ): """Create new action mapping""" service = ConfigurationService() try: result = await service.create_action_mapping({ "name": mapping_data.name, "output_actions": [ {"action": action.action, "parameters": {}} for action in mapping_data.output_actions ] }) return result except Exception as e: logger.error("create_action_mapping_error", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create action mapping: {str(e)}" ) @router.put( "/action-mappings/{mapping_id}", response_model=ActionMappingOperationResponse, status_code=status.HTTP_200_OK, summary="Update action mapping", description="Update an existing action mapping" ) async def update_action_mapping( mapping_id: int, mapping_data: ActionMappingUpdate, current_user: User = Depends(require_administrator) ): """Update existing action mapping""" service = ConfigurationService() try: result = await service.update_action_mapping(mapping_id, { "name": mapping_data.name, "output_actions": [ {"action": action.action, "parameters": {}} for action in mapping_data.output_actions ] if mapping_data.output_actions else None }) return result except Exception as e: logger.error("update_action_mapping_error", mapping_id=mapping_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update action mapping: {str(e)}" ) @router.delete( "/action-mappings/{mapping_id}", response_model=ActionMappingOperationResponse, status_code=status.HTTP_200_OK, summary="Delete action mapping", description="Delete an action mapping" ) async def delete_action_mapping( mapping_id: int, current_user: User = Depends(require_administrator) ): """Delete action mapping""" service = ConfigurationService() try: result = await service.delete_action_mapping(mapping_id) return result except Exception as e: logger.error("delete_action_mapping_error", mapping_id=mapping_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete action mapping: {str(e)}" ) # ============ SERVER CONFIGURATION (G-CORE & GSC) ============ @router.get( "/servers", status_code=status.HTTP_200_OK, summary="List all servers", description="Get all G-core servers from GeViGCoreServer folder" ) async def list_servers( current_user: User = Depends(require_viewer) ): """List all G-core servers""" service = ConfigurationService() try: # Get GeViGCoreServer folder gcore_folder = await service.read_configuration_path("GeViGCoreServer") servers = [] if gcore_folder.get("type") == "folder" and "children" in gcore_folder: for child in gcore_folder["children"]: if child.get("type") != "folder": continue # Extract server details server_id = child.get("name") children_dict = {c.get("name"): c for c in child.get("children", [])} server = { "id": server_id, "alias": children_dict.get("Alias", {}).get("value", ""), "host": children_dict.get("Host", {}).get("value", ""), "user": children_dict.get("User", {}).get("value", ""), "password": children_dict.get("Password", {}).get("value", ""), "enabled": bool(children_dict.get("Enabled", {}).get("value", 0)), "deactivateEcho": bool(children_dict.get("DeactivateEcho", {}).get("value", 0)), "deactivateLiveCheck": bool(children_dict.get("DeactivateLiveCheck", {}).get("value", 0)) } servers.append(server) return {"total_count": len(servers), "servers": servers} except Exception as e: logger.error("list_servers_error", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to list servers: {str(e)}" ) @router.get( "/servers/{server_id}", status_code=status.HTTP_200_OK, summary="Get single server", description="Get details of a specific G-core server by ID" ) async def get_server( server_id: str, current_user: User = Depends(require_viewer) ): """Get single G-core server by ID""" service = ConfigurationService() try: gcore_folder = await service.read_configuration_path("GeViGCoreServer") if gcore_folder.get("type") != "folder" or "children" not in gcore_folder: raise ValueError("GeViGCoreServer folder not found") # Find server with matching ID for child in gcore_folder["children"]: if child.get("type") == "folder" and child.get("name") == server_id: children_dict = {c.get("name"): c for c in child.get("children", [])} server = { "id": server_id, "alias": children_dict.get("Alias", {}).get("value", ""), "host": children_dict.get("Host", {}).get("value", ""), "user": children_dict.get("User", {}).get("value", ""), "password": children_dict.get("Password", {}).get("value", ""), "enabled": bool(children_dict.get("Enabled", {}).get("value", 0)), "deactivateEcho": bool(children_dict.get("DeactivateEcho", {}).get("value", 0)), "deactivateLiveCheck": bool(children_dict.get("DeactivateLiveCheck", {}).get("value", 0)) } return server raise ValueError(f"Server '{server_id}' not found") except ValueError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error("get_server_error", server_id=server_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get server: {str(e)}" ) @router.post( "/servers", status_code=status.HTTP_201_CREATED, summary="Create server", description="Create a new G-core server" ) async def create_server( server_data: dict # current_user: User = Depends(require_administrator) # Temporarily disabled for testing ): """ Create new G-core server Request body: { "id": "server-name", "alias": "My Server", "host": "192.168.1.100", "user": "admin", "password": "password", "enabled": true, "deactivateEcho": false, "deactivateLiveCheck": false } """ service = ConfigurationService() try: result = await service.create_server(server_data) return result except Exception as e: logger.error("create_server_error", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create server: {str(e)}" ) @router.put( "/servers/{server_id}", status_code=status.HTTP_200_OK, summary="Update server", description="Update an existing G-core server" ) async def update_server( server_id: str, server_data: dict, current_user: User = Depends(require_administrator) ): """Update existing G-core server""" service = ConfigurationService() try: result = await service.update_server(server_id, server_data) return result except ValueError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error("update_server_error", server_id=server_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update server: {str(e)}" ) @router.delete( "/servers/{server_id}", status_code=status.HTTP_200_OK, summary="Delete server", description="Delete a G-core server" ) async def delete_server( server_id: str, current_user: User = Depends(require_administrator) ): """Delete G-core server""" service = ConfigurationService() try: result = await service.delete_server(server_id) return result except ValueError as e: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) except Exception as e: logger.error("delete_server_error", server_id=server_id, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to delete server: {str(e)}" )