import type { AdminJob } from "@/api/types"; import { useAllAdminJobs } from "@/hooks/queries/admin/libraries"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { RefreshCw } from "lucide-react"; import { formatJobProgress } from "./adminCatalogMaintenanceFormatters"; const JOB_TYPE_LABELS: Record = { delete_library: "Library Delete", image_cache_cleanup: "Image Cache Cleanup", catalog_export: "Catalog Export", catalog_import: "Catalog Import", item_refresh: "Item Refresh", library_refresh: "Library Refresh", }; function jobTypeLabel(jobType: string) { return JOB_TYPE_LABELS[jobType] ?? jobType; } function statusVariant(status: string) { switch (status) { case "failed": return "destructive" as const; case "completed": return "secondary" as const; case "running": return "default" as const; default: return "outline" as const; } } function jobDescription(job: AdminJob) { const payload = job.request_payload as Record; switch (job.job_type) { case "delete_library": case "image_cache_cleanup": return payload.library_name ? `"${payload.library_name}"` : `Library #${payload.library_id}`; case "item_refresh": case "library_refresh": return payload.library_name ? `"${payload.library_name}"` : payload.library_id ? `Library #${payload.library_id}` : "All libraries"; case "catalog_export": { const ids = payload.library_ids as number[] | undefined; if (ids && ids.length > 0) return `${ids.length} librar${ids.length === 1 ? "y" : "ies"}`; return "All libraries"; } case "catalog_import": return (payload.source_label as string) || (payload.source_key as string) || "Catalog seed"; default: return ""; } } function jobResult(job: AdminJob) { if (job.status !== "completed") return null; const result = job.result_payload as Record; switch (job.job_type) { case "library_refresh": { const total = result.total_items ?? 0; const withIDs = result.items_with_ids ?? 0; const withoutIDs = result.items_without_ids ?? 0; const refreshedOK = result.refreshed_ok ?? 0; const refreshedFailed = result.refreshed_failed ?? 0; const pipelineOK = result.pipeline_ok ?? 0; const pipelineFailed = result.pipeline_failed ?? 0; if (total === 0) { return "No library items to refresh"; } return `Total ${total}, ${withIDs} direct, ${withoutIDs} unmatched, direct ${refreshedOK} ok/${refreshedFailed} failed, pipeline ${pipelineOK} ok/${pipelineFailed} failed`; } case "delete_library": { const files = typeof result.deleted_media_files === "number" ? result.deleted_media_files : 0; const items = typeof result.deleted_orphaned_items === "number" ? result.deleted_orphaned_items : 0; const cleanupQueued = result.image_cleanup_queued === true; const cleanupDirs = typeof result.image_cleanup_dirs === "number" ? result.image_cleanup_dirs : 0; const parts = []; if (files > 0) parts.push(`${files} files`); if (items > 0) parts.push(`${items} items`); if (cleanupQueued && cleanupDirs > 0) { parts.push( `queued cache cleanup for ${cleanupDirs} director${cleanupDirs === 1 ? "y" : "ies"}`, ); } return parts.length > 0 ? `Deleted ${parts.join(", ")}` : "Deleted (empty)"; } case "image_cache_cleanup": { const deletedPrefixes = typeof result.deleted_prefixes === "number" ? result.deleted_prefixes : 0; const deletedObjects = typeof result.deleted_s3_objects === "number" ? result.deleted_s3_objects : 0; return `Deleted ${deletedObjects} cached object${deletedObjects === 1 ? "" : "s"} across ${deletedPrefixes} prefix${deletedPrefixes === 1 ? "" : "es"}`; } case "catalog_export": return typeof result.items_exported === "number" ? `Exported ${result.items_exported} items, ${typeof result.files_exported === "number" ? result.files_exported : 0} files` : null; case "catalog_import": return typeof result.items_created === "number" ? `Imported ${result.items_created} items, ${typeof result.files_created === "number" ? result.files_created : 0} files` : null; default: return null; } } export default function AdminJobHistory() { const jobsQuery = useAllAdminJobs(); const jobs = jobsQuery.data ?? []; return (

Job History

Recent background jobs across all types.

{jobsQuery.isFetching ? ( Refreshing ) : ( {jobs.length} )}
{jobs.length === 0 ? (
No jobs yet.
) : ( jobs.map((job) => { const desc = jobDescription(job); const result = jobResult(job); const progress = formatJobProgress(job); return (
{job.status} {jobTypeLabel(job.job_type)} {desc && {desc}} {new Date(job.requested_at).toLocaleString()}
{job.message && {job.message}} {(job.status === "running" || job.status === "queued") && ( Progress: {progress} )} {result && {result}} {job.completed_at && ( Finished: {new Date(job.completed_at).toLocaleString()} )}
{job.error_message && (
{job.error_message}
)}
); }) )}
); }