CalejoControl/static/discovery.js

605 lines
23 KiB
JavaScript
Raw Normal View History

// Simplified Discovery Integration
// Updated for simplified signal names + tags architecture
console.log('=== DISCOVERY.JS FILE LOADED - START ===');
class SimplifiedProtocolDiscovery {
constructor() {
this.currentScanId = 'simplified-scan-123';
this.isScanning = false;
}
init() {
console.log('Discovery.js: init() called');
try {
this.bindDiscoveryEvents();
console.log('Discovery.js: bindDiscoveryEvents() completed successfully');
} catch (error) {
console.error('Discovery.js: Error in init():', error);
}
}
bindDiscoveryEvents() {
console.log('Binding discovery events...');
// Discovery scan button
const startScanBtn = document.getElementById('start-discovery-scan');
console.log('Start scan button:', startScanBtn);
if (startScanBtn) {
startScanBtn.addEventListener('click', () => {
console.log('Start Discovery Scan button clicked!');
this.startDiscoveryScan();
});
} else {
console.error('Start scan button not found!');
}
// Check if discovery results container exists
const resultsContainer = document.getElementById('discovery-results');
console.log('Discovery results container during init:', resultsContainer);
// Auto-fill signal form from discovery
console.log('Setting up global click event listener for use-signal-btn');
try {
const self = this; // Capture 'this' context
document.addEventListener('click', function(e) {
console.log('Global click event fired, target:', e.target.tagName, 'classes:', e.target.className);
console.log('Target dataset:', e.target.dataset);
if (e.target.classList.contains('use-signal-btn')) {
console.log('Use This Signal button clicked!');
console.log('Signal index from dataset:', e.target.dataset.signalIndex);
self.useDiscoveredEndpoint(e.target.dataset.signalIndex);
} else {
console.log('Clicked element is not a use-signal-btn');
}
});
console.log('Global click event listener set up successfully');
} catch (error) {
console.error('Error setting up event listener:', error);
}
}
async useDiscoveredEndpoint(signalIndex) {
console.log('Using discovered endpoint with index:', signalIndex);
// Get the actual discovered endpoints from the mock scan
const discoveredEndpoints = await this.mockDiscoveryScan('192.168.1.0/24');
// Map signal index to endpoint
const endpoint = discoveredEndpoints[signalIndex];
if (!endpoint) {
this.showNotification(`Endpoint with index ${signalIndex} not found`, 'error');
return;
}
// Convert to simplified signal format
const signalData = this.convertEndpointToSignal(endpoint);
// Auto-populate the signal form with retry logic
this.autoPopulateSignalFormWithRetry(signalData);
this.showNotification(`Endpoint ${endpoint.device_name} selected for signal creation`, 'success');
}
autoPopulateSignalFormWithRetry(signalData, retryCount = 0) {
console.log('Attempting to auto-populate signal form, attempt:', retryCount + 1);
if (typeof window.autoPopulateSignalForm === 'function') {
console.log('Found window.autoPopulateSignalForm, calling it...');
window.autoPopulateSignalForm(signalData);
} else {
console.error('autoPopulateSignalForm function not found');
// Retry after a delay if we haven't exceeded max retries
if (retryCount < 5) {
console.log(`Retrying in 500ms... (${retryCount + 1}/5)`);
setTimeout(() => {
this.autoPopulateSignalFormWithRetry(signalData, retryCount + 1);
}, 500);
} else {
console.error('Max retries exceeded, autoPopulateSignalForm function still not found');
this.showNotification('Error: Could not open signal form. Please ensure the protocol mapping system is loaded.', 'error');
}
}
}
convertEndpointToSignal(endpoint) {
// Generate human-readable signal name
const signalName = `${endpoint.device_name} ${endpoint.data_point}`;
// Generate meaningful tags
const tags = [
`device:${endpoint.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,
`protocol:${endpoint.protocol_type}`,
`data_point:${endpoint.data_point.toLowerCase().replace(/[^a-z0-9]/g, '_')}`,
'discovered:true'
];
// Add device-specific tags
if (endpoint.device_name.toLowerCase().includes('pump')) {
tags.push('equipment:pump');
}
if (endpoint.device_name.toLowerCase().includes('sensor')) {
tags.push('equipment:sensor');
}
if (endpoint.device_name.toLowerCase().includes('controller')) {
tags.push('equipment:controller');
}
// Add protocol-specific tags
if (endpoint.protocol_type === 'modbus_tcp') {
tags.push('interface:modbus');
} else if (endpoint.protocol_type === 'opcua') {
tags.push('interface:opcua');
}
// Generate database source
const dbSource = `measurements.${endpoint.device_name.toLowerCase().replace(/[^a-z0-9]/g, '_')}_${endpoint.data_point.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
return {
signal_name: signalName,
tags: tags,
protocol_type: endpoint.protocol_type,
protocol_address: endpoint.protocol_address,
db_source: dbSource
};
}
// Start discovery scan
async startDiscoveryScan() {
console.log('Starting discovery scan...');
// Update UI
const startBtn = document.getElementById('start-discovery-scan');
const stopBtn = document.getElementById('stop-discovery-scan');
const statusDiv = document.getElementById('discovery-status');
if (startBtn) startBtn.disabled = true;
if (stopBtn) stopBtn.disabled = false;
if (statusDiv) {
statusDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-search"></i> Discovery scan in progress...</div>';
}
try {
// Run discovery
const results = await this.discoverAndSuggestSignals();
// Update status
if (statusDiv) {
statusDiv.innerHTML = `<div class="alert alert-success"><i class="fas fa-check"></i> Discovery complete. Found ${results.length} devices.</div>`;
}
this.showNotification(`Discovery complete. Found ${results.length} devices.`, 'success');
} catch (error) {
console.error('Discovery scan failed:', error);
if (statusDiv) {
statusDiv.innerHTML = '<div class="alert alert-error"><i class="fas fa-exclamation-triangle"></i> Discovery scan failed</div>';
}
this.showNotification('Discovery scan failed', 'error');
} finally {
// Reset UI
if (startBtn) startBtn.disabled = false;
if (stopBtn) stopBtn.disabled = true;
}
}
// Advanced discovery features
async discoverAndSuggestSignals(networkRange = '192.168.1.0/24') {
console.log(`Starting discovery scan on ${networkRange}`);
this.isScanning = true;
try {
// Mock discovery results
const discoveredEndpoints = await this.mockDiscoveryScan(networkRange);
// Convert to suggested signals
const suggestedSignals = discoveredEndpoints.map(endpoint =>
this.convertEndpointToSignal(endpoint)
);
this.displayDiscoveryResults(suggestedSignals);
this.isScanning = false;
return suggestedSignals;
} catch (error) {
console.error('Discovery scan failed:', error);
this.showNotification('Discovery scan failed', 'error');
this.isScanning = false;
return [];
}
}
async mockDiscoveryScan(networkRange) {
// Simulate network discovery delay
await new Promise(resolve => setTimeout(resolve, 2000));
// Return mock discovered endpoints
return [
{
device_id: 'discovered_001',
protocol_type: 'modbus_tcp',
device_name: 'Booster Pump',
address: '192.168.1.110',
port: 502,
data_point: 'Flow Rate',
protocol_address: '30002'
},
{
device_id: 'discovered_002',
protocol_type: 'modbus_tcp',
device_name: 'Level Sensor',
address: '192.168.1.111',
port: 502,
data_point: 'Tank Level',
protocol_address: '30003'
},
{
device_id: 'discovered_003',
protocol_type: 'opcua',
device_name: 'PLC Controller',
address: '192.168.1.112',
port: 4840,
data_point: 'System Status',
protocol_address: 'ns=2;s=SystemStatus'
}
];
}
displayDiscoveryResults(suggestedSignals) {
console.log('Displaying discovery results:', suggestedSignals);
const resultsContainer = document.getElementById('discovery-results');
if (!resultsContainer) {
console.error('Discovery results container not found!');
this.showNotification('Discovery results container not found', 'error');
return;
}
console.log('Found discovery results container:', resultsContainer);
resultsContainer.innerHTML = '<h3>Discovery Results</h3>';
suggestedSignals.forEach((signal, index) => {
const signalCard = document.createElement('div');
signalCard.className = 'discovery-result-card';
signalCard.innerHTML = `
<div class="signal-info">
<strong>${signal.signal_name}</strong>
<div class="signal-tags">
${signal.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
<div class="signal-details">
<span>Protocol: ${signal.protocol_type}</span>
<span>Address: ${signal.protocol_address}</span>
</div>
</div>
<button class="use-signal-btn" data-signal-index="${index}">
Use This Signal
</button>
`;
resultsContainer.appendChild(signalCard);
});
// Add event listeners for use buttons
console.log('Adding event listener to results container');
console.log('Results container:', resultsContainer);
console.log('Results container ID:', resultsContainer.id);
console.log('Number of use-signal-btn elements:', resultsContainer.querySelectorAll('.use-signal-btn').length);
const clickHandler = (e) => {
console.log('Discovery results container clicked:', e.target);
console.log('Button classes:', e.target.classList);
console.log('Button tag name:', e.target.tagName);
if (e.target.classList.contains('use-signal-btn')) {
console.log('Use This Signal button clicked!');
const signalIndex = parseInt(e.target.dataset.signalIndex);
console.log('Signal index:', signalIndex);
const signal = suggestedSignals[signalIndex];
console.log('Signal data:', signal);
// Use the global function directly
if (typeof window.autoPopulateSignalForm === 'function') {
window.autoPopulateSignalForm(signal);
} else {
console.error('autoPopulateSignalForm function not found!');
}
} else {
console.log('Clicked element is not a use-signal-btn');
}
};
resultsContainer.addEventListener('click', clickHandler);
console.log('Event listener added to results container');
// Add "Apply All" button
const applyAllButton = document.createElement('button');
applyAllButton.className = 'apply-all-btn';
applyAllButton.textContent = 'Apply All as Protocol Signals';
applyAllButton.style.marginTop = '15px';
applyAllButton.style.padding = '10px 20px';
applyAllButton.style.background = '#28a745';
applyAllButton.style.color = 'white';
applyAllButton.style.border = 'none';
applyAllButton.style.borderRadius = '4px';
applyAllButton.style.cursor = 'pointer';
applyAllButton.style.fontWeight = 'bold';
applyAllButton.onclick = () => {
this.applyAllAsProtocolSignals(suggestedSignals);
};
resultsContainer.appendChild(applyAllButton);
}
// Apply all discovered signals as protocol signals
async applyAllAsProtocolSignals(signals) {
console.log('Applying all discovered signals as protocol signals:', signals);
let successCount = 0;
let errorCount = 0;
let duplicateCount = 0;
// First, check which signals already exist
const existingSignals = await this.getExistingSignals();
const existingSignalNames = new Set(existingSignals.map(s => s.signal_name));
for (const signal of signals) {
// Skip if signal with same name already exists
if (existingSignalNames.has(signal.signal_name)) {
console.log(`⚠ Skipping duplicate signal: ${signal.signal_name}`);
duplicateCount++;
continue;
}
try {
const response = await fetch('/api/v1/dashboard/protocol-signals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(signal)
});
const data = await response.json();
if (data.success) {
successCount++;
console.log(`✓ Created signal: ${signal.signal_name}`);
// Add to existing set to prevent duplicates in same batch
existingSignalNames.add(signal.signal_name);
} else {
errorCount++;
console.error(`✗ Failed to create signal: ${signal.signal_name}`, data.detail);
// Check if it's a duplicate error
if (data.detail && data.detail.includes('already exists')) {
duplicateCount++;
}
}
} catch (error) {
errorCount++;
console.error(`✗ Error creating signal: ${signal.signal_name}`, error);
}
}
// Show results
let message = `Created ${successCount} signals successfully.`;
if (errorCount > 0) {
message += ` ${errorCount} failed.`;
}
if (duplicateCount > 0) {
message += ` ${duplicateCount} duplicates skipped.`;
}
const notificationType = errorCount > 0 ? 'warning' : (successCount > 0 ? 'success' : 'info');
this.showNotification(message, notificationType);
// Refresh the protocol signals display
if (typeof window.loadAllSignals === 'function') {
window.loadAllSignals();
}
}
// Get existing signals to check for duplicates
async getExistingSignals() {
try {
const response = await fetch('/api/v1/dashboard/protocol-signals');
const data = await response.json();
if (data.success) {
return data.signals || [];
} else {
console.error('Failed to get existing signals:', data.detail);
return [];
}
} catch (error) {
console.error('Error getting existing signals:', error);
return [];
}
}
// Tag-based signal search
async searchSignalsByTags(tags) {
try {
const params = new URLSearchParams();
tags.forEach(tag => params.append('tags', tag));
const response = await fetch(`/api/v1/dashboard/protocol-signals?${params}`);
const data = await response.json();
if (data.success) {
return data.signals;
} else {
console.error('Failed to search signals by tags:', data.detail);
return [];
}
} catch (error) {
console.error('Error searching signals by tags:', error);
return [];
}
}
// Clear all existing signals (for testing)
async clearAllSignals() {
if (!confirm('Are you sure you want to delete ALL protocol signals? This action cannot be undone.')) {
return;
}
try {
const existingSignals = await this.getExistingSignals();
let deletedCount = 0;
for (const signal of existingSignals) {
try {
const response = await fetch(`/api/v1/dashboard/protocol-signals/${signal.signal_id}`, {
method: 'DELETE'
});
const data = await response.json();
if (data.success) {
deletedCount++;
console.log(`✓ Deleted signal: ${signal.signal_name}`);
} else {
console.error(`✗ Failed to delete signal: ${signal.signal_name}`, data.detail);
}
} catch (error) {
console.error(`✗ Error deleting signal: ${signal.signal_name}`, error);
}
}
this.showNotification(`Deleted ${deletedCount} signals successfully.`, 'success');
// Refresh the protocol signals display
if (typeof window.loadAllSignals === 'function') {
window.loadAllSignals();
}
} catch (error) {
console.error('Error clearing signals:', error);
this.showNotification('Error clearing signals', 'error');
}
}
// Signal name suggestions based on device type
generateSignalNameSuggestions(deviceName, dataPoint) {
const baseName = `${deviceName} ${dataPoint}`;
const suggestions = [
baseName,
`${dataPoint} of ${deviceName}`,
`${deviceName} ${dataPoint} Reading`,
`${dataPoint} Measurement - ${deviceName}`
];
// Add context-specific suggestions
if (dataPoint.toLowerCase().includes('speed')) {
suggestions.push(`${deviceName} Motor Speed`);
suggestions.push(`${deviceName} RPM`);
}
if (dataPoint.toLowerCase().includes('temperature')) {
suggestions.push(`${deviceName} Temperature`);
suggestions.push(`Temperature at ${deviceName}`);
}
if (dataPoint.toLowerCase().includes('pressure')) {
suggestions.push(`${deviceName} Pressure`);
suggestions.push(`Pressure Reading - ${deviceName}`);
}
return suggestions;
}
// Tag suggestions based on device and protocol
generateTagSuggestions(deviceName, protocolType, dataPoint) {
const suggestions = new Set();
// Device type tags
if (deviceName.toLowerCase().includes('pump')) {
suggestions.add('equipment:pump');
suggestions.add('fluid:water');
}
if (deviceName.toLowerCase().includes('sensor')) {
suggestions.add('equipment:sensor');
suggestions.add('type:measurement');
}
if (deviceName.toLowerCase().includes('controller')) {
suggestions.add('equipment:controller');
suggestions.add('type:control');
}
// Protocol tags
suggestions.add(`protocol:${protocolType}`);
if (protocolType === 'modbus_tcp' || protocolType === 'modbus_rtu') {
suggestions.add('interface:modbus');
} else if (protocolType === 'opcua') {
suggestions.add('interface:opcua');
}
// Data point tags
suggestions.add(`data_point:${dataPoint.toLowerCase().replace(/[^a-z0-9]/g, '_')}`);
if (dataPoint.toLowerCase().includes('speed')) {
suggestions.add('unit:rpm');
suggestions.add('type:setpoint');
}
if (dataPoint.toLowerCase().includes('temperature')) {
suggestions.add('unit:celsius');
suggestions.add('type:measurement');
}
if (dataPoint.toLowerCase().includes('pressure')) {
suggestions.add('unit:psi');
suggestions.add('type:measurement');
}
if (dataPoint.toLowerCase().includes('status')) {
suggestions.add('type:status');
suggestions.add('format:boolean');
}
// Discovery tag
suggestions.add('discovered:true');
return Array.from(suggestions);
}
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `discovery-notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
// Auto-remove after 5 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
}
// Global instance
// Expose to window for global access
window.SimplifiedProtocolDiscovery = SimplifiedProtocolDiscovery;
const simplifiedDiscovery = new SimplifiedProtocolDiscovery();
window.simplifiedDiscovery = simplifiedDiscovery;
// Initialize when DOM is loaded
console.log('Discovery.js loaded - setting up DOMContentLoaded listener');
// Check if DOM is already loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded event fired - Initializing SimplifiedProtocolDiscovery...');
window.simplifiedDiscovery.init();
console.log('SimplifiedProtocolDiscovery initialized successfully');
});
} else {
console.log('DOM already loaded - Initializing SimplifiedProtocolDiscovery immediately...');
window.simplifiedDiscovery.init();
console.log('SimplifiedProtocolDiscovery initialized successfully');
}
console.log('=== DISCOVERY.JS FILE LOADED - END ===');