diff --git a/monitor/static/index.js b/monitor/static/index.js index f564f03..6928db1 100644 --- a/monitor/static/index.js +++ b/monitor/static/index.js @@ -10,8 +10,33 @@ document.addEventListener('DOMContentLoaded', () => { let allTaskData = null; let currentFilter = 'all'; +let isRefreshing = false; // Flag to indicate page refresh state +let lastRefreshTime = 0; // Track last refresh time to prevent too frequent refreshes +const REFRESH_COOLDOWN = 3000; // 3 seconds cooldown between refreshes function refreshPage() { + // Check if enough time has passed since last refresh + const now = Date.now(); + if (isRefreshing || now - lastRefreshTime < REFRESH_COOLDOWN) { + console.log('Refresh cooldown in progress...'); + return; + } + + lastRefreshTime = now; + + // Add refresh animation to button + const refreshBtn = document.querySelector('.fab-refresh i'); + if (refreshBtn) { + refreshBtn.classList.add('fa-spin'); + // Remove spin after 1 second + setTimeout(() => { + refreshBtn.classList.remove('fa-spin'); + }, 1000); + } + + // Add no-transition to body during refresh + document.body.classList.add('no-transition'); + // Save expanded state before refresh const expandedTaskTypes = []; document.querySelectorAll('.task-type').forEach(section => { @@ -24,18 +49,64 @@ function refreshPage() { // Store in sessionStorage sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes)); + // Set refreshing flag to true before fetching data + isRefreshing = true; fetchTasks(); } function fetchTasks() { + const currentTime = Date.now(); + // Check if the last refresh was within the cooldown period + if (currentTime - lastRefreshTime < REFRESH_COOLDOWN) { + // If within cooldown, just reset the refreshing flag and return + isRefreshing = false; + return; + } + + // Show loading spinner or add refreshing class + const container = document.getElementById('task-container'); + if (isRefreshing) { + // Don't show loading spinner on refresh to avoid flickering + container.classList.add('refreshing'); + } else { + container.innerHTML = ` +
+
+
Loading task data...
+
+ `; + } + fetch('/api/tasks') .then(response => response.json()) .then(data => { allTaskData = data; renderTasks(data); updateStatistics(data); + // Reset refreshing flag and classes after rendering + container.classList.remove('refreshing'); + setTimeout(() => { + isRefreshing = false; + document.body.classList.remove('no-transition'); + document.querySelectorAll('.no-transition').forEach(el => { + el.classList.remove('no-transition'); + }); + }, 50); + // Update the last refresh time + lastRefreshTime = currentTime; }) - .catch(error => console.error('Error fetching tasks:', error)); + .catch(error => { + console.error('Error fetching tasks:', error); + container.innerHTML = ` +
+ +
Error loading task data. Please try again.
+
+ `; + isRefreshing = false; + container.classList.remove('refreshing'); + document.body.classList.remove('no-transition'); + }); } function setTaskFilter(filter) { @@ -139,6 +210,11 @@ function renderTasks(data) { const typeSection = document.createElement('div'); typeSection.className = 'task-type'; + // Add no-transition class if page is refreshing + if (isRefreshing) { + typeSection.classList.add('no-transition'); + } + // Create header with task type name and statistics const typeHeader = document.createElement('div'); typeHeader.className = 'task-type-header'; @@ -284,6 +360,8 @@ function renderTasks(data) { typeHeader.addEventListener('click', (event) => { // Prevent toggling when clicking task cards if (!event.target.closest('.task-card')) { + // Remove no-transition class if it exists before toggling + typeSection.classList.remove('no-transition'); typeSection.classList.toggle('collapsed'); // Set appropriate aria attributes for accessibility @@ -311,4 +389,20 @@ function renderTasks(data) { container.appendChild(typeSection); }); + + // Remove no-transition class after rendering all elements + if (isRefreshing) { + setTimeout(() => { + document.querySelectorAll('.no-transition').forEach(el => { + el.classList.remove('no-transition'); + }); + }, 100); + } } + +// add auto-refresh with time interval 10 seconds +setInterval(() => { + if (!isRefreshing) { + refreshPage(); + } +}, 10000); // 10 seconds interval diff --git a/monitor/static/no-transition.css b/monitor/static/no-transition.css new file mode 100644 index 0000000..fc839c1 --- /dev/null +++ b/monitor/static/no-transition.css @@ -0,0 +1,59 @@ +/* No transition class to disable animations */ +.no-transition, +.no-transition * { + -webkit-transition: none !important; + -moz-transition: none !important; + -ms-transition: none !important; + -o-transition: none !important; + transition: none !important; + animation: none !important; +} + +/* Immediate display changes for elements with no-transition */ +.task-type.no-transition.collapsed .tasks-container { + display: none; + max-height: 0 !important; + opacity: 0; + padding: 0; + margin: 0; + overflow: hidden; +} + +.task-type.no-transition:not(.collapsed) .tasks-container { + display: block; + max-height: none !important; + opacity: 1; + overflow: visible; +} + +/* Styles for refreshing state */ +.refreshing .tasks-container { + pointer-events: none; /* Prevent interactions during refresh */ +} + +.refreshing .task-card { + opacity: 0.7; /* Dim the cards during refresh */ +} + +/* Override progress bar animation during page refresh */ +.no-transition .progress-fill, +.refreshing .progress-fill { + transition: none !important; +} + +/* Error message styling */ +.error-message { + text-align: center; + padding: 30px; + color: #e74c3c; + font-size: 1.1em; + background: #fef2f2; + border-radius: 8px; + margin: 20px 0; + box-shadow: 0 2px 10px rgba(0,0,0,0.05); +} + +.error-message i { + font-size: 2em; + margin-bottom: 15px; +}