import { useMemo } from "react" import { t } from "@lingui/core/macro" import AreaChartDefault from "@/components/charts/area-chart" import { useContainerDataPoints } from "@/components/charts/hooks" import { $userSettings } from "@/lib/stores" import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils" import type { ChartConfig } from "@/components/ui/chart" import { pinnedAxisDomain } from "@/components/ui/chart" import type { ChartData, SystemStatsRecord } from "@/types" import { Separator } from "@/components/ui/separator" import NetworkSheet from "../network-sheet" import { ChartCard, FilterBar, SelectAvgMax } from "../chart-card" import { dockerOrPodman } from "../chart-data" export function BandwidthChart({ chartData, grid, dataEmpty, showMax, isLongerChart, maxValues, systemStats, }: { chartData: ChartData grid: boolean dataEmpty: boolean showMax: boolean isLongerChart: boolean maxValues: boolean systemStats: SystemStatsRecord[] }) { const maxValSelect = isLongerChart ? : null const userSettings = $userSettings.get() return ( {maxValSelect} } description={t`Network traffic of public interfaces`} > (systemStats.at(-1)?.stats.b?.[1] ?? 0) - (systemStats.at(-1)?.stats.b?.[0] ?? 0))} tickFormatter={(val) => { const { value, unit } = formatBytes(val, true, userSettings.unitNet, false) return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={(data) => { const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false) return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}` }} showTotal={true} /> ) } export function ContainerNetworkChart({ chartData, grid, dataEmpty, isPodman, networkConfig, }: { chartData: ChartData grid: boolean dataEmpty: boolean isPodman: boolean networkConfig: ChartConfig }) { const userSettings = $userSettings.get() const { filter, dataPoints, filteredKeys } = useContainerDataPoints(networkConfig, (key, data) => { const payload = data[key] if (!payload) return null const sent = payload?.b?.[0] ?? (payload?.ns ?? 0) * 1024 * 1024 const recv = payload?.b?.[1] ?? (payload?.nr ?? 0) * 1024 * 1024 return sent + recv }) const contentFormatter = useMemo(() => { const getRxTxBytes = (record?: { b?: [number, number]; ns?: number; nr?: number }) => { if (record?.b?.length && record.b.length >= 2) { return [Number(record.b[0]) || 0, Number(record.b[1]) || 0] } return [(record?.ns ?? 0) * 1024 * 1024, (record?.nr ?? 0) * 1024 * 1024] } const formatRxTx = (recv: number, sent: number) => { const { value: receivedValue, unit: receivedUnit } = formatBytes(recv, true, userSettings.unitNet, false) const { value: sentValue, unit: sentUnit } = formatBytes(sent, true, userSettings.unitNet, false) return ( {decimalString(receivedValue)} {receivedUnit} rx {decimalString(sentValue)} {sentUnit} tx ) } // biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item return (item: any, key: string) => { try { if (key === "__total__") { let totalSent = 0 let totalRecv = 0 const payloadData = item?.payload && typeof item.payload === "object" ? item.payload : {} for (const [containerKey, value] of Object.entries(payloadData)) { if (!value || typeof value !== "object") continue if (filteredKeys.has(containerKey)) continue const [sent, recv] = getRxTxBytes(value as { b?: [number, number]; ns?: number; nr?: number }) totalSent += sent totalRecv += recv } return formatRxTx(totalRecv, totalSent) } const [sent, recv] = getRxTxBytes(item?.payload?.[key]) return formatRxTx(recv, sent) } catch { return null } } }, [filteredKeys, userSettings.unitNet]) return ( } > { const { value, unit } = formatBytes(val, true, userSettings.unitNet, false) return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={contentFormatter} domain={pinnedAxisDomain()} showTotal={true} reverseStackOrder={true} filter={filter} truncate={true} itemSorter={(a, b) => b.value - a.value} /> ) }