463 lines
17 KiB
JavaScript
463 lines
17 KiB
JavaScript
// Simplified Discovery Integration
|
|
// Updated for simplified signal names + tags architecture
|
|
|
|
class SimplifiedProtocolDiscovery {
|
|
constructor() {
|
|
this.currentScanId = 'simplified-scan-123';
|
|
this.isScanning = false;
|
|
}
|
|
|
|
init() {
|
|
this.bindDiscoveryEvents();
|
|
}
|
|
|
|
bindDiscoveryEvents() {
|
|
// Discovery scan button
|
|
const startScanBtn = document.getElementById('start-discovery-scan');
|
|
if (startScanBtn) {
|
|
startScanBtn.addEventListener('click', () => {
|
|
this.startDiscoveryScan();
|
|
});
|
|
}
|
|
|
|
// Auto-fill signal form from discovery
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('use-discovered-endpoint')) {
|
|
this.useDiscoveredEndpoint(e.target.dataset.endpointId);
|
|
}
|
|
});
|
|
}
|
|
|
|
async useDiscoveredEndpoint(endpointId) {
|
|
console.log('Using discovered endpoint:', endpointId);
|
|
|
|
// Mock endpoint data (in real implementation, this would come from discovery service)
|
|
const endpoints = {
|
|
'device_001': {
|
|
device_id: 'device_001',
|
|
protocol_type: 'modbus_tcp',
|
|
device_name: 'Water Pump Controller',
|
|
address: '192.168.1.100',
|
|
port: 502,
|
|
data_point: 'Speed',
|
|
protocol_address: '40001'
|
|
},
|
|
'device_002': {
|
|
device_id: 'device_002',
|
|
protocol_type: 'opcua',
|
|
device_name: 'Temperature Sensor',
|
|
address: '192.168.1.101',
|
|
port: 4840,
|
|
data_point: 'Temperature',
|
|
protocol_address: 'ns=2;s=Temperature'
|
|
},
|
|
'device_003': {
|
|
device_id: 'device_003',
|
|
protocol_type: 'modbus_tcp',
|
|
device_name: 'Pressure Transmitter',
|
|
address: '192.168.1.102',
|
|
port: 502,
|
|
data_point: 'Pressure',
|
|
protocol_address: '30001'
|
|
}
|
|
};
|
|
|
|
const endpoint = endpoints[endpointId];
|
|
if (!endpoint) {
|
|
this.showNotification(`Endpoint ${endpointId} not found`, 'error');
|
|
return;
|
|
}
|
|
|
|
// Convert to simplified signal format
|
|
const signalData = this.convertEndpointToSignal(endpoint);
|
|
|
|
// Auto-populate the signal form
|
|
this.autoPopulateSignalForm(signalData);
|
|
|
|
this.showNotification(`Endpoint ${endpoint.device_name} selected for signal creation`, 'success');
|
|
}
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
autoPopulateSignalForm(signalData) {
|
|
console.log('Auto-populating signal form with:', signalData);
|
|
|
|
// Use the simplified protocol mapping function
|
|
if (typeof window.autoPopulateSignalForm === 'function') {
|
|
window.autoPopulateSignalForm(signalData);
|
|
} else {
|
|
console.error('Simplified protocol mapping functions not loaded');
|
|
this.showNotification('Protocol mapping system not available', 'error');
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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
|
|
resultsContainer.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('use-signal-btn')) {
|
|
const signalIndex = parseInt(e.target.dataset.signalIndex);
|
|
const signal = suggestedSignals[signalIndex];
|
|
this.autoPopulateSignalForm(signal);
|
|
}
|
|
});
|
|
|
|
// 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;
|
|
|
|
for (const signal of signals) {
|
|
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}`);
|
|
} else {
|
|
errorCount++;
|
|
console.error(`✗ Failed to create signal: ${signal.signal_name}`, data.detail);
|
|
}
|
|
} catch (error) {
|
|
errorCount++;
|
|
console.error(`✗ Error creating signal: ${signal.signal_name}`, error);
|
|
}
|
|
}
|
|
|
|
// Show results
|
|
const message = `Created ${successCount} signals successfully. ${errorCount > 0 ? `${errorCount} failed.` : ''}`;
|
|
this.showNotification(message, errorCount > 0 ? 'warning' : 'success');
|
|
|
|
// Refresh the protocol signals display
|
|
if (typeof window.loadAllSignals === 'function') {
|
|
window.loadAllSignals();
|
|
}
|
|
}
|
|
|
|
// 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 [];
|
|
}
|
|
}
|
|
|
|
// 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
|
|
const simplifiedDiscovery = new SimplifiedProtocolDiscovery();
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
simplifiedDiscovery.init();
|
|
}); |