diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index ac4f415..35e0c05 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useMemo } from "react" +import { useState, useMemo, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" @@ -36,6 +36,7 @@ interface VMData { netout?: number diskread?: number diskwrite?: number + ip?: string } interface VMConfig { @@ -109,8 +110,8 @@ const extractIPFromConfig = (config?: VMConfig): string => { const netConfig = config[netKey] if (netConfig && typeof netConfig === "string") { - // Look for ip=x.x.x.x/xx pattern - const ipMatch = netConfig.match(/ip=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})(\/\d+)?/) + // Look for ip=x.x.x.x/xx or ip=x.x.x.x pattern + const ipMatch = netConfig.match(/ip=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/) if (ipMatch) { return ipMatch[1] // Return just the IP without CIDR } @@ -141,6 +142,36 @@ export function VirtualMachines() { const [vmDetails, setVMDetails] = useState(null) const [controlLoading, setControlLoading] = useState(false) const [detailsLoading, setDetailsLoading] = useState(false) + const [vmConfigs, setVmConfigs] = useState>({}) + + useEffect(() => { + const fetchLXCIPs = async () => { + if (!vmData) return + + const lxcs = vmData.filter((vm) => vm.type === "lxc") + const configs: Record = {} + + await Promise.all( + lxcs.map(async (lxc) => { + try { + const response = await fetch(`/api/vms/${lxc.vmid}`) + if (response.ok) { + const details = await response.json() + if (details.config) { + configs[lxc.vmid] = extractIPFromConfig(details.config) + } + } + } catch (error) { + console.error(`Error fetching config for LXC ${lxc.vmid}:`, error) + } + }), + ) + + setVmConfigs(configs) + } + + fetchLXCIPs() + }, [vmData]) const handleVMClick = async (vm: VMData) => { setSelectedVM(vm) @@ -249,7 +280,6 @@ export function VirtualMachines() { return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" } } - // Safe data handling with default empty array const safeVMData = vmData || [] const totalAllocatedMemoryGB = useMemo(() => { @@ -293,7 +323,6 @@ export function VirtualMachines() { return (
- {/* VM Overview Cards */}
@@ -351,429 +380,451 @@ export function VirtualMachines() { - - - - Virtual Machines & Containers - + + Total Disk + - {safeVMData.length === 0 ? ( -
No virtual machines found
- ) : ( -
- {safeVMData.map((vm) => { - const cpuPercent = (vm.cpu * 100).toFixed(1) - const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0" - const memGB = (vm.mem / 1024 ** 3).toFixed(1) - const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1) - const typeBadge = getTypeBadge(vm.type) - const vmIP = extractIPFromConfig(vm.config) - - return ( -
handleVMClick(vm)} - > -
-
- -
-
- {vm.name} - - {typeBadge.label} - -
-
ID: {vm.vmid}
-
IP: {vmIP}
-
-
- - - {getStatusIcon(vm.status)} - {vm.status.toUpperCase()} - -
- -
-
-
CPU Usage
-
{cpuPercent}%
- -
- -
-
Memory Usage
-
- {memGB} / {maxMemGB} GB -
- -
- -
-
Disk I/O
-
-
- - ↓ {formatBytes(vm.diskread)} -
-
- - ↑ {formatBytes(vm.diskwrite)} -
-
-
- -
-
Network I/O
-
-
- - ↓ {formatBytes(vm.netin)} -
-
- - ↑ {formatBytes(vm.netout)} -
-
-
-
- -
-
Uptime
-
{formatUptime(vm.uptime)}
-
-
- ) - })} -
- )} +
+ {(safeVMData.reduce((sum, vm) => sum + (vm.maxdisk || 0), 0) / 1024 ** 3).toFixed(1)} GB +
+

Allocated disk space

