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;
+}