diff --git a/AppImage/components/metrics-dialog.tsx b/AppImage/components/metrics-dialog.tsx index a12d254..b18d72b 100644 --- a/AppImage/components/metrics-dialog.tsx +++ b/AppImage/components/metrics-dialog.tsx @@ -1,64 +1,67 @@ "use client" import { useState, useEffect } from "react" -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { ArrowLeft, Loader2 } from "lucide-react" import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts" -interface MetricsDialogProps { - open: boolean - onClose: () => void +interface MetricsViewProps { vmid: number vmName: string vmType: "qemu" | "lxc" metricType: "cpu" | "memory" | "network" | "disk" + onBack: () => void } const TIMEFRAME_OPTIONS = [ - { value: "hour", label: "1 Hora" }, - { value: "day", label: "24 Horas" }, - { value: "week", label: "7 Días" }, - { value: "month", label: "30 Días" }, - { value: "year", label: "1 Año" }, + { value: "hour", label: "1 Hour" }, + { value: "day", label: "24 Hours" }, + { value: "week", label: "7 Days" }, + { value: "month", label: "30 Days" }, + { value: "year", label: "1 Year" }, ] const METRIC_TITLES = { - cpu: "Uso de CPU", - memory: "Uso de Memoria", - network: "Tráfico de Red", - disk: "I/O de Disco", + cpu: "CPU Usage", + memory: "Memory Usage", + network: "Network Traffic", + disk: "Disk I/O", } -export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType }: MetricsDialogProps) { +export function MetricsView({ vmid, vmName, vmType, metricType, onBack }: MetricsViewProps) { const [timeframe, setTimeframe] = useState("week") const [data, setData] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { - if (open) { - fetchMetrics() - } - }, [open, vmid, timeframe]) + fetchMetrics() + }, [vmid, timeframe]) const fetchMetrics = async () => { setLoading(true) setError(null) + console.log("[v0] Fetching metrics for VMID:", vmid, "Timeframe:", timeframe, "Type:", vmType) + try { const response = await fetch(`http://localhost:8008/api/vms/${vmid}/metrics?timeframe=${timeframe}`) + console.log("[v0] Response status:", response.status) + if (!response.ok) { - throw new Error("Failed to fetch metrics") + const errorData = await response.json() + console.error("[v0] Error response:", errorData) + throw new Error(errorData.error || "Failed to fetch metrics") } const result = await response.json() + console.log("[v0] Metrics data received:", result) // Transform data for charts const transformedData = result.data.map((item: any) => ({ - time: new Date(item.time * 1000).toLocaleString("es-ES", { + time: new Date(item.time * 1000).toLocaleString("en-US", { month: "short", day: "numeric", hour: timeframe === "hour" ? "2-digit" : undefined, @@ -75,10 +78,11 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType diskwrite: item.diskwrite ? (item.diskwrite / 1024 / 1024).toFixed(2) : 0, })) + console.log("[v0] Transformed data:", transformedData.length, "points") setData(transformedData) - } catch (err) { + } catch (err: any) { console.error("[v0] Error fetching metrics:", err) - setError("Error al cargar las métricas") + setError(err.message || "Error loading metrics") } finally { setLoading(false) } @@ -104,7 +108,7 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType if (data.length === 0) { return (
-

No hay datos disponibles

+

