import { ArrowLeft, Play, RefreshCw } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; import { api, errorMessage } from '../api.js'; import { JobStatusPanel } from './JobStatusPanel.jsx'; import { RestoreModal } from './RestoreModal.jsx'; import { SnapshotFilesPanel } from './SnapshotFilesPanel.jsx'; import { SnapshotTable } from './SnapshotTable.jsx'; export function VMDetail({ jobs, onBack, onChanged, vm }) { const [snapshots, setSnapshots] = useState([]); const [selectedSnapshot, setSelectedSnapshot] = useState(null); const [inspectedSnapshot, setInspectedSnapshot] = useState(null); const [snapshotFiles, setSnapshotFiles] = useState([]); const [snapshotFilesLoading, setSnapshotFilesLoading] = useState(false); const [confirmText, setConfirmText] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const vmJobs = useMemo(() => jobs.filter((job) => job.vmName === vm.name && job.nodeId === vm.nodeId), [jobs, vm.name, vm.nodeId]); const visibleJob = vmJobs.find((job) => ['queued', 'running'].includes(job.status)) || vmJobs[0] || null; const latestSnapshot = snapshots[0]?.time; async function loadSnapshots() { setError(''); try { const result = await api.get(`/snapshots/${encodeURIComponent(vm.nodeId)}/${encodeURIComponent(vm.name)}`); setSnapshots(result.data); } catch (requestError) { setError(errorMessage(requestError)); } } useEffect(() => { loadSnapshots(); }, [vm.name, vm.nodeId]); async function createBackup() { setBusy(true); setError(''); try { await api.post(`/backup/${encodeURIComponent(vm.nodeId)}/${encodeURIComponent(vm.name)}`); await onChanged(); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusy(false); } } async function confirmRestore() { setBusy(true); setError(''); try { await api.post(`/restore/${encodeURIComponent(vm.nodeId)}/${encodeURIComponent(vm.name)}`, { snapshotId: selectedSnapshot.id, confirmVmName: confirmText, }); setSelectedSnapshot(null); setConfirmText(''); await onChanged(); } catch (requestError) { setError(errorMessage(requestError)); } finally { setBusy(false); } } async function inspectSnapshot(snapshot) { setInspectedSnapshot(snapshot); setSnapshotFiles([]); setSnapshotFilesLoading(true); setError(''); try { const result = await api.get(`/snapshots/${encodeURIComponent(vm.nodeId)}/${encodeURIComponent(vm.name)}/${encodeURIComponent(snapshot.id)}/files`); setSnapshotFiles(result.data); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSnapshotFilesLoading(false); } } return (

{vm.name}

{vm.type === 'container' ? 'Container' : 'VM'} {vm.status || 'Unknown'}

Node: {vm.nodeName || '-'}

Latest snapshot: {latestSnapshot ? formatTime(latestSnapshot) : 'None'}

{error ?
{error}
: null} { if (vm.type === 'container') { setError('Container restore is not implemented yet. Backups are available, restore needs a safe zfs receive workflow.'); return; } setSelectedSnapshot(snapshot); }} /> { setInspectedSnapshot(null); setSnapshotFiles([]); }} snapshot={inspectedSnapshot} /> { setSelectedSnapshot(null); setConfirmText(''); }} onConfirm={confirmRestore} onTextChange={setConfirmText} snapshot={selectedSnapshot} vmName={vm.name} />
); } function formatTime(value) { return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'medium' }).format(new Date(value)); }