From 32036ef64d12a803a769576d1918d4269a04fdb4 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Fri, 24 Oct 2025 21:54:34 +0200 Subject: [PATCH] Update AppImage --- AppImage/components/network-metrics.tsx | 370 +++++++++---------- AppImage/components/vm-lxc-network-chart.tsx | 195 ---------- 2 files changed, 166 insertions(+), 399 deletions(-) delete mode 100644 AppImage/components/vm-lxc-network-chart.tsx diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx index bb4d264..6d51af2 100644 --- a/AppImage/components/network-metrics.tsx +++ b/AppImage/components/network-metrics.tsx @@ -4,10 +4,9 @@ import { useState } from "react" import { Card, CardContent, CardHeader, CardTitle } from "./ui/card" import { Badge } from "./ui/badge" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog" -import { Wifi, Activity, Network, Router, AlertCircle, Zap, Info } from "lucide-react" +import { Wifi, Activity, Network, Router, AlertCircle, Zap } from "lucide-react" import useSWR from "swr" import { NetworkTrafficChart } from "./network-traffic-chart" -import { VMNetworkChart } from "./vm-lxc-network-chart" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" interface NetworkData { @@ -155,15 +154,12 @@ export function NetworkMetrics() { const [selectedInterface, setSelectedInterface] = useState(null) const [timeframe, setTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">("day") const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 }) - // REMOVED: const [vmLxcTraffic, setVmLxcTraffic] = useState>({}) const { data: interfaceHistoricalData } = useSWR(`/api/node/metrics?timeframe=${timeframe}`, fetcher, { refreshInterval: 30000, revalidateOnFocus: false, }) - // REMOVED: useEffect for VM/LXC traffic fetch - if (isLoading) { return (
@@ -195,10 +191,8 @@ export function NetworkMetrics() { ) } - // REMOVED: const trafficInFormatted = formatStorage(networkData.traffic.bytes_recv * 1024 * 1024 * 1024) // Convert GB to bytes - // REMOVED: const trafficOutFormatted = formatStorage(networkData.traffic.bytes_sent * 1024 * 1024 * 1024) - const trafficInFormatted = formatStorage(networkData.traffic.bytes_recv) - const trafficOutFormatted = formatStorage(networkData.traffic.bytes_sent) + const trafficInFormatted = formatStorage(networkTotals.received * 1024 * 1024 * 1024) // Convert GB to bytes + const trafficOutFormatted = formatStorage(networkTotals.sent * 1024 * 1024 * 1024) const packetsRecvK = networkData.traffic.packets_recv ? (networkData.traffic.packets_recv / 1000).toFixed(0) : "0" const totalErrors = (networkData.traffic.errin || 0) + (networkData.traffic.errout || 0) @@ -248,15 +242,11 @@ export function NetworkMetrics() {
Received: - - ↓ {formatStorage(networkData.traffic.bytes_recv)} - + ↓ {trafficInFormatted}
Sent: - - ↑ {formatStorage(networkData.traffic.bytes_sent)} - + ↑ {trafficOutFormatted}
@@ -353,113 +343,100 @@ export function NetworkMetrics() { - {/* VM & LXC Network Interfaces section */} - {networkData.vm_lxc_interfaces && networkData.vm_lxc_interfaces.length > 0 && ( - - - - - VM & LXC Network Interfaces - - {networkData.vm_lxc_active_count ?? 0} / {networkData.vm_lxc_total_count ?? 0} Active - - - - -
- {networkData.vm_lxc_interfaces.map((interface_, index) => { - const vmTypeBadge = getVMTypeBadge(interface_.vm_type) - // REMOVED: const trafficData = interface_.vmid && vmLxcTraffic[interface_.vmid] - // REMOVED: const bytesRecv = trafficData ? trafficData.received : interface_.bytes_recv - // REMOVED: const bytesSent = trafficData ? trafficData.sent : interface_.bytes_sent + {/* Physical Interfaces section */} + + + + + Physical Interfaces + + {networkData.physical_active_count ?? 0}/{networkData.physical_total_count ?? 0} Active + + + + +
+ {networkData.physical_interfaces.map((interface_, index) => { + const typeBadge = getInterfaceTypeBadge(interface_.type) - return ( -
setSelectedInterface(interface_)} - > - {/* First row: Icon, Name, VM/LXC Badge, VM Name, Status */} -
- -
-
{interface_.name}
- - {vmTypeBadge.label} - - {interface_.vm_name && ( -
→ {interface_.vm_name}
- )} -
- - {interface_.status.toUpperCase()} + return ( +
setSelectedInterface(interface_)} + > + {/* First row: Icon, Name, Type Badge, Status */} +
+ +
+
{interface_.name}
+ + {typeBadge.label}
- - {/* Second row: Details - Responsive layout */} -
-
-
VMID
-
{interface_.vmid ?? "N/A"}
-
- -
-
Speed
-
- - {formatSpeed(interface_.speed)} -
-
- -
-
Traffic
-
- ↓ {formatBytes(interface_.bytes_recv)} - {" / "} - ↑ {formatBytes(interface_.bytes_sent)} -
-
- - {interface_.mac_address && ( -
-
MAC
-
- {interface_.mac_address} -
-
- )} -
+ + {interface_.status.toUpperCase()} +
- ) - })} -
- - - )} + + {/* Second row: Details - Responsive layout */} +
+
+
IP Address
+
+ {interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"} +
+
+ +
+
Speed
+
+ + {formatSpeed(interface_.speed)} +
+
+ +
+
Traffic Statistics
+
+ ↓ {formatBytes(interface_.bytes_recv)} + {" / "} + ↑ {formatBytes(interface_.bytes_sent)} +
+
+ + {interface_.mac_address && ( +
+
MAC
+
+ {interface_.mac_address} +
+
+ )} +
+
+ ) + })} +
+ + {networkData.bridge_interfaces && networkData.bridge_interfaces.length > 0 && ( - + Bridge Interfaces - + {networkData.bridge_active_count ?? 0}/{networkData.bridge_total_count ?? 0} Active - - - Since Boot - @@ -540,7 +517,7 @@ export function NetworkMetrics() {
-
Traffic
+
Traffic Statistics
↓ {formatBytes(interface_.bytes_recv)} {" / "} @@ -565,96 +542,93 @@ export function NetworkMetrics() { )} - - - - - Physical Interfaces - - {networkData.physical_active_count ?? 0}/{networkData.physical_total_count ?? 0} Active - - - - Since Boot - - - - -
- {networkData.physical_interfaces.map((interface_, index) => { - const typeBadge = getInterfaceTypeBadge(interface_.type) + {/* VM & LXC Network Interfaces section */} + {networkData.vm_lxc_interfaces && networkData.vm_lxc_interfaces.length > 0 && ( + + + + + VM & LXC Network Interfaces + + {networkData.vm_lxc_active_count ?? 0} / {networkData.vm_lxc_total_count ?? 0} Active + + + + +
+ {networkData.vm_lxc_interfaces.map((interface_, index) => { + const vmTypeBadge = getVMTypeBadge(interface_.vm_type) - return ( -
setSelectedInterface(interface_)} - > - {/* First row: Icon, Name, Type Badge, Status */} -
- -
-
{interface_.name}
- - {typeBadge.label} + return ( +
setSelectedInterface(interface_)} + > + {/* First row: Icon, Name, VM/LXC Badge, VM Name, Status */} +
+ +
+
{interface_.name}
+ + {vmTypeBadge.label} + + {interface_.vm_name && ( +
→ {interface_.vm_name}
+ )} +
+ + {interface_.status.toUpperCase()}
- - {interface_.status.toUpperCase()} - -
- {/* Second row: Details - Responsive layout */} -
-
-
IP Address
-
- {interface_.addresses.length > 0 ? interface_.addresses[0].ip : "N/A"} + {/* Second row: Details - Responsive layout */} +
+
+
VMID
+
{interface_.vmid ?? "N/A"}
-
-
-
Speed
-
- - {formatSpeed(interface_.speed)} -
-
- -
-
Traffic
-
- ↓ {formatBytes(interface_.bytes_recv)} - {" / "} - ↑ {formatBytes(interface_.bytes_sent)} -
-
- - {interface_.mac_address && ( -
-
MAC
-
- {interface_.mac_address} +
+
Speed
+
+ + {formatSpeed(interface_.speed)}
- )} + +
+
Traffic Statistics
+
+ ↓ {formatBytes(interface_.bytes_recv)} + {" / "} + ↑ {formatBytes(interface_.bytes_sent)} +
+
+ + {interface_.mac_address && ( +
+
MAC
+
+ {interface_.mac_address} +
+
+ )} +
-
- ) - })} -
- - + ) + })} +
+ + + )} {/* Interface Details Modal */} setSelectedInterface(null)}> @@ -858,18 +832,6 @@ export function NetworkMetrics() {
- {/* Network chart for VM/LXC interfaces */} - {selectedInterface.type === "vm_lxc" && selectedInterface.vmid && selectedInterface.vm_type && ( -
-

Network Traffic History

- -
- )} - {/* Bond Information */} {selectedInterface.type === "bond" && selectedInterface.bond_slaves && (
diff --git a/AppImage/components/vm-lxc-network-chart.tsx b/AppImage/components/vm-lxc-network-chart.tsx deleted file mode 100644 index c971a5d..0000000 --- a/AppImage/components/vm-lxc-network-chart.tsx +++ /dev/null @@ -1,195 +0,0 @@ -"use client" - -import { useEffect, useState } from "react" -import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts" -import useSWR from "swr" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" - -interface VMNetworkChartProps { - vmid: number - vmType: "qemu" | "lxc" - initialTimeframe?: "hour" | "day" | "week" | "month" | "year" -} - -const fetcher = async (url: string) => { - const response = await fetch(url, { - method: "GET", - headers: { "Content-Type": "application/json" }, - signal: AbortSignal.timeout(10000), - }) - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`) - return response.json() -} - -const formatBytes = (bytes: number): string => { - if (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 VMNetworkChart({ vmid, vmType, initialTimeframe = "day" }: VMNetworkChartProps) { - const [timeframe, setTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">(initialTimeframe) - const [visibleLines, setVisibleLines] = useState({ received: true, sent: true }) - const [chartData, setChartData] = useState([]) - - const { data: rrdData } = useSWR(`/api/${vmType}/${vmid}/rrddata?timeframe=${timeframe}`, fetcher, { - refreshInterval: 30000, - revalidateOnFocus: false, - }) - - useEffect(() => { - if (!rrdData) return - - const transformedData = rrdData.map((point: any) => { - const timestamp = new Date(point.time * 1000) - const hours = timestamp.getHours().toString().padStart(2, "0") - const minutes = timestamp.getMinutes().toString().padStart(2, "0") - const month = (timestamp.getMonth() + 1).toString().padStart(2, "0") - const day = timestamp.getDate().toString().padStart(2, "0") - - let timeLabel = `${hours}:${minutes}` - if (timeframe === "week" || timeframe === "month") { - timeLabel = `${month}/${day}` - } else if (timeframe === "year") { - timeLabel = `${month}/${day}` - } - - // Calculate traffic in GB for the interval - const intervalSeconds = timeframe === "hour" ? 60 : timeframe === "day" ? 60 : timeframe === "week" ? 1800 : 3600 - const receivedGB = ((point.netin || 0) * intervalSeconds) / (1024 * 1024 * 1024) - const sentGB = ((point.netout || 0) * intervalSeconds) / (1024 * 1024 * 1024) - - return { - time: timeLabel, - received: receivedGB, - sent: sentGB, - } - }) - - setChartData(transformedData) - }, [rrdData, timeframe]) - - const toggleLine = (line: "received" | "sent") => { - setVisibleLines((prev) => ({ ...prev, [line]: !prev[line] })) - } - - const getTimeframeLabel = () => { - switch (timeframe) { - case "hour": - return "1 Hour" - case "day": - return "24 Hours" - case "week": - return "7 Days" - case "month": - return "30 Days" - case "year": - return "1 Year" - default: - return "24 Hours" - } - } - - const CustomTooltip = ({ active, payload }: any) => { - if (active && payload && payload.length) { - return ( -
-

{payload[0].payload.time}

- {payload.map((entry: any, index: number) => ( -
- - - {entry.name}: - - {formatBytes(entry.value * 1024 * 1024 * 1024)} -
- ))} -
- ) - } - return null - } - - return ( -
- {/* Timeframe Selector */} -
- -
- - {/* Interactive Legend */} -
- - -
- - {/* Chart */} - - - - - - - - - - - - - - - { - if (value === 0) return "0" - if (value < 0.01) return `${(value * 1024).toFixed(0)} MB` - return `${value.toFixed(2)} GB` - }} - label={{ value: "GB", angle: -90, position: "insideLeft", style: { fill: "hsl(var(--muted-foreground))" } }} - /> - } /> - {visibleLines.received && ( - - )} - {visibleLines.sent && ( - - )} - - -
- ) -}