/** * Protocol Discovery JavaScript * Handles auto-discovery of protocol endpoints and integration with protocol mapping */ class ProtocolDiscovery { constructor() { this.currentScanId = null; this.scanInterval = null; this.isScanning = false; } /** * Initialize discovery functionality */ init() { this.bindDiscoveryEvents(); this.loadDiscoveryStatus(); // Auto-refresh discovery status every 5 seconds setInterval(() => { if (this.isScanning) { this.loadDiscoveryStatus(); } }, 5000); } /** * Bind discovery event handlers */ bindDiscoveryEvents() { // Start discovery scan document.getElementById('start-discovery-scan')?.addEventListener('click', () => { this.startDiscoveryScan(); }); // Stop discovery scan document.getElementById('stop-discovery-scan')?.addEventListener('click', () => { this.stopDiscoveryScan(); }); // Apply discovery results document.getElementById('apply-discovery-results')?.addEventListener('click', () => { this.applyDiscoveryResults(); }); // Refresh discovery status document.getElementById('refresh-discovery-status')?.addEventListener('click', () => { this.loadDiscoveryStatus(); }); // Auto-fill protocol form from discovery document.addEventListener('click', (e) => { if (e.target.classList.contains('use-discovered-endpoint')) { this.useDiscoveredEndpoint(e.target.dataset.endpointId); } }); } /** * Start a new discovery scan */ async startDiscoveryScan() { try { this.setScanningState(true); const response = await fetch('/api/v1/dashboard/discovery/scan', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { this.currentScanId = result.scan_id; this.showNotification('Discovery scan started successfully', 'success'); // Start polling for scan completion this.pollScanStatus(); } else { throw new Error(result.detail || 'Failed to start discovery scan'); } } catch (error) { console.error('Error starting discovery scan:', error); this.showNotification(`Failed to start discovery scan: ${error.message}`, 'error'); this.setScanningState(false); } } /** * Stop current discovery scan */ async stopDiscoveryScan() { // Note: This would require additional API endpoint to stop scans // For now, we'll just stop polling if (this.scanInterval) { clearInterval(this.scanInterval); this.scanInterval = null; } this.setScanningState(false); this.showNotification('Discovery scan stopped', 'info'); } /** * Poll for scan completion */ async pollScanStatus() { if (!this.currentScanId) return; this.scanInterval = setInterval(async () => { try { const response = await fetch(`/api/v1/dashboard/discovery/results/${this.currentScanId}`); const result = await response.json(); if (result.success) { if (result.status === 'completed' || result.status === 'failed') { clearInterval(this.scanInterval); this.scanInterval = null; this.setScanningState(false); if (result.status === 'completed') { this.showNotification(`Discovery scan completed. Found ${result.discovered_endpoints.length} endpoints`, 'success'); this.displayDiscoveryResults(result); } else { this.showNotification('Discovery scan failed', 'error'); } } } } catch (error) { console.error('Error polling scan status:', error); clearInterval(this.scanInterval); this.scanInterval = null; this.setScanningState(false); } }, 2000); } /** * Load current discovery status */ async loadDiscoveryStatus() { try { const response = await fetch('/api/v1/dashboard/discovery/status'); const result = await response.json(); if (result.success) { this.updateDiscoveryStatusUI(result.status); } } catch (error) { console.error('Error loading discovery status:', error); } } /** * Update discovery status UI */ updateDiscoveryStatusUI(status) { const statusElement = document.getElementById('discovery-status'); const scanButton = document.getElementById('start-discovery-scan'); const stopButton = document.getElementById('stop-discovery-scan'); if (!statusElement) return; this.isScanning = status.is_scanning; if (status.is_scanning) { statusElement.innerHTML = `
Discovery scan in progress... (Scan ID: ${status.current_scan_id})
`; scanButton?.setAttribute('disabled', 'true'); stopButton?.removeAttribute('disabled'); } else { statusElement.innerHTML = `
Discovery service ready ${status.total_discovered_endpoints > 0 ? `- ${status.total_discovered_endpoints} endpoints discovered` : '' }
`; scanButton?.removeAttribute('disabled'); stopButton?.setAttribute('disabled', 'true'); } } /** * Display discovery results */ displayDiscoveryResults(result) { const resultsContainer = document.getElementById('discovery-results'); if (!resultsContainer) return; const endpoints = result.discovered_endpoints || []; if (endpoints.length === 0) { resultsContainer.innerHTML = `
No endpoints discovered in this scan
`; return; } let html = `
Discovery Results (${endpoints.length} endpoints found)
`; endpoints.forEach(endpoint => { const protocolBadge = this.getProtocolBadge(endpoint.protocol_type); const capabilities = endpoint.capabilities ? endpoint.capabilities.join(', ') : 'N/A'; const discoveredTime = endpoint.discovered_at ? new Date(endpoint.discovered_at).toLocaleString() : 'N/A'; html += ` `; }); html += `
Protocol Device Name Address Capabilities Discovered Actions
${protocolBadge} ${endpoint.device_name || 'Unknown Device'} ${endpoint.address}${endpoint.port ? ':' + endpoint.port : ''} ${capabilities} ${discoveredTime}
`; resultsContainer.innerHTML = html; // Re-bind apply button document.getElementById('apply-discovery-results')?.addEventListener('click', () => { this.applyDiscoveryResults(); }); } /** * Apply discovery results as protocol mappings */ async applyDiscoveryResults() { if (!this.currentScanId) { this.showNotification('No discovery results to apply', 'warning'); return; } // Get station and pump info from form or prompt const stationId = document.getElementById('station-id')?.value || 'station_001'; const pumpId = document.getElementById('pump-id')?.value || 'pump_001'; const dataType = document.getElementById('data-type')?.value || 'setpoint'; const dbSource = document.getElementById('db-source')?.value || 'frequency_hz'; try { const response = await fetch(`/api/v1/dashboard/discovery/apply/${this.currentScanId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ station_id: stationId, pump_id: pumpId, data_type: dataType, db_source: dbSource }) }); const result = await response.json(); if (result.success) { this.showNotification(`Successfully created ${result.created_mappings.length} protocol mappings`, 'success'); // Refresh protocol mappings grid if (window.protocolMappingGrid) { window.protocolMappingGrid.loadProtocolMappings(); } } else { throw new Error(result.detail || 'Failed to apply discovery results'); } } catch (error) { console.error('Error applying discovery results:', error); this.showNotification(`Failed to apply discovery results: ${error.message}`, 'error'); } } /** * Use discovered endpoint in protocol form */ useDiscoveredEndpoint(endpointId) { // This would fetch the specific endpoint details and populate the form // For now, we'll just show a notification this.showNotification(`Endpoint ${endpointId} selected for protocol mapping`, 'info'); // In a real implementation, we would: // 1. Fetch endpoint details // 2. Populate protocol form fields // 3. Switch to protocol mapping tab } /** * Set scanning state */ setScanningState(scanning) { this.isScanning = scanning; const scanButton = document.getElementById('start-discovery-scan'); const stopButton = document.getElementById('stop-discovery-scan'); if (scanning) { scanButton?.setAttribute('disabled', 'true'); stopButton?.removeAttribute('disabled'); } else { scanButton?.removeAttribute('disabled'); stopButton?.setAttribute('disabled', 'true'); } } /** * Get protocol badge HTML */ getProtocolBadge(protocolType) { const badges = { 'modbus_tcp': 'Modbus TCP', 'modbus_rtu': 'Modbus RTU', 'opc_ua': 'OPC UA', 'rest_api': 'REST API' }; return badges[protocolType] || `${protocolType}`; } /** * Show notification */ showNotification(message, type = 'info') { // Use existing notification system or create simple alert const alertClass = { 'success': 'alert-success', 'error': 'alert-danger', 'warning': 'alert-warning', 'info': 'alert-info' }[type] || 'alert-info'; const notification = document.createElement('div'); notification.className = `alert ${alertClass} alert-dismissible fade show`; notification.innerHTML = ` ${message} `; const container = document.getElementById('discovery-notifications') || document.body; container.appendChild(notification); // Auto-remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } } // Initialize discovery when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.protocolDiscovery = new ProtocolDiscovery(); window.protocolDiscovery.init(); });