/**
* 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 = `
| Protocol |
Device Name |
Address |
Capabilities |
Discovered |
Actions |
`;
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 += `
| ${protocolBadge} |
${endpoint.device_name || 'Unknown Device'} |
${endpoint.address}${endpoint.port ? ':' + endpoint.port : ''} |
${capabilities} |
${discoveredTime} |
|
`;
});
html += `
`;
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();
});