"use client" import { useState } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" import { Button } from "./ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog" import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs" import { Server, Play, Square, Monitor, Cpu, MemoryStick, HardDrive, Network, Power, RotateCcw, Download, StopCircle, } from "lucide-react" import useSWR from "swr" interface VMData { vmid: number name: string status: string type: string cpu: number mem: number maxmem: number disk: number maxdisk: number uptime: number netin?: number netout?: number diskread?: number diskwrite?: number } interface VMConfig { cores?: number memory?: number swap?: number rootfs?: string net0?: string net1?: string net2?: string nameserver?: string searchdomain?: string onboot?: number unprivileged?: number features?: string ostype?: string arch?: string hostname?: string // VM specific sockets?: number scsi0?: string ide0?: string boot?: string [key: string]: any } interface VMDetails extends VMData { config?: VMConfig node?: string vm_type?: string } const fetcher = async (url: string) => { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", }, signal: AbortSignal.timeout(5000), }) if (!response.ok) { throw new Error(`Flask server responded with status: ${response.status}`) } const data = await response.json() return Array.isArray(data) ? data : [] } const formatBytes = (bytes: number | undefined): string => { if (!bytes || bytes === 0) return "0 B" const k = 1024 const sizes = ["B", "KB", "MB", "GB", "TB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}` } export function VirtualMachines() { const { data: vmData, error, isLoading, mutate, } = useSWR("/api/vms", fetcher, { refreshInterval: 30000, revalidateOnFocus: false, revalidateOnReconnect: true, }) const [selectedVM, setSelectedVM] = useState(null) const [vmDetails, setVMDetails] = useState(null) const [controlLoading, setControlLoading] = useState(false) const [detailsLoading, setDetailsLoading] = useState(false) const handleVMClick = async (vm: VMData) => { setSelectedVM(vm) setDetailsLoading(true) try { const response = await fetch(`/api/vms/${vm.vmid}`) if (response.ok) { const details = await response.json() setVMDetails(details) } } catch (error) { console.error("Error fetching VM details:", error) } finally { setDetailsLoading(false) } } const handleVMControl = async (vmid: number, action: string) => { setControlLoading(true) try { const response = await fetch(`/api/vms/${vmid}/control`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ action }), }) if (response.ok) { mutate() setSelectedVM(null) setVMDetails(null) } else { console.error("Failed to control VM") } } catch (error) { console.error("Error controlling VM:", error) } finally { setControlLoading(false) } } const handleDownloadLogs = async (vmid: number, vmName: string) => { try { const response = await fetch(`/api/vms/${vmid}/logs`) if (response.ok) { const data = await response.json() // Format logs as plain text let logText = `=== Logs for ${vmName} (VMID: ${vmid}) ===\n` logText += `Node: ${data.node}\n` logText += `Type: ${data.type}\n` logText += `Total lines: ${data.log_lines}\n` logText += `Generated: ${new Date().toISOString()}\n` logText += `\n${"=".repeat(80)}\n\n` if (data.logs && Array.isArray(data.logs)) { data.logs.forEach((log: any) => { if (typeof log === "object" && log.t) { logText += `${log.t}\n` } else if (typeof log === "string") { logText += `${log}\n` } }) } const blob = new Blob([logText], { type: "text/plain" }) const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url a.download = `${vmName}-${vmid}-logs.txt` a.click() URL.revokeObjectURL(url) } } catch (error) { console.error("Error downloading logs:", error) } } const getStatusColor = (status: string) => { switch (status) { case "running": return "bg-green-500/10 text-green-500 border-green-500/20" case "stopped": return "bg-red-500/10 text-red-500 border-red-500/20" default: return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" } } const getStatusIcon = (status: string) => { switch (status) { case "running": return case "stopped": return default: return null } } const getTypeBadge = (type: string) => { if (type === "lxc") { return { color: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "LXC" } } return { color: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "VM" } } const formatUptime = (seconds: number) => { const days = Math.floor(seconds / 86400) const hours = Math.floor((seconds % 86400) / 3600) const minutes = Math.floor((seconds % 3600) / 60) return `${days}d ${hours}h ${minutes}m` } return (
{/* VM Overview Cards */}
Total VMs & LXCs
{vmData.length}
{vmData.filter((vm) => vm.status === "running").length} Running {vmData.filter((vm) => vm.status === "stopped").length} Stopped

Virtual machines configured

Total CPU
{(vmData.reduce((sum, vm) => sum + (vm.cpu || 0), 0) * 100).toFixed(0)}%

Allocated CPU usage

Total Memory
{(vmData.reduce((sum, vm) => sum + (vm.maxmem || 0), 0) / 1024 ** 3).toFixed(1)} GB

Allocated RAM

Average Load
{vmData.filter((vm) => vm.status === "running").length > 0 ? ( (vmData.reduce((sum, vm) => sum + (vm.cpu || 0), 0) / vmData.filter((vm) => vm.status === "running").length) * 100 ).toFixed(0) : 0} %

Average resource utilization

{/* Virtual Machines List */} Virtual Machines & Containers {vmData.length === 0 ? (
No virtual machines found
) : (
{vmData.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) return (
handleVMClick(vm)} >
{vm.name} {typeBadge.label}
ID: {vm.vmid}
{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)}
) })}
)}
{/* VM Details Modal */} { setSelectedVM(null) setVMDetails(null) }} > {selectedVM?.name} - Details {selectedVM && ( Basic Resources Network Options {/* Basic Information Tab */}

Basic Information

Name
{selectedVM.name}
Type
{getTypeBadge(selectedVM.type).label}
VMID
{selectedVM.vmid}
Status
{selectedVM.status.toUpperCase()}
CPU Usage
{(selectedVM.cpu * 100).toFixed(1)}%
Memory
{(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)}
{/* Control Actions */}

Control Actions

{/* Download Logs */}
{/* Resources Tab */} {detailsLoading ? (
Loading configuration...
) : vmDetails?.config ? (
{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}
)} {vmDetails.config.scsi0 && (
SCSI Disk 0
{vmDetails.config.scsi0}
)} {vmDetails.config.ide0 && (
IDE Disk 0
{vmDetails.config.ide0}
)}
) : (
No configuration data available
)}
{/* Network Tab */} {detailsLoading ? (
Loading configuration...
) : vmDetails?.config ? (
{vmDetails.config.net0 && (
Network Interface 0
{vmDetails.config.net0}
)} {vmDetails.config.net1 && (
Network Interface 1
{vmDetails.config.net1}
)} {vmDetails.config.net2 && (
Network Interface 2
{vmDetails.config.net2}
)} {vmDetails.config.nameserver && (
DNS Nameserver
{vmDetails.config.nameserver}
)} {vmDetails.config.searchdomain && (
Search Domain
{vmDetails.config.searchdomain}
)} {vmDetails.config.hostname && (
Hostname
{vmDetails.config.hostname}
)}
) : (
No network configuration available
)}
{/* Options Tab */} {detailsLoading ? (
Loading configuration...
) : vmDetails?.config ? (
{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}
)}
) : (
No options available
)}
)}
) }