151 lines
4.8 KiB
JavaScript
151 lines
4.8 KiB
JavaScript
import { useState, useEffect } from 'react';
|
|
import { jobs } from '../utils/api';
|
|
import JobDetails from './JobDetails';
|
|
|
|
export default function JobList() {
|
|
const [jobList, setJobList] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [selectedJob, setSelectedJob] = useState(null);
|
|
|
|
useEffect(() => {
|
|
loadJobs();
|
|
const interval = setInterval(loadJobs, 5000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const loadJobs = async () => {
|
|
try {
|
|
const data = await jobs.list();
|
|
setJobList(data);
|
|
} catch (error) {
|
|
console.error('Failed to load jobs:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleCancel = async (jobId) => {
|
|
if (!confirm('Are you sure you want to cancel this job?')) return;
|
|
try {
|
|
await jobs.cancel(jobId);
|
|
loadJobs();
|
|
} catch (error) {
|
|
alert('Failed to cancel job: ' + error.message);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (jobId) => {
|
|
if (!confirm('Are you sure you want to permanently delete this job? This action cannot be undone.')) return;
|
|
try {
|
|
await jobs.delete(jobId);
|
|
loadJobs();
|
|
if (selectedJob && selectedJob.id === jobId) {
|
|
setSelectedJob(null);
|
|
}
|
|
} catch (error) {
|
|
alert('Failed to delete job: ' + error.message);
|
|
}
|
|
};
|
|
|
|
const getStatusColor = (status) => {
|
|
const colors = {
|
|
pending: 'bg-yellow-400/20 text-yellow-400',
|
|
running: 'bg-orange-400/20 text-orange-400',
|
|
completed: 'bg-green-400/20 text-green-400',
|
|
failed: 'bg-red-400/20 text-red-400',
|
|
cancelled: 'bg-gray-500/20 text-gray-400',
|
|
};
|
|
return colors[status] || colors.pending;
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex justify-center items-center h-64">
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-orange-500"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (jobList.length === 0) {
|
|
return (
|
|
<div className="text-center py-12">
|
|
<p className="text-gray-400 text-lg">No jobs yet. Submit a job to get started!</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
{jobList.map((job) => (
|
|
<div
|
|
key={job.id}
|
|
className="bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition-shadow p-6 border-l-4 border-orange-500 border border-gray-700"
|
|
>
|
|
<div className="flex justify-between items-start mb-4">
|
|
<h3 className="text-xl font-semibold text-gray-100">{job.name}</h3>
|
|
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(job.status)}`}>
|
|
{job.status}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="space-y-2 text-sm text-gray-400 mb-4">
|
|
<p>Frames: {job.frame_start} - {job.frame_end}</p>
|
|
<p>Format: {job.output_format}</p>
|
|
<p>Created: {new Date(job.created_at).toLocaleString()}</p>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<div className="flex justify-between text-xs text-gray-400 mb-1">
|
|
<span>Progress</span>
|
|
<span>{job.progress.toFixed(1)}%</span>
|
|
</div>
|
|
<div className="w-full bg-gray-700 rounded-full h-2">
|
|
<div
|
|
className="bg-orange-500 h-2 rounded-full transition-all duration-300"
|
|
style={{ width: `${job.progress}%` }}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => setSelectedJob(job)}
|
|
className="flex-1 px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-500 transition-colors font-medium"
|
|
>
|
|
View Details
|
|
</button>
|
|
{(job.status === 'pending' || job.status === 'running') && (
|
|
<button
|
|
onClick={() => handleCancel(job.id)}
|
|
className="px-4 py-2 bg-gray-700 text-gray-200 rounded-lg hover:bg-gray-600 transition-colors font-medium"
|
|
>
|
|
Cancel
|
|
</button>
|
|
)}
|
|
{(job.status === 'completed' || job.status === 'failed' || job.status === 'cancelled') && (
|
|
<button
|
|
onClick={() => handleDelete(job.id)}
|
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-500 transition-colors font-medium"
|
|
title="Delete job"
|
|
>
|
|
Delete
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{selectedJob && (
|
|
<JobDetails
|
|
job={selectedJob}
|
|
onClose={() => setSelectedJob(null)}
|
|
onUpdate={loadJobs}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|