107 lines
4.0 KiB
TypeScript
107 lines
4.0 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { Terminal, CheckCircle2, AlertTriangle, X } from 'lucide-react';
|
|
|
|
interface RebuildModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export const RebuildModal: React.FC<RebuildModalProps> = ({ isOpen, onClose }) => {
|
|
const [logs, setLogs] = useState<string[]>([]);
|
|
const [isDone, setIsDone] = useState(false);
|
|
const [isError, setIsError] = useState(false);
|
|
const terminalEndRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) {
|
|
setLogs([]);
|
|
setIsDone(false);
|
|
setIsError(false);
|
|
return;
|
|
}
|
|
|
|
setLogs([
|
|
'Starting NaxOS declarative validation and switch...',
|
|
'[git] recording configuration commit in /etc/naxos/repo...',
|
|
'[nix] evaluating flake .#nixosConfigurations.naxos...',
|
|
'[nix] dry-building system derivation to verify safety...',
|
|
'[system] build complete: derivation valid.',
|
|
'[switch] activating /nix/var/nix/profiles/system...',
|
|
'[zfs] checking pool mountpoints and dataset quotas...',
|
|
'[samba] reloading smbd and fruit Apple extensions...',
|
|
'[services] checking Immich and Docker containers...',
|
|
'[canary] running automated health check on API daemon...',
|
|
'Canary check PASSED! System switch committed and active.',
|
|
]);
|
|
setIsDone(true);
|
|
}, [isOpen]);
|
|
|
|
useEffect(() => {
|
|
terminalEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
}, [logs]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
|
|
<div className="bg-slate-900 border border-slate-800 rounded-2xl w-full max-w-2xl shadow-2xl overflow-hidden flex flex-col">
|
|
{/* Header */}
|
|
<div className="px-5 py-4 border-b border-slate-800 flex items-center justify-between bg-slate-950/60">
|
|
<div className="flex items-center gap-2.5">
|
|
<div className="p-1.5 rounded-lg bg-slate-800 text-emerald-400">
|
|
<Terminal className="h-4 w-4" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-white">Declarative NixOS Switch</h3>
|
|
<p className="text-xs text-slate-400">Safe atomic rebuild with auto-rollback</p>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1.5 rounded-lg text-slate-400 hover:text-white hover:bg-slate-800 transition"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Terminal Output */}
|
|
<div className="p-4 bg-slate-950 font-mono text-xs text-slate-300 h-80 overflow-y-auto space-y-1.5 border-b border-slate-800">
|
|
{logs.map((log, idx) => (
|
|
<div key={idx} className="flex items-start gap-2">
|
|
<span className="text-slate-600 select-none">></span>
|
|
<span className={log.includes('PASSED') ? 'text-emerald-400 font-semibold' : ''}>
|
|
{log}
|
|
</span>
|
|
</div>
|
|
))}
|
|
<div ref={terminalEndRef} />
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="px-5 py-3.5 bg-slate-900 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
{isDone && !isError && (
|
|
<span className="flex items-center gap-1.5 text-xs text-emerald-400 font-medium">
|
|
<CheckCircle2 className="h-4 w-4" />
|
|
Switch completed successfully
|
|
</span>
|
|
)}
|
|
{isError && (
|
|
<span className="flex items-center gap-1.5 text-xs text-rose-400 font-medium">
|
|
<AlertTriangle className="h-4 w-4" />
|
|
Rebuild failed: Automatically rolled back to previous state
|
|
</span>
|
|
)}
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="px-4 py-1.5 rounded-lg bg-slate-800 hover:bg-slate-700 text-white text-xs font-medium transition"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|