diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx index 8d3a714..894cec3 100644 --- a/AppImage/components/network-metrics.tsx +++ b/AppImage/components/network-metrics.tsx @@ -1,31 +1,111 @@ "use client" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area } from "recharts" -import { Wifi, Globe, Shield, Activity, Network, Router } from "lucide-react" +import { Wifi, Globe, Shield, Activity, Network, Router, AlertCircle } from "lucide-react" -const networkTraffic = [ - { time: "00:00", incoming: 45, outgoing: 32 }, - { time: "04:00", incoming: 52, outgoing: 28 }, - { time: "08:00", incoming: 78, outgoing: 65 }, - { time: "12:00", incoming: 65, outgoing: 45 }, - { time: "16:00", incoming: 82, outgoing: 58 }, - { time: "20:00", incoming: 58, outgoing: 42 }, - { time: "24:00", incoming: 43, outgoing: 35 }, -] +interface NetworkData { + interfaces: NetworkInterface[] + traffic: { + bytes_sent: number + bytes_recv: number + packets_sent?: number + packets_recv?: number + } +} -const connectionData = [ - { time: "00:00", connections: 1250 }, - { time: "04:00", connections: 980 }, - { time: "08:00", connections: 1850 }, - { time: "12:00", connections: 1650 }, - { time: "16:00", connections: 2100 }, - { time: "20:00", connections: 1580 }, - { time: "24:00", connections: 1320 }, -] +interface NetworkInterface { + name: string + status: string + addresses: Array<{ + ip: string + netmask: string + }> +} + +const fetchNetworkData = async (): Promise => { + try { + console.log("[v0] Fetching network data from Flask server...") + const response = await fetch("/api/network", { + 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() + console.log("[v0] Successfully fetched network data from Flask:", data) + return data + } catch (error) { + console.error("[v0] Failed to fetch network data from Flask server:", error) + return null + } +} export function NetworkMetrics() { + const [networkData, setNetworkData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + setLoading(true) + setError(null) + const result = await fetchNetworkData() + + if (!result) { + setError("Flask server not available. Please ensure the server is running.") + } else { + setNetworkData(result) + } + + setLoading(false) + } + + fetchData() + const interval = setInterval(fetchData, 30000) + return () => clearInterval(interval) + }, []) + + if (loading) { + return ( +
+
+
Loading network data...
+
+
+ ) + } + + if (error || !networkData) { + return ( +
+ + +
+ +
+
Flask Server Not Available
+
+ {error || "Unable to connect to the Flask server. Please ensure the server is running and try again."} +
+
+
+
+
+
+ ) + } + + const trafficInMB = (networkData.traffic.bytes_recv / (1024 * 1024)).toFixed(1) + const trafficOutMB = (networkData.traffic.bytes_sent / (1024 * 1024)).toFixed(1) + return (
{/* Network Overview Cards */} @@ -36,30 +116,30 @@ export function NetworkMetrics() { -
156 MB/s
+
{trafficInMB} MB
- ↓ 89 MB/s - ↑ 67 MB/s + ↓ {trafficInMB} MB + ↑ {trafficOutMB} MB
-

Peak: 245 MB/s at 16:30

+

Total data transferred

- Active Connections + Active Interfaces -
1,847
+
+ {networkData.interfaces.filter((i) => i.status === "up").length} +
- Normal + Online
-

- ↑ 12% from last hour -

+

{networkData.interfaces.length} total interfaces

@@ -75,7 +155,7 @@ export function NetworkMetrics() { Protected
-

247 blocked attempts today

+

System protected

@@ -83,97 +163,19 @@ export function NetworkMetrics() { - Latency + Packets -
12ms
+
+ {networkData.traffic.packets_recv ? (networkData.traffic.packets_recv / 1000).toFixed(0) : "N/A"}K +
- Excellent + Received
-

Avg response time

-
- - - - {/* Network Charts */} -
- - - - - Network Traffic (24h) - - - - - - - - - [`${value} MB/s`, name === "incoming" ? "Incoming" : "Outgoing"]} - /> - - - - - - - - - - - - Active Connections (24h) - - - - - - - - - [`${value}`, "Connections"]} - /> - - - +

Total packets received

@@ -188,44 +190,7 @@ export function NetworkMetrics() {
- {[ - { - name: "vmbr0", - type: "Bridge", - status: "up", - ip: "192.168.1.100/24", - speed: "1000 Mbps", - rx: "2.3 GB", - tx: "1.8 GB", - }, - { - name: "enp1s0", - type: "Physical", - status: "up", - ip: "192.168.1.101/24", - speed: "1000 Mbps", - rx: "1.2 GB", - tx: "890 MB", - }, - { - name: "vmbr1", - type: "Bridge", - status: "up", - ip: "10.0.0.1/24", - speed: "1000 Mbps", - rx: "456 MB", - tx: "234 MB", - }, - { - name: "tap101i0", - type: "TAP", - status: "up", - ip: "10.0.0.101/24", - speed: "1000 Mbps", - rx: "123 MB", - tx: "89 MB", - }, - ].map((interface_, index) => ( + {networkData.interfaces.map((interface_, index) => (
{interface_.name}
-
- {interface_.type} • {interface_.speed} -
+
Network Interface
IP Address
-
{interface_.ip}
-
- -
-
RX / TX
-
- {interface_.rx} / {interface_.tx} +
+ {interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"}
- +
+
Netmask
+
+ {interface_.addresses.length > 0 ? interface_.addresses[0].netmask : "N/A"} +
+
+ + {interface_.status.toUpperCase()}
diff --git a/AppImage/components/storage-metrics.tsx b/AppImage/components/storage-metrics.tsx index b22a03f..3b8de08 100644 --- a/AppImage/components/storage-metrics.tsx +++ b/AppImage/components/storage-metrics.tsx @@ -1,24 +1,111 @@ "use client" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Progress } from "./ui/progress" import { Badge } from "./ui/badge" -import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip } from "recharts" -import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity } from "lucide-react" +import { HardDrive, Database, Archive, AlertTriangle, CheckCircle, Activity, AlertCircle } from "lucide-react" -const storageData = [ - { name: "Used", value: 1250, color: "#3b82f6" }, // Blue - { name: "Available", value: 750, color: "#10b981" }, // Green -] +interface StorageData { + total: number + used: number + available: number + disks: DiskInfo[] +} -const diskPerformance = [ - { disk: "sda", read: 45, write: 32, iops: 1250 }, - { disk: "sdb", read: 67, write: 28, iops: 980 }, - { disk: "sdc", read: 23, write: 45, iops: 1100 }, - { disk: "nvme0n1", read: 156, write: 89, iops: 3400 }, -] +interface DiskInfo { + name: string + mountpoint: string + fstype: string + total: number + used: number + available: number + usage_percent: number + health: string + temperature: number +} + +const fetchStorageData = async (): Promise => { + try { + console.log("[v0] Fetching storage data from Flask server...") + const response = await fetch("/api/storage", { + 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() + console.log("[v0] Successfully fetched storage data from Flask:", data) + return data + } catch (error) { + console.error("[v0] Failed to fetch storage data from Flask server:", error) + return null + } +} export function StorageMetrics() { + const [storageData, setStorageData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + setLoading(true) + setError(null) + const result = await fetchStorageData() + + if (!result) { + setError("Flask server not available. Please ensure the server is running.") + } else { + setStorageData(result) + } + + setLoading(false) + } + + fetchData() + const interval = setInterval(fetchData, 30000) + return () => clearInterval(interval) + }, []) + + if (loading) { + return ( +
+
+
Loading storage data...
+
+
+ ) + } + + if (error || !storageData) { + return ( +
+ + +
+ +
+
Flask Server Not Available
+
+ {error || "Unable to connect to the Flask server. Please ensure the server is running and try again."} +
+
+
+
+
+
+ ) + } + + const usagePercent = storageData.total > 0 ? (storageData.used / storageData.total) * 100 : 0 + return (
{/* Storage Overview Cards */} @@ -29,21 +116,23 @@ export function StorageMetrics() { -
2.0 TB
- -

1.25 TB used • 750 GB available

+
{storageData.total.toFixed(1)} GB
+ +

+ {storageData.used.toFixed(1)} GB used • {storageData.available.toFixed(1)} GB available +

- VM & LXC Storage + Used Storage -
890 GB
- -

71.2% of allocated space

+
{storageData.used.toFixed(1)} GB
+ +

{usagePercent.toFixed(1)}% of total space

@@ -51,17 +140,17 @@ export function StorageMetrics() { - Backups + Available -
245 GB
+
{storageData.available.toFixed(1)} GB
- 12 Backups + {((storageData.available / storageData.total) * 100).toFixed(1)}% Free
-

Last backup: 2h ago

+

Available space

@@ -69,104 +158,17 @@ export function StorageMetrics() { - IOPS + Disks -
6.7K
+
{storageData.disks.length}
- Read: 4.2K - Write: 2.5K + + {storageData.disks.filter((d) => d.health === "healthy").length} Healthy +
-

Average operations/sec

-
- -
- - {/* Storage Distribution and Performance */} -
- - - - - Storage Distribution - - - -
-
-
- Used Storage - 1.25 TB (62.5%) -
-
-
-
-
- -
-
- Available Storage - 750 GB (37.5%) -
-
-
-
-
- -
-
-
-
-
- Used -
-
1.25 TB
-
-
-
-
- Available -
-
750 GB
-
-
-
-
-
-
- - - - - - Disk Performance - - - - - - - - - - - - - +

Storage devices

@@ -181,12 +183,7 @@ export function StorageMetrics() {
- {[ - { name: "/dev/sda", type: "HDD", size: "1TB", used: "650GB", health: "healthy", temp: "42°C" }, - { name: "/dev/sdb", type: "HDD", size: "1TB", used: "480GB", health: "healthy", temp: "38°C" }, - { name: "/dev/sdc", type: "SSD", size: "500GB", used: "120GB", health: "healthy", temp: "35°C" }, - { name: "/dev/nvme0n1", type: "NVMe", size: "1TB", used: "340GB", health: "warning", temp: "55°C" }, - ].map((disk, index) => ( + {storageData.disks.map((disk, index) => (
{disk.name}
- {disk.type} • {disk.size} + {disk.fstype} • {disk.mountpoint}
@@ -204,17 +201,14 @@ export function StorageMetrics() {
- {disk.used} / {disk.size} + {disk.used.toFixed(1)} GB / {disk.total.toFixed(1)} GB
- +
Temp
-
{disk.temp}
+
{disk.temperature}°C
({ - cpu_usage: Math.floor(Math.random() * 20) + 60, // 60-80% - memory_usage: Math.floor(Math.random() * 10) + 45, // 45-55% - memory_total: 32.0, - memory_used: 15.8 + Math.random() * 2, // 15.8-17.8 GB - temperature: Math.floor(Math.random() * 8) + 48, // 48-56°C - uptime: "15d 7h 23m", - load_average: [1.23, 1.45, 1.67], - hostname: "proxmox-demo", - node_id: "pve-demo-node", - timestamp: new Date().toISOString(), -}) +interface HistoricalData { + timestamp: string + cpu_usage: number + memory_used: number + memory_total: number +} -const demVMData: VMData[] = [ - { - vmid: 100, - name: "web-server-01", - status: "running", - cpu: 0.45, - mem: 8589934592, - maxmem: 17179869184, - disk: 53687091200, - maxdisk: 107374182400, - uptime: 1324800, - }, - { - vmid: 101, - name: "database-server", - status: "running", - cpu: 0.23, - mem: 4294967296, - maxmem: 8589934592, - disk: 26843545600, - maxdisk: 53687091200, - uptime: 864000, - }, - { - vmid: 102, - name: "backup-server", - status: "stopped", - cpu: 0, - mem: 0, - maxmem: 4294967296, - disk: 10737418240, - maxdisk: 21474836480, - uptime: 0, - }, -] +const historicalDataStore: HistoricalData[] = [] +const MAX_HISTORICAL_POINTS = 24 // Store 24 data points for 24h view -const fetchSystemData = async (): Promise<{ data: SystemData | null; isDemo: boolean }> => { +const fetchSystemData = async (): Promise => { try { - console.log("[v0] Attempting to fetch system data from Flask server...") + console.log("[v0] Fetching system data from Flask server...") const response = await fetch("/api/system", { method: "GET", headers: { "Content-Type": "application/json", }, - signal: AbortSignal.timeout(3000), // Reduced timeout for faster fallback + signal: AbortSignal.timeout(5000), }) if (!response.ok) { @@ -98,22 +59,36 @@ const fetchSystemData = async (): Promise<{ data: SystemData | null; isDemo: boo const data = await response.json() console.log("[v0] Successfully fetched real system data from Flask:", data) - return { data, isDemo: false } + + // Store historical data + historicalDataStore.push({ + timestamp: data.timestamp, + cpu_usage: data.cpu_usage, + memory_used: data.memory_used, + memory_total: data.memory_total, + }) + + // Keep only last MAX_HISTORICAL_POINTS + if (historicalDataStore.length > MAX_HISTORICAL_POINTS) { + historicalDataStore.shift() + } + + return data } catch (error) { - console.log("[v0] Flask server not available, using demo data for development") - return { data: generateDemoSystemData(), isDemo: true } + console.error("[v0] Failed to fetch system data from Flask server:", error) + return null } } -const fetchVMData = async (): Promise<{ data: VMData[]; isDemo: boolean }> => { +const fetchVMData = async (): Promise => { try { - console.log("[v0] Attempting to fetch VM data from Flask server...") + console.log("[v0] Fetching VM data from Flask server...") const response = await fetch("/api/vms", { method: "GET", headers: { "Content-Type": "application/json", }, - signal: AbortSignal.timeout(3000), // Reduced timeout for faster fallback + signal: AbortSignal.timeout(5000), }) if (!response.ok) { @@ -122,36 +97,29 @@ const fetchVMData = async (): Promise<{ data: VMData[]; isDemo: boolean }> => { const data = await response.json() console.log("[v0] Successfully fetched VM data from Flask:", data) - return { data: data.vms || [], isDemo: false } + return Array.isArray(data) ? data : data.vms || [] } catch (error) { - console.log("[v0] Flask server not available, using demo VM data") - return { data: demVMData, isDemo: true } + console.error("[v0] Failed to fetch VM data from Flask server:", error) + return [] } } -const generateChartData = (systemData?: SystemData) => { - const cpuData = [] - const memoryData = [] - - for (let i = 0; i < 24; i += 4) { - const time = `${i.toString().padStart(2, "0")}:00` - // Use real CPU data as base if available, otherwise use random data - const baseCpu = systemData?.cpu_usage || 60 - cpuData.push({ - time, - value: Math.max(0, Math.min(100, baseCpu + (Math.random() - 0.5) * 20)), - }) - - // Use real memory data as base if available - const baseMemory = systemData?.memory_used || 15.8 - const totalMemory = systemData?.memory_total || 32 - memoryData.push({ - time, - used: Math.max(0, baseMemory + (Math.random() - 0.5) * 4), - available: Math.max(0, totalMemory - (baseMemory + (Math.random() - 0.5) * 4)), - }) +const generateChartData = () => { + if (historicalDataStore.length === 0) { + return { cpuData: [], memoryData: [] } } + const cpuData = historicalDataStore.map((point) => ({ + time: new Date(point.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }), + value: point.cpu_usage, + })) + + const memoryData = historicalDataStore.map((point) => ({ + time: new Date(point.timestamp).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" }), + used: point.memory_used, + available: point.memory_total - point.memory_used, + })) + return { cpuData, memoryData } } @@ -160,29 +128,28 @@ export function SystemOverview() { const [vmData, setVmData] = useState([]) const [chartData, setChartData] = useState(generateChartData()) const [loading, setLoading] = useState(true) - const [isDemo, setIsDemo] = useState(false) // Added demo mode state + const [error, setError] = useState(null) useEffect(() => { const fetchData = async () => { try { setLoading(true) + setError(null) const [systemResult, vmResult] = await Promise.all([fetchSystemData(), fetchVMData()]) - setSystemData(systemResult.data) - setVmData(vmResult.data) - setIsDemo(systemResult.isDemo || vmResult.isDemo) // Set demo mode if either fetch is demo - - if (systemResult.data) { - setChartData(generateChartData(systemResult.data)) + if (!systemResult) { + setError("Flask server not available. Please ensure the server is running.") + setLoading(false) + return } + + setSystemData(systemResult) + setVmData(vmResult) + setChartData(generateChartData()) } catch (err) { console.error("[v0] Error fetching data:", err) - const fallbackData = generateDemoSystemData() - setSystemData(fallbackData) - setVmData(demVMData) - setChartData(generateChartData(fallbackData)) - setIsDemo(true) + setError("Failed to connect to Flask server. Please check your connection.") } finally { setLoading(false) } @@ -191,20 +158,13 @@ export function SystemOverview() { fetchData() const interval = setInterval(() => { - if (!isDemo) { - fetchData() - } else { - // In demo mode, just update with new random data - const newDemoData = generateDemoSystemData() - setSystemData(newDemoData) - setChartData(generateChartData(newDemoData)) - } - }, 30000) // Update every 30 seconds instead of 5 + fetchData() + }, 30000) // Update every 30 seconds return () => { clearInterval(interval) } - }, [isDemo]) + }, []) if (loading) { return ( @@ -229,7 +189,33 @@ export function SystemOverview() { ) } - if (!systemData) return null + if (error || !systemData) { + return ( +
+ + +
+ +
+
Flask Server Not Available
+
+ {error || "Unable to connect to the Flask server. Please ensure the server is running and try again."} +
+
+ Troubleshooting: +
    +
  • Check if the Flask server is running on the correct port
  • +
  • Verify network connectivity
  • +
  • Check server logs for errors
  • +
+
+
+
+
+
+
+ ) + } const vmStats = { total: vmData.length, @@ -239,6 +225,7 @@ export function SystemOverview() { } const getTemperatureStatus = (temp: number) => { + if (temp === 0) return { status: "N/A", color: "bg-gray-500/10 text-gray-500 border-gray-500/20" } if (temp < 60) return { status: "Normal", color: "bg-green-500/10 text-green-500 border-green-500/20" } if (temp < 75) return { status: "Warm", color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" } return { status: "Hot", color: "bg-red-500/10 text-red-500 border-red-500/20" } @@ -248,160 +235,156 @@ export function SystemOverview() { return (
- {isDemo && ( - - -
- - - Demo Mode: Flask server not available. Showing simulated data for development. In the - AppImage, this will connect to the real Flask server. - -
-
-
- )} - {/* Key Metrics Cards */}
- + CPU Usage -
{systemData.cpu_usage}%
+
{systemData.cpu_usage}%
-

- {isDemo ? "Simulated data" : "Real-time data from Flask server"} -

+

Real-time data from Flask server

- + Memory Usage -
- {systemData.memory_used.toFixed(1)} GB -
+
{systemData.memory_used.toFixed(1)} GB
-

+

{systemData.memory_usage.toFixed(1)}% of {systemData.memory_total} GB

- + Temperature -
{systemData.temperature}°C
+
+ {systemData.temperature === 0 ? "N/A" : `${systemData.temperature}°C`} +
{tempStatus.status}
-

- {isDemo ? "Simulated temperature" : "Live temperature reading"} +

+ {systemData.temperature === 0 ? "No sensor available" : "Live temperature reading"}

- + Active VMs -
{vmStats.running}
-
- +
{vmStats.running}
+
+ {vmStats.running} Running {vmStats.stopped > 0 && ( - + {vmStats.stopped} Stopped )}
-

Total: {vmStats.total} VMs configured

+

Total: {vmStats.total} VMs configured

{/* Charts Section */}
- + - CPU Usage (24h) + CPU Usage (Last {historicalDataStore.length} readings) - - - - - - - - - + {chartData.cpuData.length > 0 ? ( + + + + + + + + + + ) : ( +
+ Collecting data... Check back in a few minutes +
+ )}
- + - Memory Usage (24h) + Memory Usage (Last {historicalDataStore.length} readings) - - - - - - - - - - + {chartData.memoryData.length > 0 ? ( + + + + + + + + + + + ) : ( +
+ Collecting data... Check back in a few minutes +
+ )}
{/* System Information */} -
- +
+ @@ -410,76 +393,51 @@ export function SystemOverview() {
- Hostname: - {systemData.hostname} + Hostname: + {systemData.hostname}
- Uptime: - {systemData.uptime} + Uptime: + {systemData.uptime}
- Node ID: - {systemData.node_id} + Node ID: + {systemData.node_id}
- Last Update: - - {new Date(systemData.timestamp).toLocaleTimeString()} - + Last Update: + {new Date(systemData.timestamp).toLocaleTimeString()}
- - - - - Active Sessions - - - -
- Web Console: - - 3 active - -
-
- SSH Sessions: - - 1 active - -
-
- API Calls: - 247/hour -
-
-
- - + - Power & Performance + Performance Metrics
- Power State: - - Running - -
-
- Load Average: - + Load Average: + {systemData.load_average.map((avg) => avg.toFixed(2)).join(", ")}
- Boot Time: - 2.3s + Total Memory: + {systemData.memory_total} GB +
+
+ Available Memory: + + {(systemData.memory_total - systemData.memory_used).toFixed(1)} GB + +
+
+ CPU Cores: + {navigator.hardwareConcurrency || "N/A"}
diff --git a/AppImage/components/virtual-machines.tsx b/AppImage/components/virtual-machines.tsx index 8a8ea83..6bf1df1 100644 --- a/AppImage/components/virtual-machines.tsx +++ b/AppImage/components/virtual-machines.tsx @@ -1,147 +1,105 @@ "use client" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Progress } from "./ui/progress" -import { Server, Play, Square, RotateCcw, Monitor, Cpu, MemoryStick } from "lucide-react" +import { Server, Play, Square, Monitor, Cpu, MemoryStick, AlertCircle } from "lucide-react" -const virtualMachines = [ - { - id: 100, - name: "web-server-01", - type: "vm", - status: "running", - os: "Ubuntu 22.04", - cpu: 4, - memory: 8192, - disk: 50, - uptime: "15d 7h 23m", - cpuUsage: 45, - memoryUsage: 62, - diskUsage: 78, - }, - { - id: 101, - name: "database-01", - type: "vm", - status: "running", - os: "CentOS 8", - cpu: 8, - memory: 16384, - disk: 100, - uptime: "12d 3h 45m", - cpuUsage: 78, - memoryUsage: 85, - diskUsage: 45, - }, - { - id: 102, - name: "backup-server", - type: "vm", - status: "stopped", - os: "Debian 11", - cpu: 2, - memory: 4096, - disk: 200, - uptime: "0d 0h 0m", - cpuUsage: 0, - memoryUsage: 0, - diskUsage: 23, - }, - { - id: 103, - name: "dev-environment", - type: "vm", - status: "running", - os: "Ubuntu 20.04", - cpu: 6, - memory: 12288, - disk: 75, - uptime: "3d 12h 18m", - cpuUsage: 32, - memoryUsage: 58, - diskUsage: 67, - }, - { - id: 104, - name: "monitoring-01", - type: "vm", - status: "running", - os: "Alpine Linux", - cpu: 2, - memory: 2048, - disk: 25, - uptime: "8d 15h 32m", - cpuUsage: 15, - memoryUsage: 34, - diskUsage: 42, - }, - { - id: 105, - name: "mail-server", - type: "vm", - status: "stopped", - os: "Ubuntu 22.04", - cpu: 4, - memory: 8192, - disk: 60, - uptime: "0d 0h 0m", - cpuUsage: 0, - memoryUsage: 0, - diskUsage: 56, - }, - { - id: 200, - name: "nginx-proxy", - type: "lxc", - status: "running", - os: "Ubuntu 22.04 LXC", - cpu: 1, - memory: 512, - disk: 8, - uptime: "25d 14h 12m", - cpuUsage: 8, - memoryUsage: 45, - diskUsage: 32, - }, - { - id: 201, - name: "redis-cache", - type: "lxc", - status: "running", - os: "Alpine Linux LXC", - cpu: 1, - memory: 1024, - disk: 4, - uptime: "18d 6h 45m", - cpuUsage: 12, - memoryUsage: 38, - diskUsage: 28, - }, - { - id: 202, - name: "log-collector", - type: "lxc", - status: "stopped", - os: "Debian 11 LXC", - cpu: 1, - memory: 256, - disk: 2, - uptime: "0d 0h 0m", - cpuUsage: 0, - memoryUsage: 0, - diskUsage: 15, - }, -] +interface VMData { + vmid: number + name: string + status: string + cpu: number + mem: number + maxmem: number + disk: number + maxdisk: number + uptime: number +} + +const fetchVMData = async (): Promise => { + try { + console.log("[v0] Fetching VM data from Flask server...") + const response = await fetch("/api/vms", { + 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() + console.log("[v0] Successfully fetched VM data from Flask:", data) + return Array.isArray(data) ? data : [] + } catch (error) { + console.error("[v0] Failed to fetch VM data from Flask server:", error) + throw error + } +} export function VirtualMachines() { - const runningVMs = virtualMachines.filter((vm) => vm.status === "running").length - const stoppedVMs = virtualMachines.filter((vm) => vm.status === "stopped").length - const runningLXC = virtualMachines.filter((vm) => vm.type === "lxc" && vm.status === "running").length - const totalVMs = virtualMachines.filter((vm) => vm.type === "vm").length - const totalLXC = virtualMachines.filter((vm) => vm.type === "lxc").length - const totalCPU = virtualMachines.reduce((sum, vm) => sum + vm.cpu, 0) - const totalMemory = virtualMachines.reduce((sum, vm) => sum + vm.memory, 0) + const [vmData, setVmData] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true) + setError(null) + const result = await fetchVMData() + setVmData(result) + } catch (err) { + setError("Flask server not available. Please ensure the server is running.") + } finally { + setLoading(false) + } + } + + fetchData() + const interval = setInterval(fetchData, 30000) + return () => clearInterval(interval) + }, []) + + if (loading) { + return ( +
+
+
Loading VM data...
+
+
+ ) + } + + if (error) { + return ( +
+ + +
+ +
+
Flask Server Not Available
+
+ {error || "Unable to connect to the Flask server. Please ensure the server is running and try again."} +
+
+
+
+
+
+ ) + } + + const runningVMs = vmData.filter((vm) => vm.status === "running").length + const stoppedVMs = vmData.filter((vm) => vm.status === "stopped").length + const totalCPU = vmData.reduce((sum, vm) => sum + (vm.cpu || 0), 0) + const totalMemory = vmData.reduce((sum, vm) => sum + (vm.maxmem || 0), 0) const getStatusColor = (status: string) => { switch (status) { @@ -161,46 +119,48 @@ export function VirtualMachines() { case "stopped": return default: - return + return null } } + 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 & LXC + Total VMs -
{virtualMachines.length}
+
{vmData.length}
- {runningVMs} VMs - - - {runningLXC} LXC + {runningVMs} Running {stoppedVMs} Stopped
-

- {totalVMs} VMs • {totalLXC} LXC -

+

Virtual machines configured

- Total CPU Cores + Total CPU -
{totalCPU}
-

Allocated across all VMs and LXC containers

+
{(totalCPU * 100).toFixed(0)}%
+

Allocated CPU usage

@@ -210,8 +170,8 @@ export function VirtualMachines() { -
{(totalMemory / 1024).toFixed(1)} GB
-

Allocated RAM across all VMs and LXC containers

+
{(totalMemory / 1024 ** 3).toFixed(1)} GB
+

Allocated RAM

@@ -221,7 +181,9 @@ export function VirtualMachines() { -
42%
+
+ {runningVMs > 0 ? ((totalCPU / runningVMs) * 100).toFixed(0) : 0}% +

Average resource utilization

@@ -232,84 +194,72 @@ export function VirtualMachines() { - Virtual Machines & LXC Containers + Virtual Machines -
- {virtualMachines.map((vm) => ( -
-
-
- -
-
- {vm.name} - - {vm.type.toUpperCase()} + {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) + + return ( +
+
+
+ +
+
+ {vm.name} + + VM + +
+
ID: {vm.vmid}
+
+
+ +
+ + {getStatusIcon(vm.status)} + {vm.status.toUpperCase()}
-
- ID: {vm.id} • {vm.os} +
+ +
+
+
CPU Usage
+
{cpuPercent}%
+ +
+ +
+
Memory Usage
+
+ {memGB} GB / {maxMemGB} GB +
+ +
+ +
+
Uptime
+
{formatUptime(vm.uptime)}
- -
- - {getStatusIcon(vm.status)} - {vm.status.toUpperCase()} - -
-
- -
-
-
Resources
-
-
- CPU: - {vm.cpu} cores -
-
- Memory: - {(vm.memory / 1024).toFixed(1)} GB -
-
- Disk: - {vm.disk} GB -
-
-
- -
-
CPU Usage
-
{vm.cpuUsage}%
- -
- -
-
Memory Usage
-
{vm.memoryUsage}%
- -
- -
-
Uptime
-
{vm.uptime}
-
Disk: {vm.diskUsage}% used
-
-
-
- ))} -
+ ) + })} +
+ )}