diff --git a/AppImage/components/restore-progress-card.tsx b/AppImage/components/restore-progress-card.tsx index b57c06c8..86a5c922 100644 --- a/AppImage/components/restore-progress-card.tsx +++ b/AppImage/components/restore-progress-card.tsx @@ -65,6 +65,16 @@ interface RestoreRollback { components_to_uninstall?: string[] } +interface DataPoolsImport { + ok: string[] + forced: string[] + partial: string[] + missing: string[] + failed: string[] + finished_at?: string + log_path?: string +} + interface RestoreState { status: "running" | "complete" | "failed" started_at: string @@ -79,6 +89,7 @@ interface RestoreState { summary: RestoreSummary | null acknowledged: boolean duration?: string + data_pools_import?: DataPoolsImport } interface HistoryEntry { @@ -371,6 +382,8 @@ const RestoreDetailModal: React.FC<{ )} + {state.data_pools_import && } +
Rollback delta
@@ -392,6 +405,94 @@ const RestoreDetailModal: React.FC<{ ) } +// ── Data-pools auto-import block ────────────────────────────── +// Rendered inside the RestoreDetailModal (and the compact header +// badge on the card) so the operator sees which ZFS data pools +// were imported automatically during the restore, which ones ZFS +// took with -f because the on-disk hostid didn't match the fresh +// install, and which ones were skipped and need manual attention. +// The section is populated by backup_host.sh:_rs_persist_datapool_import +// and survives the ScriptTerminalModal being closed. +const DataPoolsBlock: React.FC<{ section: DataPoolsImport }> = ({ section }) => { + const total = + section.ok.length + + section.forced.length + + section.partial.length + + section.missing.length + + section.failed.length + if (total === 0) return null + + const Row: React.FC<{ + label: string + tone: "ok" | "warn" | "info" | "error" + items: string[] + help?: string + }> = ({ label, tone, items, help }) => { + if (items.length === 0) return null + const toneClass = + tone === "ok" + ? "text-emerald-400" + : tone === "warn" + ? "text-amber-400" + : tone === "error" + ? "text-red-400" + : "text-blue-400" + return ( +
+
+ {tone === "ok" && } + {tone === "warn" && } + {tone === "error" && } + {tone === "info" && } + {label} + ({items.length}) +
+
{items.join(", ")}
+ {help &&
{help}
} +
+ ) + } + + return ( +
+
+ + ZFS data pools — auto-import +
+
+ + + + + +
+ {section.log_path && ( +
Log: {section.log_path}
+ )} +
+ ) +} + // ── History browser modal ───────────────────────────────────── const RestoreHistoryModal: React.FC<{ open: boolean; onClose: () => void }> = ({ open, onClose }) => { @@ -510,6 +611,14 @@ export const RestoreProgressCard: React.FC = () => { } const hasWarnings = state.sanity_warnings.length > 0 + const pools = state.data_pools_import + const poolCount = + (pools?.ok.length ?? 0) + + (pools?.forced.length ?? 0) + + (pools?.partial.length ?? 0) + + (pools?.missing.length ?? 0) + + (pools?.failed.length ?? 0) + const poolWarnings = (pools?.partial.length ?? 0) + (pools?.missing.length ?? 0) + (pools?.failed.length ?? 0) const barColor = state.status === "failed" ? "bg-red-500" : state.status === "complete" ? "bg-emerald-500" : "bg-blue-500" @@ -530,6 +639,20 @@ export const RestoreProgressCard: React.FC = () => { {state.sanity_warnings.length} boot warning{state.sanity_warnings.length === 1 ? "" : "s"} )} + {poolCount > 0 && ( + 0 + ? "text-amber-400 border-amber-500/40 bg-amber-500/10 gap-1" + : "text-emerald-400 border-emerald-500/40 bg-emerald-500/10 gap-1" + } + > + + {poolCount} ZFS pool{poolCount === 1 ? "" : "s"} + {poolWarnings > 0 && ` · ${poolWarnings} need attention`} + + )}