import Heading from "@/components/ui/heading"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; import { Toaster } from "@/components/ui/sonner"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import axios from "axios"; import { toast } from "sonner"; import { useJobStatus } from "@/api/ws"; import { Switch } from "@/components/ui/switch"; import { LuCheck, LuX } from "react-icons/lu"; import { cn } from "@/lib/utils"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { MediaSyncStats } from "@/types/ws"; export default function MediaSyncSettingsView() { const { t } = useTranslation("views/settings"); const [selectedMediaTypes, setSelectedMediaTypes] = useState([ "all", ]); const [dryRun, setDryRun] = useState(true); const [force, setForce] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const MEDIA_TYPES = [ { id: "event_snapshots", label: t("maintenance.sync.event_snapshots") }, { id: "event_thumbnails", label: t("maintenance.sync.event_thumbnails") }, { id: "review_thumbnails", label: t("maintenance.sync.review_thumbnails") }, { id: "previews", label: t("maintenance.sync.previews") }, { id: "exports", label: t("maintenance.sync.exports") }, { id: "recordings", label: t("maintenance.sync.recordings") }, ]; // Subscribe to media sync status via WebSocket const { payload: currentJob } = useJobStatus("media_sync"); const isJobRunning = Boolean( currentJob && (currentJob.status === "queued" || currentJob.status === "running"), ); const handleMediaTypeChange = useCallback((id: string, checked: boolean) => { setSelectedMediaTypes((prev) => { if (id === "all") { return checked ? ["all"] : []; } let next = prev.filter((t) => t !== "all"); if (checked) { next.push(id); } else { next = next.filter((t) => t !== id); } return next.length === 0 ? ["all"] : next; }); }, []); const handleStartSync = useCallback(async () => { setIsSubmitting(true); try { const response = await axios.post( "/media/sync", { dry_run: dryRun, media_types: selectedMediaTypes, force: force, }, { headers: { "Content-Type": "application/json", }, }, ); if (response.status === 202) { toast.success(t("maintenance.sync.started"), { position: "top-center", closeButton: true, }); } else if (response.status === 409) { toast.error(t("maintenance.sync.alreadyRunning"), { position: "top-center", closeButton: true, }); } } catch { toast.error(t("maintenance.sync.error"), { position: "top-center", closeButton: true, }); } finally { setIsSubmitting(false); } }, [selectedMediaTypes, dryRun, force, t]); return ( <>
{t("maintenance.sync.title")}

{t("maintenance.sync.desc")}

{/* Media Types Selection */}
handleMediaTypeChange("all", checked) } disabled={isJobRunning} />
{MEDIA_TYPES.map((type) => (
handleMediaTypeChange(type.id, checked) } disabled={ isJobRunning || selectedMediaTypes.includes("all") } />
))}
{/* Options */}

{dryRun ? t("maintenance.sync.dryRunEnabled") : t("maintenance.sync.dryRunDisabled")}

{t("maintenance.sync.forceDesc")}

{/* Action Buttons */}
{t("maintenance.sync.currentStatus")}
{currentJob?.status === "success" && ( )} {currentJob?.status === "failed" && ( )} {(currentJob?.status === "running" || currentJob?.status === "queued") && ( )} {t( `maintenance.sync.status.${currentJob?.status ?? "notRunning"}`, )}
{/* Current Job Status */}
{currentJob?.start_time && (
{t("maintenance.sync.startTime")}: {formatUnixTimestampToDateTime( currentJob?.start_time ?? "-", )}
)} {currentJob?.end_time && (
{t("maintenance.sync.endTime")}: {formatUnixTimestampToDateTime(currentJob?.end_time)}
)} {currentJob?.results && (

{t("maintenance.sync.results")}

{/* Individual media type results */}
{Object.entries(currentJob.results) .filter(([key]) => key !== "totals") .map(([mediaType, stats]) => { const mediaStats = stats as MediaSyncStats; return (

{t(`maintenance.sync.${mediaType}`)}

{t( "maintenance.sync.resultsFields.filesChecked", )} {mediaStats.files_checked}
{t( "maintenance.sync.resultsFields.orphansFound", )} 0 ? "text-yellow-500" : "" } > {mediaStats.orphans_found}
{t( "maintenance.sync.resultsFields.orphansDeleted", )} 0 && "text-success", mediaStats.orphans_deleted === 0 && mediaStats.aborted && "text-destructive", )} > {mediaStats.orphans_deleted}
{mediaStats.aborted && (
{t( "maintenance.sync.resultsFields.aborted", )}
)} {mediaStats.error && (
{t( "maintenance.sync.resultsFields.error", )} {": "} {mediaStats.error}
)}
); })}
{/* Totals */} {currentJob.results.totals && (

{t("maintenance.sync.resultsFields.totals")}

{t( "maintenance.sync.resultsFields.filesChecked", )} {currentJob.results.totals.files_checked}
{t( "maintenance.sync.resultsFields.orphansFound", )} 0 ? "font-medium text-yellow-500" : "font-medium" } > {currentJob.results.totals.orphans_found}
{t( "maintenance.sync.resultsFields.orphansDeleted", )} 0 ? "text-success" : "text-muted-foreground", )} > {currentJob.results.totals.orphans_deleted}
)}
)} {currentJob?.error_message && (

{t("maintenance.sync.errorLabel")}

{currentJob?.error_message}

)}
); }