No data available

) } @@ -139,7 +143,7 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType labelStyle={{ color: "hsl(var(--foreground))" }} /> - + ) @@ -159,8 +163,8 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType labelStyle={{ color: "hsl(var(--foreground))" }} /> - - + + ) @@ -180,19 +184,12 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType labelStyle={{ color: "hsl(var(--foreground))" }} /> - + @@ -203,42 +200,40 @@ export function MetricsDialog({ open, onClose, vmid, vmName, vmType, metricType } return ( - - - {/* Fixed Header */} - -
-
- -
- - {METRIC_TITLES[metricType]} - {vmName} - -

- VMID: {vmid} • Tipo: {vmType.toUpperCase()} -

-
+
+ {/* Fixed Header */} +
+
+
+ +
+

+ {METRIC_TITLES[metricType]} - {vmName} +

+

+ VMID: {vmid} • Type: {vmType.toUpperCase()} +

-
- + +
+
- {/* Scrollable Content */} -
{renderChart()}
- -
+ {/* Scrollable Content */} +
{renderChart()}
+ ) } diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 9f97efc..a1be30d 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -20,7 +20,7 @@ import { Container, } from "lucide-react" import useSWR from "swr" -import { MetricsDialog } from "./metrics-dialog" +import { MetricsView } from "./metrics-dialog" interface VMData { vmid: number @@ -185,8 +185,9 @@ export function VirtualMachines() { const [controlLoading, setControlLoading] = useState(false) const [detailsLoading, setDetailsLoading] = useState(false) const [vmConfigs, setVmConfigs] = useState>({}) - const [metricsDialogOpen, setMetricsDialogOpen] = useState(false) + const [currentView, setCurrentView] = useState<"main" | "metrics">("main") const [selectedMetric, setSelectedMetric] = useState<"cpu" | "memory" | "disk" | "network" | null>(null) + const [metricsDialogOpen, setMetricsDialogOpen] = useState(false) // Declare and initialize metricsDialogOpen useEffect(() => { const fetchLXCIPs = async () => { @@ -219,6 +220,8 @@ export function VirtualMachines() { const handleVMClick = async (vm: VMData) => { setSelectedVM(vm) + setCurrentView("main") + setSelectedMetric(null) setDetailsLoading(true) try { const response = await fetch(`/api/vms/${vm.vmid}`) @@ -233,6 +236,17 @@ export function VirtualMachines() { } } + const handleMetricClick = (metric: "cpu" | "memory" | "disk" | "network") => { + setSelectedMetric(metric) + setCurrentView("metrics") + setMetricsDialogOpen(true) // Open the metrics dialog + } + + const handleBackToMain = () => { + setCurrentView("main") + setSelectedMetric(null) + } + const handleVMControl = async (vmid: number, action: string) => { setControlLoading(true) try { @@ -707,368 +721,360 @@ export function VirtualMachines() { onOpenChange={() => { setSelectedVM(null) setVMDetails(null) + setCurrentView("main") + setSelectedMetric(null) + setMetricsDialogOpen(false) // Close the metrics dialog when the VM dialog is closed }} > - - -
- - {selectedVM?.name} -
- {selectedVM && ( -
- - {getTypeBadge(selectedVM.type).icon} - {getTypeBadge(selectedVM.type).label} - - - {selectedVM.status.toUpperCase()} - -
- )} -
-
- -
-
- {selectedVM && ( - <> -
-

- Basic Information -

-
-
-
Name
-
{selectedVM.name}
-
-
-
VMID
-
{selectedVM.vmid}
-
-
-
CPU Usage
-
{ - setSelectedMetric("cpu") - setMetricsDialogOpen(true) - }} - > -
- {(selectedVM.cpu * 100).toFixed(1)}% -
- -
-
-
-
Memory
-
{ - setSelectedMetric("memory") - setMetricsDialogOpen(true) - }} - > -
- {(selectedVM.mem / 1024 ** 3).toFixed(1)} / {(selectedVM.maxmem / 1024 ** 3).toFixed(1)} GB -
- -
-
-
-
Disk
-
{ - setSelectedMetric("disk") - setMetricsDialogOpen(true) - }} - > -
- {(selectedVM.disk / 1024 ** 3).toFixed(1)} / {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)}{" "} - GB -
- -
-
-
-
Uptime
-
{formatUptime(selectedVM.uptime)}
-
-
-
Disk I/O
-
{ - setSelectedMetric("disk") - setMetricsDialogOpen(true) - }} - > -
- - {((selectedVM.diskread || 0) / 1024 ** 2).toFixed(2)} MB -
-
- - {((selectedVM.diskwrite || 0) / 1024 ** 2).toFixed(2)} MB -
-
-
-
-
Network I/O
-
{ - setSelectedMetric("network") - setMetricsDialogOpen(true) - }} - > -
- - {((selectedVM.netin || 0) / 1024 ** 2).toFixed(2)} MB -
-
- - {((selectedVM.netout || 0) / 1024 ** 2).toFixed(2)} MB -
-
-
-
+ {currentView === "main" ? ( + <> + + +
+ + {selectedVM?.name}
+ {selectedVM && ( +
+ + {getTypeBadge(selectedVM.type).icon} + {getTypeBadge(selectedVM.type).label} + + + {selectedVM.status.toUpperCase()} + +
+ )} +
+
- {detailsLoading ? ( -
Loading configuration...
- ) : vmDetails?.config ? ( +
+
+ {selectedVM && ( <>

- Resources + Basic Information

- {vmDetails.config.cores && ( -
-
CPU Cores
-
{vmDetails.config.cores}
+
+
Name
+
{selectedVM.name}
+
+
+
VMID
+
{selectedVM.vmid}
+
+
+
CPU Usage
+
handleMetricClick("cpu")} + > +
+ {(selectedVM.cpu * 100).toFixed(1)}% +
+
- )} - {vmDetails.config.sockets && ( -
-
CPU Sockets
-
{vmDetails.config.sockets}
+
+
+
Memory
+
handleMetricClick("memory")} + > +
+ {(selectedVM.mem / 1024 ** 3).toFixed(1)} / {(selectedVM.maxmem / 1024 ** 3).toFixed(1)}{" "} + GB +
+
- )} - {vmDetails.config.memory && ( -
-
Memory
-
{vmDetails.config.memory} MB
+
+
+
Disk
+
handleMetricClick("disk")} + > +
+ {(selectedVM.disk / 1024 ** 3).toFixed(1)} /{" "} + {(selectedVM.maxdisk / 1024 ** 3).toFixed(1)} GB +
+
- )} - {vmDetails.config.swap && ( -
-
Swap
-
{vmDetails.config.swap} MB
-
- )} - {vmDetails.config.rootfs && ( -
-
Root Filesystem
-
- {vmDetails.config.rootfs} +
+
+
Uptime
+
{formatUptime(selectedVM.uptime)}
+
+
+
Disk I/O
+
handleMetricClick("disk")} + > +
+ + {((selectedVM.diskread || 0) / 1024 ** 2).toFixed(2)} MB +
+
+ + {((selectedVM.diskwrite || 0) / 1024 ** 2).toFixed(2)} MB
- )} - {Object.keys(vmDetails.config) - .filter((key) => key.match(/^(scsi|sata|ide|virtio)\d+$/)) - .map((diskKey) => ( -
-
- {diskKey.toUpperCase().replace(/(\d+)/, " $1")} -
-
- {vmDetails.config[diskKey]} -
+
+
+
Network I/O
+
handleMetricClick("network")} + > +
+ + {((selectedVM.netin || 0) / 1024 ** 2).toFixed(2)} MB
- ))} +
+ + {((selectedVM.netout || 0) / 1024 ** 2).toFixed(2)} MB +
+
+
-
-

- Network -

-
- {Object.keys(vmDetails.config) - .filter((key) => key.match(/^net\d+$/)) - .map((netKey) => ( -
-
- Network Interface {netKey.replace("net", "")} + {detailsLoading ? ( +
Loading configuration...
+ ) : vmDetails?.config ? ( + <> +
+

+ Resources +

+
+ {vmDetails.config.cores && ( +
+
CPU Cores
+
{vmDetails.config.cores}
-
- {vmDetails.config[netKey]} + )} + {vmDetails.config.sockets && ( +
+
CPU Sockets
+
{vmDetails.config.sockets}
-
- ))} - {vmDetails.config.nameserver && ( -
-
DNS Nameserver
-
{vmDetails.config.nameserver}
+ )} + {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]} +
+
+ ))}
- )} - {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"} - +
+

+ 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}
+
+ )}
- )} - {vmDetails.config.unprivileged !== undefined && ( -
-
Unprivileged
- - {vmDetails.config.unprivileged ? "Yes" : "No"} - +
+ +
+

+ 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}
+
+ )}
- )} - {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} - ) : null} - - )} -
-
+ )} +
+
-
-

- Control Actions -

-
- - - - -
-
+
+

+ Control Actions +

+
+ + + + +
+
+ + ) : ( + /* Render metrics view when currentView is "metrics" */ + selectedVM && + selectedMetric && ( + + ) + )} - - {/* MetricsDialog component usage */} - {selectedVM && selectedMetric && ( - { - setMetricsDialogOpen(false) - setSelectedMetric(null) - }} - vmid={selectedVM.vmid} - vmName={selectedVM.name} - vmType={selectedVM.type} - metricType={selectedMetric} - /> - )}
) }