"use client"; import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Header } from "@/components/header"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Skeleton } from "@/components/ui/skeleton"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { BriefcaseBusiness, Clock, PlayCircle, CheckCircle2, XCircle, ExternalLink, AlertCircle, } from "lucide-react"; import { ListProjects, ListJobs, Job } from "@/lib/types"; import { format } from "date-fns"; export default function JobsPage() { const queryClient = useQueryClient(); const [selectedProject, setSelectedProject] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [cancelDialogOpen, setCancelDialogOpen] = useState(false); const [selectedJob, setSelectedJob] = useState(null); // Fetch projects const { data: projects, isLoading: isProjectsLoading } = useQuery({ queryKey: ["projects"], queryFn: async (): Promise => { const res = await fetch("/api/scrapyd/projects"); if (!res.ok) throw new Error("Failed to fetch projects"); return res.json(); }, }); // Fetch jobs for selected project const { data: jobs, isLoading: isJobsLoading } = useQuery({ queryKey: ["jobs", selectedProject], queryFn: async (): Promise => { const res = await fetch(`/api/scrapyd/jobs?project=${selectedProject}`); if (!res.ok) throw new Error("Failed to fetch jobs"); return res.json(); }, enabled: !!selectedProject, refetchInterval: 5000, // Refresh every 5 seconds for real-time updates }); // Cancel job mutation const cancelJobMutation = useMutation({ mutationFn: async (data: { project: string; job: string }) => { const res = await fetch("/api/scrapyd/jobs", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!res.ok) throw new Error("Failed to cancel job"); return res.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["jobs"] }); queryClient.invalidateQueries({ queryKey: ["daemon-status"] }); setCancelDialogOpen(false); setSelectedJob(null); }, }); const handleCancelJob = () => { if (selectedJob) { cancelJobMutation.mutate({ project: selectedProject, job: selectedJob.id, }); } }; // Combine and filter jobs const allJobs: Array = []; if (jobs) { allJobs.push(...jobs.pending.map((j) => ({ ...j, status: "pending" }))); allJobs.push(...jobs.running.map((j) => ({ ...j, status: "running" }))); allJobs.push(...jobs.finished.map((j) => ({ ...j, status: "finished" }))); } const filteredJobs = statusFilter === "all" ? allJobs : allJobs.filter((j) => j.status === statusFilter); const getStatusBadge = (status: string) => { switch (status) { case "pending": return ( Pending ); case "running": return ( Running ); case "finished": return ( Finished ); default: return {status}; } }; return (
{/* Filters */}
Project {isProjectsLoading ? ( ) : ( )} Status Filter
{/* Jobs Statistics */} {selectedProject && jobs && (
Pending
{jobs.pending.length}
Running
{jobs.running.length}
Finished
{jobs.finished.length}
)} {/* Jobs Table */} {selectedProject && ( Jobs for "{selectedProject}" {isJobsLoading ? (
{[1, 2, 3].map((i) => ( ))}
) : filteredJobs.length > 0 ? (
Job ID Spider Status Start Time PID Actions {filteredJobs.map((job) => ( {job.id.substring(0, 8)}...
{job.spider}
{getStatusBadge(job.status)} {format(new Date(job.start_time), "PPp")} {job.pid ? ( {job.pid} ) : ( - )}
{job.log_url && ( )} {(job.status === "pending" || job.status === "running") && ( )}
))}
) : (

No jobs found

{statusFilter === "all" ? "No jobs have been scheduled for this project" : `No ${statusFilter} jobs found`}

)}
)} {!selectedProject && !isProjectsLoading && (

Select a project

Choose a project to view its jobs

)} {/* Cancel Job Dialog */} Cancel Job Are you sure you want to cancel this job? {selectedJob && (

Spider: {selectedJob.spider}

Job ID: {selectedJob.id}

)}
); }