- - {/* VM Details Modal */} - { - setSelectedVM(null) - setVMDetails(null) - }} - > - - - -
- - {selectedVM?.name} -
- {selectedVM && ( -
- - {getTypeBadge(selectedVM.type).label} - - - {selectedVM.status.toUpperCase()} - -
- )} -
-
- -
- {selectedVM && ( - <> -
-

- Basic Information -

-
-
-
Name
-
{selectedVM.name}
-
-
-
VMID
-
{selectedVM.vmid}
-
-
-
CPU Usage
-
80 - ? "text-red-500" - : selectedVM.cpu * 100 > 60 - ? "text-yellow-500" - : "text-green-500" - }`} - > - {(selectedVM.cpu * 100).toFixed(1)}% -
-
-
-
Memory
-
80 - ? "text-red-500" - : (selectedVM.mem / selectedVM.maxmem) * 100 > 60 - ? "text-yellow-500" - : "text-blue-500" - }`} - > - {(selectedVM.mem / 1024 ** 3).toFixed(1)} / {(selectedVM.maxmem / 1024 ** 3).toFixed(1)} GB -
-
-
-
Disk
-
- {(selectedVM.disk / 1024 ** 3).toFixed(1)} / {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)} GB -
-
-
-
Uptime
-
{formatUptime(selectedVM.uptime)}
-
-
-
Disk I/O
-
-
- ↓ {formatBytes(selectedVM.diskread)} -
-
- ↑ {formatBytes(selectedVM.diskwrite)} -
-
-
-
-
Network I/O
-
-
- ↓ {formatBytes(selectedVM.netin)} -
-
- ↑ {formatBytes(selectedVM.netout)} -
-
-
-
-
- - {/* Resources Configuration */} - {detailsLoading ? ( -
Loading configuration...
- ) : vmDetails?.config ? ( - <> -
-

- Resources -

-
- {vmDetails.config.cores && ( -
-
CPU Cores
-
{vmDetails.config.cores}
-
- )} - {vmDetails.config.sockets && ( -
-
CPU Sockets
-
{vmDetails.config.sockets}
-
- )} - {vmDetails.config.memory && ( -
-
Memory
-
{vmDetails.config.memory} MB
-
- )} - {vmDetails.config.swap && ( -
-
Swap
-
{vmDetails.config.swap} MB
-
- )} - {vmDetails.config.rootfs && ( -
-
Root Filesystem
-
- {vmDetails.config.rootfs} -
-
- )} - {Object.keys(vmDetails.config) - .filter((key) => key.match(/^(scsi|sata|ide|virtio)\d+$/)) - .map((diskKey) => ( -
-
- {diskKey.toUpperCase().replace(/(\d+)/, " $1")} -
-
- {vmDetails.config[diskKey]} -
-
- ))} -
-
- - {/* Network Configuration */} -
-

- Network -

-
- {Object.keys(vmDetails.config) - .filter((key) => key.match(/^net\d+$/)) - .map((netKey) => ( -
-
- Network Interface {netKey.replace("net", "")} -
-
- {vmDetails.config[netKey]} -
-
- ))} - {vmDetails.config.nameserver && ( -
-
DNS Nameserver
-
{vmDetails.config.nameserver}
-
- )} - {vmDetails.config.searchdomain && ( -
-
Search Domain
-
{vmDetails.config.searchdomain}
-
- )} - {vmDetails.config.hostname && ( -
-
Hostname
-
{vmDetails.config.hostname}
-
- )} -
-
- - {/* Options */} -
-

- Options -

-
- {vmDetails.config.onboot !== undefined && ( -
-
Start on Boot
- - {vmDetails.config.onboot ? "Yes" : "No"} - -
- )} - {vmDetails.config.unprivileged !== undefined && ( -
-
Unprivileged
- - {vmDetails.config.unprivileged ? "Yes" : "No"} - -
- )} - {vmDetails.config.ostype && ( -
-
OS Type
-
{vmDetails.config.ostype}
-
- )} - {vmDetails.config.arch && ( -
-
Architecture
-
{vmDetails.config.arch}
-
- )} - {vmDetails.config.boot && ( -
-
Boot Order
-
{vmDetails.config.boot}
-
- )} - {vmDetails.config.features && ( -
-
Features
-
{vmDetails.config.features}
-
- )} -
-
- - ) : null} - - {/* Control Actions */} -
-

- Control Actions -

