diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index c7e64115..3dbe0704 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -8,7 +8,8 @@ import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" import { Button } from "./ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog" -import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, Terminal } from 'lucide-react' +import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle, Container, ChevronDown, ChevronUp, Terminal, Archive, Plus, Loader2, Clock, Database } from 'lucide-react' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" import useSWR from "swr" import { MetricsView } from "./metrics-dialog" import { LxcTerminalModal } from "./lxc-terminal-modal" @@ -121,6 +122,29 @@ interface VMDetails extends VMData { } } +interface BackupStorage { + storage: string + type: string + content: string + total: number + used: number + avail: number + total_human?: string + used_human?: string + avail_human?: string +} + +interface VMBackup { + volid: string + storage: string + type: string + size: number + size_human: string + timestamp: number + date: string + notes?: string +} + const fetcher = async (url: string) => { return fetchApi(url) } @@ -271,6 +295,13 @@ export function VirtualMachines() { const [ipsLoaded, setIpsLoaded] = useState(false) const [loadingIPs, setLoadingIPs] = useState(false) const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes") + + // Backup states + const [vmBackups, setVmBackups] = useState([]) + const [backupStorages, setBackupStorages] = useState([]) + const [selectedBackupStorage, setSelectedBackupStorage] = useState("") + const [loadingBackups, setLoadingBackups] = useState(false) + const [creatingBackup, setCreatingBackup] = useState(false) useEffect(() => { const fetchLXCIPs = async () => { @@ -348,6 +379,11 @@ export function VirtualMachines() { setIsEditingNotes(false) setEditedNotes("") setDetailsLoading(true) + + // Load backups immediately (independent of config) + fetchBackupStorages() + fetchVmBackups(vm.vmid) + try { const details = await fetchApi(`/api/vms/${vm.vmid}`) setVMDetails(details) @@ -366,6 +402,53 @@ export function VirtualMachines() { setCurrentView("main") } + // Backup functions + const fetchBackupStorages = async () => { + try { + const response = await fetchApi("/api/backup-storages") + if (response.storages) { + setBackupStorages(response.storages) + if (response.storages.length > 0 && !selectedBackupStorage) { + setSelectedBackupStorage(response.storages[0].storage) + } + } + } catch (error) { + console.error("Error fetching backup storages:", error) + } + } + + const fetchVmBackups = async (vmid: number) => { + setLoadingBackups(true) + try { + const response = await fetchApi(`/api/vms/${vmid}/backups`) + if (response.backups) { + setVmBackups(response.backups) + } + } catch (error) { + console.error("Error fetching VM backups:", error) + setVmBackups([]) + } finally { + setLoadingBackups(false) + } + } + + const handleCreateBackup = async () => { + if (!selectedVM || !selectedBackupStorage) return + + setCreatingBackup(true) + try { + await fetchApi(`/api/vms/${selectedVM.vmid}/backup`, { + method: "POST", + body: JSON.stringify({ storage: selectedBackupStorage }), + }) + setTimeout(() => fetchVmBackups(selectedVM.vmid), 2000) + } catch (error) { + console.error("Error creating backup:", error) + } finally { + setCreatingBackup(false) + } + } + const handleVMControl = async (vmid: number, action: string) => { setControlLoading(true) try { @@ -1144,8 +1227,8 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => { -
-
+
+
{selectedVM && ( <>
@@ -1235,6 +1318,91 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
+ {/* Backups Section */} + + +
+
+
+ +
+

Backups

+
+
+ + {/* Create Backup Row */} +
+ + +
+ + {/* Divider */} +
+ + {/* Backup List */} +
+ Available backups + {vmBackups.length} +
+ + {loadingBackups ? ( +
+ + Loading backups... +
+ ) : vmBackups.length === 0 ? ( +
+ + No backups found +
+ ) : ( +
+ {vmBackups.map((backup, index) => ( +
+
+
+ + {backup.date} +
+ + {backup.size_human} + +
+ ))} +
+ )} + + + {detailsLoading ? (
Loading configuration...
) : vmDetails?.config ? ( @@ -1242,9 +1410,12 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
-

- Resources -

+
+
+ +
+

Resources

+