/** * 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
`; 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 || 'pressure'; const dbSource = document.getElementById('db-source')?.value || 'influxdb'; try { const response = await fetch(`/api/v1/dashboard/discovery/apply/${this.currentScanId}?station_id=${stationId}&pump_id=${pumpId}&data_type=${dataType}&db_source=${dbSource}`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (result.success) { if (result.created_mappings.length > 0) { this.showNotification(`Successfully created ${result.created_mappings.length} protocol mappings from discovery results`, 'success'); // Refresh protocol mappings grid if (window.protocolMappingGrid) { window.protocolMappingGrid.loadProtocolMappings(); } } else { this.showNotification('No protocol mappings were created. Check the discovery results for compatible endpoints.', 'warning'); } } 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 */ async useDiscoveredEndpoint(endpointId) { try { // Get current scan results to find the endpoint if (!this.currentScanId) { this.showNotification('No discovery results available', 'warning'); return; } const response = await fetch(`/api/v1/dashboard/discovery/results/${this.currentScanId}`); const result = await response.json(); if (!result.success) { throw new Error('Failed to fetch discovery results'); } // Find the specific endpoint const endpoint = result.discovered_endpoints.find(ep => ep.device_id === endpointId); if (!endpoint) { this.showNotification(`Endpoint ${endpointId} not found in current scan`, 'warning'); return; } // Populate protocol mapping form with endpoint data this.populateProtocolForm(endpoint); // Switch to protocol mapping tab this.switchToProtocolMappingTab(); this.showNotification(`Endpoint ${endpoint.device_name || endpointId} selected for protocol mapping`, 'success'); } catch (error) { console.error('Error using discovered endpoint:', error); this.showNotification(`Failed to use endpoint: ${error.message}`, 'error'); } } /** * Populate protocol mapping form with endpoint data */ populateProtocolForm(endpoint) { // Create a new protocol mapping ID const mappingId = `${endpoint.device_id}_${endpoint.protocol_type}`; // Set form values (these would be used when creating a new mapping) const formData = { mapping_id: mappingId, protocol_type: endpoint.protocol_type === 'opc_ua' ? 'opcua' : endpoint.protocol_type, protocol_address: this.getDefaultProtocolAddress(endpoint), device_name: endpoint.device_name || endpoint.device_id, device_address: endpoint.address, device_port: endpoint.port || '', station_id: 'station_001', // Default station ID equipment_id: 'equipment_001', // Default equipment ID data_type_id: 'datatype_001' // Default data type ID }; // Store form data for later use this.selectedEndpoint = formData; // Show form data in console for debugging console.log('Protocol form populated with:', formData); // Auto-populate the protocol mapping form this.autoPopulateProtocolForm(formData); } /** * Auto-populate the protocol mapping form with endpoint data */ autoPopulateProtocolForm(formData) { // First, open the "Add New Mapping" modal this.openAddMappingModal(); // Wait a moment for the modal to open, then populate fields setTimeout(() => { // Find and populate form fields in the modal const mappingIdField = document.getElementById('mapping-id'); const protocolTypeField = document.getElementById('protocol-type'); const protocolAddressField = document.getElementById('protocol-address'); const deviceNameField = document.getElementById('device-name'); const deviceAddressField = document.getElementById('device-address'); const devicePortField = document.getElementById('device-port'); const stationIdField = document.getElementById('station-id'); const equipmentIdField = document.getElementById('equipment-id'); const dataTypeIdField = document.getElementById('data-type-id'); if (mappingIdField) mappingIdField.value = formData.mapping_id; if (protocolTypeField) protocolTypeField.value = formData.protocol_type; if (protocolAddressField) protocolAddressField.value = formData.protocol_address; if (deviceNameField) deviceNameField.value = formData.device_name; if (deviceAddressField) deviceAddressField.value = formData.device_address; if (devicePortField) devicePortField.value = formData.device_port; if (stationIdField) stationIdField.value = formData.station_id; if (equipmentIdField) equipmentIdField.value = formData.equipment_id; if (dataTypeIdField) dataTypeIdField.value = formData.data_type_id; // Show success message this.showNotification(`Protocol form populated with ${formData.device_name}. Please review and complete any missing information.`, 'success'); }, 100); } /** * Open the "Add New Mapping" modal */ openAddMappingModal() { // Look for the showAddMappingModal function or button click if (typeof showAddMappingModal === 'function') { showAddMappingModal(); } else { // Try to find and click the "Add New Mapping" button const addButton = document.querySelector('button[onclick*="showAddMappingModal"]'); if (addButton) { addButton.click(); } else { // Fallback: show a message to manually open the modal this.showNotification('Please click "Add New Mapping" to create a protocol mapping with the discovered endpoint data.', 'info'); } } } /** * Get default protocol address based on endpoint type */ getDefaultProtocolAddress(endpoint) { const protocolType = endpoint.protocol_type; const deviceName = endpoint.device_name || endpoint.device_id; switch (protocolType) { case 'modbus_tcp': return '40001'; // Default holding register case 'opc_ua': return `ns=2;s=${deviceName.replace(/\s+/g, '_')}`; case 'rest_api': return `http://${endpoint.address}${endpoint.port ? ':' + endpoint.port : ''}/api/data`; default: return endpoint.address; } } /** * Switch to protocol mapping tab */ switchToProtocolMappingTab() { // Find and click the protocol mapping tab const mappingTab = document.querySelector('[data-tab="protocol-mapping"]'); if (mappingTab) { mappingTab.click(); } else { // Fallback: scroll to protocol mapping section const mappingSection = document.querySelector('#protocol-mapping-section'); if (mappingSection) { mappingSection.scrollIntoView({ behavior: 'smooth' }); } } // Show guidance message this.showNotification('Please complete the protocol mapping form with station, pump, and data type information', 'info'); } /** * 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(); });