Update network-metrics.tsx

This commit is contained in:
MacRimi
2025-10-26 11:41:18 +01:00
parent 811e2155a6
commit a4a455f31e

View File

@@ -157,6 +157,12 @@ 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 { data: modalNetworkData } = useSWR<NetworkData>(selectedInterface ? "/api/network" : null, fetcher, {
refreshInterval: 15000, // Refresh every 15 seconds when modal is open
revalidateOnFocus: false,
revalidateOnReconnect: true,
})
const { data: interfaceHistoricalData } = useSWR<any>(`/api/node/metrics?timeframe=${timeframe}`, fetcher, {
refreshInterval: 30000,
revalidateOnFocus: false,
@@ -220,7 +226,11 @@ export function NetworkMetrics() {
...(networkData.vm_lxc_interfaces || []),
]
const vmLxcInterfaces = networkData.vm_lxc_interfaces || []
const vmLxcInterfaces = (networkData.vm_lxc_interfaces || []).sort((a, b) => {
const vmidA = a.vmid ?? Number.MAX_SAFE_INTEGER
const vmidB = b.vmid ?? Number.MAX_SAFE_INTEGER
return vmidA - vmidB
})
const topInterface =
vmLxcInterfaces.length > 0
@@ -586,7 +596,7 @@ export function NetworkMetrics() {
</CardHeader>
<CardContent>
<div className="space-y-4">
{networkData.vm_lxc_interfaces.map((interface_, index) => {
{vmLxcInterfaces.map((interface_, index) => {
const vmTypeBadge = getVMTypeBadge(interface_.vm_type)
return (
@@ -688,32 +698,46 @@ export function NetworkMetrics() {
{selectedInterface && (
<div className="space-y-6">
{(() => {
// Find the current interface data from modalNetworkData if available
const currentInterfaceData = modalNetworkData
? [
...(modalNetworkData.physical_interfaces || []),
...(modalNetworkData.bridge_interfaces || []),
...(modalNetworkData.vm_lxc_interfaces || []),
].find((iface) => iface.name === selectedInterface.name)
: selectedInterface
const displayInterface = currentInterfaceData || selectedInterface
return (
<>
{/* Basic Information */}
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">Basic Information</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-muted-foreground">Interface Name</div>
<div className="font-medium">{selectedInterface.name}</div>
<div className="font-medium">{displayInterface.name}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Type</div>
<Badge variant="outline" className={getInterfaceTypeBadge(selectedInterface.type).color}>
{getInterfaceTypeBadge(selectedInterface.type).label}
<Badge variant="outline" className={getInterfaceTypeBadge(displayInterface.type).color}>
{getInterfaceTypeBadge(displayInterface.type).label}
</Badge>
</div>
{selectedInterface.type === "bridge" && selectedInterface.bridge_physical_interface && (
{displayInterface.type === "bridge" && displayInterface.bridge_physical_interface && (
<div className="col-span-2">
<div className="text-sm text-muted-foreground">Physical Interface</div>
<div className="font-medium text-blue-500 text-lg break-all">
{selectedInterface.bridge_physical_interface}
{displayInterface.bridge_physical_interface}
</div>
{selectedInterface.bridge_physical_interface.startsWith("bond") &&
networkData?.physical_interfaces && (
{displayInterface.bridge_physical_interface.startsWith("bond") &&
modalNetworkData?.physical_interfaces && (
<>
{(() => {
const bondInterface = networkData.physical_interfaces.find(
(iface) => iface.name === selectedInterface.bridge_physical_interface,
const bondInterface = modalNetworkData.physical_interfaces.find(
(iface) => iface.name === displayInterface.bridge_physical_interface,
)
if (bondInterface?.bond_slaves && bondInterface.bond_slaves.length > 0) {
return (
@@ -737,11 +761,11 @@ export function NetworkMetrics() {
})()}
</>
)}
{selectedInterface.bridge_bond_slaves && selectedInterface.bridge_bond_slaves.length > 0 && (
{displayInterface.bridge_bond_slaves && displayInterface.bridge_bond_slaves.length > 0 && (
<div className="mt-2">
<div className="text-sm text-muted-foreground mb-2">Bond Members</div>
<div className="flex flex-wrap gap-2">
{selectedInterface.bridge_bond_slaves.map((slave, idx) => (
{displayInterface.bridge_bond_slaves.map((slave, idx) => (
<Badge
key={idx}
variant="outline"
@@ -755,14 +779,14 @@ export function NetworkMetrics() {
)}
</div>
)}
{selectedInterface.type === "vm_lxc" && selectedInterface.vm_name && (
{displayInterface.type === "vm_lxc" && displayInterface.vm_name && (
<div className="col-span-2">
<div className="text-sm text-muted-foreground">VM/LXC Name</div>
<div className="font-medium text-orange-500 text-lg flex items-center gap-2">
{selectedInterface.vm_name}
{selectedInterface.vm_type && (
<Badge variant="outline" className={getVMTypeBadge(selectedInterface.vm_type).color}>
{getVMTypeBadge(selectedInterface.vm_type).label}
{displayInterface.vm_name}
{displayInterface.vm_type && (
<Badge variant="outline" className={getVMTypeBadge(displayInterface.vm_type).color}>
{getVMTypeBadge(displayInterface.vm_type).label}
</Badge>
)}
</div>
@@ -773,41 +797,41 @@ export function NetworkMetrics() {
<Badge
variant="outline"
className={
selectedInterface.status === "up"
displayInterface.status === "up"
? "bg-green-500/10 text-green-500 border-green-500/20"
: "bg-red-500/10 text-red-500 border-red-500/20"
}
>
{selectedInterface.status.toUpperCase()}
{displayInterface.status.toUpperCase()}
</Badge>
</div>
<div>
<div className="text-sm text-muted-foreground">Speed</div>
<div className="font-medium">{formatSpeed(selectedInterface.speed)}</div>
<div className="font-medium">{formatSpeed(displayInterface.speed)}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Duplex</div>
<div className="font-medium capitalize">{selectedInterface.duplex}</div>
<div className="font-medium capitalize">{displayInterface.duplex}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">MTU</div>
<div className="font-medium">{selectedInterface.mtu}</div>
<div className="font-medium">{displayInterface.mtu}</div>
</div>
{selectedInterface.mac_address && (
{displayInterface.mac_address && (
<div className="col-span-2">
<div className="text-sm text-muted-foreground">MAC Address</div>
<div className="font-medium font-mono">{selectedInterface.mac_address}</div>
<div className="font-medium font-mono">{displayInterface.mac_address}</div>
</div>
)}
</div>
</div>
{/* IP Addresses */}
{selectedInterface.addresses.length > 0 && (
{displayInterface.addresses.length > 0 && (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">IP Addresses</h3>
<div className="space-y-2">
{selectedInterface.addresses.map((addr, idx) => (
{displayInterface.addresses.map((addr, idx) => (
<div key={idx} className="flex items-center justify-between p-3 rounded-lg bg-muted/50">
<div>
<div className="font-medium font-mono">{addr.ip}</div>
@@ -820,7 +844,7 @@ export function NetworkMetrics() {
)}
{/* Network Traffic Statistics - Only show if interface is UP and NOT a VM interface */}
{selectedInterface.status.toLowerCase() === "up" && selectedInterface.vm_type !== "vm" ? (
{displayInterface.status.toLowerCase() === "up" && displayInterface.vm_type !== "vm" ? (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-4">
Network Traffic Statistics (
@@ -856,69 +880,73 @@ export function NetworkMetrics() {
<div className="bg-muted/30 rounded-lg p-4">
<NetworkTrafficChart
timeframe={modalTimeframe}
interfaceName={selectedInterface.name}
interfaceName={displayInterface.name}
onTotalsCalculated={setInterfaceTotals}
/>
</div>
</div>
</div>
) : selectedInterface.status.toLowerCase() === "up" && selectedInterface.vm_type === "vm" ? (
) : displayInterface.status.toLowerCase() === "up" && displayInterface.vm_type === "vm" ? (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-4">Traffic since last boot</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-muted-foreground">Bytes Received</div>
<div className="font-medium text-green-500 text-lg">
{formatBytes(selectedInterface.bytes_recv)}
{formatBytes(displayInterface.bytes_recv)}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Bytes Sent</div>
<div className="font-medium text-blue-500 text-lg">
{formatBytes(selectedInterface.bytes_sent)}
{formatBytes(displayInterface.bytes_sent)}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Packets Received</div>
<div className="font-medium">{selectedInterface.packets_recv?.toLocaleString() || "N/A"}</div>
<div className="font-medium">
{displayInterface.packets_recv?.toLocaleString() || "N/A"}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Packets Sent</div>
<div className="font-medium">{selectedInterface.packets_sent?.toLocaleString() || "N/A"}</div>
<div className="font-medium">
{displayInterface.packets_sent?.toLocaleString() || "N/A"}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Errors In</div>
<div className="font-medium text-red-500">{selectedInterface.errors_in || 0}</div>
<div className="font-medium text-red-500">{displayInterface.errors_in || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Errors Out</div>
<div className="font-medium text-red-500">{selectedInterface.errors_out || 0}</div>
<div className="font-medium text-red-500">{displayInterface.errors_out || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Drops In</div>
<div className="font-medium text-yellow-500">{selectedInterface.drops_in || 0}</div>
<div className="font-medium text-yellow-500">{displayInterface.drops_in || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Drops Out</div>
<div className="font-medium text-yellow-500">{selectedInterface.drops_out || 0}</div>
<div className="font-medium text-yellow-500">{displayInterface.drops_out || 0}</div>
</div>
{selectedInterface.packet_loss_in !== undefined && (
{displayInterface.packet_loss_in !== undefined && (
<div>
<div className="text-sm text-muted-foreground">Packet Loss In</div>
<div
className={`font-medium ${selectedInterface.packet_loss_in > 1 ? "text-red-500" : "text-green-500"}`}
className={`font-medium ${displayInterface.packet_loss_in > 1 ? "text-red-500" : "text-green-500"}`}
>
{selectedInterface.packet_loss_in}%
{displayInterface.packet_loss_in}%
</div>
</div>
)}
{selectedInterface.packet_loss_out !== undefined && (
{displayInterface.packet_loss_out !== undefined && (
<div>
<div className="text-sm text-muted-foreground">Packet Loss Out</div>
<div
className={`font-medium ${selectedInterface.packet_loss_out > 1 ? "text-red-500" : "text-green-500"}`}
className={`font-medium ${displayInterface.packet_loss_out > 1 ? "text-red-500" : "text-green-500"}`}
>
{selectedInterface.packet_loss_out}%
{displayInterface.packet_loss_out}%
</div>
</div>
)}
@@ -935,7 +963,7 @@ export function NetworkMetrics() {
)}
{/* Cumulative Statistics - Only show if interface is UP and NOT a VM interface */}
{selectedInterface.status.toLowerCase() === "up" && selectedInterface.vm_type !== "vm" && (
{displayInterface.status.toLowerCase() === "up" && displayInterface.vm_type !== "vm" && (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-4">
Cumulative Statistics (Since Last Boot)
@@ -943,45 +971,49 @@ export function NetworkMetrics() {
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-muted-foreground">Packets Received</div>
<div className="font-medium">{selectedInterface.packets_recv?.toLocaleString() || "N/A"}</div>
<div className="font-medium">
{displayInterface.packets_recv?.toLocaleString() || "N/A"}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Packets Sent</div>
<div className="font-medium">{selectedInterface.packets_sent?.toLocaleString() || "N/A"}</div>
<div className="font-medium">
{displayInterface.packets_sent?.toLocaleString() || "N/A"}
</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Errors In</div>
<div className="font-medium text-red-500">{selectedInterface.errors_in || 0}</div>
<div className="font-medium text-red-500">{displayInterface.errors_in || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Errors Out</div>
<div className="font-medium text-red-500">{selectedInterface.errors_out || 0}</div>
<div className="font-medium text-red-500">{displayInterface.errors_out || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Drops In</div>
<div className="font-medium text-yellow-500">{selectedInterface.drops_in || 0}</div>
<div className="font-medium text-yellow-500">{displayInterface.drops_in || 0}</div>
</div>
<div>
<div className="text-sm text-muted-foreground">Drops Out</div>
<div className="font-medium text-yellow-500">{selectedInterface.drops_out || 0}</div>
<div className="font-medium text-yellow-500">{displayInterface.drops_out || 0}</div>
</div>
{selectedInterface.packet_loss_in !== undefined && (
{displayInterface.packet_loss_in !== undefined && (
<div>
<div className="text-sm text-muted-foreground">Packet Loss In</div>
<div
className={`font-medium ${selectedInterface.packet_loss_in > 1 ? "text-red-500" : "text-green-500"}`}
className={`font-medium ${displayInterface.packet_loss_in > 1 ? "text-red-500" : "text-green-500"}`}
>
{selectedInterface.packet_loss_in}%
{displayInterface.packet_loss_in}%
</div>
</div>
)}
{selectedInterface.packet_loss_out !== undefined && (
{displayInterface.packet_loss_out !== undefined && (
<div>
<div className="text-sm text-muted-foreground">Packet Loss Out</div>
<div
className={`font-medium ${selectedInterface.packet_loss_out > 1 ? "text-red-500" : "text-green-500"}`}
className={`font-medium ${displayInterface.packet_loss_out > 1 ? "text-red-500" : "text-green-500"}`}
>
{selectedInterface.packet_loss_out}%
{displayInterface.packet_loss_out}%
</div>
</div>
)}
@@ -990,24 +1022,24 @@ export function NetworkMetrics() {
)}
{/* Bond Information */}
{selectedInterface.type === "bond" && selectedInterface.bond_slaves && (
{displayInterface.type === "bond" && displayInterface.bond_slaves && (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">Bond Configuration</h3>
<div className="space-y-3">
<div>
<div className="text-sm text-muted-foreground">Bonding Mode</div>
<div className="font-medium">{selectedInterface.bond_mode || "Unknown"}</div>
<div className="font-medium">{displayInterface.bond_mode || "Unknown"}</div>
</div>
{selectedInterface.bond_active_slave && (
{displayInterface.bond_active_slave && (
<div>
<div className="text-sm text-muted-foreground">Active Slave</div>
<div className="font-medium">{selectedInterface.bond_active_slave}</div>
<div className="font-medium">{displayInterface.bond_active_slave}</div>
</div>
)}
<div>
<div className="text-sm text-muted-foreground mb-2">Slave Interfaces</div>
<div className="flex flex-wrap gap-2">
{selectedInterface.bond_slaves.map((slave, idx) => (
{displayInterface.bond_slaves.map((slave, idx) => (
<Badge
key={idx}
variant="outline"
@@ -1023,14 +1055,14 @@ export function NetworkMetrics() {
)}
{/* Bridge Information */}
{selectedInterface.type === "bridge" && selectedInterface.bridge_members && (
{displayInterface.type === "bridge" && displayInterface.bridge_members && (
<div>
<h3 className="text-sm font-semibold text-muted-foreground mb-3">Bridge Configuration</h3>
<div>
<div className="text-sm text-muted-foreground mb-2">Virtual Member Interfaces</div>
<div className="flex flex-wrap gap-2">
{selectedInterface.bridge_members.length > 0 ? (
selectedInterface.bridge_members
{displayInterface.bridge_members.length > 0 ? (
displayInterface.bridge_members
.filter(
(member) =>
!member.startsWith("enp") &&
@@ -1056,6 +1088,9 @@ export function NetworkMetrics() {
</div>
</div>
)}
</>
)
})()}
</div>
)}
</DialogContent>