mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-20 04:26:18 +00:00
Update AppImage
This commit is contained in:
@@ -1,60 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
import { Dialog, DialogContent } from "@radix-ui/react-dialog"
|
|
||||||
import { getUnitsSettings, formatNetworkTraffic, getNetworkLabel } from "@/lib/network-utils"
|
|
||||||
|
|
||||||
export function InterfaceDetailsModal({ interface_, onClose, timeframe }: InterfaceDetailsModalProps) {
|
|
||||||
const [metricsData, setMetricsData] = useState<any[]>([])
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
|
|
||||||
const handleSettingsChange = () => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('storage', handleSettingsChange)
|
|
||||||
window.addEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('storage', handleSettingsChange)
|
|
||||||
window.removeEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const totalReceived = metricsData.length > 0
|
|
||||||
? Math.max(0, (metricsData[metricsData.length - 1].netin || 0) - (metricsData[0].netin || 0))
|
|
||||||
: 0
|
|
||||||
|
|
||||||
const totalSent = metricsData.length > 0
|
|
||||||
? Math.max(0, (metricsData[metricsData.length - 1].netout || 0) - (metricsData[0].netout || 0))
|
|
||||||
: 0
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onOpenChange={onClose}>
|
|
||||||
<DialogContent className="max-w-5xl max-h-[90vh] overflow-y-auto">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-sm font-medium text-muted-foreground mb-2">
|
|
||||||
Network Traffic Statistics (Last 24 Hours)
|
|
||||||
</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">{getNetworkLabel(networkUnit, "received")}</p>
|
|
||||||
<p className="text-2xl font-bold text-green-500">{formatNetworkTraffic(totalReceived, networkUnit)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-muted-foreground">{getNetworkLabel(networkUnit, "sent")}</p>
|
|
||||||
<p className="text-2xl font-bold text-blue-500">{formatNetworkTraffic(totalSent, networkUnit)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { ArrowLeft, Loader2 } from 'lucide-react'
|
import { ArrowLeft, Loader2 } from "lucide-react"
|
||||||
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
|
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"
|
||||||
import { fetchApi } from "@/lib/api-config"
|
import { fetchApi } from "@/lib/api-config"
|
||||||
import { getUnitsSettings, formatNetworkTraffic } from "@/lib/network-utils"
|
|
||||||
|
|
||||||
interface MetricsViewProps {
|
interface MetricsViewProps {
|
||||||
vmid: number
|
vmid: number
|
||||||
@@ -85,7 +84,6 @@ const CustomDiskTooltip = ({ active, payload, label }: any) => {
|
|||||||
|
|
||||||
const CustomNetworkTooltip = ({ active, payload, label }: any) => {
|
const CustomNetworkTooltip = ({ active, payload, label }: any) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
const unit = networkUnit === "Bits" ? "Mb" : "MB"
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
|
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
|
||||||
<p className="text-sm font-semibold text-white mb-2">{label}</p>
|
<p className="text-sm font-semibold text-white mb-2">{label}</p>
|
||||||
@@ -94,7 +92,7 @@ const CustomNetworkTooltip = ({ active, payload, label }: any) => {
|
|||||||
<div key={index} className="flex items-center gap-2">
|
<div key={index} className="flex items-center gap-2">
|
||||||
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
|
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
|
||||||
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
|
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
|
||||||
<span className="text-sm font-semibold text-white">{entry.value} {unit}</span>
|
<span className="text-sm font-semibold text-white">{entry.value} MB</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -111,20 +109,10 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [hiddenDiskLines, setHiddenDiskLines] = useState<string[]>([])
|
const [hiddenDiskLines, setHiddenDiskLines] = useState<string[]>([])
|
||||||
const [hiddenNetworkLines, setHiddenNetworkLines] = useState<string[]>([])
|
const [hiddenNetworkLines, setHiddenNetworkLines] = useState<string[]>([])
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const settings = getUnitsSettings()
|
fetchMetrics()
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
}, [vmid, timeframe])
|
||||||
|
|
||||||
const handleStorageChange = () => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('storage', handleStorageChange)
|
|
||||||
return () => window.removeEventListener('storage', handleStorageChange)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const fetchMetrics = async () => {
|
const fetchMetrics = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -169,9 +157,6 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const netinValue = item.netin ? (item.netin / 1024 / 1024) : 0
|
|
||||||
const netoutValue = item.netout ? (item.netout / 1024 / 1024) : 0
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
time: timeLabel,
|
time: timeLabel,
|
||||||
timestamp: item.time,
|
timestamp: item.time,
|
||||||
@@ -179,8 +164,8 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
memory: item.mem ? Number(((item.mem / item.maxmem) * 100).toFixed(2)) : 0,
|
memory: item.mem ? Number(((item.mem / item.maxmem) * 100).toFixed(2)) : 0,
|
||||||
memoryGB: item.mem ? Number((item.mem / 1024 / 1024 / 1024).toFixed(2)) : 0,
|
memoryGB: item.mem ? Number((item.mem / 1024 / 1024 / 1024).toFixed(2)) : 0,
|
||||||
maxMemoryGB: item.maxmem ? Number((item.maxmem / 1024 / 1024 / 1024).toFixed(2)) : 0,
|
maxMemoryGB: item.maxmem ? Number((item.maxmem / 1024 / 1024 / 1024).toFixed(2)) : 0,
|
||||||
netin: networkUnit === "Bits" ? Number((netinValue * 8).toFixed(2)) : Number(netinValue.toFixed(2)),
|
netin: item.netin ? Number((item.netin / 1024 / 1024).toFixed(2)) : 0,
|
||||||
netout: networkUnit === "Bits" ? Number((netoutValue * 8).toFixed(2)) : Number(netoutValue.toFixed(2)),
|
netout: item.netout ? Number((item.netout / 1024 / 1024).toFixed(2)) : 0,
|
||||||
diskread: item.diskread ? Number((item.diskread / 1024 / 1024).toFixed(2)) : 0,
|
diskread: item.diskread ? Number((item.diskread / 1024 / 1024).toFixed(2)) : 0,
|
||||||
diskwrite: item.diskwrite ? Number((item.diskwrite / 1024 / 1024).toFixed(2)) : 0,
|
diskwrite: item.diskwrite ? Number((item.diskwrite / 1024 / 1024).toFixed(2)) : 0,
|
||||||
}
|
}
|
||||||
@@ -194,10 +179,6 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchMetrics()
|
|
||||||
}, [vmid, timeframe])
|
|
||||||
|
|
||||||
const formatXAxisTick = (tick: any) => {
|
const formatXAxisTick = (tick: any) => {
|
||||||
return tick
|
return tick
|
||||||
}
|
}
|
||||||
@@ -378,7 +359,7 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="text-foreground"
|
className="text-foreground"
|
||||||
tick={{ fill: "currentColor" }}
|
tick={{ fill: "currentColor" }}
|
||||||
label={{ value: networkUnit === "Bits" ? "Mb" : "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
|
label={{ value: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
|
||||||
domain={[0, "dataMax"]}
|
domain={[0, "dataMax"]}
|
||||||
/>
|
/>
|
||||||
<Tooltip content={<CustomNetworkTooltip />} />
|
<Tooltip content={<CustomNetworkTooltip />} />
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
import { Card, CardContent } from "./ui/card"
|
import { Card, CardContent } from "./ui/card"
|
||||||
import { Badge } from "./ui/badge"
|
import { Badge } from "./ui/badge"
|
||||||
import { Wifi, Zap } from 'lucide-react'
|
import { Wifi, Zap } from "lucide-react"
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { fetchApi } from "../lib/api-config"
|
import { fetchApi } from "../lib/api-config"
|
||||||
import { getUnitsSettings, formatNetworkTraffic } from "../lib/network-utils"
|
|
||||||
|
|
||||||
interface NetworkCardProps {
|
interface NetworkCardProps {
|
||||||
interface_: {
|
interface_: {
|
||||||
@@ -88,31 +87,11 @@ export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps
|
|||||||
const typeBadge = getInterfaceTypeBadge(interface_.type)
|
const typeBadge = getInterfaceTypeBadge(interface_.type)
|
||||||
const vmTypeBadge = interface_.vm_type ? getVMTypeBadge(interface_.vm_type) : null
|
const vmTypeBadge = interface_.vm_type ? getVMTypeBadge(interface_.vm_type) : null
|
||||||
|
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
|
||||||
|
|
||||||
const [trafficData, setTrafficData] = useState<{ received: number; sent: number }>({
|
const [trafficData, setTrafficData] = useState<{ received: number; sent: number }>({
|
||||||
received: 0,
|
received: 0,
|
||||||
sent: 0,
|
sent: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
|
|
||||||
const handleSettingsChange = () => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('storage', handleSettingsChange)
|
|
||||||
window.addEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('storage', handleSettingsChange)
|
|
||||||
window.removeEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTrafficData = async () => {
|
const fetchTrafficData = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -228,9 +207,9 @@ export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps
|
|||||||
<div className="font-medium text-foreground text-xs">
|
<div className="font-medium text-foreground text-xs">
|
||||||
{interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm" ? (
|
{interface_.status.toLowerCase() === "up" && interface_.vm_type !== "vm" ? (
|
||||||
<>
|
<>
|
||||||
<span className="text-green-500">↓ {formatNetworkTraffic(trafficData.received, networkUnit)}</span>
|
<span className="text-green-500">↓ {formatStorage(trafficData.received * 1024 * 1024 * 1024)}</span>
|
||||||
{" / "}
|
{" / "}
|
||||||
<span className="text-blue-500">↑ {formatNetworkTraffic(trafficData.sent, networkUnit)}</span>
|
<span className="text-blue-500">↑ {formatStorage(trafficData.sent * 1024 * 1024 * 1024)}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ const fetcher = async (url: string): Promise<NetworkData> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getUnitsSettings = (): "Bytes" | "Bits" => {
|
const getUnitsSettings = (): "Bytes" | "Bits" => {
|
||||||
const raw = localStorage.getItem("proxmenux-network-unit");
|
if (typeof window === "undefined") return "Bytes"
|
||||||
const networkUnit = raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes";
|
const raw = localStorage.getItem("proxmenux-network-unit")
|
||||||
return networkUnit;
|
return raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes"
|
||||||
};
|
}
|
||||||
|
|
||||||
export function NetworkMetrics() {
|
export function NetworkMetrics() {
|
||||||
const {
|
const {
|
||||||
@@ -154,13 +154,19 @@ export function NetworkMetrics() {
|
|||||||
const [modalTimeframe, setModalTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">("day")
|
const [modalTimeframe, setModalTimeframe] = useState<"hour" | "day" | "week" | "month" | "year">("day")
|
||||||
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
|
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 [interfaceTotals, setInterfaceTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
|
||||||
|
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes");
|
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const networkUnitSetting = getUnitsSettings();
|
setNetworkUnit(getUnitsSettings())
|
||||||
setNetworkUnit(networkUnitSetting);
|
|
||||||
}, []);
|
const handleUnitChange = (e: CustomEvent) => {
|
||||||
|
setNetworkUnit(e.detail === "Bits" ? "Bits" : "Bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("networkUnitChanged" as any, handleUnitChange)
|
||||||
|
return () => window.removeEventListener("networkUnitChanged" as any, handleUnitChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const { data: modalNetworkData } = useSWR<NetworkData>(selectedInterface ? "/api/network" : null, fetcher, {
|
const { data: modalNetworkData } = useSWR<NetworkData>(selectedInterface ? "/api/network" : null, fetcher, {
|
||||||
refreshInterval: 17000,
|
refreshInterval: 17000,
|
||||||
@@ -905,7 +911,6 @@ export function NetworkMetrics() {
|
|||||||
interfaceName={displayInterface.name}
|
interfaceName={displayInterface.name}
|
||||||
onTotalsCalculated={setInterfaceTotals}
|
onTotalsCalculated={setInterfaceTotals}
|
||||||
refreshInterval={60000}
|
refreshInterval={60000}
|
||||||
networkUnit={networkUnit}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,29 @@ interface NetworkTrafficChartProps {
|
|||||||
interfaceName?: string
|
interfaceName?: string
|
||||||
onTotalsCalculated?: (totals: { received: number; sent: number }) => void
|
onTotalsCalculated?: (totals: { received: number; sent: number }) => void
|
||||||
refreshInterval?: number // En milisegundos, por defecto 60000 (60 segundos)
|
refreshInterval?: number // En milisegundos, por defecto 60000 (60 segundos)
|
||||||
networkUnit?: string // Added networkUnit prop with default value
|
networkUnit?: "Bytes" | "Bits" // Added networkUnit prop
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomNetworkTooltip = ({ active, payload, label, networkUnit }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
|
||||||
|
<p className="text-sm font-semibold text-white mb-2">{label}</p>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
{payload.map((entry: any, index: number) => (
|
||||||
|
<div key={index} className="flex items-center gap-2">
|
||||||
|
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
|
||||||
|
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
|
||||||
|
<span className="text-sm font-semibold text-white">
|
||||||
|
{entry.value.toFixed(3)} {networkUnit === "Bits" ? "Gb" : "GB"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NetworkTrafficChart({
|
export function NetworkTrafficChart({
|
||||||
@@ -25,7 +47,7 @@ export function NetworkTrafficChart({
|
|||||||
interfaceName,
|
interfaceName,
|
||||||
onTotalsCalculated,
|
onTotalsCalculated,
|
||||||
refreshInterval = 60000,
|
refreshInterval = 60000,
|
||||||
networkUnit = "Bytes", // Added networkUnit prop with default value
|
networkUnit = "Bytes", // Default to Bytes
|
||||||
}: NetworkTrafficChartProps) {
|
}: NetworkTrafficChartProps) {
|
||||||
const [data, setData] = useState<NetworkMetricsData[]>([])
|
const [data, setData] = useState<NetworkMetricsData[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@@ -39,7 +61,7 @@ export function NetworkTrafficChart({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsInitialLoad(true)
|
setIsInitialLoad(true)
|
||||||
fetchMetrics()
|
fetchMetrics()
|
||||||
}, [timeframe, interfaceName])
|
}, [timeframe, interfaceName, networkUnit]) // Added networkUnit to dependencies
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refreshInterval > 0) {
|
if (refreshInterval > 0) {
|
||||||
@@ -49,7 +71,7 @@ export function NetworkTrafficChart({
|
|||||||
|
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}
|
}
|
||||||
}, [timeframe, interfaceName, refreshInterval])
|
}, [timeframe, interfaceName, refreshInterval, networkUnit]) // Added networkUnit to dependencies
|
||||||
|
|
||||||
const fetchMetrics = async () => {
|
const fetchMetrics = async () => {
|
||||||
if (isInitialLoad) {
|
if (isInitialLoad) {
|
||||||
@@ -121,16 +143,14 @@ export function NetworkTrafficChart({
|
|||||||
const netOutBytes = (item.netout || 0) * intervalSeconds
|
const netOutBytes = (item.netout || 0) * intervalSeconds
|
||||||
|
|
||||||
if (networkUnit === "Bits") {
|
if (networkUnit === "Bits") {
|
||||||
// Convert to Gigabits: bytes * 8 / 1024^3
|
|
||||||
return {
|
return {
|
||||||
time: timeLabel,
|
time: timeLabel,
|
||||||
timestamp: item.time,
|
timestamp: item.time,
|
||||||
netIn: Number((netInBytes * 8 / 1024 / 1024 / 1024).toFixed(4)),
|
netIn: Number(((netInBytes * 8) / 1024 / 1024 / 1024).toFixed(4)),
|
||||||
netOut: Number((netOutBytes * 8 / 1024 / 1024 / 1024).toFixed(4)),
|
netOut: Number(((netOutBytes * 8) / 1024 / 1024 / 1024).toFixed(4)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: Convert to Gigabytes
|
|
||||||
return {
|
return {
|
||||||
time: timeLabel,
|
time: timeLabel,
|
||||||
timestamp: item.time,
|
timestamp: item.time,
|
||||||
@@ -190,28 +210,6 @@ export function NetworkTrafficChart({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CustomNetworkTooltip = ({ active, payload, label }: any) => {
|
|
||||||
if (active && payload && payload.length) {
|
|
||||||
return (
|
|
||||||
<div className="bg-gray-900/95 backdrop-blur-sm border border-gray-700 rounded-lg p-3 shadow-xl">
|
|
||||||
<p className="text-sm font-semibold text-white mb-2">{label}</p>
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
{payload.map((entry: any, index: number) => (
|
|
||||||
<div key={index} className="flex items-center gap-2">
|
|
||||||
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: entry.color }} />
|
|
||||||
<span className="text-xs text-gray-300 min-w-[60px]">{entry.name}:</span>
|
|
||||||
<span className="text-sm font-semibold text-white">
|
|
||||||
{entry.value.toFixed(3)} {networkUnit === "Bits" ? "Gb" : "GB"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading && isInitialLoad) {
|
if (loading && isInitialLoad) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-[300px]">
|
<div className="flex items-center justify-center h-[300px]">
|
||||||
@@ -255,10 +253,15 @@ export function NetworkTrafficChart({
|
|||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="text-foreground"
|
className="text-foreground"
|
||||||
tick={{ fill: "currentColor", fontSize: 12 }}
|
tick={{ fill: "currentColor", fontSize: 12 }}
|
||||||
label={{ value: networkUnit === "Bits" ? "Gb" : "GB", angle: -90, position: "insideLeft", fill: "currentColor" }}
|
label={{
|
||||||
|
value: networkUnit === "Bits" ? "Gb" : "GB", // Dynamic label based on unit
|
||||||
|
angle: -90,
|
||||||
|
position: "insideLeft",
|
||||||
|
fill: "currentColor",
|
||||||
|
}}
|
||||||
domain={[0, "auto"]}
|
domain={[0, "auto"]}
|
||||||
/>
|
/>
|
||||||
<Tooltip content={<CustomNetworkTooltip />} />
|
<Tooltip content={<CustomNetworkTooltip networkUnit={networkUnit} />} /> // Pass networkUnit to tooltip
|
||||||
<Legend verticalAlign="top" height={36} content={renderLegend} />
|
<Legend verticalAlign="top" height={36} content={renderLegend} />
|
||||||
<Area
|
<Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Shield, Lock, User, AlertCircle, CheckCircle, Info, LogOut, Wrench, Pac
|
|||||||
import { APP_VERSION } from "./release-notes-modal"
|
import { APP_VERSION } from "./release-notes-modal"
|
||||||
import { getApiUrl, fetchApi } from "../lib/api-config"
|
import { getApiUrl, fetchApi } from "../lib/api-config"
|
||||||
import { TwoFactorSetup } from "./two-factor-setup"
|
import { TwoFactorSetup } from "./two-factor-setup"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" // Added Select components
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
|
||||||
|
|
||||||
interface ProxMenuxTool {
|
interface ProxMenuxTool {
|
||||||
key: string
|
key: string
|
||||||
@@ -55,33 +55,16 @@ export function Settings() {
|
|||||||
const [generatingToken, setGeneratingToken] = useState(false)
|
const [generatingToken, setGeneratingToken] = useState(false)
|
||||||
const [tokenCopied, setTokenCopied] = useState(false)
|
const [tokenCopied, setTokenCopied] = useState(false)
|
||||||
|
|
||||||
const [networkUnitSettings, setNetworkUnitSettings] = useState("Bytes");
|
// Network unit settings state
|
||||||
const [loadingUnitSettings, setLoadingUnitSettings] = useState(true);
|
const [networkUnitSettings, setNetworkUnitSettings] = useState("Bytes")
|
||||||
|
const [loadingUnitSettings, setLoadingUnitSettings] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkAuthStatus()
|
checkAuthStatus()
|
||||||
loadProxmenuxTools()
|
loadProxmenuxTools()
|
||||||
getUnitsSettings(); // Load network unit settings on mount
|
getUnitsSettings() // Load units settings on mount
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const changeNetworkUnit = (unit: string) => {
|
|
||||||
const settings = { networkUnit: unit }
|
|
||||||
localStorage.setItem('unitsSettings', JSON.stringify(settings))
|
|
||||||
localStorage.setItem("proxmenux-network-unit", unit); // Keep legacy for backwards compatibility
|
|
||||||
setNetworkUnitSettings(unit);
|
|
||||||
|
|
||||||
// Dispatch custom event to notify other components
|
|
||||||
window.dispatchEvent(new CustomEvent('unitsSettingsChanged', { detail: settings }))
|
|
||||||
window.dispatchEvent(new Event('storage'))
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUnitsSettings = () => {
|
|
||||||
const networkUnit =
|
|
||||||
localStorage.getItem("proxmenux-network-unit") || "Bytes";
|
|
||||||
setNetworkUnitSettings(networkUnit);
|
|
||||||
setLoadingUnitSettings(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkAuthStatus = async () => {
|
const checkAuthStatus = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(getApiUrl("/api/auth/status"))
|
const response = await fetch(getApiUrl("/api/auth/status"))
|
||||||
@@ -370,6 +353,19 @@ export function Settings() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeNetworkUnit = (unit: string) => {
|
||||||
|
localStorage.setItem("proxmenux-network-unit", unit)
|
||||||
|
setNetworkUnitSettings(unit)
|
||||||
|
// Dispatch custom event to notify other components
|
||||||
|
window.dispatchEvent(new CustomEvent("networkUnitChanged", { detail: unit }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUnitsSettings = () => {
|
||||||
|
const networkUnit = localStorage.getItem("proxmenux-network-unit") || "Bytes"
|
||||||
|
setNetworkUnitSettings(networkUnit)
|
||||||
|
setLoadingUnitSettings(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
@@ -671,6 +667,37 @@ export function Settings() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Network Units Settings */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Ruler className="h-5 w-5 text-green-500" />
|
||||||
|
<CardTitle>Network Units</CardTitle>
|
||||||
|
</div>
|
||||||
|
<CardDescription>Change how network traffic is displayed</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{loadingUnitSettings ? (
|
||||||
|
<div className="flex items-center justify-center py-8">
|
||||||
|
<div className="animate-spin h-8 w-8 border-4 border-green-500 border-t-transparent rounded-full" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-foreground flex items-center justify-between">
|
||||||
|
<div className="flex items-center">Network Unit Display</div>
|
||||||
|
<Select value={networkUnitSettings} onValueChange={changeNetworkUnit}>
|
||||||
|
<SelectTrigger className="w-28 h-8 text-xs">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Bytes">Bytes</SelectItem>
|
||||||
|
<SelectItem value="Bits">Bits</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* API Access Tokens */}
|
{/* API Access Tokens */}
|
||||||
{authEnabled && (
|
{authEnabled && (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -864,42 +891,6 @@ export function Settings() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ProxMenux Unit Settings */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Ruler className="h-5 w-5 text-green-500" />
|
|
||||||
<CardTitle>ProxMenux Unit Settings</CardTitle>
|
|
||||||
</div>
|
|
||||||
<CardDescription>Change settings related to units</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{loadingUnitSettings ? (
|
|
||||||
<div className="flex items-center justify-center py-8">
|
|
||||||
<div className="animate-spin h-8 w-8 border-4 border-green-500 border-t-transparent rounded-full" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-foreground flex items-center justify-between">
|
|
||||||
<div className="flex items-center">
|
|
||||||
Network Unit Display
|
|
||||||
</div>
|
|
||||||
<Select
|
|
||||||
value={networkUnitSettings}
|
|
||||||
onValueChange={changeNetworkUnit}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-28 h-8 text-xs">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Bits">Bits</SelectItem>
|
|
||||||
<SelectItem value="Bytes">Bytes</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* ProxMenux Optimizations */}
|
{/* ProxMenux Optimizations */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { NodeMetricsCharts } from "./node-metrics-charts"
|
|||||||
import { NetworkTrafficChart } from "./network-traffic-chart"
|
import { NetworkTrafficChart } from "./network-traffic-chart"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select"
|
||||||
import { fetchApi } from "../lib/api-config"
|
import { fetchApi } from "../lib/api-config"
|
||||||
import { getUnitsSettings } from "../lib/network-utils"
|
|
||||||
|
|
||||||
interface SystemData {
|
interface SystemData {
|
||||||
cpu_usage: number
|
cpu_usage: number
|
||||||
@@ -147,6 +146,12 @@ const fetchProxmoxStorageData = async (): Promise<ProxmoxStorageData | null> =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getUnitsSettings = (): "Bytes" | "Bits" => {
|
||||||
|
if (typeof window === "undefined") return "Bytes"
|
||||||
|
const raw = window.localStorage.getItem("proxmenux-network-unit")
|
||||||
|
return raw && raw.toLowerCase() === "bits" ? "Bits" : "Bytes"
|
||||||
|
}
|
||||||
|
|
||||||
export function SystemOverview() {
|
export function SystemOverview() {
|
||||||
const [systemData, setSystemData] = useState<SystemData | null>(null)
|
const [systemData, setSystemData] = useState<SystemData | null>(null)
|
||||||
const [vmData, setVmData] = useState<VMData[]>([])
|
const [vmData, setVmData] = useState<VMData[]>([])
|
||||||
@@ -162,25 +167,7 @@ export function SystemOverview() {
|
|||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [networkTimeframe, setNetworkTimeframe] = useState("day")
|
const [networkTimeframe, setNetworkTimeframe] = useState("day")
|
||||||
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
|
const [networkTotals, setNetworkTotals] = useState<{ received: number; sent: number }>({ received: 0, sent: 0 })
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes") // Added networkUnit state
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
|
|
||||||
const handleSettingsChange = () => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('storage', handleSettingsChange)
|
|
||||||
window.addEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('storage', handleSettingsChange)
|
|
||||||
window.removeEventListener('unitsSettingsChanged', handleSettingsChange)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAllData = async () => {
|
const fetchAllData = async () => {
|
||||||
@@ -235,11 +222,20 @@ export function SystemOverview() {
|
|||||||
if (data) setNetworkData(data)
|
if (data) setNetworkData(data)
|
||||||
}, 59000)
|
}, 59000)
|
||||||
|
|
||||||
|
setNetworkUnit(getUnitsSettings()) // Load initial setting
|
||||||
|
|
||||||
|
const handleUnitChange = (e: CustomEvent) => {
|
||||||
|
setNetworkUnit(e.detail === "Bits" ? "Bits" : "Bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("networkUnitChanged" as any, handleUnitChange)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(systemInterval)
|
clearInterval(systemInterval)
|
||||||
clearInterval(vmInterval)
|
clearInterval(vmInterval)
|
||||||
clearInterval(storageInterval)
|
clearInterval(storageInterval)
|
||||||
clearInterval(networkInterval)
|
clearInterval(networkInterval)
|
||||||
|
window.removeEventListener("networkUnitChanged" as any, handleUnitChange)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -318,13 +314,21 @@ export function SystemOverview() {
|
|||||||
return (bytes / 1024 ** 3).toFixed(2)
|
return (bytes / 1024 ** 3).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatStorage = (sizeInGB: number): string => {
|
const formatStorage = (sizeInGB: number, unit: "Bytes" | "Bits" = "Bytes"): string => {
|
||||||
if (sizeInGB < 1) {
|
let size = sizeInGB
|
||||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
let suffix = "B"
|
||||||
} else if (sizeInGB > 999) {
|
|
||||||
return `${(sizeInGB / 1024).toFixed(2)} TB`
|
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 {
|
} else {
|
||||||
return `${sizeInGB.toFixed(2)} GB`
|
return `${size.toFixed(2)} G${suffix}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,11 +471,9 @@ export function SystemOverview() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="bg-card border-border">
|
<Card className="bg-card border-border">
|
||||||
<CardHeader>
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-foreground flex items-center">
|
<CardTitle className="text-sm font-medium text-muted-foreground">Temperature</CardTitle>
|
||||||
<Thermometer className="h-5 w-5 mr-2" />
|
<Thermometer className="h-4 w-4 text-muted-foreground" />
|
||||||
Temperature
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-xl lg:text-2xl font-bold text-foreground">
|
<div className="text-xl lg:text-2xl font-bold text-foreground">
|
||||||
@@ -621,29 +623,18 @@ export function SystemOverview() {
|
|||||||
<Network className="h-5 w-5 mr-2" />
|
<Network className="h-5 w-5 mr-2" />
|
||||||
Network Overview
|
Network Overview
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<Select value={networkTimeframe} onValueChange={setNetworkTimeframe}>
|
||||||
<Select value={networkTimeframe} onValueChange={setNetworkTimeframe}>
|
<SelectTrigger className="w-28 h-8 text-xs">
|
||||||
<SelectTrigger className="w-28 h-8 text-xs">
|
<SelectValue />
|
||||||
<SelectValue />
|
</SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectContent>
|
||||||
<SelectContent>
|
<SelectItem value="hour">1 Hour</SelectItem>
|
||||||
<SelectItem value="hour">1 Hour</SelectItem>
|
<SelectItem value="day">24 Hours</SelectItem>
|
||||||
<SelectItem value="day">24 Hours</SelectItem>
|
<SelectItem value="week">7 Days</SelectItem>
|
||||||
<SelectItem value="week">7 Days</SelectItem>
|
<SelectItem value="month">30 Days</SelectItem>
|
||||||
<SelectItem value="month">30 Days</SelectItem>
|
<SelectItem value="year">1 Year</SelectItem>
|
||||||
<SelectItem value="year">1 Year</SelectItem>
|
</SelectContent>
|
||||||
</SelectContent>
|
</Select>
|
||||||
</Select>
|
|
||||||
<Select value={networkUnit} onValueChange={setNetworkUnit}>
|
|
||||||
<SelectTrigger className="w-28 h-8 text-xs ml-2">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Bytes">Bytes</SelectItem>
|
|
||||||
<SelectItem value="Bits">Bits</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -700,21 +691,21 @@ export function SystemOverview() {
|
|||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-muted-foreground">Received:</span>
|
<span className="text-sm text-muted-foreground">Received:</span>
|
||||||
<span className="text-lg font-semibold text-green-500 flex items-center gap-1">
|
<span className="text-lg font-semibold text-green-500 flex items-center gap-1">
|
||||||
↓ {formatNetworkTraffic(networkTotals.received, networkUnit)}
|
↓ {formatStorage(networkTotals.received, networkUnit)}
|
||||||
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
|
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<span className="text-sm text-muted-foreground">Sent:</span>
|
<span className="text-sm text-muted-foreground">Sent:</span>
|
||||||
<span className="text-lg font-semibold text-blue-500 flex items-center gap-1">
|
<span className="text-lg font-semibold text-blue-500 flex items-center gap-1">
|
||||||
↑ {formatNetworkTraffic(networkTotals.sent, networkUnit)}
|
↑ {formatStorage(networkTotals.sent, networkUnit)}
|
||||||
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
|
<span className="text-xs text-muted-foreground">({getTimeframeLabel(networkTimeframe)})</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pt-3 border-t border-border">
|
<div className="pt-3 border-t border-border">
|
||||||
<NetworkTrafficChart timeframe={networkTimeframe} onTotalsCalculated={setNetworkTotals} />
|
<NetworkTrafficChart timeframe={networkTimeframe} onTotalsCalculated={setNetworkTotals} networkUnit={networkUnit} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -28,41 +28,6 @@ import { MetricsView } from "./metrics-dialog"
|
|||||||
import { formatStorage } from "@/lib/utils" // Import formatStorage utility
|
import { formatStorage } from "@/lib/utils" // Import formatStorage utility
|
||||||
import { fetchApi } from "../lib/api-config"
|
import { fetchApi } from "../lib/api-config"
|
||||||
|
|
||||||
const getUnitsSettings = () => {
|
|
||||||
if (typeof window === 'undefined') return { networkUnit: 'Bytes' as const }
|
|
||||||
|
|
||||||
try {
|
|
||||||
const settings = localStorage.getItem('unitsSettings')
|
|
||||||
if (settings) {
|
|
||||||
const parsed = JSON.parse(settings)
|
|
||||||
return { networkUnit: parsed.networkUnit || 'Bytes' }
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[v0] Error reading units settings:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { networkUnit: 'Bytes' as const }
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatBytes = (bytes: number | undefined, unit: "Bytes" | "Bits" = "Bytes"): string => {
|
|
||||||
if (!bytes || bytes === 0) return unit === "Bits" ? "0 b" : "0 B"
|
|
||||||
|
|
||||||
if (unit === "Bits") {
|
|
||||||
// Convert bytes to bits (*8)
|
|
||||||
const bits = bytes * 8
|
|
||||||
const k = 1000 // Use decimal for bits (networking standard)
|
|
||||||
const sizes = ["b", "Kb", "Mb", "Gb", "Tb"]
|
|
||||||
const i = Math.floor(Math.log(bits) / Math.log(k))
|
|
||||||
return `${(bits / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`
|
|
||||||
} else {
|
|
||||||
// Bytes mode (existing behavior)
|
|
||||||
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]}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface VMData {
|
interface VMData {
|
||||||
vmid: number
|
vmid: number
|
||||||
name: string
|
name: string
|
||||||
@@ -172,6 +137,13 @@ const fetcher = async (url: string) => {
|
|||||||
return fetchApi(url)
|
return fetchApi(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 formatUptime = (seconds: number) => {
|
const formatUptime = (seconds: number) => {
|
||||||
const days = Math.floor(seconds / 86400)
|
const days = Math.floor(seconds / 86400)
|
||||||
@@ -300,20 +272,6 @@ export function VirtualMachines() {
|
|||||||
const [selectedMetric, setSelectedMetric] = useState<string | null>(null)
|
const [selectedMetric, setSelectedMetric] = useState<string | null>(null)
|
||||||
const [ipsLoaded, setIpsLoaded] = useState(false)
|
const [ipsLoaded, setIpsLoaded] = useState(false)
|
||||||
const [loadingIPs, setLoadingIPs] = useState(false)
|
const [loadingIPs, setLoadingIPs] = useState(false)
|
||||||
const [networkUnit, setNetworkUnit] = useState<"Bytes" | "Bits">("Bytes")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
|
|
||||||
const handleStorageChange = () => {
|
|
||||||
const settings = getUnitsSettings()
|
|
||||||
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('storage', handleStorageChange)
|
|
||||||
return () => window.removeEventListener('storage', handleStorageChange)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchLXCIPs = async () => {
|
const fetchLXCIPs = async () => {
|
||||||
@@ -980,11 +938,11 @@ export function VirtualMachines() {
|
|||||||
<div className="text-sm font-semibold space-y-0.5">
|
<div className="text-sm font-semibold space-y-0.5">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Network className="h-3 w-3 text-green-500" />
|
<Network className="h-3 w-3 text-green-500" />
|
||||||
<span className="text-green-500">↓ {formatBytes(vm.netin, networkUnit)}</span>
|
<span className="text-green-500">↓ {formatBytes(vm.netin)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Network className="h-3 w-3 text-blue-500" />
|
<Network className="h-3 w-3 text-blue-500" />
|
||||||
<span className="text-blue-500">↑ {formatBytes(vm.netout, networkUnit)}</span>
|
<span className="text-blue-500">↑ {formatBytes(vm.netout)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1194,11 +1152,11 @@ export function VirtualMachines() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-sm text-green-500 flex items-center gap-1">
|
<div className="text-sm text-green-500 flex items-center gap-1">
|
||||||
<span>↓</span>
|
<span>↓</span>
|
||||||
<span>{formatBytes(selectedVM.diskread || 0, "Bytes")}</span>
|
<span>{((selectedVM.diskread || 0) / 1024 ** 2).toFixed(2)} MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-blue-500 flex items-center gap-1">
|
<div className="text-sm text-blue-500 flex items-center gap-1">
|
||||||
<span>↑</span>
|
<span>↑</span>
|
||||||
<span>{formatBytes(selectedVM.diskwrite || 0, "Bytes")}</span>
|
<span>{((selectedVM.diskwrite || 0) / 1024 ** 2).toFixed(2)} MB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1209,11 +1167,11 @@ export function VirtualMachines() {
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-sm text-green-500 flex items-center gap-1">
|
<div className="text-sm text-green-500 flex items-center gap-1">
|
||||||
<span>↓</span>
|
<span>↓</span>
|
||||||
<span>{formatBytes(selectedVM.netin || 0, networkUnit)}</span>
|
<span>{((selectedVM.netin || 0) / 1024 ** 2).toFixed(2)} MB</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-blue-500 flex items-center gap-1">
|
<div className="text-sm text-blue-500 flex items-center gap-1">
|
||||||
<span>↑</span>
|
<span>↑</span>
|
||||||
<span>{formatBytes(selectedVM.netout || 0, networkUnit)}</span>
|
<span>{((selectedVM.netout || 0) / 1024 ** 2).toFixed(2)} MB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
export const getUnitsSettings = () => {
|
|
||||||
if (typeof window === 'undefined') return { networkUnit: 'Bytes' as const }
|
|
||||||
|
|
||||||
try {
|
|
||||||
const settings = localStorage.getItem('unitsSettings')
|
|
||||||
if (settings) {
|
|
||||||
const parsed = JSON.parse(settings)
|
|
||||||
return { networkUnit: parsed.networkUnit || 'Bytes' }
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[v0] Error reading units settings:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { networkUnit: 'Bytes' as const }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const formatNetworkTraffic = (sizeInGB: number, unit: "Bytes" | "Bits" = "Bytes"): string => {
|
|
||||||
if (unit === "Bits") {
|
|
||||||
const sizeInGb = sizeInGB * 8 // Convert gigabytes to gigabits
|
|
||||||
|
|
||||||
// Use decimal (base 1000) for bits
|
|
||||||
if (sizeInGb < 0.001) {
|
|
||||||
return `${(sizeInGb * 1000 * 1000).toFixed(2)} Mb`
|
|
||||||
} else if (sizeInGb < 1) {
|
|
||||||
return `${(sizeInGb * 1000).toFixed(2)} Mb`
|
|
||||||
} else if (sizeInGb < 1000) {
|
|
||||||
return `${sizeInGb.toFixed(1)} Gb`
|
|
||||||
} else if (sizeInGb < 1000000) {
|
|
||||||
return `${(sizeInGb / 1000).toFixed(2)} Tb`
|
|
||||||
} else {
|
|
||||||
return `${(sizeInGb / 1000000).toFixed(2)} Pb`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Bytes mode - use binary base (1024)
|
|
||||||
if (sizeInGB < 1) {
|
|
||||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
|
||||||
} else if (sizeInGB < 1024) {
|
|
||||||
return `${sizeInGB.toFixed(1)} GB`
|
|
||||||
} else {
|
|
||||||
return `${(sizeInGB / 1024).toFixed(1)} TB`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNetworkLabel = (unit: "Bytes" | "Bits", type: "received" | "sent"): string => {
|
|
||||||
if (unit === "Bits") {
|
|
||||||
return type === "received" ? "Bits Received" : "Bits Sent"
|
|
||||||
}
|
|
||||||
return type === "received" ? "Bytes Received" : "Bytes Sent"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user