"use client"; import { useEffect, useMemo, useState } from "react"; import { Box, Group, Loader, Paper, Stack, Text, ThemeIcon, Timeline } from "@mantine/core"; import { IconCheck, IconDatabaseImport, IconFileZip, IconKey, IconLoader, IconRefresh, IconTransform, } from "@tabler/icons-react"; import type { Icon } from "@tabler/icons-react"; import { useScopedI18n } from "@homarr/translation/client"; import type { MigrationFile } from "./types"; const BASE_PHASE_DURATIONS = { extracting: 1200, encrypting: 1500, swapping: 800, restarting: 10000, } as const; const MIGRATION_STEP_DURATION = 100; interface TimelineStep { key: string; label: string; icon: Icon; duration: number; } interface RestoreProgressPanelProps { active: boolean; migrations: MigrationFile[]; onComplete?: () => void; } export const RestoreProgressPanel = ({ active, migrations, onComplete }: RestoreProgressPanelProps) => { const t = useScopedI18n("management.page.tool.backup.restore.progress"); const [currentStepIdx, setCurrentStepIdx] = useState(-1); const steps = useMemo((): TimelineStep[] => { const result: TimelineStep[] = [ { key: "extracting", label: t("extracting"), icon: IconFileZip, duration: BASE_PHASE_DURATIONS.extracting }, ]; if (migrations.length > 0) { for (const migration of migrations) { result.push({ key: `migration-${migration.idx}`, label: migration.tag, icon: IconDatabaseImport, duration: MIGRATION_STEP_DURATION, }); } } else { result.push({ key: "migrating", label: t("migrating"), icon: IconDatabaseImport, duration: 300, }); } result.push( { key: "encrypting", label: t("encrypting"), icon: IconKey, duration: BASE_PHASE_DURATIONS.encrypting }, { key: "swapping", label: t("swapping"), icon: IconTransform, duration: BASE_PHASE_DURATIONS.swapping }, { key: "restarting", label: t("restarting"), icon: IconRefresh, duration: BASE_PHASE_DURATIONS.restarting }, ); return result; }, [migrations, t]); useEffect(() => { if (!active) return; let cancelled = false; const run = async () => { for (const [i, step] of steps.entries()) { if (cancelled) return; setCurrentStepIdx(i); await new Promise((r) => setTimeout(r, step.duration)); } if (!cancelled) { onComplete?.(); } }; void run(); return () => { cancelled = true; }; }, [active, onComplete, steps]); if (!active && currentStepIdx === -1) return null; return ( {t("title")} {steps.map((step, i) => { const isDone = i < currentStepIdx; const isActive = i === currentStepIdx; const isMigration = step.key.startsWith("migration-"); return ( ) : isActive ? ( ) : ( ) } > {step.label} ); })} ); };