diff --git a/src/dashboard/api.py b/src/dashboard/api.py index a1f35eb..48741c0 100644 --- a/src/dashboard/api.py +++ b/src/dashboard/api.py @@ -15,6 +15,7 @@ from .configuration_manager import ( configuration_manager, OPCUAConfig, ModbusTCPConfig, PumpStationConfig, PumpConfig, SafetyLimitsConfig, DataPointMapping, ProtocolType, ProtocolMapping ) +from src.discovery.protocol_discovery import discovery_service, DiscoveryStatus, DiscoveredEndpoint from datetime import datetime logger = logging.getLogger(__name__) @@ -967,6 +968,175 @@ async def delete_protocol_mapping(mapping_id: str): logger.error(f"Error deleting protocol mapping: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to delete protocol mapping: {str(e)}") + +# Protocol Discovery API Endpoints + +@dashboard_router.get("/discovery/status") +async def get_discovery_status(): + """Get current discovery service status""" + try: + status = discovery_service.get_discovery_status() + return { + "success": True, + "status": status + } + except Exception as e: + logger.error(f"Error getting discovery status: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to get discovery status: {str(e)}") + + +@dashboard_router.post("/discovery/scan") +async def start_discovery_scan(background_tasks: BackgroundTasks): + """Start a new discovery scan""" + try: + # Check if scan is already running + status = discovery_service.get_discovery_status() + if status["is_scanning"]: + raise HTTPException(status_code=409, detail="Discovery scan already in progress") + + # Start discovery scan in background + scan_id = f"scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + async def run_discovery(): + await discovery_service.discover_all_protocols(scan_id) + + background_tasks.add_task(run_discovery) + + return { + "success": True, + "scan_id": scan_id, + "message": "Discovery scan started successfully" + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Error starting discovery scan: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to start discovery scan: {str(e)}") + + +@dashboard_router.get("/discovery/results/{scan_id}") +async def get_discovery_results(scan_id: str): + """Get results for a specific discovery scan""" + try: + result = discovery_service.get_scan_result(scan_id) + + if not result: + raise HTTPException(status_code=404, detail=f"Discovery scan {scan_id} not found") + + # Convert discovered endpoints to dict format + endpoints_data = [] + for endpoint in result.discovered_endpoints: + endpoint_data = { + "protocol_type": endpoint.protocol_type.value, + "address": endpoint.address, + "port": endpoint.port, + "device_id": endpoint.device_id, + "device_name": endpoint.device_name, + "capabilities": endpoint.capabilities, + "response_time": endpoint.response_time, + "discovered_at": endpoint.discovered_at.isoformat() if endpoint.discovered_at else None + } + endpoints_data.append(endpoint_data) + + return { + "success": True, + "scan_id": scan_id, + "status": result.status.value, + "scan_duration": result.scan_duration, + "errors": result.errors, + "timestamp": result.timestamp.isoformat() if result.timestamp else None, + "discovered_endpoints": endpoints_data + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Error getting discovery results: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to get discovery results: {str(e)}") + + +@dashboard_router.get("/discovery/recent") +async def get_recent_discoveries(): + """Get most recently discovered endpoints""" + try: + recent_endpoints = discovery_service.get_recent_discoveries(limit=20) + + # Convert to dict format + endpoints_data = [] + for endpoint in recent_endpoints: + endpoint_data = { + "protocol_type": endpoint.protocol_type.value, + "address": endpoint.address, + "port": endpoint.port, + "device_id": endpoint.device_id, + "device_name": endpoint.device_name, + "capabilities": endpoint.capabilities, + "response_time": endpoint.response_time, + "discovered_at": endpoint.discovered_at.isoformat() if endpoint.discovered_at else None + } + endpoints_data.append(endpoint_data) + + return { + "success": True, + "recent_endpoints": endpoints_data + } + except Exception as e: + logger.error(f"Error getting recent discoveries: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to get recent discoveries: {str(e)}") + + +@dashboard_router.post("/discovery/apply/{scan_id}") +async def apply_discovery_results(scan_id: str, station_id: str, pump_id: str, data_type: str, db_source: str): + """Apply discovered endpoints as protocol mappings""" + try: + result = discovery_service.get_scan_result(scan_id) + + if not result: + raise HTTPException(status_code=404, detail=f"Discovery scan {scan_id} not found") + + if result.status != DiscoveryStatus.COMPLETED: + raise HTTPException(status_code=400, detail="Cannot apply incomplete discovery scan") + + created_mappings = [] + errors = [] + + for endpoint in result.discovered_endpoints: + try: + # Create protocol mapping from discovered endpoint + mapping_id = f"{endpoint.device_id}_{data_type}" + + protocol_mapping = ProtocolMapping( + id=mapping_id, + station_id=station_id, + pump_id=pump_id, + protocol_type=endpoint.protocol_type, + protocol_address=endpoint.address, + data_type=data_type, + db_source=db_source + ) + + # Add to configuration manager + success = configuration_manager.add_protocol_mapping(protocol_mapping) + + if success: + created_mappings.append(mapping_id) + else: + errors.append(f"Failed to create mapping for {endpoint.device_name}") + + except Exception as e: + errors.append(f"Error creating mapping for {endpoint.device_name}: {str(e)}") + + return { + "success": True, + "created_mappings": created_mappings, + "errors": errors, + "message": f"Created {len(created_mappings)} protocol mappings from discovery results" + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Error applying discovery results: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to apply discovery results: {str(e)}") + @dashboard_router.post("/protocol-mappings/{mapping_id}/validate") async def validate_protocol_mapping(mapping_id: str, mapping_data: dict): """Validate a protocol mapping without saving it""" diff --git a/src/dashboard/templates.py b/src/dashboard/templates.py index ddb118c..abd3fd9 100644 --- a/src/dashboard/templates.py +++ b/src/dashboard/templates.py @@ -516,6 +516,37 @@ DASHBOARD_HTML = """ + +