diff --git a/frontend/src/pages/Logs.js b/frontend/src/pages/Logs.js
index f42d827..bdc91ba 100644
--- a/frontend/src/pages/Logs.js
+++ b/frontend/src/pages/Logs.js
@@ -296,13 +296,14 @@ const Logs = () => {
const dashboardData = await taskService.getDashboardData();
console.log('Dashboard data for logs:', dashboardData);
- console.log('Submitted tasks found:', dashboardData.submitted_tasks?.length || 0);
+ console.log('Tasks found:', dashboardData.tasks?.length || 0);
- if (Array.isArray(dashboardData.submitted_tasks)) {
- for (const task of dashboardData.submitted_tasks.slice(-10)) {
+ if (Array.isArray(dashboardData.tasks)) {
+ for (const task of dashboardData.tasks.slice(-20)) { // Get last 20 tasks
try {
- console.log('Loading log for task:', task.task_id, 'from:', task.tes_url);
- const logResponse = await logService.getTaskLogs(task.task_id, task.tes_url);
+ console.log('Loading log for task:', task.task_id || task.id, 'from:', task.tes_url);
+ const taskId = task.task_id || task.id;
+ const logResponse = await logService.getTaskLogs(taskId, task.tes_url);
let content = 'No log content available';
console.log('Task log response:', logResponse);
@@ -312,30 +313,31 @@ const Logs = () => {
} else if (logResponse && logResponse.log) {
content = logResponse.log;
} else {
- content = `Task Log for ${task.task_id}\n\nTask Details:\n- Status: ${task.status}\n- TES Instance: ${task.tes_name}\n- Submitted: ${task.submitted_at}\n- Type: ${task.type || 'Unknown'}\n\nNote: Full log details not available (task may still be running)`;
+ content = `Task Log for ${taskId}\n\nTask Details:\n- Name: ${task.name || task.task_name || 'Unnamed'}\n- Status: ${task.status || task.state}\n- TES Instance: ${task.tes_name || 'Unknown'}\n- Submitted: ${task.submitted_at || task.creation_time}\n- Type: ${task.type || 'Task Submission'}\n\nNote: Full log details not available (task may still be running or logs not yet generated)`;
}
allLogs.push({
- id: task.task_id,
+ id: taskId,
type: 'task',
- title: `Task ${task.task_id} (${task.tes_name})`,
+ title: `Task ${taskId} (${task.tes_name || 'Unknown'})`,
content: content,
- timestamp: task.submitted_at || new Date().toISOString(),
+ timestamp: task.submitted_at || task.creation_time || new Date().toISOString(),
metadata: {
- status: task.status,
+ status: task.status || task.state,
tesInstance: task.tes_name || 'Unknown'
}
});
} catch (err) {
- console.error('Error loading task log for', task.task_id, ':', err);
+ console.error('Error loading task log for', task.task_id || task.id, ':', err);
+ const taskId = task.task_id || task.id;
allLogs.push({
- id: task.task_id,
+ id: taskId,
type: 'task',
- title: `Task ${task.task_id} (${task.tes_name})`,
- content: `Task Log for ${task.task_id}\n\nError: ${err.message}\n\nTask Details:\n- Status: ${task.status}\n- TES Instance: ${task.tes_name}\n- Submitted: ${task.submitted_at}\n- Type: ${task.type || 'Unknown'}\n\nNote: This task exists but logs couldn't be fetched.`,
- timestamp: task.submitted_at || new Date().toISOString(),
+ title: `Task ${taskId} (${task.tes_name || 'Unknown'})`,
+ content: `Task Log for ${taskId}\n\nError: ${err.message}\n\nTask Details:\n- Name: ${task.name || task.task_name || 'Unnamed'}\n- Status: ${task.status || task.state}\n- TES Instance: ${task.tes_name || 'Unknown'}\n- Submitted: ${task.submitted_at || task.creation_time}\n- Type: ${task.type || 'Task Submission'}\n\nNote: This task exists but logs couldn't be fetched.`,
+ timestamp: task.submitted_at || task.creation_time || new Date().toISOString(),
metadata: {
- status: task.status,
+ status: task.status || task.state,
tesInstance: task.tes_name || 'Unknown'
}
});
diff --git a/frontend/src/pages/Tasks.js b/frontend/src/pages/Tasks.js
index f921d76..779290a 100644
--- a/frontend/src/pages/Tasks.js
+++ b/frontend/src/pages/Tasks.js
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { taskService } from '../services/taskService';
@@ -13,7 +13,9 @@ import {
Plus,
Eye,
FileText,
- Search
+ Search,
+ ChevronUp,
+ ChevronDown
} from 'lucide-react';
const PageContainer = styled.div`
@@ -133,6 +135,21 @@ const TableHeader = styled.th`
font-weight: 600;
color: #495057;
font-size: 14px;
+ cursor: ${props => props.sortable ? 'pointer' : 'default'};
+ user-select: none;
+ position: relative;
+
+ &:hover {
+ background: ${props => props.sortable ? '#e9ecef' : '#f8f9fa'};
+ }
+`;
+
+const SortIndicator = styled.span`
+ display: inline-flex;
+ align-items: center;
+ margin-left: 4px;
+ opacity: ${props => props.active ? '1' : '0.3'};
+ transition: opacity 0.2s;
`;
const TableRow = styled.tr`
@@ -193,6 +210,8 @@ const Tasks = () => {
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState('');
const [filteredTasks, setFilteredTasks] = useState([]);
+ const [sortColumn, setSortColumn] = useState('creation_time');
+ const [sortDirection, setSortDirection] = useState('desc'); // 'asc' or 'desc'
const {
data: tasksData,
@@ -201,6 +220,74 @@ const Tasks = () => {
refetch
} = usePolling(taskService.listTasks, POLLING_INTERVALS.NORMAL);
+ const handleSort = (column) => {
+ if (sortColumn === column) {
+ setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc');
+ } else {
+ // Set new column with ascending as default
+ setSortColumn(column);
+ setSortDirection('asc');
+ }
+ };
+
+ const getSortIndicator = (column) => {
+ if (sortColumn !== column) {
+ return (
+
+
+
+ );
+ }
+ return (
+
+ {sortDirection === 'asc' ? : }
+
+ );
+ };
+
+ const sortTasks = useCallback((tasks) => {
+ const sorted = [...tasks].sort((a, b) => {
+ let aValue, bValue;
+
+ switch (sortColumn) {
+ case 'id':
+ aValue = a.id || '';
+ bValue = b.id || '';
+ break;
+ case 'name':
+ aValue = (a.name || '').toLowerCase();
+ bValue = (b.name || '').toLowerCase();
+ break;
+ case 'state':
+ aValue = a.state || '';
+ bValue = b.state || '';
+ break;
+ case 'tes_name':
+ aValue = (a.tes_name || a.tes_url || '').toLowerCase();
+ bValue = (b.tes_name || b.tes_url || '').toLowerCase();
+ break;
+ case 'creation_time':
+ // Parse dates for comparison
+ aValue = a.creation_time ? new Date(a.creation_time).getTime() : 0;
+ bValue = b.creation_time ? new Date(b.creation_time).getTime() : 0;
+ break;
+ default:
+ return 0;
+ }
+
+ // Handle comparison
+ if (aValue < bValue) {
+ return sortDirection === 'asc' ? -1 : 1;
+ }
+ if (aValue > bValue) {
+ return sortDirection === 'asc' ? 1 : -1;
+ }
+ return 0;
+ });
+
+ return sorted;
+ }, [sortColumn, sortDirection]);
+
const handleCancelTask = async (tesUrl, taskId) => {
if (window.confirm('Are you sure you want to cancel this task?')) {
try {
@@ -238,19 +325,22 @@ const Tasks = () => {
!task.error_prone_instance;
});
- if (!searchTerm) {
- setFilteredTasks(healthyTasks);
- } else {
- const filtered = healthyTasks.filter(task =>
+ // Apply search filter
+ let tasksToDisplay = healthyTasks;
+ if (searchTerm) {
+ tasksToDisplay = healthyTasks.filter(task =>
task.id?.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.state?.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.tes_url?.toLowerCase().includes(searchTerm.toLowerCase()) ||
task.tes_name?.toLowerCase().includes(searchTerm.toLowerCase())
);
- setFilteredTasks(filtered);
}
- }, [tasksData, searchTerm]);
+
+ // Apply sorting
+ const sortedTasks = sortTasks(tasksToDisplay);
+ setFilteredTasks(sortedTasks);
+ }, [tasksData, searchTerm, sortTasks]);
return (
@@ -306,11 +396,26 @@ const Tasks = () => {
- Task ID
- Name
- Status
- TES Instance
- Created
+ handleSort('id')}>
+ Task ID
+ {getSortIndicator('id')}
+
+ handleSort('name')}>
+ Name
+ {getSortIndicator('name')}
+
+ handleSort('state')}>
+ Status
+ {getSortIndicator('state')}
+
+ handleSort('tes_name')}>
+ TES Instance
+ {getSortIndicator('tes_name')}
+
+ handleSort('creation_time')}>
+ Created
+ {getSortIndicator('creation_time')}
+
Actions