""" Dashboard API for Calejo Control Adapter Provides REST endpoints for configuration management and system monitoring """ import json import logging from typing import Dict, Any, List, Optional from fastapi import APIRouter, HTTPException, BackgroundTasks from fastapi.responses import HTMLResponse from pydantic import BaseModel, ValidationError from config.settings import Settings logger = logging.getLogger(__name__) # API Router dashboard_router = APIRouter(prefix="/api/v1/dashboard", tags=["dashboard"]) # Pydantic models for configuration class DatabaseConfig(BaseModel): db_host: str = "localhost" db_port: int = 5432 db_name: str = "calejo" db_user: str = "calejo" db_password: str = "" class OPCUAConfig(BaseModel): enabled: bool = True host: str = "localhost" port: int = 4840 class ModbusConfig(BaseModel): enabled: bool = True host: str = "localhost" port: int = 502 unit_id: int = 1 class RESTAPIConfig(BaseModel): enabled: bool = True host: str = "0.0.0.0" port: int = 8080 cors_enabled: bool = True class MonitoringConfig(BaseModel): health_monitor_port: int = 9090 metrics_enabled: bool = True class SecurityConfig(BaseModel): jwt_secret_key: str = "" api_key: str = "" class SystemConfig(BaseModel): database: DatabaseConfig opcua: OPCUAConfig modbus: ModbusConfig rest_api: RESTAPIConfig monitoring: MonitoringConfig security: SecurityConfig class ValidationResult(BaseModel): valid: bool errors: List[str] = [] warnings: List[str] = [] class DashboardStatus(BaseModel): application_status: str database_status: str opcua_status: str modbus_status: str rest_api_status: str monitoring_status: str # Global settings instance settings = Settings() @dashboard_router.get("/config", response_model=SystemConfig) async def get_configuration(): """Get current system configuration""" try: config = SystemConfig( database=DatabaseConfig( db_host=settings.db_host, db_port=settings.db_port, db_name=settings.db_name, db_user=settings.db_user, db_password="********" # Don't expose actual password ), opcua=OPCUAConfig( enabled=settings.opcua_enabled, host=settings.opcua_host, port=settings.opcua_port ), modbus=ModbusConfig( enabled=settings.modbus_enabled, host=settings.modbus_host, port=settings.modbus_port, unit_id=settings.modbus_unit_id ), rest_api=RESTAPIConfig( enabled=settings.rest_api_enabled, host=settings.rest_api_host, port=settings.rest_api_port, cors_enabled=settings.rest_api_cors_enabled ), monitoring=MonitoringConfig( health_monitor_port=settings.health_monitor_port, metrics_enabled=True ), security=SecurityConfig( jwt_secret_key="********", api_key="********" ) ) return config except Exception as e: logger.error(f"Error getting configuration: {str(e)}") raise HTTPException(status_code=500, detail="Failed to retrieve configuration") @dashboard_router.post("/config", response_model=ValidationResult) async def update_configuration(config: SystemConfig, background_tasks: BackgroundTasks): """Update system configuration""" try: # Validate configuration validation_result = validate_configuration(config) if validation_result.valid: # Save configuration in background background_tasks.add_task(save_configuration, config) return validation_result except Exception as e: logger.error(f"Error updating configuration: {str(e)}") raise HTTPException(status_code=500, detail="Failed to update configuration") @dashboard_router.get("/status", response_model=DashboardStatus) async def get_system_status(): """Get current system status""" try: # This would integrate with the health monitor # For now, return mock status status = DashboardStatus( application_status="running", database_status="connected", opcua_status="listening", modbus_status="listening", rest_api_status="running", monitoring_status="active" ) return status except Exception as e: logger.error(f"Error getting system status: {str(e)}") raise HTTPException(status_code=500, detail="Failed to retrieve system status") @dashboard_router.post("/restart") async def restart_system(): """Restart the system (admin only)""" # This would trigger a system restart # For now, just log the request logger.info("System restart requested via dashboard") return {"message": "Restart request received", "status": "pending"} @dashboard_router.get("/backup") async def create_backup(): """Create a system backup""" # This would trigger the backup script logger.info("Backup requested via dashboard") return {"message": "Backup initiated", "status": "in_progress"} @dashboard_router.get("/logs") async def get_system_logs(limit: int = 100): """Get system logs""" try: # This would read from the application logs # For now, return mock logs logs = [ {"timestamp": "2024-01-01T10:00:00", "level": "INFO", "message": "System started"}, {"timestamp": "2024-01-01T10:01:00", "level": "INFO", "message": "Database connected"}, {"timestamp": "2024-01-01T10:02:00", "level": "INFO", "message": "OPC UA server started"} ] return {"logs": logs[:limit]} except Exception as e: logger.error(f"Error getting logs: {str(e)}") raise HTTPException(status_code=500, detail="Failed to retrieve logs") def validate_configuration(config: SystemConfig) -> ValidationResult: """Validate configuration before applying""" errors = [] warnings = [] # Database validation if not config.database.db_host: errors.append("Database host is required") if not config.database.db_name: errors.append("Database name is required") if not config.database.db_user: errors.append("Database user is required") # Port validation if not (1 <= config.database.db_port <= 65535): errors.append("Database port must be between 1 and 65535") if not (1 <= config.opcua.port <= 65535): errors.append("OPC UA port must be between 1 and 65535") if not (1 <= config.modbus.port <= 65535): errors.append("Modbus port must be between 1 and 65535") if not (1 <= config.rest_api.port <= 65535): errors.append("REST API port must be between 1 and 65535") if not (1 <= config.monitoring.health_monitor_port <= 65535): errors.append("Health monitor port must be between 1 and 65535") # Security warnings if config.security.jwt_secret_key == "your-secret-key-change-in-production": warnings.append("Default JWT secret key detected - please change for production") if config.security.api_key == "your-api-key-here": warnings.append("Default API key detected - please change for production") return ValidationResult( valid=len(errors) == 0, errors=errors, warnings=warnings ) def save_configuration(config: SystemConfig): """Save configuration to settings file""" try: # This would update the settings file # For now, just log the configuration logger.info(f"Configuration update received: {config.json(indent=2)}") # In a real implementation, this would: # 1. Update the settings file # 2. Restart affected services # 3. Verify the new configuration except Exception as e: logger.error(f"Error saving configuration: {str(e)}")