CalejoControl/src/dashboard/api.py

237 lines
8.0 KiB
Python
Raw Normal View History

"""
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)}")