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
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'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
} |