import { useState } from "react"; import type { MigrationReport } from "../../lib/types"; import Button from "../ui/Button"; import StatusIndicator from "../ui/StatusIndicator"; import { ROLLBACK_SCOPE, aptRetryCommand, failureReportText, } from "./migrationCopy"; interface Props { report: MigrationReport; /** Disables the action row while confirm/rollback is in flight. */ busy?: boolean; onKeep: () => void; onRollback: () => void; /** Only offered when there is nothing to keep or roll back. */ onDismiss: () => void; } /** * The outcome of a migration, rendered identically in the Overview banner and * in the modal so a user who closed the modal is not shown a different story. * * A **partial** is the case this component exists for. The user arrived here * because containers degrade silently — a run that quietly dropped `socat` and * called itself a success would be exactly the same bug in a new place. So a * partial is painted as a warning, names every package and the reason it * failed, and hands over the literal `apt-get` line to finish the job. */ export default function MigrationReportCard({ report, busy = false, onKeep, onRollback, onDismiss, }: Props) { const [copied, setCopied] = useState<"command" | "detail" | null>(null); const partial = report.phase === "partial"; const failed = report.phase === "failed"; const rolledBack = report.phase === "rolled_back"; const copy = async (what: "command" | "detail", text: string) => { try { await navigator.clipboard.writeText(text); setCopied(what); setTimeout(() => setCopied(null), 2000); } catch { // Clipboard can be denied; the text is selectable on screen either way. } }; // Partial and failed are painted as failures. A partial that reads as a // success is precisely how a container ends up silently degraded. const tone = partial || failed ? "error" : rolledBack ? "off" : "ok"; const heading = partial ? "Updated, but not completely" : failed ? "Update failed" : rolledBack ? "Rolled back" : "Container base updated"; return (
{report.phase === "succeeded" && (

{report.packages_installed.length} package {report.packages_installed.length === 1 ? "" : "s"} reinstalled,{" "} {report.features_restored.length} feature {report.features_restored.length === 1 ? "" : "s"} restored. {report.paths_copied.length > 0 ? ` ${report.paths_copied.length} path${report.paths_copied.length === 1 ? "" : "s"} copied across.` : ""}

)} {failed && (

{report.message || "Update failed. Your container has been restored to its previous state."}

)} {rolledBack && (

{report.message || "The previous system layer has been put back."}

)} {partial && (

{report.packages_installed.length} of{" "} {report.packages_requested.length} packages went back on.{" "} {report.packages_failed.length} did not , so this container is still missing something it had before.

    {report.packages_failed.map((failure) => (
  • {failure.name} — {failure.reason}
  • ))}
{report.packages_failed.length > 0 && (

Finish by hand in a shell inside the container:

{aptRetryCommand(report.packages_failed)}
)}
)} {report.features_restored.length > 0 && !failed && (

Restored

{report.features_restored.join(", ")}

)} {report.message && !failed && !rolledBack && (

{report.message}

)} {report.rollback_available && (

{ROLLBACK_SCOPE}

)}
{report.rollback_available ? ( <> ) : ( )}
); }