// filepath: /home/adlsdztony/codes/OSWorld/monitor/static/index.js document.addEventListener('DOMContentLoaded', () => { fetchTasks(); // 筛选功能绑定 document.getElementById('total-tasks').parentElement.addEventListener('click', () => setTaskFilter('all')); document.getElementById('active-tasks').parentElement.addEventListener('click', () => setTaskFilter('active')); document.getElementById('completed-tasks').parentElement.addEventListener('click', () => setTaskFilter('completed')); document.getElementById('error-tasks').parentElement.addEventListener('click', () => setTaskFilter('error')); }); let allTaskData = null; let currentFilter = 'all'; function refreshPage() { // Save expanded state before refresh const expandedTaskTypes = []; document.querySelectorAll('.task-type').forEach(section => { if (!section.classList.contains('collapsed')) { const typeName = section.querySelector('.task-type-name').textContent.trim(); expandedTaskTypes.push(typeName); } }); // Store in sessionStorage sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes)); fetchTasks(); } function fetchTasks() { fetch('/api/tasks') .then(response => response.json()) .then(data => { allTaskData = data; renderTasks(data); updateStatistics(data); }) .catch(error => console.error('Error fetching tasks:', error)); } function setTaskFilter(filter) { currentFilter = filter; if (!allTaskData) return; renderTasks(allTaskData); // 高亮选中卡片 document.querySelectorAll('.stat-card').forEach(card => card.classList.remove('selected')); if (filter === 'all') { document.getElementById('total-tasks').parentElement.classList.add('selected'); } else if (filter === 'active') { document.getElementById('active-tasks').parentElement.classList.add('selected'); } else if (filter === 'completed') { document.getElementById('completed-tasks').parentElement.classList.add('selected'); } else if (filter === 'error') { document.getElementById('error-tasks').parentElement.classList.add('selected'); } } // 更新统计信息 function updateStatistics(data) { let totalTasks = 0; let activeTasks = 0; let completedTasks = 0; let errorTasks = 0; Object.entries(data).forEach(([taskType, tasks]) => { totalTasks += tasks.length; tasks.forEach(task => { if (task.status.status === 'Running' || task.status.status === 'Preparing' || task.status.status === 'Initializing') { activeTasks++; } else if (task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)') { completedTasks++; } else if (task.status.status === 'Error') { errorTasks++; } }); }); document.getElementById('total-tasks').textContent = totalTasks; document.getElementById('active-tasks').textContent = activeTasks; document.getElementById('completed-tasks').textContent = completedTasks; document.getElementById('error-tasks').textContent = errorTasks; // 高亮显示当前选中的统计卡片 document.querySelectorAll('.stat-card').forEach(card => card.classList.remove('selected')); if (currentFilter === 'all') { document.getElementById('total-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'active') { document.getElementById('active-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'completed') { document.getElementById('completed-tasks').parentElement.classList.add('selected'); } else if (currentFilter === 'error') { document.getElementById('error-tasks').parentElement.classList.add('selected'); } } function renderTasks(data) { const container = document.getElementById('task-container'); container.innerHTML = ''; let filteredData = {}; if (currentFilter === 'all') { filteredData = data; } else { Object.entries(data).forEach(([taskType, tasks]) => { let filteredTasks = []; if (currentFilter === 'active') { filteredTasks = tasks.filter(task => ['Running', 'Preparing', 'Initializing'].includes(task.status.status)); } else if (currentFilter === 'completed') { filteredTasks = tasks.filter(task => task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)'); } else if (currentFilter === 'error') { filteredTasks = tasks.filter(task => task.status.status === 'Error'); } if (filteredTasks.length > 0) { filteredData[taskType] = filteredTasks; } }); } if (Object.keys(filteredData).length === 0) { container.innerHTML = '
No tasks at the moment
'; return; } Object.entries(filteredData).forEach(([taskType, tasks]) => { // Calculate task statistics for this type let runningCount = 0; let completedCount = 0; let errorCount = 0; tasks.forEach(task => { if (task.status.status === 'Running' || task.status.status === 'Preparing' || task.status.status === 'Initializing') { runningCount++; } else if (task.status.status === 'Done' || task.status.status === 'Done (Message Exit)' || task.status.status === 'Done (Max Steps)') { completedCount++; } else if (task.status.status === 'Error') { errorCount++; } }); // Create the task type card const typeSection = document.createElement('div'); typeSection.className = 'task-type'; // Create header with task type name and statistics const typeHeader = document.createElement('div'); typeHeader.className = 'task-type-header'; typeHeader.innerHTML = ` ${taskType}
${errorCount > 0 ? ` ${errorCount} error` : ''} ${tasks.length} total ${runningCount} active ${completedCount} completed
`; typeSection.appendChild(typeHeader); // Create container for task cards const tasksContainer = document.createElement('div'); tasksContainer.className = 'tasks-container'; // Set default collapsed state typeSection.classList.add('collapsed'); tasksContainer.setAttribute('aria-hidden', 'true'); if (tasks.length === 0) { const noTasks = document.createElement('div'); noTasks.className = 'no-tasks'; noTasks.innerHTML = ' No Tasks Available'; tasksContainer.appendChild(noTasks); } else { // Add scrolling for large task lists if (tasks.length > 10) { tasksContainer.style.maxHeight = '600px'; tasksContainer.style.overflowY = 'auto'; } tasks.forEach(task => { const taskCard = document.createElement('div'); taskCard.className = 'task-card'; const taskHeader = document.createElement('div'); taskHeader.className = 'task-header'; const taskTitle = document.createElement('div'); taskTitle.className = 'task-title'; taskTitle.innerHTML = ` Task ID: ${task.id}`; taskHeader.appendChild(taskTitle); const taskStatus = document.createElement('div'); taskStatus.className = 'task-status'; let statusClass = ''; let statusIcon = ''; switch(task.status.status) { case 'Not Started': statusClass = 'status-not-started'; statusIcon = 'fa-hourglass-start'; break; case 'Preparing': case 'Initializing': statusClass = 'status-preparing'; statusIcon = 'fa-spinner fa-pulse'; break; case 'Running': statusClass = 'status-running'; statusIcon = 'fa-running'; break; case 'Done': case 'Done (Message Exit)': case 'Done (Max Steps)': statusClass = 'status-completed'; statusIcon = 'fa-check-circle'; break; case 'Error': statusClass = 'status-error'; statusIcon = 'fa-exclamation-circle'; break; default: statusClass = 'status-unknown'; statusIcon = 'fa-question-circle'; break; } taskStatus.classList.add(statusClass); taskStatus.innerHTML = ` ${task.status.status}`; taskHeader.appendChild(taskStatus); taskCard.appendChild(taskHeader); const taskInstruction = document.createElement('div'); taskInstruction.className = 'task-instruction'; taskInstruction.innerHTML = ` Instruction: ${task.instruction}`; taskCard.appendChild(taskInstruction); const taskProgress = document.createElement('div'); taskProgress.className = 'task-details'; if (task.status.progress > 0) { const progressText = document.createElement('div'); progressText.innerHTML = ` Progress: ${task.status.progress}/${task.status.max_steps} step(s)`; taskProgress.appendChild(progressText); const progressBar = document.createElement('div'); progressBar.className = 'progress-bar'; const progressFill = document.createElement('div'); progressFill.className = 'progress-fill'; const percentage = (task.status.progress / task.status.max_steps) * 100; progressFill.style.width = `${percentage}%`; progressBar.appendChild(progressFill); taskProgress.appendChild(progressBar); const progressPercentage = document.createElement('div'); progressPercentage.className = 'progress-percentage'; progressPercentage.textContent = `${Math.round(percentage)}%`; taskProgress.appendChild(progressPercentage); } if (task.status.last_update) { const timestamp = document.createElement('div'); timestamp.className = 'timestamp'; timestamp.innerHTML = ` Last Update: ${task.status.last_update}`; taskProgress.appendChild(timestamp); } if (task.status.result) { const resultDiv = document.createElement('div'); resultDiv.className = 'task-result'; resultDiv.innerHTML = ` Result: ${task.status.result}`; taskProgress.appendChild(resultDiv); } taskCard.appendChild(taskProgress); if (task.status.status !== 'Not Started') { taskCard.style.cursor = 'pointer'; taskCard.addEventListener('click', () => { window.location.href = `/task/${taskType}/${task.id}`; }); } tasksContainer.appendChild(taskCard); }); } typeSection.appendChild(tasksContainer); // Toggle collapse when clicking on the header typeHeader.addEventListener('click', (event) => { // Prevent toggling when clicking task cards if (!event.target.closest('.task-card')) { typeSection.classList.toggle('collapsed'); // Set appropriate aria attributes for accessibility const isCollapsed = typeSection.classList.contains('collapsed'); tasksContainer.setAttribute('aria-hidden', isCollapsed); // Update session storage with current expanded state const expandedTaskTypes = []; document.querySelectorAll('.task-type').forEach(section => { if (!section.classList.contains('collapsed')) { const typeName = section.querySelector('.task-type-name').textContent.trim(); expandedTaskTypes.push(typeName); } }); sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes)); } }); // Check if this task type was expanded before refresh const expandedTaskTypes = JSON.parse(sessionStorage.getItem('expandedTaskTypes') || '[]'); if (expandedTaskTypes.includes(taskType)) { typeSection.classList.remove('collapsed'); tasksContainer.setAttribute('aria-hidden', 'false'); } container.appendChild(typeSection); }); } // add auto-refresh with time interval 10 seconds setInterval(() => { refreshPage(); }, 10000); // 10 seconds interval