You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

405 lines
14 KiB
JavaScript

// Global variables to store the original data and filter states
let originalData = [];
let historyChart = null;
let selectedDate = null;
let showRedOnly = false;
let showYellowOnly = false;
let textFilterValue = '';
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Set default date to today
const today = new Date();
const formattedDate = today.toISOString().split('T')[0]; // Format: YYYY-MM-DD
document.getElementById('dateFilter').value = formattedDate;
selectedDate = formattedDate;
// Initial data load
fetchData(selectedDate);
// Update the last updated time
updateLastUpdated();
// Even though we have meta refresh, we'll also set up a JavaScript timer
// as a backup and to update the "last updated" time without a full page refresh
setInterval(function() {
updateLastUpdated();
}, 500000); // Update the time every 5 seconds
// Add event listener for the date filter button
document.getElementById('applyDateFilter').addEventListener('click', function() {
const dateInput = document.getElementById('dateFilter');
selectedDate = dateInput.value;
fetchData(selectedDate);
});
// Add event listeners for status filter checkboxes
document.getElementById('redStatusFilter').addEventListener('change', function() {
showRedOnly = this.checked;
if (showRedOnly) {
// If red is checked, uncheck yellow
document.getElementById('yellowStatusFilter').checked = false;
showYellowOnly = false;
}
// Update the table with the current filter settings
updateTable(originalData);
});
document.getElementById('yellowStatusFilter').addEventListener('change', function() {
showYellowOnly = this.checked;
if (showYellowOnly) {
// If yellow is checked, uncheck red
document.getElementById('redStatusFilter').checked = false;
showRedOnly = false;
}
// Update the table with the current filter settings
updateTable(originalData);
});
// Add event listener for text filter input
document.getElementById('textFilter').addEventListener('input', function() {
textFilterValue = this.value.toLowerCase().trim();
// Update the table with the current filter settings
updateTable(originalData);
});
// Initialize the chart modal event
const chartModal = document.getElementById('chartModal');
chartModal.addEventListener('hidden.bs.modal', function () {
// Destroy the chart when the modal is closed to prevent memory leaks
if (historyChart) {
historyChart.destroy();
historyChart = null;
}
});
});
/**
* Fetch monitoring data from the API
* @param {string} date - The date to filter data (format: YYYY-MM-DD)
*/
function fetchData(date) {
const loadingMessage = document.getElementById('loading-message');
const errorMessage = document.getElementById('error-message');
// Show loading message
loadingMessage.classList.remove('d-none');
errorMessage.classList.add('d-none');
// Build the URL with date parameter if provided
let url = BASE_URL + '/api/data';
if (date) {
url += `?date=${encodeURIComponent(date)}`;
}
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
// Hide loading message
loadingMessage.classList.add('d-none');
// Store the original data
originalData = data;
// Update the table with the data
updateTable(data);
// Update last updated time
updateLastUpdated();
})
.catch(error => {
// Hide loading message and show error message
loadingMessage.classList.add('d-none');
errorMessage.classList.remove('d-none');
console.error('Error fetching data:', error);
});
}
/**
* Update the monitoring data with equipment groups
*/
function updateTable(data) {
const equipmentGroupsContainer = document.getElementById('equipment-groups');
// Clear existing content
equipmentGroupsContainer.innerHTML = '';
if (data.length === 0) {
equipmentGroupsContainer.innerHTML = `<div class="alert alert-info">No monitoring data available</div>`;
updateStatusCounts(0, 0);
return;
}
const tableResponsive = document.createElement('div');
tableResponsive.className = 'table-responsive';
const table = document.createElement('table');
table.className = 'table table-hover';
const tableHeader = document.createElement('thead');
tableHeader.innerHTML = `
<tr>
<th>Label</th>
<th>Status</th>
<th>Latest Value</th>
<th>Latest Timestamp</th>
<th>Messages</th>
</tr>
`;
const tableBody = document.createElement('tbody');
let totalParts = 0;
let redStatusCount = 0;
let yellowStatusCount = 0;
data.forEach(equipment => {
// Filter parts based on status filter checkboxes and text filter
let filteredParts = equipment.parts;
if (showRedOnly) {
filteredParts = filteredParts.filter(part => part.status === 'red');
} else if (showYellowOnly) {
filteredParts = filteredParts.filter(part => part.status === 'yellow');
}
// Apply text filter if there's any text in the input
if (textFilterValue) {
filteredParts = filteredParts.filter(part => {
// Check if any of the part's text content matches the filter
const label = part.label ? part.label.toLowerCase() : '';
const value = part.latest_value !== null ? String(part.latest_value).toLowerCase() : '';
const messages = part.messages ? part.messages.join(' ').toLowerCase() : '';
return label.includes(textFilterValue) ||
value.includes(textFilterValue) ||
messages.includes(textFilterValue) ||
equipment.equipment_name.toLowerCase().includes(textFilterValue);
});
}
if (filteredParts.length === 0) return;
// Insert equipment name as a row
const equipmentRow = document.createElement('tr');
equipmentRow.innerHTML = `
<td colspan="5" class="fw-bold bg-light">${equipment.equipment_name}</td>
`;
tableBody.appendChild(equipmentRow);
// Insert part rows
filteredParts.forEach(part => {
const row = document.createElement('tr');
// Count red and yellow statuses
if (part.status === 'red') {
redStatusCount++;
} else if (part.status === 'yellow') {
yellowStatusCount++;
}
let messagesHtml = '';
if (part.messages && part.messages.length > 0) {
messagesHtml = '<ul class="message-list">';
part.messages.forEach(message => {
messagesHtml += `<li>${message}</li>`;
});
messagesHtml += '</ul>';
} else {
messagesHtml = '<span class="text-muted">No issues</span>';
}
const timestamp = part.latest_created_at
? new Date(part.latest_created_at).toLocaleString()
: 'N/A';
row.innerHTML = `
<td><a href="#" class="part-id-link" data-part-id="${part.part_id}">${part.label}</a></td>
<td class="text-center">
<div class="status-cell">
<span class="status-indicator ${part.status} me-2"></span>
</div>
</td>
<td>${part.latest_value !== null ? part.latest_value : 'N/A'}</td>
<td>${timestamp}</td>
<td>${messagesHtml}</td>
`;
tableBody.appendChild(row);
totalParts++;
});
});
if (totalParts === 0) {
equipmentGroupsContainer.innerHTML = `<div class="alert alert-info">No data matching the current filter</div>`;
updateStatusCounts(0, 0);
return;
}
table.appendChild(tableHeader);
table.appendChild(tableBody);
// Create responsive scroll container
const scrollContainer = document.createElement('div');
scrollContainer.className = 'overflow-auto';
// Set different max heights based on screen size
const setResponsiveHeight = () => {
if (window.innerWidth <= 576) {
// Mobile phones
scrollContainer.style.maxHeight = 'calc(100vh - 400px)';
} else if (window.innerWidth <= 768) {
// Tablets
scrollContainer.style.maxHeight = 'calc(100vh - 200px)';
} else {
// Desktops
scrollContainer.style.maxHeight = 'calc(100vh - 200px)';
}
};
// Set initial height
setResponsiveHeight();
// Update height on window resize
window.addEventListener('resize', setResponsiveHeight);
scrollContainer.style.border = '1px solid #dee2e6';
scrollContainer.appendChild(table);
tableResponsive.appendChild(scrollContainer);
equipmentGroupsContainer.appendChild(tableResponsive);
// Update the status counts display
updateStatusCounts(redStatusCount, yellowStatusCount);
// Setup chart links
document.querySelectorAll('.part-id-link').forEach(link => {
link.addEventListener('click', function (e) {
e.preventDefault();
const partId = this.getAttribute('data-part-id');
showPartHistoryChart(partId);
});
});
}
/**
* Update the last updated time
*/
function updateLastUpdated() {
const lastUpdatedElement = document.getElementById('last-updated');
const now = new Date();
// lastUpdatedElement.textContent = now.toLocaleString();
}
/**
* Update the status counts display
* @param {number} redCount - Number of red status items
* @param {number} yellowCount - Number of yellow status items
*/
function updateStatusCounts(redCount, yellowCount) {
const redStatusCountElement = document.getElementById('red-status-count');
const yellowStatusCountElement = document.getElementById('yellow-status-count');
redStatusCountElement.textContent = redCount;
yellowStatusCountElement.textContent = yellowCount;
}
/**
* Fetch historical data for a specific part_id
*/
function fetchPartHistory(partId) {
return fetch(`${BASE_URL}/api/part-history?part_id=${encodeURIComponent(partId)}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.catch(error => {
console.error('Error fetching part history:', error);
return [];
});
}
/**
* Display the chart for a specific part_id
*/
function showPartHistoryChart(partId) {
// Show the modal
const chartModal = new bootstrap.Modal(document.getElementById('chartModal'));
chartModal.show();
// Fetch the historical data
fetchPartHistory(partId).then(result => {
// Update the modal title with part label if available
const label = result.label || partId;
document.getElementById('modal-part-id').textContent = `${partId} (${label})`;
const historyData = result.history || [];
if (historyData.length === 0) {
// Handle empty data
document.getElementById('historyChart').innerHTML = 'No historical data available for this part.';
return;
}
// Prepare the data for the chart
const chartLabels = [];
const chartValues = [];
historyData.forEach(item => {
// Format the date for display
const date = new Date(item.created_at);
chartLabels.push(date.toLocaleString());
chartValues.push(item.value);
});
// Create the chart
const ctx = document.getElementById('historyChart').getContext('2d');
// Destroy existing chart if it exists
if (historyChart) {
historyChart.destroy();
}
// Create new chart
historyChart = new Chart(ctx, {
type: 'line',
data: {
labels: chartLabels,
datasets: [{
label: `Values for ${label}`,
data: chartValues,
borderColor: '#1CA3B7',
tension: 0.1,
fill: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
title: {
display: true,
text: 'Date and Time'
}
},
y: {
title: {
display: true,
text: 'Value'
}
}
}
}
});
});
}