// Protocol Mapping Functions let currentProtocolFilter = 'all'; let editingMappingId = null; let tagMetadata = { stations: [], equipment: [], dataTypes: [] }; // Tag Metadata Functions async function loadTagMetadata() { try { // Load stations const stationsResponse = await fetch('/api/v1/dashboard/metadata/stations'); const stationsData = await stationsResponse.json(); if (stationsData.success) { tagMetadata.stations = stationsData.stations; populateStationDropdown(); } // Load data types const dataTypesResponse = await fetch('/api/v1/dashboard/metadata/data-types'); const dataTypesData = await dataTypesResponse.json(); if (dataTypesData.success) { tagMetadata.dataTypes = dataTypesData.data_types; populateDataTypeDropdown(); } // Load equipment for all stations const equipmentResponse = await fetch('/api/v1/dashboard/metadata/equipment'); const equipmentData = await equipmentResponse.json(); if (equipmentData.success) { tagMetadata.equipment = equipmentData.equipment; } } catch (error) { console.error('Error loading tag metadata:', error); } } function populateStationDropdown() { const stationSelect = document.getElementById('station_id'); stationSelect.innerHTML = ''; tagMetadata.stations.forEach(station => { const option = document.createElement('option'); option.value = station.id; option.textContent = `${station.name} (${station.id})`; stationSelect.appendChild(option); }); } function populateEquipmentDropdown(stationId = null) { const equipmentSelect = document.getElementById('equipment_id'); equipmentSelect.innerHTML = ''; let filteredEquipment = tagMetadata.equipment; if (stationId) { filteredEquipment = tagMetadata.equipment.filter(eq => eq.station_id === stationId); } filteredEquipment.forEach(equipment => { const option = document.createElement('option'); option.value = equipment.id; option.textContent = `${equipment.name} (${equipment.id})`; equipmentSelect.appendChild(option); }); } function populateDataTypeDropdown() { const dataTypeSelect = document.getElementById('data_type_id'); dataTypeSelect.innerHTML = ''; tagMetadata.dataTypes.forEach(dataType => { const option = document.createElement('option'); option.value = dataType.id; option.textContent = `${dataType.name} (${dataType.id})`; if (dataType.units) { option.textContent += ` [${dataType.units}]`; } dataTypeSelect.appendChild(option); }); } // Event listener for station selection change document.addEventListener('DOMContentLoaded', function() { const stationSelect = document.getElementById('station_id'); if (stationSelect) { stationSelect.addEventListener('change', function() { const stationId = this.value; populateEquipmentDropdown(stationId); }); } // Load tag metadata when page loads loadTagMetadata(); }); function selectProtocol(protocol) { currentProtocolFilter = protocol; // Update active button document.querySelectorAll('.protocol-btn').forEach(btn => { btn.classList.remove('active'); }); event.target.classList.add('active'); // Reload mappings with filter loadProtocolMappings(); } async function loadProtocolMappings() { try { // Ensure tag metadata is loaded first if (tagMetadata.stations.length === 0 || tagMetadata.dataTypes.length === 0) { await loadTagMetadata(); } const params = new URLSearchParams(); if (currentProtocolFilter !== 'all') { params.append('protocol_type', currentProtocolFilter); } const response = await fetch(`/api/v1/dashboard/protocol-mappings?${params}`); const data = await response.json(); if (data.success) { displayProtocolMappings(data.mappings); } else { showProtocolMappingAlert('Failed to load protocol mappings', 'error'); } } catch (error) { console.error('Error loading protocol mappings:', error); showProtocolMappingAlert('Error loading protocol mappings', 'error'); } } function displayProtocolMappings(mappings) { const tbody = document.getElementById('protocol-mappings-body'); tbody.innerHTML = ''; if (mappings.length === 0) { tbody.innerHTML = 'No protocol mappings found'; return; } mappings.forEach(mapping => { // Look up human-readable names from tag metadata const station = tagMetadata.stations.find(s => s.id === mapping.station_id); const equipment = tagMetadata.equipment.find(e => e.id === mapping.equipment_id); const dataType = tagMetadata.dataTypes.find(dt => dt.id === mapping.data_type_id); const stationDisplay = station ? `${station.name} (${station.id})` : (mapping.station_id || '-'); const equipmentDisplay = equipment ? `${equipment.name} (${equipment.id})` : (mapping.equipment_id || '-'); const dataTypeDisplay = dataType ? `${dataType.name} (${dataType.id})` : (mapping.data_type_id || '-'); const row = document.createElement('tr'); row.innerHTML = ` ${mapping.id} ${mapping.protocol_type} ${stationDisplay} ${equipmentDisplay} ${dataTypeDisplay} ${mapping.protocol_address} ${mapping.db_source} `; tbody.appendChild(row); }); } function showAddMappingModal() { editingMappingId = null; document.getElementById('modal-title').textContent = 'Add Protocol Mapping'; document.getElementById('mapping-form').reset(); document.getElementById('protocol_address_help').textContent = ''; document.getElementById('mapping-modal').style.display = 'block'; } function showEditMappingModal(mapping) { editingMappingId = mapping.id; document.getElementById('modal-title').textContent = 'Edit Protocol Mapping'; document.getElementById('mapping_id').value = mapping.id; document.getElementById('protocol_type').value = mapping.protocol_type; // Set dropdown values const stationSelect = document.getElementById('station_id'); const equipmentSelect = document.getElementById('equipment_id'); const dataTypeSelect = document.getElementById('data_type_id'); stationSelect.value = mapping.station_id || ''; if (mapping.station_id) { populateEquipmentDropdown(mapping.station_id); } equipmentSelect.value = mapping.equipment_id || ''; dataTypeSelect.value = mapping.data_type_id || ''; document.getElementById('protocol_address').value = mapping.protocol_address; document.getElementById('db_source').value = mapping.db_source; updateProtocolFields(); document.getElementById('mapping-modal').style.display = 'block'; } function closeMappingModal() { document.getElementById('mapping-modal').style.display = 'none'; editingMappingId = null; } function updateProtocolFields() { const protocolType = document.getElementById('protocol_type').value; const helpText = document.getElementById('protocol_address_help'); switch (protocolType) { case 'modbus_tcp': helpText.textContent = 'Modbus address format: 40001 (holding register), 30001 (input register), 10001 (coil), 00001 (discrete input)'; break; case 'opcua': helpText.textContent = 'OPC UA NodeId format: ns=2;s=MyVariable or ns=2;i=1234'; break; case 'modbus_rtu': helpText.textContent = 'Modbus RTU address format: 40001 (holding register), 30001 (input register), 10001 (coil), 00001 (discrete input)'; break; case 'rest_api': helpText.textContent = 'REST API endpoint format: /api/v1/data/endpoint'; break; default: helpText.textContent = ''; } } async function validateMapping() { const formData = getMappingFormData(); try { const response = await fetch(`/api/v1/dashboard/protocol-mappings/${editingMappingId || 'new'}/validate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await response.json(); if (data.success) { if (data.valid) { showProtocolMappingAlert('Mapping validation successful!', 'success'); } else { showProtocolMappingAlert(`Validation failed: ${data.errors.join(', ')}`, 'error'); } } else { showProtocolMappingAlert('Validation error', 'error'); } } catch (error) { console.error('Error validating mapping:', error); showProtocolMappingAlert('Error validating mapping', 'error'); } } async function saveMapping(event) { event.preventDefault(); const formData = getMappingFormData(); try { let response; if (editingMappingId) { response = await fetch(`/api/v1/dashboard/protocol-mappings/${editingMappingId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); } else { response = await fetch('/api/v1/dashboard/protocol-mappings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); } const data = await response.json(); if (data.success) { showProtocolMappingAlert(`Protocol mapping ${editingMappingId ? 'updated' : 'created'} successfully!`, 'success'); closeMappingModal(); loadProtocolMappings(); } else { showProtocolMappingAlert(`Failed to save mapping: ${data.detail || 'Unknown error'}`, 'error'); } } catch (error) { console.error('Error saving mapping:', error); showProtocolMappingAlert('Error saving mapping', 'error'); } } function getMappingFormData() { return { protocol_type: document.getElementById('protocol_type').value, station_id: document.getElementById('station_id').value, equipment_id: document.getElementById('equipment_id').value, data_type_id: document.getElementById('data_type_id').value, protocol_address: document.getElementById('protocol_address').value, db_source: document.getElementById('db_source').value }; } async function editMapping(mappingId) { try { const response = await fetch(`/api/v1/dashboard/protocol-mappings?protocol_type=all`); const data = await response.json(); if (data.success) { const mapping = data.mappings.find(m => m.id === mappingId); if (mapping) { showEditMappingModal(mapping); } else { showProtocolMappingAlert('Mapping not found', 'error'); } } else { showProtocolMappingAlert('Failed to load mapping', 'error'); } } catch (error) { console.error('Error loading mapping:', error); showProtocolMappingAlert('Error loading mapping', 'error'); } } async function deleteMapping(mappingId) { if (!confirm(`Are you sure you want to delete mapping ${mappingId}?`)) { return; } try { const response = await fetch(`/api/v1/dashboard/protocol-mappings/${mappingId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showProtocolMappingAlert('Mapping deleted successfully!', 'success'); loadProtocolMappings(); } else { showProtocolMappingAlert(`Failed to delete mapping: ${data.detail || 'Unknown error'}`, 'error'); } } catch (error) { console.error('Error deleting mapping:', error); showProtocolMappingAlert('Error deleting mapping', 'error'); } } function showProtocolMappingAlert(message, type) { const alertsDiv = document.getElementById('protocol-mapping-alerts'); const alertDiv = document.createElement('div'); alertDiv.className = `alert ${type === 'error' ? 'error' : 'success'}`; alertDiv.textContent = message; alertsDiv.innerHTML = ''; alertsDiv.appendChild(alertDiv); setTimeout(() => { alertDiv.remove(); }, 5000); } async function exportProtocolMappings() { try { const response = await fetch('/api/v1/dashboard/protocol-mappings?protocol_type=all'); const data = await response.json(); if (data.success) { const csvContent = convertToCSV(data.mappings); downloadCSV(csvContent, 'protocol_mappings.csv'); } else { showProtocolMappingAlert('Failed to export mappings', 'error'); } } catch (error) { console.error('Error exporting mappings:', error); showProtocolMappingAlert('Error exporting mappings', 'error'); } } function convertToCSV(mappings) { const headers = ['ID', 'Protocol', 'Station', 'Pump', 'Data Type', 'Protocol Address', 'Database Source']; const rows = mappings.map(mapping => [ mapping.id, mapping.protocol_type, mapping.station_id || '', mapping.pump_id || '', mapping.data_type, mapping.protocol_address, mapping.db_source ]); return [headers, ...rows].map(row => row.map(field => `"${field}"`).join(',')).join('\n'); } function downloadCSV(content, filename) { const blob = new Blob([content], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url); } // Initialize form submission handler document.addEventListener('DOMContentLoaded', function() { const mappingForm = document.getElementById('mapping-form'); if (mappingForm) { mappingForm.addEventListener('submit', saveMapping); } });