// 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 = '/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 = `
No monitoring data available
`; 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 = ` Label Status Latest Value Latest Timestamp Messages `; 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 = ` ${equipment.equipment_name} `; 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 = ''; } else { messagesHtml = 'No issues'; } const timestamp = part.latest_created_at ? new Date(part.latest_created_at).toLocaleString() : 'N/A'; row.innerHTML = ` ${part.label}
${part.latest_value !== null ? part.latest_value : 'N/A'} ${timestamp} ${messagesHtml} `; tableBody.appendChild(row); totalParts++; }); }); if (totalParts === 0) { equipmentGroupsContainer.innerHTML = `
No data matching the current filter
`; 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(`/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' } } } } }); }); }