mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-11-19 03:56:18 +00:00
Update AppImage
This commit is contained in:
54
AppImage/components/interface-details-modal.tsx
Normal file
54
AppImage/components/interface-details-modal.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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 handleStorageChange = () => {
|
||||||
|
const settings = getUnitsSettings()
|
||||||
|
setNetworkUnit(settings.networkUnit as "Bytes" | "Bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('storage', handleStorageChange)
|
||||||
|
return () => window.removeEventListener('storage', handleStorageChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
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,9 +3,10 @@
|
|||||||
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
|
||||||
@@ -84,6 +85,7 @@ 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>
|
||||||
@@ -92,7 +94,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} MB</span>
|
<span className="text-sm font-semibold text-white">{entry.value} {unit}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -109,10 +111,20 @@ 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(() => {
|
||||||
fetchMetrics()
|
const settings = getUnitsSettings()
|
||||||
}, [vmid, timeframe])
|
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)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const fetchMetrics = async () => {
|
const fetchMetrics = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -157,6 +169,9 @@ 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,
|
||||||
@@ -164,8 +179,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: item.netin ? Number((item.netin / 1024 / 1024).toFixed(2)) : 0,
|
netin: networkUnit === "Bits" ? Number((netinValue * 8).toFixed(2)) : Number(netinValue.toFixed(2)),
|
||||||
netout: item.netout ? Number((item.netout / 1024 / 1024).toFixed(2)) : 0,
|
netout: networkUnit === "Bits" ? Number((netoutValue * 8).toFixed(2)) : Number(netoutValue.toFixed(2)),
|
||||||
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,
|
||||||
}
|
}
|
||||||
@@ -179,6 +194,10 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMetrics()
|
||||||
|
}, [vmid, timeframe])
|
||||||
|
|
||||||
const formatXAxisTick = (tick: any) => {
|
const formatXAxisTick = (tick: any) => {
|
||||||
return tick
|
return tick
|
||||||
}
|
}
|
||||||
@@ -359,7 +378,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: "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
|
label={{ value: networkUnit === "Bits" ? "Mb" : "MB", angle: -90, position: "insideLeft", fill: "currentColor" }}
|
||||||
domain={[0, "dataMax"]}
|
domain={[0, "dataMax"]}
|
||||||
/>
|
/>
|
||||||
<Tooltip content={<CustomNetworkTooltip />} />
|
<Tooltip content={<CustomNetworkTooltip />} />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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, getNetworkLabel } from "../lib/network-utils"
|
||||||
|
|
||||||
interface NetworkCardProps {
|
interface NetworkCardProps {
|
||||||
interface_: {
|
interface_: {
|
||||||
@@ -83,55 +84,6 @@ const formatStorage = (bytes: number): string => {
|
|||||||
return `${value.toFixed(decimals)} ${sizes[i]}`
|
return `${value.toFixed(decimals)} ${sizes[i]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
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 formatNetworkTraffic = (sizeInGB: number, unit: "Bytes" | "Bits" = "Bytes"): string => {
|
|
||||||
if (unit === "Bits") {
|
|
||||||
// Convert GB to Gb (Gigabits)
|
|
||||||
const sizeInGb = sizeInGB * 8
|
|
||||||
|
|
||||||
if (sizeInGb < 0.001) {
|
|
||||||
// Less than 0.001 Gb, show in Mb
|
|
||||||
return `${(sizeInGb * 1024).toFixed(1)} Mb`
|
|
||||||
} else if (sizeInGb < 1) {
|
|
||||||
// Less than 1 Gb, show in Mb
|
|
||||||
return `${(sizeInGb * 1024).toFixed(1)} Mb`
|
|
||||||
} else if (sizeInGb < 1024) {
|
|
||||||
// Less than 1024 Gb, show in Gb
|
|
||||||
return `${sizeInGb.toFixed(1)} Gb`
|
|
||||||
} else {
|
|
||||||
// 1024 Gb or more, show in Tb
|
|
||||||
return `${(sizeInGb / 1024).toFixed(1)} Tb`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Bytes mode (existing behavior)
|
|
||||||
if (sizeInGB < 1) {
|
|
||||||
// Less than 1 GB, show in MB
|
|
||||||
return `${(sizeInGB * 1024).toFixed(1)} MB`
|
|
||||||
} else if (sizeInGB < 1024) {
|
|
||||||
// Less than 1024 GB, show in GB
|
|
||||||
return `${sizeInGB.toFixed(1)} GB`
|
|
||||||
} else {
|
|
||||||
// 1024 GB or more, show in TB
|
|
||||||
return `${(sizeInGB / 1024).toFixed(1)} TB`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NetworkCard({ interface_, timeframe, onClick }: NetworkCardProps) {
|
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
|
||||||
|
|||||||
46
AppImage/lib/network-utils.ts
Normal file
46
AppImage/lib/network-utils.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
if (sizeInGb < 0.001) {
|
||||||
|
return `${(sizeInGb * 1024).toFixed(1)} Mb`
|
||||||
|
} else 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`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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