diff --git a/apps/backoffice/app/api/jobs/[id]/replay/route.ts b/apps/backoffice/app/api/jobs/[id]/replay/route.ts new file mode 100644 index 0000000..3922c77 --- /dev/null +++ b/apps/backoffice/app/api/jobs/[id]/replay/route.ts @@ -0,0 +1,38 @@ +import { NextRequest, NextResponse } from "next/server"; +import { apiFetch, IndexJobDto, rebuildIndex, rebuildThumbnails, regenerateThumbnails, startMetadataBatch, startMetadataRefresh } from "@/lib/api"; + +export async function POST( + _request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const { id } = await params; + try { + const job = await apiFetch(`/index/jobs/${id}`); + const libraryId = job.library_id ?? undefined; + + switch (job.type) { + case "rebuild": + return NextResponse.json(await rebuildIndex(libraryId)); + case "full_rebuild": + return NextResponse.json(await rebuildIndex(libraryId, true)); + case "rescan": + return NextResponse.json(await rebuildIndex(libraryId, false, true)); + case "scan": + return NextResponse.json(await rebuildIndex(libraryId)); + case "thumbnail_rebuild": + return NextResponse.json(await rebuildThumbnails(libraryId)); + case "thumbnail_regenerate": + return NextResponse.json(await regenerateThumbnails(libraryId)); + case "metadata_batch": + if (!libraryId) return NextResponse.json({ error: "Library ID required for metadata batch" }, { status: 400 }); + return NextResponse.json(await startMetadataBatch(libraryId)); + case "metadata_refresh": + if (!libraryId) return NextResponse.json({ error: "Library ID required for metadata refresh" }, { status: 400 }); + return NextResponse.json(await startMetadataRefresh(libraryId)); + default: + return NextResponse.json({ error: `Cannot replay job type: ${job.type}` }, { status: 400 }); + } + } catch (error) { + return NextResponse.json({ error: "Failed to replay job" }, { status: 500 }); + } +} diff --git a/apps/backoffice/app/components/JobRow.tsx b/apps/backoffice/app/components/JobRow.tsx index b3c5d24..fdf3edd 100644 --- a/apps/backoffice/app/components/JobRow.tsx +++ b/apps/backoffice/app/components/JobRow.tsx @@ -30,11 +30,14 @@ interface JobRowProps { libraryName: string | undefined; highlighted?: boolean; onCancel: (id: string) => void; + onReplay: (id: string) => void; formatDate: (date: string) => string; formatDuration: (start: string, end: string | null) => string; } -export function JobRow({ job, libraryName, highlighted, onCancel, formatDate, formatDuration }: JobRowProps) { +const REPLAYABLE_TYPES = new Set(["rebuild", "full_rebuild", "rescan", "scan", "thumbnail_rebuild", "thumbnail_regenerate", "metadata_batch", "metadata_refresh"]); + +export function JobRow({ job, libraryName, highlighted, onCancel, onReplay, formatDate, formatDuration }: JobRowProps) { const { t } = useTranslation(); const isActive = job.status === "running" || job.status === "pending" || job.status === "extracting_pages" || job.status === "generating_thumbnails"; const [showProgress, setShowProgress] = useState(highlighted || isActive); @@ -213,7 +216,7 @@ export function JobRow({ job, libraryName, highlighted, onCancel, formatDate, fo {t("jobRow.view")} - {(job.status === "pending" || job.status === "running" || job.status === "extracting_pages" || job.status === "generating_thumbnails") && ( + {isActive && ( + )} diff --git a/apps/backoffice/app/components/JobsList.tsx b/apps/backoffice/app/components/JobsList.tsx index 6a29ea3..1f3bb46 100644 --- a/apps/backoffice/app/components/JobsList.tsx +++ b/apps/backoffice/app/components/JobsList.tsx @@ -95,6 +95,14 @@ export function JobsList({ initialJobs, libraries, highlightJobId }: JobsListPro } }; + const handleReplay = async (id: string) => { + const response = await fetch(`/api/jobs/${id}/replay`, { method: "POST" }); + if (!response.ok) { + const data = await response.json().catch(() => ({})); + console.error("Failed to replay job:", data?.error ?? response.status); + } + }; + return (
@@ -119,6 +127,7 @@ export function JobsList({ initialJobs, libraries, highlightJobId }: JobsListPro libraryName={job.library_id ? libraries.get(job.library_id) : undefined} highlighted={job.id === highlightJobId} onCancel={handleCancel} + onReplay={handleReplay} formatDate={formatDate} formatDuration={formatDuration} /> diff --git a/apps/backoffice/lib/i18n/en.ts b/apps/backoffice/lib/i18n/en.ts index d9ea2a5..d6ba002 100644 --- a/apps/backoffice/lib/i18n/en.ts +++ b/apps/backoffice/lib/i18n/en.ts @@ -282,6 +282,7 @@ const en: Record = { "jobRow.metadataLinks": "{{count}} links analyzed", "jobRow.errors": "{{count}} errors", "jobRow.view": "View", + "jobRow.replay": "Replay", // Job progress "jobProgress.loadingProgress": "Loading progress...", diff --git a/apps/backoffice/lib/i18n/fr.ts b/apps/backoffice/lib/i18n/fr.ts index 0d02292..fc1e63f 100644 --- a/apps/backoffice/lib/i18n/fr.ts +++ b/apps/backoffice/lib/i18n/fr.ts @@ -280,6 +280,7 @@ const fr = { "jobRow.metadataLinks": "{{count}} liens analysés", "jobRow.errors": "{{count}} erreurs", "jobRow.view": "Voir", + "jobRow.replay": "Rejouer", // Job progress "jobProgress.loadingProgress": "Chargement de la progression...",