Update virtual-machines.tsx

This commit is contained in:
MacRimi
2025-10-05 17:01:50 +02:00
parent fbcf755591
commit 79e7fd175e

View File

@@ -1,6 +1,6 @@
"use client" "use client"
import { useState, useMemo } from "react" import { useState, useMemo, useEffect } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
import { Badge } from "./ui/badge" import { Badge } from "./ui/badge"
import { Progress } from "./ui/progress" import { Progress } from "./ui/progress"
@@ -36,6 +36,7 @@ interface VMData {
netout?: number netout?: number
diskread?: number diskread?: number
diskwrite?: number diskwrite?: number
ip?: string
} }
interface VMConfig { interface VMConfig {
@@ -109,8 +110,8 @@ const extractIPFromConfig = (config?: VMConfig): string => {
const netConfig = config[netKey] const netConfig = config[netKey]
if (netConfig && typeof netConfig === "string") { if (netConfig && typeof netConfig === "string") {
// Look for ip=x.x.x.x/xx pattern // 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})(\/\d+)?/) const ipMatch = netConfig.match(/ip=([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/)
if (ipMatch) { if (ipMatch) {
return ipMatch[1] // Return just the IP without CIDR return ipMatch[1] // Return just the IP without CIDR
} }
@@ -141,6 +142,36 @@ export function VirtualMachines() {
const [vmDetails, setVMDetails] = useState<VMDetails | null>(null) const [vmDetails, setVMDetails] = useState<VMDetails | null>(null)
const [controlLoading, setControlLoading] = useState(false) const [controlLoading, setControlLoading] = useState(false)
const [detailsLoading, setDetailsLoading] = useState(false) const [detailsLoading, setDetailsLoading] = useState(false)
const [vmConfigs, setVmConfigs] = useState<Record<number, string>>({})
useEffect(() => {
const fetchLXCIPs = async () => {
if (!vmData) return
const lxcs = vmData.filter((vm) => vm.type === "lxc")
const configs: Record<number, string> = {}
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) => { const handleVMClick = async (vm: VMData) => {
setSelectedVM(vm) setSelectedVM(vm)
@@ -249,7 +280,6 @@ export function VirtualMachines() {
return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" } 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 safeVMData = vmData || []
const totalAllocatedMemoryGB = useMemo(() => { const totalAllocatedMemoryGB = useMemo(() => {
@@ -293,7 +323,6 @@ export function VirtualMachines() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
{/* VM Overview Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="bg-card border-border"> <Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
@@ -350,6 +379,20 @@ export function VirtualMachines() {
</CardContent> </CardContent>
</Card> </Card>
<Card className="bg-card border-border">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground">Total Disk</CardTitle>
<HardDrive className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-foreground">
{(safeVMData.reduce((sum, vm) => sum + (vm.maxdisk || 0), 0) / 1024 ** 3).toFixed(1)} GB
</div>
<p className="text-xs text-muted-foreground mt-2">Allocated disk space</p>
</CardContent>
</Card>
</div>
<Card className="bg-card border-border"> <Card className="bg-card border-border">
<CardHeader> <CardHeader>
<CardTitle className="text-foreground flex items-center"> <CardTitle className="text-foreground flex items-center">
@@ -368,7 +411,7 @@ export function VirtualMachines() {
const memGB = (vm.mem / 1024 ** 3).toFixed(1) const memGB = (vm.mem / 1024 ** 3).toFixed(1)
const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1) const maxMemGB = (vm.maxmem / 1024 ** 3).toFixed(1)
const typeBadge = getTypeBadge(vm.type) const typeBadge = getTypeBadge(vm.type)
const vmIP = extractIPFromConfig(vm.config) const lxcIP = vm.type === "lxc" ? vmConfigs[vm.vmid] : null
return ( return (
<div <div
@@ -379,15 +422,29 @@ export function VirtualMachines() {
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-4">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Server className="h-6 w-6 text-muted-foreground flex-shrink-0" /> <Server className="h-6 w-6 text-muted-foreground flex-shrink-0" />
<div className="min-w-0"> <div className="min-w-0 flex-1">
<div className="font-semibold text-foreground text-lg flex items-center flex-wrap gap-2"> <div className="font-semibold text-foreground text-lg flex items-center flex-wrap gap-2">
<span className="truncate">{vm.name}</span> <span className="truncate">{vm.name}</span>
<Badge variant="outline" className={`text-xs flex-shrink-0 ${typeBadge.color}`}> <Badge variant="outline" className={`text-xs flex-shrink-0 ${typeBadge.color}`}>
{typeBadge.label} {typeBadge.label}
</Badge> </Badge>
</div> </div>
<div className="flex items-center gap-2 flex-wrap">
<div className="text-sm text-muted-foreground">ID: {vm.vmid}</div> <div className="text-sm text-muted-foreground">ID: {vm.vmid}</div>
<div className="text-sm text-muted-foreground">IP: {vmIP}</div> {lxcIP && (
<>
<span className="text-muted-foreground"></span>
<div className="flex items-center gap-1 text-sm">
<Network
className={`h-3 w-3 ${lxcIP === "DHCP" ? "text-yellow-500" : "text-green-500"}`}
/>
<span className={lxcIP === "DHCP" ? "text-yellow-500" : "text-green-500"}>
IP: {lxcIP}
</span>
</div>
</>
)}
</div>
</div> </div>
</div> </div>
@@ -456,7 +513,6 @@ export function VirtualMachines() {
</CardContent> </CardContent>
</Card> </Card>
{/* VM Details Modal */}
<Dialog <Dialog
open={!!selectedVM} open={!!selectedVM}
onOpenChange={() => { onOpenChange={() => {
@@ -563,7 +619,6 @@ export function VirtualMachines() {
</div> </div>
</div> </div>
{/* Resources Configuration */}
{detailsLoading ? ( {detailsLoading ? (
<div className="text-center py-8 text-muted-foreground">Loading configuration...</div> <div className="text-center py-8 text-muted-foreground">Loading configuration...</div>
) : vmDetails?.config ? ( ) : vmDetails?.config ? (
@@ -620,7 +675,6 @@ export function VirtualMachines() {
</div> </div>
</div> </div>
{/* Network Configuration */}
<div> <div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide"> <h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
Network Network
@@ -659,7 +713,6 @@ export function VirtualMachines() {
</div> </div>
</div> </div>
{/* Options */}
<div> <div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide"> <h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
Options Options
@@ -724,7 +777,6 @@ export function VirtualMachines() {
</> </>
) : null} ) : null}
{/* Control Actions */}
<div> <div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide"> <h3 className="text-sm font-semibold text-muted-foreground mb-3 uppercase tracking-wide">
Control Actions Control Actions
@@ -774,6 +826,5 @@ export function VirtualMachines() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</div> </div>
</div>
) )
} }