-
- - - - -
-
- - )} -
-
-
+ + + + + + Virtual Machines & Containers + + + + {safeVMData.length === 0 ? ( +
No virtual machines found
+ ) : ( +
+ {safeVMData.map((vm) => { + const cpuPercent = (vm.cpu * 100).toFixed(1) + const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0" + const memGB = (vm.mem / 1024 ** 3).toFixed(1) + const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1) + const typeBadge = getTypeBadge(vm.type) + const lxcIP = vm.type === "lxc" ? vmConfigs[vm.vmid] : null + + return ( +
handleVMClick(vm)} + > +
+
+ +
+
+ {vm.name} + + {typeBadge.label} + +
+
+
ID: {vm.vmid}
+ {lxcIP && ( + <> + +
+ + + IP: {lxcIP} + +
+ + )} +
+
+
+ + + {getStatusIcon(vm.status)} + {vm.status.toUpperCase()} + +
+ +
+
+
CPU Usage
+
{cpuPercent}%
+ +
+ +
+
Memory Usage
+
+ {memGB} / {maxMemGB} GB +
+ +
+ +
+
Disk I/O
+
+
+ + ↓ {formatBytes(vm.diskread)} +
+
+ + ↑ {formatBytes(vm.diskwrite)} +
+
+
+ +
+
Network I/O
+
+
+ + ↓ {formatBytes(vm.netin)} +
+
+ + ↑ {formatBytes(vm.netout)} +
+
+
+
+ +
+
Uptime
+
{formatUptime(vm.uptime)}
+
+
+ ) + })} +
+ )} +
+
+ + { + setSelectedVM(null) + setVMDetails(null) + }} + > + + + +
+ + {selectedVM?.name} +
+ {selectedVM && ( +
+ + {getTypeBadge(selectedVM.type).label} + + + {selectedVM.status.toUpperCase()} + +
+ )} +
+
+ +
+ {selectedVM && ( + <> +
+

+ Basic Information +

+
+
+
Name
+
{selectedVM.name}
+
+
+
VMID
+
{selectedVM.vmid}
+
+
+
CPU Usage
+
80 + ? "text-red-500" + : selectedVM.cpu * 100 > 60 + ? "text-yellow-500" + : "text-green-500" + }`} + > + {(selectedVM.cpu * 100).toFixed(1)}% +
+
+
+
Memory
+
80 + ? "text-red-500" + : (selectedVM.mem / selectedVM.maxmem) * 100 > 60 + ? "text-yellow-500" + : "text-blue-500" + }`} + > + {(selectedVM.mem / 1024 ** 3).toFixed(1)} / {(selectedVM.maxmem / 1024 ** 3).toFixed(1)} GB +
+
+
+
Disk
+
+ {(selectedVM.disk / 1024 ** 3).toFixed(1)} / {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)} GB +
+
+
+
Uptime
+
{formatUptime(selectedVM.uptime)}
+
+
+
Disk I/O
+
+
+ ↓ {formatBytes(selectedVM.diskread)} +
+
+ ↑ {formatBytes(selectedVM.diskwrite)} +
+
+
+
+
Network I/O
+
+
+ ↓ {formatBytes(selectedVM.netin)} +
+
+ ↑ {formatBytes(selectedVM.netout)} +
+
+
+
+
+ + {detailsLoading ? ( +
Loading configuration...
+ ) : vmDetails?.config ? ( + <> +
+

+ Resources +

+
+ {vmDetails.config.cores && ( +
+
CPU Cores
+
{vmDetails.config.cores}
+
+ )} + {vmDetails.config.sockets && ( +
+
CPU Sockets
+
{vmDetails.config.sockets}
+
+ )} + {vmDetails.config.memory && ( +
+
Memory
+
{vmDetails.config.memory} MB
+
+ )} + {vmDetails.config.swap && ( +
+
Swap
+
{vmDetails.config.swap} MB
+
+ )} + {vmDetails.config.rootfs && ( +
+
Root Filesystem
+
+ {vmDetails.config.rootfs} +
+
+ )} + {Object.keys(vmDetails.config) + .filter((key) => key.match(/^(scsi|sata|ide|virtio)\d+$/)) + .map((diskKey) => ( +
+
+ {diskKey.toUpperCase().replace(/(\d+)/, " $1")} +
+
+ {vmDetails.config[diskKey]} +
+
+ ))} +
+
+ +
+

+ Network +

+
+ {Object.keys(vmDetails.config) + .filter((key) => key.match(/^net\d+$/)) + .map((netKey) => ( +
+
+ Network Interface {netKey.replace("net", "")} +
+
+ {vmDetails.config[netKey]} +
+
+ ))} + {vmDetails.config.nameserver && ( +
+
DNS Nameserver
+
{vmDetails.config.nameserver}
+
+ )} + {vmDetails.config.searchdomain && ( +
+
Search Domain
+
{vmDetails.config.searchdomain}
+
+ )} + {vmDetails.config.hostname && ( +
+
Hostname
+
{vmDetails.config.hostname}
+
+ )} +
+
+ +
+

+ Options +

+
+ {vmDetails.config.onboot !== undefined && ( +
+
Start on Boot
+ + {vmDetails.config.onboot ? "Yes" : "No"} + +
+ )} + {vmDetails.config.unprivileged !== undefined && ( +
+
Unprivileged
+ + {vmDetails.config.unprivileged ? "Yes" : "No"} + +
+ )} + {vmDetails.config.ostype && ( +
+
OS Type
+
{vmDetails.config.ostype}
+
+ )} + {vmDetails.config.arch && ( +
+
Architecture
+
{vmDetails.config.arch}
+
+ )} + {vmDetails.config.boot && ( +
+
Boot Order
+
{vmDetails.config.boot}
+
+ )} + {vmDetails.config.features && ( +
+
Features
+
{vmDetails.config.features}
+
+ )} +
+
+ + ) : null} + +
+

+ Control Actions +

+
+ + + + +
+
+ + )} +
+
+
) }