mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-18 11:36:17 +00:00
Update virtual-machines.tsx
This commit is contained in:
@@ -6,7 +6,19 @@ import { Badge } from "./ui/badge"
|
|||||||
import { Progress } from "./ui/progress"
|
import { Progress } from "./ui/progress"
|
||||||
import { Button } from "./ui/button"
|
import { Button } from "./ui/button"
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog"
|
||||||
import { Server, Play, Square, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, StopCircle } from "lucide-react"
|
import {
|
||||||
|
Server,
|
||||||
|
Play,
|
||||||
|
Square,
|
||||||
|
Cpu,
|
||||||
|
MemoryStick,
|
||||||
|
HardDrive,
|
||||||
|
Network,
|
||||||
|
Power,
|
||||||
|
RotateCcw,
|
||||||
|
StopCircle,
|
||||||
|
Container,
|
||||||
|
} from "lucide-react"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
|
|
||||||
interface VMData {
|
interface VMData {
|
||||||
@@ -127,6 +139,18 @@ const formatStorage = (sizeInGB: number): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getUsageColor = (percent: number): string => {
|
||||||
|
if (percent >= 80) return "text-red-500"
|
||||||
|
if (percent >= 50) return "text-yellow-500"
|
||||||
|
return "text-white"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProgressColor = (percent: number): string => {
|
||||||
|
if (percent >= 80) return "[&>div]:bg-red-500"
|
||||||
|
if (percent >= 50) return "[&>div]:bg-yellow-500"
|
||||||
|
return "[&>div]:bg-blue-500"
|
||||||
|
}
|
||||||
|
|
||||||
export function VirtualMachines() {
|
export function VirtualMachines() {
|
||||||
const {
|
const {
|
||||||
data: vmData,
|
data: vmData,
|
||||||
@@ -276,9 +300,17 @@ export function VirtualMachines() {
|
|||||||
|
|
||||||
const getTypeBadge = (type: string) => {
|
const getTypeBadge = (type: string) => {
|
||||||
if (type === "lxc") {
|
if (type === "lxc") {
|
||||||
return { color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "LXC" }
|
return {
|
||||||
|
color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20",
|
||||||
|
label: "LXC",
|
||||||
|
icon: <Container className="h-3 w-3 mr-1" />,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
color: "bg-purple-500/10 text-purple-500 border-purple-500/20",
|
||||||
|
label: "VM",
|
||||||
|
icon: <Server className="h-3 w-3 mr-1" />,
|
||||||
}
|
}
|
||||||
return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeVMData = vmData || []
|
const safeVMData = vmData || []
|
||||||
@@ -391,7 +423,7 @@ export function VirtualMachines() {
|
|||||||
</span>{" "}
|
</span>{" "}
|
||||||
of {physicalMemoryGB.toFixed(1)} GB
|
of {physicalMemoryGB.toFixed(1)} GB
|
||||||
</div>
|
</div>
|
||||||
<Progress value={memoryUsagePercent} className="h-2 mt-2 [&>div]:bg-blue-500" />
|
<Progress value={memoryUsagePercent} className="h-2 [&>div]:bg-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
@@ -450,107 +482,94 @@ export function VirtualMachines() {
|
|||||||
{safeVMData.length === 0 ? (
|
{safeVMData.length === 0 ? (
|
||||||
<div className="text-center py-8 text-muted-foreground">No virtual machines found</div>
|
<div className="text-center py-8 text-muted-foreground">No virtual machines found</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-3">
|
||||||
{safeVMData.map((vm) => {
|
{safeVMData.map((vm) => {
|
||||||
const cpuPercent = (vm.cpu * 100).toFixed(1)
|
const cpuPercent = (vm.cpu * 100).toFixed(1)
|
||||||
const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0"
|
const memPercent = vm.maxmem > 0 ? ((vm.mem / vm.maxmem) * 100).toFixed(1) : "0"
|
||||||
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 diskPercent = vm.maxdisk > 0 ? ((vm.disk / vm.maxdisk) * 100).toFixed(1) : "0"
|
||||||
|
const diskGB = (vm.disk / 1024 ** 3).toFixed(1)
|
||||||
|
const maxDiskGB = (vm.maxdisk / 1024 ** 3).toFixed(1)
|
||||||
const typeBadge = getTypeBadge(vm.type)
|
const typeBadge = getTypeBadge(vm.type)
|
||||||
const lxcIP = vm.type === "lxc" ? vmConfigs[vm.vmid] : null
|
const lxcIP = vm.type === "lxc" ? vmConfigs[vm.vmid] : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={vm.vmid}
|
key={vm.vmid}
|
||||||
className="p-6 rounded-lg border border-border bg-card/50 hover:bg-card/80 transition-colors cursor-pointer"
|
className="p-4 rounded-lg border border-border bg-card/50 hover:bg-card/80 transition-colors cursor-pointer"
|
||||||
onClick={() => handleVMClick(vm)}
|
onClick={() => handleVMClick(vm)}
|
||||||
>
|
>
|
||||||
<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 gap-2 sm:gap-3 mb-3">
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<Server className="h-6 w-6 text-muted-foreground flex-shrink-0" />
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<div className="font-semibold text-foreground text-lg flex items-center flex-wrap gap-2">
|
|
||||||
<span className="truncate">{vm.name}</span>
|
|
||||||
<Badge variant="outline" className={`text-xs flex-shrink-0 ${typeBadge.color}`}>
|
|
||||||
{typeBadge.label}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<div className="text-sm text-muted-foreground">ID: {vm.vmid}</div>
|
<Badge variant="outline" className={`text-xs flex-shrink-0 ${getStatusColor(vm.status)}`}>
|
||||||
{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>
|
|
||||||
|
|
||||||
<Badge
|
|
||||||
variant="outline"
|
|
||||||
className={`${getStatusColor(vm.status)} flex-shrink-0 self-start sm:self-center`}
|
|
||||||
>
|
|
||||||
{getStatusIcon(vm.status)}
|
{getStatusIcon(vm.status)}
|
||||||
{vm.status.toUpperCase()}
|
{vm.status.toUpperCase()}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<Badge variant="outline" className={`text-xs flex-shrink-0 ${typeBadge.color}`}>
|
||||||
|
{typeBadge.icon}
|
||||||
|
{typeBadge.label}
|
||||||
|
</Badge>
|
||||||
|
<span className="font-semibold text-foreground">{vm.name}</span>
|
||||||
|
<span className="text-sm text-muted-foreground">ID: {vm.vmid}</span>
|
||||||
|
{lxcIP && (
|
||||||
|
<span
|
||||||
|
className={`text-sm flex items-center gap-1 ${lxcIP === "DHCP" ? "text-yellow-500" : "text-green-500"}`}
|
||||||
|
>
|
||||||
|
<Network className="h-3 w-3" />
|
||||||
|
{lxcIP}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className="text-sm text-muted-foreground ml-auto">Uptime: {formatUptime(vm.uptime)}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-muted-foreground mb-2">CPU Usage</div>
|
<div className="text-xs text-muted-foreground mb-1">CPU Usage</div>
|
||||||
<div className="text-lg font-semibold text-foreground mb-1">{cpuPercent}%</div>
|
<div className={`text-sm font-semibold mb-1 ${getUsageColor(Number.parseFloat(cpuPercent))}`}>
|
||||||
<Progress value={Number.parseFloat(cpuPercent)} className="h-2 [&>div]:bg-blue-500" />
|
{cpuPercent}%
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={Number.parseFloat(cpuPercent)}
|
||||||
|
className={`h-1.5 ${getProgressColor(Number.parseFloat(cpuPercent))}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-muted-foreground mb-2">Memory Usage</div>
|
<div className="text-xs text-muted-foreground mb-1">Memory</div>
|
||||||
<div className="text-lg font-semibold text-foreground mb-1">
|
<div className={`text-sm font-semibold mb-1 ${getUsageColor(Number.parseFloat(memPercent))}`}>
|
||||||
{memGB} / {maxMemGB} GB
|
{memGB} / {maxMemGB} GB
|
||||||
</div>
|
</div>
|
||||||
<Progress value={Number.parseFloat(memPercent)} className="h-2 [&>div]:bg-blue-500" />
|
<Progress
|
||||||
|
value={Number.parseFloat(memPercent)}
|
||||||
|
className={`h-1.5 ${getProgressColor(Number.parseFloat(memPercent))}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden md:block">
|
<div className="col-span-2 md:col-span-1">
|
||||||
<div className="text-sm text-muted-foreground mb-2">Disk I/O</div>
|
<div className="text-xs text-muted-foreground mb-1">Disk Usage</div>
|
||||||
<div className="text-sm font-semibold text-foreground">
|
<div className={`text-sm font-semibold mb-1 ${getUsageColor(Number.parseFloat(diskPercent))}`}>
|
||||||
<div className="flex items-center gap-1">
|
{diskGB} / {maxDiskGB} GB
|
||||||
<HardDrive className="h-3 w-3 text-green-500" />
|
|
||||||
<span className="text-green-500">↓ {formatBytes(vm.diskread)}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1 mt-1">
|
|
||||||
<HardDrive className="h-3 w-3 text-blue-500" />
|
|
||||||
<span className="text-blue-500">↑ {formatBytes(vm.diskwrite)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={Number.parseFloat(diskPercent)}
|
||||||
|
className={`h-1.5 ${getProgressColor(Number.parseFloat(diskPercent))}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden md:block">
|
<div className="col-span-2 md:col-span-1">
|
||||||
<div className="text-sm text-muted-foreground mb-2">Network I/O</div>
|
<div className="text-xs text-muted-foreground mb-1">Network I/O</div>
|
||||||
<div className="text-sm font-semibold text-foreground">
|
<div className="text-xs font-semibold">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Network className="h-3 w-3 text-green-500" />
|
|
||||||
<span className="text-green-500">↓ {formatBytes(vm.netin)}</span>
|
<span className="text-green-500">↓ {formatBytes(vm.netin)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 mt-1">
|
<div className="flex items-center gap-1">
|
||||||
<Network className="h-3 w-3 text-blue-500" />
|
|
||||||
<span className="text-blue-500">↑ {formatBytes(vm.netout)}</span>
|
<span className="text-blue-500">↑ {formatBytes(vm.netout)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 pt-4 border-t border-border">
|
|
||||||
<div className="text-sm text-muted-foreground">Uptime</div>
|
|
||||||
<div className="text-lg font-semibold text-foreground">{formatUptime(vm.uptime)}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -576,6 +595,7 @@ export function VirtualMachines() {
|
|||||||
{selectedVM && (
|
{selectedVM && (
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<Badge variant="outline" className={`${getTypeBadge(selectedVM.type).color} flex-shrink-0`}>
|
<Badge variant="outline" className={`${getTypeBadge(selectedVM.type).color} flex-shrink-0`}>
|
||||||
|
{getTypeBadge(selectedVM.type).icon}
|
||||||
{getTypeBadge(selectedVM.type).label}
|
{getTypeBadge(selectedVM.type).label}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className={`${getStatusColor(selectedVM.status)} flex-shrink-0`}>
|
<Badge variant="outline" className={`${getStatusColor(selectedVM.status)} flex-shrink-0`}>
|
||||||
@@ -604,37 +624,37 @@ export function VirtualMachines() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">CPU Usage</div>
|
<div className="text-xs text-muted-foreground mb-1">CPU Usage</div>
|
||||||
<div
|
<div className={`font-semibold mb-1 ${getUsageColor(selectedVM.cpu * 100)}`}>
|
||||||
className={`font-semibold ${
|
|
||||||
(selectedVM.cpu * 100) > 80
|
|
||||||
? "text-red-500"
|
|
||||||
: selectedVM.cpu * 100 > 60
|
|
||||||
? "text-yellow-500"
|
|
||||||
: "text-green-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{(selectedVM.cpu * 100).toFixed(1)}%
|
{(selectedVM.cpu * 100).toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={selectedVM.cpu * 100}
|
||||||
|
className={`h-1.5 ${getProgressColor(selectedVM.cpu * 100)}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">Memory</div>
|
<div className="text-xs text-muted-foreground mb-1">Memory</div>
|
||||||
<div
|
<div
|
||||||
className={`font-semibold ${
|
className={`font-semibold mb-1 ${getUsageColor((selectedVM.mem / selectedVM.maxmem) * 100)}`}
|
||||||
((selectedVM.mem / selectedVM.maxmem) * 100) > 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
|
{(selectedVM.mem / 1024 ** 3).toFixed(1)} / {(selectedVM.maxmem / 1024 ** 3).toFixed(1)} GB
|
||||||
</div>
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={(selectedVM.mem / selectedVM.maxmem) * 100}
|
||||||
|
className={`h-1.5 ${getProgressColor((selectedVM.mem / selectedVM.maxmem) * 100)}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">Disk</div>
|
<div className="text-xs text-muted-foreground mb-1">Disk</div>
|
||||||
<div className="font-semibold text-foreground">
|
<div
|
||||||
|
className={`font-semibold mb-1 ${getUsageColor((selectedVM.disk / selectedVM.maxdisk) * 100)}`}
|
||||||
|
>
|
||||||
{(selectedVM.disk / 1024 ** 3).toFixed(1)} / {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)} GB
|
{(selectedVM.disk / 1024 ** 3).toFixed(1)} / {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)} GB
|
||||||
</div>
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={(selectedVM.disk / selectedVM.maxdisk) * 100}
|
||||||
|
className={`h-1.5 ${getProgressColor((selectedVM.disk / selectedVM.maxdisk) * 100)}`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs text-muted-foreground mb-1">Uptime</div>
|
<div className="text-xs text-muted-foreground mb-1">Uptime</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user