diff --git a/AppImage/components/network-card.tsx b/AppImage/components/network-card.tsx
index 06f8f95..bb658a2 100644
--- a/AppImage/components/network-card.tsx
+++ b/AppImage/components/network-card.tsx
@@ -2,9 +2,10 @@
import { Card, CardContent } from "./ui/card"
import { Badge } from "./ui/badge"
-import { Wifi, Zap } from "lucide-react"
+import { Wifi, Zap } from 'lucide-react'
import { useState, useEffect } from "react"
import { fetchApi } from "../lib/api-config"
+import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
interface NetworkCardProps {
interface_: {
@@ -59,39 +60,37 @@ const getVMTypeBadge = (vmType: string | undefined) => {
return { color: "bg-gray-500/10 text-gray-500 border-gray-500/20", label: "Unknown" }
}
-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]}`
-}
-
const formatSpeed = (speed: number): string => {
if (speed === 0) return "N/A"
if (speed >= 1000) return `${(speed / 1000).toFixed(1)} Gbps`
return `${speed} Mbps`
}
-const formatStorage = (bytes: number): string => {
- if (bytes === 0) return "0 B"
- const k = 1024
- const sizes = ["B", "KB", "MB", "GB", "TB", "PB"]
- const i = Math.floor(Math.log(bytes) / Math.log(k))
- const value = bytes / Math.pow(k, i)
- const decimals = value >= 10 ? 1 : 2
- return `${value.toFixed(decimals)} ${sizes[i]}`
-}
-
export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps) {
const typeBadge = getInterfaceTypeBadge(interface_.type)
const vmTypeBadge = interface_.vm_type ? getVMTypeBadge(interface_.vm_type) : null
+ const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">(getNetworkUnit())
+
const [trafficData, setTrafficData] = useState<{ received: number; sent: number }>({
received: 0,
sent: 0,
})
+ useEffect(() => {
+ const handleUnitChange = () => {
+ setNetworkUnit(getNetworkUnit())
+ }
+
+ window.addEventListener("networkUnitChanged", handleUnitChange)
+ window.addEventListener("storage", handleUnitChange)
+
+ return () => {
+ window.removeEventListener("networkUnitChanged", handleUnitChange)
+ window.removeEventListener("storage", handleUnitChange)
+ }
+ }, [])
+
useEffect(() => {
const fetchTrafficData = async () => {
try {
@@ -207,15 +206,15 @@ export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps
{interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm" ? (
<>
- ↓ {formatStorage(trafficData.received * 1024 * 1024 * 1024)}
+ ↓ {formatNetworkTraffic(trafficData.received * 1024 * 1024 * 1024, networkUnit)}
{" / "}
- ↑ {formatStorage(trafficData.sent * 1024 * 1024 * 1024)}
+ ↑ {formatNetworkTraffic(trafficData.sent * 1024 * 1024 * 1024, networkUnit)}
>
) : (
<>
- ↓ {formatBytes(interface_.bytes_recv)}
+ ↓ {formatNetworkTraffic(interface_.bytes_recv || 0, networkUnit)}
{" / "}
- ↑ {formatBytes(interface_.bytes_sent)}
+ ↑ {formatNetworkTraffic(interface_.bytes_sent || 0, networkUnit)}
>
)}
diff --git a/AppImage/components/network-metrics.tsx b/AppImage/components/network-metrics.tsx
index ff67ef7..c6737b9 100644
--- a/AppImage/components/network-metrics.tsx
+++ b/AppImage/components/network-metrics.tsx
@@ -9,6 +9,7 @@ import useSWR from "swr"
import { NetworkTrafficChart } from "./network-traffic-chart"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { fetchApi } from "../lib/api-config"
+import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
interface NetworkData {
interfaces: NetworkInterface[]
@@ -132,11 +133,6 @@ const fetcher = async (url: string): Promise => {
return fetchApi(url)
}
-const getUnitsSettings = (): "Bytes" | "Bits" => {
- if (typeof window === "undefined") return "Bytes"
- const raw = localStorage.getItem("proxmenux-network-unit")
- return raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes"
-}
export function NetworkMetrics() {
const {
@@ -155,10 +151,10 @@ export function NetworkMetrics() {
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
const [interfaceTotals, setInterfaceTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
- const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
+ const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">(() => getNetworkUnit())
useEffect(() => {
- setNetworkUnit(getUnitsSettings())
+ setNetworkUnit(getNetworkUnit())
const handleUnitChange = (e: CustomEvent) => {
setNetworkUnit(e.detail === "Bits" ? "Bits" : "Bytes")
@@ -210,8 +206,8 @@ export function NetworkMetrics() {
)
}
- const trafficInFormatted = formatStorage(networkTotals.received * 1024 * 1024 * 1024) // Convert GB to bytes
- const trafficOutFormatted = formatStorage(networkTotals.sent * 1024 * 1024 * 1024)
+ const trafficInFormatted = formatNetworkTraffic(networkTotals.received * 1024 * 1024 * 1024, networkUnit)
+ const trafficOutFormatted = formatNetworkTraffic(networkTotals.sent * 1024 * 1024 * 1024, networkUnit)
const packetsRecvK = networkData.traffic.packets_recv ? (networkData.traffic.packets_recv / 1000).toFixed(0) : "0"
const totalErrors = (networkData.traffic.errin || 0) + (networkData.traffic.errout || 0)
@@ -731,13 +727,6 @@ export function NetworkMetrics() {
const displayInterface = currentInterfaceData || selectedInterface
- console.log("[v0] Selected Interface:", selectedInterface.name)
- console.log("[v0] Selected Interface bytes_recv:", selectedInterface.bytes_recv)
- console.log("[v0] Selected Interface bytes_sent:", selectedInterface.bytes_sent)
- console.log("[v0] Display Interface bytes_recv:", displayInterface.bytes_recv)
- console.log("[v0] Display Interface bytes_sent:", displayInterface.bytes_sent)
- console.log("[v0] Modal Network Data available:", !!modalNetworkData)
-
return (
<>
{/* Basic Information */}
@@ -888,29 +877,32 @@ export function NetworkMetrics() {
)
- {/* Traffic Data - Top Row */}
-
Bytes Received
+
+ {networkUnit === "Bits" ? "Bits Received" : "Bytes Received"}
+
- {formatStorage(interfaceTotals.received * 1024 * 1024 * 1024)}
+ {formatNetworkTraffic(interfaceTotals.received * 1024 * 1024 * 1024, networkUnit)}
-
Bytes Sent
+
+ {networkUnit === "Bits" ? "Bits Sent" : "Bytes Sent"}
+
- {formatStorage(interfaceTotals.sent * 1024 * 1024 * 1024)}
+ {formatNetworkTraffic(interfaceTotals.sent * 1024 * 1024 * 1024, networkUnit)}
- {/* Network Traffic Chart - Full Width Below */}
@@ -951,15 +943,19 @@ export function NetworkMetrics() {
Traffic since last boot
-
Bytes Received
+
+ {networkUnit === "Bits" ? "Bits Received" : "Bytes Received"}
+
- {formatBytes(displayInterface.bytes_recv)}
+ {formatNetworkTraffic(displayInterface.bytes_recv || 0, networkUnit)}
-
Bytes Sent
+
+ {networkUnit === "Bits" ? "Bits Sent" : "Bytes Sent"}
+
- {formatBytes(displayInterface.bytes_sent)}
+ {formatNetworkTraffic(displayInterface.bytes_sent || 0, networkUnit)}
diff --git a/AppImage/components/network-traffic-chart.tsx b/AppImage/components/network-traffic-chart.tsx
index e3a6c8e..8515db8 100644
--- a/AppImage/components/network-traffic-chart.tsx
+++ b/AppImage/components/network-traffic-chart.tsx
@@ -4,6 +4,7 @@ import { useState, useEffect } from "react"
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
import { Loader2 } from 'lucide-react'
import { fetchApi } from "@/lib/api-config"
+import { getNetworkUnit } from "@/lib/format-network"
interface NetworkMetricsData {
time: string
@@ -47,7 +48,7 @@ export function NetworkTrafficChart({
interfaceName,
onTotalsCalculated,
refreshInterval = 60000,
- networkUnit = "Bytes", // Default to Bytes
+ networkUnit: networkUnitProp, // Rename prop to avoid conflict
}: NetworkTrafficChartProps) {
const [data, setData] = useState
([])
const [loading, setLoading] = useState(true)
@@ -57,11 +58,36 @@ export function NetworkTrafficChart({
netIn: true,
netOut: true,
})
+
+ const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">(
+ networkUnitProp || getNetworkUnit()
+ )
+
+ useEffect(() => {
+ const handleUnitChange = () => {
+ const newUnit = getNetworkUnit()
+ setNetworkUnit(newUnit)
+ }
+
+ window.addEventListener("networkUnitChanged", handleUnitChange)
+ window.addEventListener("storage", handleUnitChange)
+
+ return () => {
+ window.removeEventListener("networkUnitChanged", handleUnitChange)
+ window.removeEventListener("storage", handleUnitChange)
+ }
+ }, [])
+
+ useEffect(() => {
+ if (networkUnitProp) {
+ setNetworkUnit(networkUnitProp)
+ }
+ }, [networkUnitProp])
useEffect(() => {
setIsInitialLoad(true)
fetchMetrics()
- }, [timeframe, interfaceName, networkUnit]) // Added networkUnit to dependencies
+ }, [timeframe, interfaceName, networkUnit])
useEffect(() => {
if (refreshInterval > 0) {
diff --git a/AppImage/components/settings.tsx b/AppImage/components/settings.tsx
index 536f747..f5cc39b 100644
--- a/AppImage/components/settings.tsx
+++ b/AppImage/components/settings.tsx
@@ -10,6 +10,7 @@ import { APP_VERSION } from "./release-notes-modal"
import { getApiUrl, fetchApi } from "../lib/api-config"
import { TwoFactorSetup } from "./two-factor-setup"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
+import { getNetworkUnit } from "../lib/format-network"
interface ProxMenuxTool {
key: string
@@ -55,8 +56,7 @@ export function Settings() {
const [generatingToken, setGeneratingToken] = useState(false)
const [tokenCopied, setTokenCopied] = useState(false)
- // Network unit settings state
- const [networkUnitSettings, setNetworkUnitSettings] = useState("Bytes")
+ const [networkUnitSettings, setNetworkUnitSettings] = useState<"Bytes" | "Bits">("Bytes")
const [loadingUnitSettings, setLoadingUnitSettings] = useState(true)
useEffect(() => {
@@ -354,14 +354,23 @@ export function Settings() {
}
const changeNetworkUnit = (unit: string) => {
- localStorage.setItem("proxmenux-network-unit", unit)
- setNetworkUnitSettings(unit)
+ const networkUnit = unit as "Bytes" | "Bits"
+ localStorage.setItem("proxmenux-network-unit", networkUnit)
+ setNetworkUnitSettings(networkUnit)
+
// Dispatch custom event to notify other components
- window.dispatchEvent(new CustomEvent("networkUnitChanged", { detail: unit }))
+ window.dispatchEvent(new CustomEvent("networkUnitChanged", { detail: networkUnit }))
+
+ // Also dispatch storage event for backward compatibility
+ window.dispatchEvent(new StorageEvent("storage", {
+ key: "proxmenux-network-unit",
+ newValue: networkUnit,
+ url: window.location.href
+ }))
}
const getUnitsSettings = () => {
- const networkUnit = localStorage.getItem("proxmenux-network-unit") || "Bytes"
+ const networkUnit = getNetworkUnit()
setNetworkUnitSettings(networkUnit)
setLoadingUnitSettings(false)
}
diff --git a/AppImage/components/system-overview.tsx b/AppImage/components/system-overview.tsx
index 1cf67d5..8609355 100644
--- a/AppImage/components/system-overview.tsx
+++ b/AppImage/components/system-overview.tsx
@@ -9,6 +9,7 @@ import { NodeMetricsCharts } from "./node-metrics-charts"
import { NetworkTrafficChart } from "./network-traffic-chart"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
import { fetchApi } from "../lib/api-config"
+import { formatNetworkTraffic, getNetworkUnit } from "../lib/format-network"
interface SystemData {
cpu_usage: number
@@ -222,7 +223,7 @@ export function SystemOverview() {
if (data) setNetworkData(data)
}, 59000)
- setNetworkUnit(getUnitsSettings()) // Load initial setting
+ setNetworkUnit(getNetworkUnit()) // Load initial setting
const handleUnitChange = (e: CustomEvent) => {
setNetworkUnit(e.detail === "Bits" ? "Bits" : "Bytes")
@@ -314,24 +315,6 @@ export function SystemOverview() {
return (bytes / 1024 ** 3).toFixed(2)
}
- const formatStorage = (sizeInGB: number, unit: "Bytes" | "Bits" = "Bytes"): string => {
- let size = sizeInGB
- let suffix = "B"
-
- if (unit === "Bits") {
- size = size * 8
- suffix = "b"
- }
-
- if (size < 1) {
- return `${(size * 1024).toFixed(1)} M${suffix}`
- } else if (size > 999) {
- return `${(size / 1024).toFixed(2)} T${suffix}`
- } else {
- return `${size.toFixed(2)} G${suffix}`
- }
- }
-
const tempStatus = getTemperatureStatus(systemData.temperature)
const localStorage = proxmoxStorageData?.storage.find((s) => s.name === "local")
@@ -520,7 +503,7 @@ export function SystemOverview() {
Total Node Capacity:
- {formatStorage(totalCapacity)}
+ {formatNetworkTraffic(totalCapacity, "Bytes")}
@@ -938,11 +950,11 @@ export function VirtualMachines() {
- ↓ {formatBytes(vm.netin)}
+ ↓ {formatBytes(vm.netin, true)}
- ↑ {formatBytes(vm.netout)}
+ ↑ {formatBytes(vm.netout, true)}
@@ -1167,11 +1179,11 @@ export function VirtualMachines() {
↓
- {((selectedVM.netin || 0) / 1024 ** 2).toFixed(2)} MB
+ {formatNetworkTraffic(selectedVM.netin || 0, networkUnit)}
↑
- {((selectedVM.netout || 0) / 1024 ** 2).toFixed(2)} MB
+ {formatNetworkTraffic(selectedVM.netout || 0, networkUnit)}
diff --git a/AppImage/lib/format-network.ts b/AppImage/lib/format-network.ts
new file mode 100644
index 0000000..39e645b
--- /dev/null
+++ b/AppImage/lib/format-network.ts
@@ -0,0 +1,67 @@
+/**
+ * Utility functions for formatting network traffic data
+ * Supports conversion between Bytes and Bits based on user preferences
+ */
+
+export type NetworkUnit = 'Bytes' | 'Bits';
+
+/**
+ * Format network traffic value with appropriate unit
+ * @param bytes - Value in bytes
+ * @param unit - Target unit ('Bytes' or 'Bits')
+ * @param decimals - Number of decimal places (default: 2)
+ * @returns Formatted string with value and unit
+ */
+export function formatNetworkTraffic(
+ bytes: number,
+ unit: NetworkUnit = 'Bytes',
+ decimals: number = 2
+): string {
+ if (bytes === 0) return unit === 'Bits' ? '0 b' : '0 B';
+
+ const k = unit === 'Bits' ? 1000 : 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+
+ // For Bits: convert bytes to bits first (multiply by 8)
+ const value = unit === 'Bits' ? bytes * 8 : bytes;
+
+ const sizes = unit === 'Bits'
+ ? ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb']
+ : ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+
+ const i = Math.floor(Math.log(value) / Math.log(k));
+ const formattedValue = parseFloat((value / Math.pow(k, i)).toFixed(dm));
+
+ return `${formattedValue} ${sizes[i]}`;
+}
+
+/**
+ * Get the current network unit preference from localStorage
+ * @returns 'Bytes' or 'Bits'
+ */
+export function getNetworkUnit(): NetworkUnit {
+ if (typeof window === 'undefined') return 'Bytes';
+
+ const stored = localStorage.getItem('networkUnit');
+ return stored === 'Bits' ? 'Bits' : 'Bytes';
+}
+
+/**
+ * Get the label for network traffic based on current unit
+ * @param direction - 'received' or 'sent'
+ * @returns Label string
+ */
+export function getNetworkLabel(direction: 'received' | 'sent'): string {
+ const unit = getNetworkUnit();
+ const prefix = direction === 'received' ? 'Received' : 'Sent';
+ return unit === 'Bits' ? `${prefix}` : `${prefix}`;
+}
+
+/**
+ * Get the unit suffix for displaying in charts
+ * @returns Unit suffix string (e.g., 'GB' or 'Gb')
+ */
+export function getNetworkUnitSuffix(): string {
+ const unit = getNetworkUnit();
+ return unit === 'Bits' ? 'b' : 'B';
+}