416 lines
15 KiB
JavaScript
416 lines
15 KiB
JavaScript
// 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 = '<option value="">Select Station</option>';
|
|
|
|
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 = '<option value="">Select Equipment</option>';
|
|
|
|
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 = '<option value="">Select Data Type</option>';
|
|
|
|
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 = '<tr><td colspan="8" style="text-align: center; padding: 20px;">No protocol mappings found</td></tr>';
|
|
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 = `
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${mapping.id}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${mapping.protocol_type}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${stationDisplay}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${equipmentDisplay}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${dataTypeDisplay}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${mapping.protocol_address}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">${mapping.db_source}</td>
|
|
<td style="padding: 10px; border: 1px solid #ddd;">
|
|
<button onclick="editMapping('${mapping.id}')" style="background: #007acc; margin-right: 5px;">Edit</button>
|
|
<button onclick="deleteMapping('${mapping.id}')" style="background: #dc3545;">Delete</button>
|
|
</td>
|
|
`;
|
|
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);
|
|
}
|
|
}); |