From 7b963ca87777b16031f7a3c7e265b19afda80d65 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sun, 3 Aug 2025 00:30:04 +0530 Subject: [PATCH 01/11] Network tab first implementation --- .../Components/NetworkStats/NetworkCharts.jsx | 59 +++++++++++ .../NetworkStats/NetworkStatBoxes.jsx | 82 ++++++++++++++ .../Details/Components/NetworkStats/index.jsx | 100 ++++++++++++++++++ .../Components/NetworkStats/skeleton.jsx | 56 ++++++++++ .../Pages/Infrastructure/Details/index.jsx | 69 ++++++++---- 5 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx new file mode 100644 index 000000000..497fa1410 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -0,0 +1,59 @@ +// NetworkCharts.jsx +import { Grid, Card, CardContent, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import AreaChart from "../../../../../Components/Charts/AreaChart"; +import { TzTick } from '../../../../../Components/Charts/Utils/chartUtils'; + +const BytesTick = ({ x, y, payload }) => { + const value = payload.value; + const label = + value >= 1024 ** 3 + ? `${(value / 1024 ** 3).toFixed(2)} GB` + : value >= 1024 ** 2 + ? `${(value / 1024 ** 2).toFixed(2)} MB` + : `${(value / 1024).toFixed(2)} KB`; + + return {label}; +}; + +const NetworkCharts = ({ eth0Data, dateRange }) => { + const theme = useTheme(); + const textColor = theme.palette.primary.contrastTextTertiary; + + const charts = [ + { title: "eth0 Bytes/sec", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, + { title: "eth0 Packets/sec", key: "packetsPerSec", color: theme.palette.success.main }, + { title: "eth0 Errors", key: "errors", color: theme.palette.error.main }, + { title: "eth0 Drops", key: "drops", color: theme.palette.warning.main } + ]; + + return ( + + {charts.map((chart) => ( + + + + + {chart.title} + + } + strokeColor={chart.color} + gradient + gradientStartColor={chart.color} + gradientEndColor="#fff" + height={200} + /> + + + + ))} + + ); +}; + +export default NetworkCharts; \ No newline at end of file diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx new file mode 100644 index 000000000..abe67fa8e --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -0,0 +1,82 @@ +// NetworkStatBoxes.jsx +import DataUsageIcon from "@mui/icons-material/DataUsage"; +import NetworkCheckIcon from "@mui/icons-material/NetworkCheck"; +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import StatusBoxes from "../../../../../Components/StatusBoxes"; +import StatBox from "../../../../../Components/StatBox"; +import { Typography } from "@mui/material"; + +const INTERFACE_LABELS = { + en0: "Ethernet/Wi-Fi (Primary)", + wlan0: "Wi-Fi (Secondary)", +}; + +function formatBytes(bytes) { + if (bytes === 0 || bytes == null) 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]}`; +} + +// Format numbers with commas +function formatNumber(num) { + return num != null ? num.toLocaleString() : "0"; +} + +const NetworkStatBoxes = ({ shouldRender, net }) => { + const filtered = net?.filter( + (iface) => iface.name === "en0" || iface.name === "wlan0" + ) || []; + + if (!net?.length) { + return No network stats available.; + } + + return ( + + {filtered.map((iface) => ( + <> + + + + + + + + ))} + + ); +}; + +export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx new file mode 100644 index 000000000..ecb62e769 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -0,0 +1,100 @@ +import NetworkStatBoxes from "./NetworkStatBoxes"; +import NetworkCharts from "./NetworkCharts"; +import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; + +function filterByDateRange(data, dateRange) { + if (!Array.isArray(data)) return []; + const now = Date.now(); + let cutoff; + switch (dateRange) { + case "recent": + cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours + break; + case "day": + cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours + break; + case "week": + cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days + break; + case "month": + cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days + break; + default: + cutoff = 0; + } + return data.filter((d) => new Date(d.time).getTime() >= cutoff); +} + +const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { + const eth0Data = getEth0TimeSeries(checks); + const xAxisFormatter = getXAxisFormatter(checks); + const filteredEth0Data = filterByDateRange(eth0Data, dateRange); + + return ( + <> + + + + + ); +}; + +export default Network; + +/* ---------- Helper functions ---------- */ +function getEth0TimeSeries(checks) { + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); + const series = []; + let prev = null; + + for (const check of sorted) { + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (!eth) { + prev = check; + continue; + } + if (prev) { + const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); + const t1 = new Date(check._id); + const t0 = new Date(prev._id); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + prev = check; + continue; + } + const dt = (t1 - t0) / 1000; + if (dt > 0) { + series.push({ + time: check._id, + bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, + packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, + errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), + drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + }); + } + } + prev = check; + } + + return series; +} + +function getXAxisFormatter(checks) { + if (!checks || checks.length === 0) return (val) => val; + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const first = new Date(sorted[0]._id); + const last = new Date(sorted[sorted.length - 1]._id); + const diffDays = (last - first) / (1000 * 60 * 60 * 24); + + return diffDays > 2 + ? (val) => new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) + : (val) => + new Date(val).toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); +} diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx new file mode 100644 index 000000000..52fcfad87 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/skeleton.jsx @@ -0,0 +1,56 @@ +import { + Card, + CardContent, + Skeleton, + Table, + TableHead, + TableRow, + TableCell, + TableBody, +} from "@mui/material"; + +const SkeletonLayout = () => { + return ( + + + + + + + Name + Bytes Sent + Bytes Received + Packets Sent + Packets Received + Errors In + Errors Out + Drops In + Drops Out + + + + {Array.from({ length: 5 }).map((_, idx) => ( + + {Array.from({ length: 9 }).map((__, colIdx) => ( + + + + ))} + + ))} + +
+
+
+ ); +}; + +export default SkeletonLayout; diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx index 30840851f..78b78fa78 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -1,5 +1,5 @@ // Components -import { Stack, Typography } from "@mui/material"; +import { Stack, Typography, Tab } from "@mui/material"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader"; import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader"; @@ -7,6 +7,9 @@ import StatusBoxes from "./Components/StatusBoxes"; import GaugeBoxes from "./Components/GaugeBoxes"; import AreaChartBoxes from "./Components/AreaChartBoxes"; import GenericFallback from "../../../Components/GenericFallback"; +import NetworkStats from "./Components/NetworkStats"; +import CustomTabList from "../../../Components/Tab"; +import TabContext from "@mui/lab/TabContext"; // Utils import { useTheme } from "@emotion/react"; @@ -22,11 +25,11 @@ const BREADCRUMBS = [ { name: "details", path: "" }, ]; const InfrastructureDetails = () => { - // Redux state // Local state const [dateRange, setDateRange] = useState("recent"); const [trigger, setTrigger] = useState(false); + const [tab, setTab] = useState("details"); // Utils const theme = useTheme(); @@ -87,23 +90,51 @@ const InfrastructureDetails = () => { monitor={monitor} triggerUpdate={triggerUpdate} /> - - - - + + setTab(v)} + > + + + + {tab === "details" && ( + <> + + + + + + )} + {tab === "network" && ( + + )} + ); }; From 10c5463892617e1a8254ec36c2f084dc0f09c429 Mon Sep 17 00:00:00 2001 From: owiaseimdad Date: Sun, 3 Aug 2025 01:26:13 +0530 Subject: [PATCH 02/11] Fixed the tool tip. --- .../Components/Charts/Utils/chartUtils.jsx | 5 +- .../Components/NetworkStats/NetworkCharts.jsx | 80 +++++++-- .../NetworkStats/NetworkStatBoxes.jsx | 118 ++++++------- .../Details/Components/NetworkStats/index.jsx | 163 +++++++++--------- 4 files changed, 214 insertions(+), 152 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index 414e1ff9c..bbf81ae38 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -112,6 +112,7 @@ export const InfrastructureTooltip = ({ yLabel, dotColor, dateRange, + formatter = getFormattedPercentage, }) => { const uiTimezone = useSelector((state) => state.ui.timezone); const theme = useTheme(); @@ -166,8 +167,8 @@ export const InfrastructureTooltip = ({ sx={{ opacity: 0.8 }} > {yIdx >= 0 - ? `${yLabel} ${getFormattedPercentage(payload[0].payload[hardwareType][yIdx][metric])}` - : `${yLabel} ${getFormattedPercentage(payload[0].payload[yKey])}`} + ? `${yLabel} ${formatter(payload[0].payload[hardwareType][yIdx][metric])}` + : `${yLabel} ${formatter(payload[0].payload[yKey])}`} diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 497fa1410..9ef548b2e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -2,7 +2,7 @@ import { Grid, Card, CardContent, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import AreaChart from "../../../../../Components/Charts/AreaChart"; -import { TzTick } from '../../../../../Components/Charts/Utils/chartUtils'; +import { TzTick, InfrastructureTooltip } from '../../../../../Components/Charts/Utils/chartUtils'; const BytesTick = ({ x, y, payload }) => { const value = payload.value; @@ -16,37 +16,89 @@ const BytesTick = ({ x, y, payload }) => { return {label}; }; +const getFormattedNetworkMetric = (value) => { + if (typeof value !== "number" || isNaN(value)) return "0"; + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; +}; + const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const textColor = theme.palette.primary.contrastTextTertiary; const charts = [ - { title: "eth0 Bytes/sec", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, - { title: "eth0 Packets/sec", key: "packetsPerSec", color: theme.palette.success.main }, - { title: "eth0 Errors", key: "errors", color: theme.palette.error.main }, - { title: "eth0 Drops", key: "drops", color: theme.palette.warning.main } + { title: "Bytes per second", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, + { title: "Packets per second", key: "packetsPerSec", color: theme.palette.success.main }, + { title: "Errors", key: "errors", color: theme.palette.error.main }, + { title: "Drops", key: "drops", color: theme.palette.warning.main } ]; + const formatYAxis = (key, value) => { + if (key === "bytesPerSec") { + // Format as MB/s or GB/s if large + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; + } + return Math.round(value).toLocaleString(); + }; + + const CustomTick = ({ x, y, payload, chartKey }) => { + // Ensure value is always rounded for display + let value = payload.value; + if (typeof value === 'number') { + value = Math.round(value); + } + return ( + + {formatYAxis(chartKey, value)} + + ); + }; + + const chartConfigs = charts.map((chart) => ({ + data: eth0Data, + dataKeys: [chart.key], + heading: chart.title, + strokeColor: chart.color, + gradientStartColor: chart.color, + yTick: chart.yTick || , + xTick: , + toolTip: ( + + ), + })); + return ( - {charts.map((chart) => ( - + {chartConfigs.map((config, idx) => ( + - {chart.title} + {config.heading} } - strokeColor={chart.color} + yTick={config.yTick} + xTick={config.xTick} + strokeColor={config.strokeColor} gradient - gradientStartColor={chart.color} + gradientStartColor={config.gradientStartColor} gradientEndColor="#fff" height={200} + customTooltip={config.toolTip} /> diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index abe67fa8e..5253742a6 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -7,76 +7,78 @@ import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; const INTERFACE_LABELS = { - en0: "Ethernet/Wi-Fi (Primary)", - wlan0: "Wi-Fi (Secondary)", + en0: "Ethernet/Wi-Fi (Primary)", + wlan0: "Wi-Fi (Secondary)", }; function formatBytes(bytes) { - if (bytes === 0 || bytes == null) 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]}`; + if (bytes === 0 || bytes == null) 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]}`; } // Format numbers with commas function formatNumber(num) { - return num != null ? num.toLocaleString() : "0"; + return num != null ? num.toLocaleString() : "0"; } const NetworkStatBoxes = ({ shouldRender, net }) => { - const filtered = net?.filter( - (iface) => iface.name === "en0" || iface.name === "wlan0" - ) || []; + const filtered = + net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; - if (!net?.length) { - return No network stats available.; - } + if (!net?.length) { + return No network stats available.; + } - return ( - - {filtered.map((iface) => ( - <> - - - - - - - - ))} - - ); + return ( + + {filtered.map((iface) => ( + <> + + + + + + + + ))} + + ); }; export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index ecb62e769..60825be0f 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,98 +3,105 @@ import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; function filterByDateRange(data, dateRange) { - if (!Array.isArray(data)) return []; - const now = Date.now(); - let cutoff; - switch (dateRange) { - case "recent": - cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours - break; - case "day": - cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours - break; - case "week": - cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days - break; - case "month": - cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days - break; - default: - cutoff = 0; - } - return data.filter((d) => new Date(d.time).getTime() >= cutoff); + if (!Array.isArray(data)) return []; + const now = Date.now(); + let cutoff; + switch (dateRange) { + case "recent": + cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours + break; + case "day": + cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours + break; + case "week": + cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days + break; + case "month": + cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days + break; + default: + cutoff = 0; + } + return data.filter((d) => new Date(d.time).getTime() >= cutoff); } const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = getEth0TimeSeries(checks); - const xAxisFormatter = getXAxisFormatter(checks); - const filteredEth0Data = filterByDateRange(eth0Data, dateRange); + const eth0Data = getEth0TimeSeries(checks); + const xAxisFormatter = getXAxisFormatter(checks); + const filteredEth0Data = filterByDateRange(eth0Data, dateRange); - return ( - <> - - - - - ); + return ( + <> + + + + + ); }; export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); - const series = []; - let prev = null; + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); + const series = []; + let prev = null; - for (const check of sorted) { - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (!eth) { - prev = check; - continue; - } - if (prev) { - const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); - const t1 = new Date(check._id); - const t0 = new Date(prev._id); - if (!prevEth || isNaN(t1) || isNaN(t0)) { - prev = check; - continue; - } - const dt = (t1 - t0) / 1000; - if (dt > 0) { - series.push({ - time: check._id, - bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, - packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, - errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), - drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), - }); - } - } - prev = check; - } + for (const check of sorted) { + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (!eth) { + prev = check; + continue; + } + if (prev) { + const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); + const t1 = new Date(check._id); + const t0 = new Date(prev._id); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + prev = check; + continue; + } + const dt = (t1 - t0) / 1000; + if (dt > 0) { + series.push({ + time: check._id, + bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, + packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, + errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), + drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + }); + } + } + prev = check; + } - return series; + return series; } function getXAxisFormatter(checks) { - if (!checks || checks.length === 0) return (val) => val; - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const first = new Date(sorted[0]._id); - const last = new Date(sorted[sorted.length - 1]._id); - const diffDays = (last - first) / (1000 * 60 * 60 * 24); + if (!checks || checks.length === 0) return (val) => val; + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const first = new Date(sorted[0]._id); + const last = new Date(sorted[sorted.length - 1]._id); + const diffDays = (last - first) / (1000 * 60 * 60 * 24); - return diffDays > 2 - ? (val) => new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) - : (val) => - new Date(val).toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); + return diffDays > 2 + ? (val) => + new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) + : (val) => + new Date(val).toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); } From e42b4b28081760fef67cd8638bb0e62e1c8d41ad Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 20:46:42 +0530 Subject: [PATCH 03/11] fixed the minor changes for FE. --- .../Components/Charts/Utils/chartUtils.jsx | 2 +- .../Components/NetworkStats/NetworkCharts.jsx | 219 ++++++++++-------- .../NetworkStats/NetworkStatBoxes.jsx | 64 ++--- .../Details/Components/NetworkStats/index.jsx | 132 +++++++---- 4 files changed, 234 insertions(+), 183 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index bbf81ae38..af5f27c66 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -88,7 +88,7 @@ PercentTick.propTypes = { */ const getFormattedPercentage = (value) => { if (typeof value !== "number") return value; - return `${(value * 100).toFixed(2)}.%`; + return `${(value * 100).toFixed(2)}%`; }; /** diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 9ef548b2e..343f7681b 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,111 +1,130 @@ // NetworkCharts.jsx -import { Grid, Card, CardContent, Typography } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import AreaChart from "../../../../../Components/Charts/AreaChart"; -import { TzTick, InfrastructureTooltip } from '../../../../../Components/Charts/Utils/chartUtils'; +import PropTypes from "prop-types"; +import { Stack } from "@mui/material"; +import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; -const BytesTick = ({ x, y, payload }) => { - const value = payload.value; - const label = - value >= 1024 ** 3 - ? `${(value / 1024 ** 3).toFixed(2)} GB` - : value >= 1024 ** 2 - ? `${(value / 1024 ** 2).toFixed(2)} MB` - : `${(value / 1024).toFixed(2)} KB`; - - return {label}; -}; +// Utils +import { + TzTick, + InfrastructureTooltip, +} from "../../../../../Components/Charts/Utils/chartUtils"; +import { useTheme } from "@emotion/react"; const getFormattedNetworkMetric = (value) => { - if (typeof value !== "number" || isNaN(value)) return "0"; - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; + if (typeof value !== "number" || isNaN(value)) return "0"; + if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; + if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; + if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; + return `${Math.round(value)} B/s`; }; const NetworkCharts = ({ eth0Data, dateRange }) => { - const theme = useTheme(); - const textColor = theme.palette.primary.contrastTextTertiary; + const theme = useTheme(); - const charts = [ - { title: "Bytes per second", key: "bytesPerSec", color: theme.palette.info.main, yTick: }, - { title: "Packets per second", key: "packetsPerSec", color: theme.palette.success.main }, - { title: "Errors", key: "errors", color: theme.palette.error.main }, - { title: "Drops", key: "drops", color: theme.palette.warning.main } - ]; + const configs = [ + { + type: "network-bytes", + data: eth0Data, + dataKeys: ["bytesPerSec"], + heading: "Bytes per second", + strokeColor: theme.palette.info.main, + gradientStartColor: theme.palette.info.main, + yLabel: "Bytes per second", + xTick: , + toolTip: ( + + ), + }, + { + type: "network-packets", + data: eth0Data, + dataKeys: ["packetsPerSec"], + heading: "Packets per second", + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + yLabel: "Packets per second", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + { + type: "network-errors", + data: eth0Data, + dataKeys: ["errors"], + heading: "Errors", + strokeColor: theme.palette.error.main, + gradientStartColor: theme.palette.error.main, + yLabel: "Errors", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + { + type: "network-drops", + data: eth0Data, + dataKeys: ["drops"], + heading: "Drops", + strokeColor: theme.palette.warning.main, + gradientStartColor: theme.palette.warning.main, + yLabel: "Drops", + xTick: , + toolTip: ( + Math.round(value).toLocaleString()} + /> + ), + }, + ]; - const formatYAxis = (key, value) => { - if (key === "bytesPerSec") { - // Format as MB/s or GB/s if large - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; - } - return Math.round(value).toLocaleString(); - }; - - const CustomTick = ({ x, y, payload, chartKey }) => { - // Ensure value is always rounded for display - let value = payload.value; - if (typeof value === 'number') { - value = Math.round(value); - } - return ( - - {formatYAxis(chartKey, value)} - - ); - }; - - const chartConfigs = charts.map((chart) => ({ - data: eth0Data, - dataKeys: [chart.key], - heading: chart.title, - strokeColor: chart.color, - gradientStartColor: chart.color, - yTick: chart.yTick || , - xTick: , - toolTip: ( - - ), - })); - - return ( - - {chartConfigs.map((config, idx) => ( - - - - - {config.heading} - - - - - - ))} - - ); + return ( + *": { + flexBasis: `calc(50% - ${theme.spacing(8)})`, + maxWidth: `calc(50% - ${theme.spacing(8)})`, + }, + }} + > + {configs.map((config) => ( + + ))} + + ); }; -export default NetworkCharts; \ No newline at end of file +NetworkCharts.propTypes = { + eth0Data: PropTypes.array.isRequired, + dateRange: PropTypes.string.isRequired, +}; + +export default NetworkCharts; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 5253742a6..b2f2f7841 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,16 +1,9 @@ // NetworkStatBoxes.jsx -import DataUsageIcon from "@mui/icons-material/DataUsage"; -import NetworkCheckIcon from "@mui/icons-material/NetworkCheck"; -import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; +import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; -const INTERFACE_LABELS = { - en0: "Ethernet/Wi-Fi (Primary)", - wlan0: "Wi-Fi (Secondary)", -}; - function formatBytes(bytes) { if (bytes === 0 || bytes == null) return "0 B"; const k = 1024; @@ -37,48 +30,57 @@ const NetworkStatBoxes = ({ shouldRender, net }) => { shouldRender={shouldRender} flexWrap="wrap" > - {filtered.map((iface) => ( - <> + {filtered + .map((iface) => [ + />, + />, + />, + />, + />, - - ))} + />, + ]) + .flat()} ); }; +NetworkStatBoxes.propTypes = { + shouldRender: PropTypes.bool.isRequired, + net: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + bytes_sent: PropTypes.number, + bytes_recv: PropTypes.number, + packets_sent: PropTypes.number, + packets_recv: PropTypes.number, + err_in: PropTypes.number, + err_out: PropTypes.number, + }) + ), +}; + export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 60825be0f..6b8ccb85a 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -1,34 +1,10 @@ +import PropTypes from "prop-types"; import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -function filterByDateRange(data, dateRange) { - if (!Array.isArray(data)) return []; - const now = Date.now(); - let cutoff; - switch (dateRange) { - case "recent": - cutoff = now - 2 * 60 * 60 * 1000; // last 2 hours - break; - case "day": - cutoff = now - 24 * 60 * 60 * 1000; // last 24 hours - break; - case "week": - cutoff = now - 7 * 24 * 60 * 60 * 1000; // last 7 days - break; - case "month": - cutoff = now - 30 * 24 * 60 * 60 * 1000; // last 30 days - break; - default: - cutoff = 0; - } - return data.filter((d) => new Date(d.time).getTime() >= cutoff); -} - const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { const eth0Data = getEth0TimeSeries(checks); - const xAxisFormatter = getXAxisFormatter(checks); - const filteredEth0Data = filterByDateRange(eth0Data, dateRange); return ( <> @@ -42,66 +18,120 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { setDateRange={setDateRange} /> ); }; +Network.propTypes = { + net: PropTypes.array, + checks: PropTypes.array, + isLoading: PropTypes.bool.isRequired, + dateRange: PropTypes.string.isRequired, + setDateRange: PropTypes.func.isRequired, +}; + export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { + console.log(`[NetworkStats] Processing ${checks?.length || 0} checks`); + if (checks && checks.length > 0) { + console.log("[NetworkStats] First check _id:", checks[0]._id); + console.log("[NetworkStats] Last check _id:", checks[checks.length - 1]._id); + console.log( + "[NetworkStats] Sample check structure:", + JSON.stringify(checks[0], null, 2) + ); + } + const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); const series = []; let prev = null; for (const check of sorted) { + console.log(`[NetworkStats] Processing check: ${check._id}`); const eth = (check.net || []).find((iface) => iface.name === "en0"); if (!eth) { + console.log("[NetworkStats] No en0 interface found in check:", check._id); prev = check; continue; } + + console.log(`[NetworkStats] Found en0 interface in check ${check._id}:`, eth); + if (prev) { + console.log(`[NetworkStats] Have previous check: ${prev._id}`); const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); const t1 = new Date(check._id); const t0 = new Date(prev._id); + console.log(`[NetworkStats] Time difference: ${t1 - t0}ms`); + if (!prevEth || isNaN(t1) || isNaN(t0)) { + console.log("[NetworkStats] Skipping - invalid prev data or time"); prev = check; continue; } + const dt = (t1 - t0) / 1000; + console.log(`[NetworkStats] Delta time: ${dt}s`); + if (dt > 0) { - series.push({ - time: check._id, - bytesPerSec: (eth.bytesSent - prevEth.bytesSent) / dt, - packetsPerSec: (eth.packetsSent - prevEth.packetsSent) / dt, - errors: (eth.errIn ?? 0) + (eth.errOut ?? 0), - drops: (eth.dropIn ?? 0) + (eth.dropOut ?? 0), + const bytesField = eth.avgBytesSent; + const prevBytesField = prevEth.avgBytesSent; + + console.log(`[NetworkStats] Bytes comparison:`, { + current: bytesField, + previous: prevBytesField, + diff: bytesField - prevBytesField, }); + + if (bytesField !== undefined && prevBytesField !== undefined) { + const dataPoint = { + _id: check._id, // Use _id instead of time to match AreaChartBoxes + bytesPerSec: (bytesField - prevBytesField) / dt, + packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, + errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), + drops: 0, // Skip drops for now since we don't have avgDropIn/Out + }; + console.log(`[NetworkStats] Adding data point:`, dataPoint); + series.push(dataPoint); + } else { + console.warn("[NetworkStats] Missing bytes fields:", { eth, prevEth }); + } + } else { + console.log("[NetworkStats] Skipping - zero or negative time delta"); } + } else { + console.log("[NetworkStats] No previous check yet, setting as prev"); } prev = check; } + console.log(`[NetworkStats] Generated ${series.length} time series data points`); + + // If we only have one check, create a single data point with absolute values + if (series.length === 0 && sorted.length === 1) { + const check = sorted[0]; + const eth = (check.net || []).find((iface) => iface.name === "en0"); + if (eth) { + console.log( + "[NetworkStats] Only one data point available, showing absolute values" + ); + series.push({ + _id: check._id, // Use _id instead of time to match AreaChartBoxes + bytesPerSec: eth.avgBytesSent || 0, // Show absolute value instead of rate + packetsPerSec: eth.avgPacketsSent || 0, // Show absolute value instead of rate + errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), + drops: 0, + }); + } + } + + if (series.length > 0) { + console.log("[NetworkStats] Sample series data:", series[0]); + } return series; } - -function getXAxisFormatter(checks) { - if (!checks || checks.length === 0) return (val) => val; - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const first = new Date(sorted[0]._id); - const last = new Date(sorted[sorted.length - 1]._id); - const diffDays = (last - first) / (1000 * 60 * 60 * 24); - - return diffDays > 2 - ? (val) => - new Date(val).toLocaleDateString(undefined, { month: "short", day: "numeric" }) - : (val) => - new Date(val).toLocaleTimeString(undefined, { - hour: "2-digit", - minute: "2-digit", - hour12: false, - }); -} From 765a1a24ed97825184a53a6c673ee006b5dff77e Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 5 Aug 2025 21:04:15 +0530 Subject: [PATCH 04/11] Added en.json based values and console removal. --- .../Components/NetworkStats/NetworkCharts.jsx | 26 +++++----- .../NetworkStats/NetworkStatBoxes.jsx | 16 +++--- .../Details/Components/NetworkStats/index.jsx | 49 ++----------------- .../Pages/Infrastructure/Details/index.jsx | 5 +- client/src/locales/en.json | 13 +++++ 5 files changed, 43 insertions(+), 66 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 343f7681b..7aad494ab 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -9,6 +9,7 @@ import { InfrastructureTooltip, } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; const getFormattedNetworkMetric = (value) => { if (typeof value !== "number" || isNaN(value)) return "0"; @@ -20,22 +21,23 @@ const getFormattedNetworkMetric = (value) => { const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); + const { t } = useTranslation(); const configs = [ { type: "network-bytes", data: eth0Data, dataKeys: ["bytesPerSec"], - heading: "Bytes per second", + heading: t("bytesPerSecond"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, - yLabel: "Bytes per second", + yLabel: t("bytesPerSecond"), xTick: , toolTip: ( @@ -45,16 +47,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-packets", data: eth0Data, dataKeys: ["packetsPerSec"], - heading: "Packets per second", + heading: t("packetsPerSecond"), strokeColor: theme.palette.success.main, gradientStartColor: theme.palette.success.main, - yLabel: "Packets per second", + yLabel: t("packetsPerSecond"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> @@ -64,16 +66,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-errors", data: eth0Data, dataKeys: ["errors"], - heading: "Errors", + heading: t("errors"), strokeColor: theme.palette.error.main, gradientStartColor: theme.palette.error.main, - yLabel: "Errors", + yLabel: t("errors"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> @@ -83,16 +85,16 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { type: "network-drops", data: eth0Data, dataKeys: ["drops"], - heading: "Drops", + heading: t("drops"), strokeColor: theme.palette.warning.main, gradientStartColor: theme.palette.warning.main, - yLabel: "Drops", + yLabel: t("drops"), xTick: , toolTip: ( Math.round(value).toLocaleString()} /> diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index b2f2f7841..3125e86a9 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; function formatBytes(bytes) { if (bytes === 0 || bytes == null) return "0 B"; @@ -18,11 +19,12 @@ function formatNumber(num) { } const NetworkStatBoxes = ({ shouldRender, net }) => { + const { t } = useTranslation(); const filtered = net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; if (!net?.length) { - return No network stats available.; + return {t("noNetworkStatsAvailable")}; } return ( @@ -34,32 +36,32 @@ const NetworkStatBoxes = ({ shouldRender, net }) => { .map((iface) => [ , , , , , , ]) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 6b8ccb85a..03e0c019d 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -37,101 +37,62 @@ export default Network; /* ---------- Helper functions ---------- */ function getEth0TimeSeries(checks) { - console.log(`[NetworkStats] Processing ${checks?.length || 0} checks`); - if (checks && checks.length > 0) { - console.log("[NetworkStats] First check _id:", checks[0]._id); - console.log("[NetworkStats] Last check _id:", checks[checks.length - 1]._id); - console.log( - "[NetworkStats] Sample check structure:", - JSON.stringify(checks[0], null, 2) - ); - } - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); const series = []; let prev = null; for (const check of sorted) { - console.log(`[NetworkStats] Processing check: ${check._id}`); const eth = (check.net || []).find((iface) => iface.name === "en0"); if (!eth) { - console.log("[NetworkStats] No en0 interface found in check:", check._id); prev = check; continue; } - console.log(`[NetworkStats] Found en0 interface in check ${check._id}:`, eth); - if (prev) { - console.log(`[NetworkStats] Have previous check: ${prev._id}`); const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); const t1 = new Date(check._id); const t0 = new Date(prev._id); - console.log(`[NetworkStats] Time difference: ${t1 - t0}ms`); if (!prevEth || isNaN(t1) || isNaN(t0)) { - console.log("[NetworkStats] Skipping - invalid prev data or time"); prev = check; continue; } const dt = (t1 - t0) / 1000; - console.log(`[NetworkStats] Delta time: ${dt}s`); if (dt > 0) { const bytesField = eth.avgBytesSent; const prevBytesField = prevEth.avgBytesSent; - console.log(`[NetworkStats] Bytes comparison:`, { - current: bytesField, - previous: prevBytesField, - diff: bytesField - prevBytesField, - }); - if (bytesField !== undefined && prevBytesField !== undefined) { const dataPoint = { - _id: check._id, // Use _id instead of time to match AreaChartBoxes + _id: check._id, bytesPerSec: (bytesField - prevBytesField) / dt, packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, // Skip drops for now since we don't have avgDropIn/Out + drops: 0, }; - console.log(`[NetworkStats] Adding data point:`, dataPoint); series.push(dataPoint); - } else { - console.warn("[NetworkStats] Missing bytes fields:", { eth, prevEth }); } - } else { - console.log("[NetworkStats] Skipping - zero or negative time delta"); } - } else { - console.log("[NetworkStats] No previous check yet, setting as prev"); } prev = check; } - console.log(`[NetworkStats] Generated ${series.length} time series data points`); - // If we only have one check, create a single data point with absolute values if (series.length === 0 && sorted.length === 1) { const check = sorted[0]; const eth = (check.net || []).find((iface) => iface.name === "en0"); if (eth) { - console.log( - "[NetworkStats] Only one data point available, showing absolute values" - ); series.push({ - _id: check._id, // Use _id instead of time to match AreaChartBoxes - bytesPerSec: eth.avgBytesSent || 0, // Show absolute value instead of rate - packetsPerSec: eth.avgPacketsSent || 0, // Show absolute value instead of rate + _id: check._id, + bytesPerSec: eth.avgBytesSent || 0, + packetsPerSec: eth.avgPacketsSent || 0, errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), drops: 0, }); } } - if (series.length > 0) { - console.log("[NetworkStats] Sample series data:", series[0]); - } return series; } diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/index.jsx index 78b78fa78..72f384101 100644 --- a/client/src/Pages/Infrastructure/Details/index.jsx +++ b/client/src/Pages/Infrastructure/Details/index.jsx @@ -25,7 +25,6 @@ const BREADCRUMBS = [ { name: "details", path: "" }, ]; const InfrastructureDetails = () => { - // Local state const [dateRange, setDateRange] = useState("recent"); const [trigger, setTrigger] = useState(false); @@ -96,11 +95,11 @@ const InfrastructureDetails = () => { onChange={(e, v) => setTab(v)} > diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 230637474..bf949ea45 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -201,6 +201,9 @@ "avgCpuTemperature": "Average CPU Temperature", "bar": "Bar", "basicInformation": "Basic Information", + "bytesPerSecond": "Bytes per second", + "bytesReceived": "Bytes Received", + "bytesSent": "Bytes Sent", "bulkImport": { "fallbackPage": "Import a file to upload a list of servers in bulk", "invalidFileType": "Invalid file type", @@ -332,6 +335,11 @@ }, "disk": "Disk", "diskUsage": "Disk Usage", + "drops": "Drops", + "errors": "Errors", + "errorsIn": "Errors In", + "errorsOut": "Errors Out", + "details": "Details", "displayName": "Display name", "distributedRightCategoryTitle": "Monitor", "distributedStatusHeaderText": "Real-time, real-device coverage", @@ -632,7 +640,9 @@ }, "ms": "ms", "navControls": "Controls", + "network": "Network", "nextWindow": "Next window", + "noNetworkStatsAvailable": "No network stats available.", "notFoundButton": "Go to the main dashboard", "notificationConfig": { "description": "Select the notifications channels you want to use", @@ -744,6 +754,9 @@ "passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.", "saving": "Saving..." }, + "packetsPerSecond": "Packets per second", + "packetsReceived": "Packets Received", + "packetsSent": "Packets Sent", "pause": "Pause", "pingMonitoring": "Ping monitoring", "pingMonitoringDescription": "Check whether your server is available or not.", From f5d0b74ccf67e4412e3bcfdbc5997d034aad543d Mon Sep 17 00:00:00 2001 From: Owaise Date: Sat, 9 Aug 2025 12:14:25 +0530 Subject: [PATCH 05/11] Fixed the processing to be done in backend now. --- .../Components/Charts/Utils/chartUtils.jsx | 43 +++++++++ .../Components/NetworkStats/NetworkCharts.jsx | 3 + .../Details/Components/NetworkStats/index.jsx | 89 ++++--------------- server/src/db/mongo/modules/monitorModule.js | 55 +++++++++++- .../db/mongo/modules/monitorModuleQueries.js | 34 +++++-- 5 files changed, 144 insertions(+), 80 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index af5f27c66..e2560aa2e 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -91,6 +91,49 @@ const getFormattedPercentage = (value) => { return `${(value * 100).toFixed(2)}%`; }; +/** + * Custom tick component for rendering network bytes per second. + * + * @param {Object} props - The properties object. + * @param {number} props.x - The x-coordinate for the tick. + * @param {number} props.y - The y-coordinate for the tick. + * @param {Object} props.payload - The payload object containing tick data. + * @param {number} props.index - The index of the tick. + * @returns {JSX.Element|null} The rendered tick component or null for the first tick. + */ +export const NetworkTick = ({ x, y, payload, index }) => { + const theme = useTheme(); + if (index === 0) return null; + + const formatBytes = (bytes) => { + if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB/s`; + if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB/s`; + if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB/s`; + return `${bytes} B/s`; + }; + + return ( + + {formatBytes(payload?.value)} + + ); +}; + +NetworkTick.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.object, + index: PropTypes.number, +}; + + /** * Custom tooltip component for displaying infrastructure data. * diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 7aad494ab..74ddb6bae 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -7,6 +7,7 @@ import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Componen import { TzTick, InfrastructureTooltip, + NetworkTick, } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; @@ -23,6 +24,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); + const configs = [ { type: "network-bytes", @@ -33,6 +35,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , + yTick: , toolTip: ( { - const eth0Data = getEth0TimeSeries(checks); + const eth0Data = (checks || []) + .map((check) => { + const en0 = (check.net || []).find((iface) => iface.name === "en0"); + if (!en0) return null; + + return { + _id: check._id, + bytesPerSec: en0.avgBytesRecv, + packetsPerSec: en0.avgPacketsRecv, + errors: (en0.avgErrOut ?? 0), + drops: (en0.avgDropOut ?? 0) + }; + }) + .filter(Boolean); + + console.log(eth0Data); return ( <> - + - + ); }; @@ -34,65 +43,3 @@ Network.propTypes = { }; export default Network; - -/* ---------- Helper functions ---------- */ -function getEth0TimeSeries(checks) { - const sorted = [...(checks || [])].sort((a, b) => new Date(a._id) - new Date(b._id)); - const series = []; - let prev = null; - - for (const check of sorted) { - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (!eth) { - prev = check; - continue; - } - - if (prev) { - const prevEth = (prev.net || []).find((iface) => iface.name === "en0"); - const t1 = new Date(check._id); - const t0 = new Date(prev._id); - - if (!prevEth || isNaN(t1) || isNaN(t0)) { - prev = check; - continue; - } - - const dt = (t1 - t0) / 1000; - - if (dt > 0) { - const bytesField = eth.avgBytesSent; - const prevBytesField = prevEth.avgBytesSent; - - if (bytesField !== undefined && prevBytesField !== undefined) { - const dataPoint = { - _id: check._id, - bytesPerSec: (bytesField - prevBytesField) / dt, - packetsPerSec: (eth.avgPacketsSent - prevEth.avgPacketsSent) / dt, - errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, - }; - series.push(dataPoint); - } - } - } - prev = check; - } - - // If we only have one check, create a single data point with absolute values - if (series.length === 0 && sorted.length === 1) { - const check = sorted[0]; - const eth = (check.net || []).find((iface) => iface.name === "en0"); - if (eth) { - series.push({ - _id: check._id, - bytesPerSec: eth.avgBytesSent || 0, - packetsPerSec: eth.avgPacketsSent || 0, - errors: (eth.avgErrIn ?? 0) + (eth.avgErrOut ?? 0), - drops: 0, - }); - } - } - - return series; -} diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 76f055f94..4cd28ea9d 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -326,10 +326,53 @@ class MonitorModule { } }; + processNetworkRates = (checks) => { + if (!Array.isArray(checks) || checks.length === 0) return []; + + const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); + const lastSeen = {}; + + for (const check of sorted) { + if (!Array.isArray(check.net)) { + check.net = []; + continue; + } + + const newNet = []; + + for (const iface of check.net) { + const prev = lastSeen[iface.name]; + const t1 = new Date(check._id); + + if (prev) { + const t0 = new Date(prev._id); + const dt = (t1 - t0) / 1000; + + if (dt > 0) { + newNet.push({ + name: iface.name, + avgBytesRecv: (iface.avgBytesRecv - prev.avgBytesRecv), + avgPacketsRecv: (iface.avgPacketsRecv - prev.avgPacketsRecv), + avgErrOut: iface.avgErrOut - prev.avgErrOut, + avgDropOut: iface.avgDropOut, + }); + } + } + + lastSeen[iface.name] = { ...iface, _id: check._id }; + } + + check.net = newNet; + } + + return sorted; + }; + getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); const dates = this.getDateRange(dateRange); + const formatLookup = { recent: "%Y-%m-%dT%H:%M:00Z", day: "%Y-%m-%dT%H:00:00Z", @@ -337,13 +380,19 @@ class MonitorModule { month: "%Y-%m-%dT00:00:00Z", }; const dateString = formatLookup[dateRange]; + const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - const monitorStats = { + const stats = hardwareStats[0]; + if (stats && stats.checks) { + // Replace net with per-second rates + stats.checks = this.processNetworkRates(stats.checks); + } + + return { ...monitor.toObject(), - stats: hardwareStats[0], + stats, }; - return monitorStats; } catch (error) { error.service = SERVICE_NAME; error.method = "getHardwareDetailsById"; diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index df15593e2..79804f183 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -393,7 +393,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { 0, ], }, - bytesSent: { + avgBytesSent: { $avg: { $map: { input: "$net", @@ -404,7 +404,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - bytesRecv: { + avgBytesRecv: { $avg: { $map: { input: "$net", @@ -415,7 +415,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsSent: { + avgPacketsSent: { $avg: { $map: { input: "$net", @@ -426,7 +426,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - packetsRecv: { + avgPacketsRecv: { $avg: { $map: { input: "$net", @@ -437,7 +437,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errIn: { + avgErrIn: { $avg: { $map: { input: "$net", @@ -448,7 +448,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, - errOut: { + avgErrOut: { $avg: { $map: { input: "$net", @@ -459,6 +459,28 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, }, }, + avgDropIn: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.drop_in", "$$netIndex"], + }, + }, + }, + }, + avgDropOut: { + $avg: { + $map: { + input: "$net", + as: "netArray", + in: { + $arrayElemAt: ["$$netArray.drop_out", "$$netIndex"], + }, + }, + }, + }, }, }, }, From 47c231a2141675df72cf68efca55b7340d942a89 Mon Sep 17 00:00:00 2001 From: Owaise Date: Sat, 9 Aug 2025 12:17:25 +0530 Subject: [PATCH 06/11] Formating done. --- .../src/Components/Charts/Utils/chartUtils.jsx | 1 - .../Components/NetworkStats/NetworkCharts.jsx | 1 - .../Details/Components/NetworkStats/index.jsx | 16 +++++++++++----- server/src/db/mongo/modules/monitorModule.js | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index e2560aa2e..40eb5b539 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -133,7 +133,6 @@ NetworkTick.propTypes = { index: PropTypes.number, }; - /** * Custom tooltip component for displaying infrastructure data. * diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 74ddb6bae..5dd9feaad 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -24,7 +24,6 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); - const configs = [ { type: "network-bytes", diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 06cb25d17..f8bdf5e8a 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -10,11 +10,11 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { if (!en0) return null; return { - _id: check._id, + _id: check._id, bytesPerSec: en0.avgBytesRecv, packetsPerSec: en0.avgPacketsRecv, - errors: (en0.avgErrOut ?? 0), - drops: (en0.avgDropOut ?? 0) + errors: en0.avgErrOut ?? 0, + drops: en0.avgDropOut ?? 0, }; }) .filter(Boolean); @@ -23,13 +23,19 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { return ( <> - + - + ); }; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 4cd28ea9d..f0b7d190c 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -351,8 +351,8 @@ class MonitorModule { if (dt > 0) { newNet.push({ name: iface.name, - avgBytesRecv: (iface.avgBytesRecv - prev.avgBytesRecv), - avgPacketsRecv: (iface.avgPacketsRecv - prev.avgPacketsRecv), + avgBytesRecv: iface.avgBytesRecv - prev.avgBytesRecv, + avgPacketsRecv: iface.avgPacketsRecv - prev.avgPacketsRecv, avgErrOut: iface.avgErrOut - prev.avgErrOut, avgDropOut: iface.avgDropOut, }); From 355a445ead81c2e647022c20c7fad408164946de Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 12 Aug 2025 10:05:58 +0530 Subject: [PATCH 07/11] Fixed the code for linus, windows and mac based. Also made changes to pipeline. --- .../Details/Components/NetworkStats/index.jsx | 28 +-- server/src/db/mongo/modules/monitorModule.js | 46 ----- .../db/mongo/modules/monitorModuleQueries.js | 162 +++++++++--------- 3 files changed, 100 insertions(+), 136 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index f8bdf5e8a..22a6c3112 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,24 +3,32 @@ import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = (checks || []) +const getNetworkInterfaceData = (checks) => { + const interfaceNames = ["eth0", "Ethernet", "en0"]; + + return (checks || []) .map((check) => { - const en0 = (check.net || []).find((iface) => iface.name === "en0"); - if (!en0) return null; + const networkInterface = (check.net || []).find((iface) => + interfaceNames.includes(iface.name) + ); + + if (!networkInterface) { + return null; + } return { _id: check._id, - bytesPerSec: en0.avgBytesRecv, - packetsPerSec: en0.avgPacketsRecv, - errors: en0.avgErrOut ?? 0, - drops: en0.avgDropOut ?? 0, + bytesPerSec: networkInterface.avgBytesRecv, + packetsPerSec: networkInterface.avgPacketsRecv, + errors: networkInterface.avgErrOut ?? 0, + drops: networkInterface.avgDropOut ?? 0, }; }) .filter(Boolean); +}; - console.log(eth0Data); - +const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { + const eth0Data = getNetworkInterfaceData(checks); return ( <> { - if (!Array.isArray(checks) || checks.length === 0) return []; - - const sorted = [...checks].sort((a, b) => new Date(a._id) - new Date(b._id)); - const lastSeen = {}; - - for (const check of sorted) { - if (!Array.isArray(check.net)) { - check.net = []; - continue; - } - - const newNet = []; - - for (const iface of check.net) { - const prev = lastSeen[iface.name]; - const t1 = new Date(check._id); - - if (prev) { - const t0 = new Date(prev._id); - const dt = (t1 - t0) / 1000; - - if (dt > 0) { - newNet.push({ - name: iface.name, - avgBytesRecv: iface.avgBytesRecv - prev.avgBytesRecv, - avgPacketsRecv: iface.avgPacketsRecv - prev.avgPacketsRecv, - avgErrOut: iface.avgErrOut - prev.avgErrOut, - avgDropOut: iface.avgDropOut, - }); - } - } - - lastSeen[iface.name] = { ...iface, _id: check._id }; - } - - check.net = newNet; - } - - return sorted; - }; - getHardwareDetailsById = async ({ monitorId, dateRange }) => { try { const monitor = await this.Monitor.findById(monitorId); @@ -384,10 +342,6 @@ class MonitorModule { const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); const stats = hardwareStats[0]; - if (stats && stats.checks) { - // Replace net with per-second rates - stats.checks = this.processNetworkRates(stats.checks); - } return { ...monitor.toObject(), diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index b3417d05a..c6812b1bc 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -378,108 +378,110 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, net: { $map: { - input: { $range: [0, "$$netCount"] }, + input: { + $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }], + }, as: "netIndex", in: { name: { $arrayElemAt: [ { $map: { - input: "$net", - as: "netArray", - in: { $arrayElemAt: ["$$netArray.name", "$$netIndex"] }, + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.name", }, }, - 0, + "$$netIndex", ], }, avgBytesSent: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.bytes_sent", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_sent", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, + "$$netIndex", + ], + }, + ], }, avgBytesRecv: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.bytes_recv", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_recv", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, + "$$netIndex", + ], + }, + ], }, avgPacketsSent: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.packets_sent", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.packets_sent", + }, + }, + "$$netIndex", + ], }, - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, + "$$netIndex", + ], + }, + ], }, avgPacketsRecv: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.packets_recv", "$$netIndex"], - }, + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.packets_recv", + }, + }, + "$$netIndex", + ], }, - }, - }, - avgErrIn: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.err_in", "$$netIndex"], - }, + { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_recv" } }, + "$$netIndex", + ], }, - }, - }, - avgErrOut: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.err_out", "$$netIndex"], - }, - }, - }, - }, - avgDropIn: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.drop_in", "$$netIndex"], - }, - }, - }, - }, - avgDropOut: { - $avg: { - $map: { - input: "$net", - as: "netArray", - in: { - $arrayElemAt: ["$$netArray.drop_out", "$$netIndex"], - }, - }, - }, + ], }, }, }, From e27cc8a12babcbe8f64b072403b5dfb9a14cdb08 Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 12 Aug 2025 23:48:47 +0530 Subject: [PATCH 08/11] Added delta instead of avg, and formatted the code to re-use already existing code. --- .../Components/Charts/Utils/chartUtils.jsx | 18 +-- .../Components/NetworkStats/NetworkCharts.jsx | 35 +++-- .../NetworkStats/NetworkStatBoxes.jsx | 19 +-- .../Details/Components/NetworkStats/index.jsx | 31 +++-- .../Details/Hooks/useHardwareUtils.jsx | 25 +++- server/src/db/mongo/modules/monitorModule.js | 11 ++ .../db/mongo/modules/monitorModuleQueries.js | 122 +++++++++++++++++- 7 files changed, 201 insertions(+), 60 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index 40eb5b539..a7db3ea93 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -101,16 +101,17 @@ const getFormattedPercentage = (value) => { * @param {number} props.index - The index of the tick. * @returns {JSX.Element|null} The rendered tick component or null for the first tick. */ -export const NetworkTick = ({ x, y, payload, index }) => { +export const NetworkTick = ({ x, y, payload, index, formatter}) => { const theme = useTheme(); if (index === 0) return null; - const formatBytes = (bytes) => { - if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB/s`; - if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB/s`; - if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(1)} KB/s`; - return `${bytes} B/s`; - }; + if (formatter === undefined) { + formatter = (value, space=false) => { + if (typeof value !== "number") return value; + // need to add space between value and unit + return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; + }; + } return ( { fontSize={11} fontWeight={400} > - {formatBytes(payload?.value)} + {formatter(payload?.value, true)} ); }; @@ -131,6 +132,7 @@ NetworkTick.propTypes = { y: PropTypes.number, payload: PropTypes.object, index: PropTypes.number, + formatter: PropTypes.func, }; /** diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 5dd9feaad..84325c29e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -1,9 +1,7 @@ -// NetworkCharts.jsx import PropTypes from "prop-types"; -import { Stack } from "@mui/material"; +import { Stack, Typography } from "@mui/material"; import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart"; -// Utils import { TzTick, InfrastructureTooltip, @@ -11,43 +9,42 @@ import { } from "../../../../../Components/Charts/Utils/chartUtils"; import { useTheme } from "@emotion/react"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -const getFormattedNetworkMetric = (value) => { - if (typeof value !== "number" || isNaN(value)) return "0"; - if (value >= 1024 ** 3) return `${(value / 1024 ** 3).toFixed(1)} GB/s`; - if (value >= 1024 ** 2) return `${(value / 1024 ** 2).toFixed(1)} MB/s`; - if (value >= 1024) return `${(value / 1024).toFixed(1)} KB/s`; - return `${Math.round(value)} B/s`; -}; - -const NetworkCharts = ({ eth0Data, dateRange }) => { +const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); + const {formatBytesString} = useHardwareUtils(); + + if (!ethernetData?.length) { + return {t("noNetworkStatsAvailable")}; + } + const configs = [ { type: "network-bytes", - data: eth0Data, + data: ethernetData, dataKeys: ["bytesPerSec"], heading: t("bytesPerSecond"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , - yTick: , + yTick: , toolTip: ( ), }, { type: "network-packets", - data: eth0Data, + data: ethernetData, dataKeys: ["packetsPerSec"], heading: t("packetsPerSecond"), strokeColor: theme.palette.success.main, @@ -66,7 +63,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-errors", - data: eth0Data, + data: ethernetData, dataKeys: ["errors"], heading: t("errors"), strokeColor: theme.palette.error.main, @@ -85,7 +82,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }, { type: "network-drops", - data: eth0Data, + data: ethernetData, dataKeys: ["drops"], heading: t("drops"), strokeColor: theme.palette.warning.main, @@ -127,7 +124,7 @@ const NetworkCharts = ({ eth0Data, dateRange }) => { }; NetworkCharts.propTypes = { - eth0Data: PropTypes.array.isRequired, + ethernetData: PropTypes.array.isRequired, dateRange: PropTypes.string.isRequired, }; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx index 3125e86a9..98ba90dae 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkStatBoxes.jsx @@ -1,27 +1,19 @@ -// NetworkStatBoxes.jsx import PropTypes from "prop-types"; import StatusBoxes from "../../../../../Components/StatusBoxes"; import StatBox from "../../../../../Components/StatBox"; import { Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; +import { useHardwareUtils } from "../../Hooks/useHardwareUtils"; -function formatBytes(bytes) { - if (bytes === 0 || bytes == null) 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]}`; -} - -// Format numbers with commas function formatNumber(num) { return num != null ? num.toLocaleString() : "0"; } -const NetworkStatBoxes = ({ shouldRender, net }) => { +const NetworkStatBoxes = ({ shouldRender, net, ifaceName }) => { const { t } = useTranslation(); - const filtered = - net?.filter((iface) => iface.name === "en0" || iface.name === "wlan0") || []; + const { formatBytes } = useHardwareUtils(); + + const filtered = net?.filter((iface) => iface.name === ifaceName) || []; if (!net?.length) { return {t("noNetworkStatsAvailable")}; @@ -83,6 +75,7 @@ NetworkStatBoxes.propTypes = { err_out: PropTypes.number, }) ), + ifaceName: PropTypes.string.isRequired, }; export default NetworkStatBoxes; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 22a6c3112..623720aba 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -3,37 +3,40 @@ import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const getNetworkInterfaceData = (checks) => { +const getInterfaceName = (net) => { const interfaceNames = ["eth0", "Ethernet", "en0"]; + const found = (net || []).find((iface) => interfaceNames.includes(iface.name)); + return found ? found.name : null; +}; +const getNetworkInterfaceData = (checks, ifaceName) => { return (checks || []) .map((check) => { - const networkInterface = (check.net || []).find((iface) => - interfaceNames.includes(iface.name) + const networkInterface = (check.net || []).find( + (iface) => iface.name === ifaceName ); - - if (!networkInterface) { - return null; - } - + if (!networkInterface) return null; return { _id: check._id, - bytesPerSec: networkInterface.avgBytesRecv, - packetsPerSec: networkInterface.avgPacketsRecv, - errors: networkInterface.avgErrOut ?? 0, - drops: networkInterface.avgDropOut ?? 0, + bytesPerSec: networkInterface.deltaBytesRecv, + packetsPerSec: networkInterface.deltaPacketsRecv, + errors: networkInterface.deltaErrOut ?? 0, + drops: networkInterface.deltaDropOut ?? 0, }; }) .filter(Boolean); }; const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const eth0Data = getNetworkInterfaceData(checks); + const ifaceName = getInterfaceName(net); + const ethernetData = getNetworkInterfaceData(checks, ifaceName); + return ( <> { setDateRange={setDateRange} /> diff --git a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx index a2e4c5153..930ca7ba3 100644 --- a/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx +++ b/client/src/Pages/Infrastructure/Details/Hooks/useHardwareUtils.jsx @@ -57,7 +57,7 @@ const useHardwareUtils = () => { if (GB >= 1) { return ( <> - {Number(GB.toFixed(0))} + {Number(GB.toFixed(2))} {space ? " " : ""} {t("gb")} @@ -65,7 +65,7 @@ const useHardwareUtils = () => { } else { return ( <> - {Number(MB.toFixed(0))} + {Number(MB.toFixed(2))} {space ? " " : ""} {t("mb")} @@ -73,6 +73,26 @@ const useHardwareUtils = () => { } }; + const formatBytesString = (bytes, space = false) => { + if ( + bytes === undefined || + bytes === null || + typeof bytes !== "number" || + bytes === 0 + ) { + return `0${space ? " " : ""}${t("gb")}`; + } + + const GB = bytes / (1024 * 1024 * 1024); + const MB = bytes / (1024 * 1024); + + if (GB >= 1) { + return `${Number(GB.toFixed(2))}${space ? " " : ""}${t("gb")}`; + } else { + return `${Number(MB.toFixed(2))}${space ? " " : ""}${t("mb")}`; + } + }; + /** * Converts a decimal value to a percentage * @@ -134,6 +154,7 @@ const useHardwareUtils = () => { decimalToPercentage, buildTemps, getDimensions, + formatBytesString, }; }; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 0e301cdcf..3afe664f6 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -343,6 +343,17 @@ class MonitorModule { const stats = hardwareStats[0]; + if (stats?.net?.length) { + const elapsedSeconds = (new Date(dates.end).getTime() - new Date(dates.start).getTime()) / 1000; + stats.net = stats.net.map((iface) => ({ + ...iface, + bytesSentPerSec: iface.deltaBytesSent / elapsedSeconds, + bytesRecvPerSec: iface.deltaBytesRecv / elapsedSeconds, + packetsSentPerSec: iface.deltaPacketsSent / elapsedSeconds, + packetsRecvPerSec: iface.deltaPacketsRecv / elapsedSeconds, + })); + } + return { ...monitor.toObject(), stats, diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index c6812b1bc..91fb83444 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -395,7 +395,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { "$$netIndex", ], }, - avgBytesSent: { + deltaBytesSent: { $subtract: [ { $arrayElemAt: [ @@ -417,7 +417,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgBytesRecv: { + deltaBytesRecv: { $subtract: [ { $arrayElemAt: [ @@ -439,7 +439,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsSent: { + deltaPacketsSent: { $subtract: [ { $arrayElemAt: [ @@ -461,7 +461,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, - avgPacketsRecv: { + deltaPacketsRecv: { $subtract: [ { $arrayElemAt: [ @@ -483,6 +483,120 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, ], }, + deltaErrIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], + }, + ], + }, + deltaErrOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_out" } }, "$$netIndex"], + }, + ], + }, + deltaDropIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_in" } }, "$$netIndex"], + }, + ], + }, + deltaDropOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.drop_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_out" } }, "$$netIndex"], + }, + ], + }, + deltaFifoIn: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_in", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_in" } }, "$$netIndex"], + }, + ], + }, + deltaFifoOut: { + $subtract: [ + { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.fifo_out", + }, + }, + "$$netIndex", + ], + }, + { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_out" } }, "$$netIndex"], + }, + ], + }, }, }, }, From f5aefb1f0b573fa4623f0512ae98d1bdc910e71c Mon Sep 17 00:00:00 2001 From: Owaise Date: Tue, 12 Aug 2025 23:49:45 +0530 Subject: [PATCH 09/11] Ran formattor. --- client/src/Components/Charts/Utils/chartUtils.jsx | 4 ++-- .../Details/Components/NetworkStats/NetworkCharts.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index a7db3ea93..ce15d27cd 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -101,12 +101,12 @@ const getFormattedPercentage = (value) => { * @param {number} props.index - The index of the tick. * @returns {JSX.Element|null} The rendered tick component or null for the first tick. */ -export const NetworkTick = ({ x, y, payload, index, formatter}) => { +export const NetworkTick = ({ x, y, payload, index, formatter }) => { const theme = useTheme(); if (index === 0) return null; if (formatter === undefined) { - formatter = (value, space=false) => { + formatter = (value, space = false) => { if (typeof value !== "number") return value; // need to add space between value and unit return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 84325c29e..98eced859 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -15,7 +15,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); - const {formatBytesString} = useHardwareUtils(); + const { formatBytesString } = useHardwareUtils(); if (!ethernetData?.length) { return {t("noNetworkStatsAvailable")}; @@ -31,7 +31,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { gradientStartColor: theme.palette.info.main, yLabel: t("bytesPerSecond"), xTick: , - yTick: , + yTick: , toolTip: ( Date: Thu, 14 Aug 2025 00:04:53 +0530 Subject: [PATCH 10/11] Metrics validated. --- server/src/db/mongo/modules/monitorModule.js | 11 - .../db/mongo/modules/monitorModuleQueries.js | 474 ++++++++++++------ 2 files changed, 316 insertions(+), 169 deletions(-) diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 3afe664f6..0e301cdcf 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -343,17 +343,6 @@ class MonitorModule { const stats = hardwareStats[0]; - if (stats?.net?.length) { - const elapsedSeconds = (new Date(dates.end).getTime() - new Date(dates.start).getTime()) / 1000; - stats.net = stats.net.map((iface) => ({ - ...iface, - bytesSentPerSec: iface.deltaBytesSent / elapsedSeconds, - bytesRecvPerSec: iface.deltaBytesRecv / elapsedSeconds, - packetsSentPerSec: iface.deltaPacketsSent / elapsedSeconds, - packetsRecvPerSec: iface.deltaPacketsRecv / elapsedSeconds, - })); - } - return { ...monitor.toObject(), stats, diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js index 91fb83444..7753066fc 100755 --- a/server/src/db/mongo/modules/monitorModuleQueries.js +++ b/server/src/db/mongo/modules/monitorModuleQueries.js @@ -263,6 +263,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { net: { $push: "$net", }, + updatedAts: { + $push: "$updatedAt", + }, }, }, { @@ -378,9 +381,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { }, net: { $map: { - input: { - $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }], - }, + input: { $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }] }, as: "netIndex", in: { name: { @@ -395,211 +396,368 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => { "$$netIndex", ], }, - deltaBytesSent: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.bytes_sent", + bytesSentPerSecond: { + $let: { + vars: { + first: { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_sent", + }, }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_sent" } }, - "$$netIndex", - ], - }, - ], + }, }, deltaBytesRecv: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.bytes_recv", + $let: { + vars: { + first: { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.bytes_recv", + }, }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.bytes_recv" } }, - "$$netIndex", - ], - }, - ], + }, }, deltaPacketsSent: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.packets_sent", + $let: { + vars: { + first: { + $arrayElemAt: [ + { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.packets_sent", + }, }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_sent" } }, - "$$netIndex", - ], - }, - ], + }, }, deltaPacketsRecv: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.packets_recv", + $let: { + vars: { + first: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.packets_recv", + }, }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { + $arrayElemAt: [ + "$net", + { + $subtract: [{ $size: "$net" }, 1], + }, + ], + }, + as: "iface", + in: "$$iface.packets_recv", + }, + }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { + $arrayElemAt: [ + "$updatedAts", + { + $subtract: [{ $size: "$updatedAts" }, 1], + }, + ], + }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [ - { $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.packets_recv" } }, - "$$netIndex", - ], - }, - ], + }, }, deltaErrIn: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.err_in", + $let: { + vars: { + first: { + $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, + as: "iface", + in: "$$iface.err_in", + }, }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_in" } }, "$$netIndex"], - }, - ], + }, }, deltaErrOut: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.err_out", + $let: { + vars: { + first: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.err_out", + }, }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { + $arrayElemAt: [ + "$net", + { + $subtract: [{ $size: "$net" }, 1], + }, + ], + }, + as: "iface", + in: "$$iface.err_out", + }, + }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { + $arrayElemAt: [ + "$updatedAts", + { + $subtract: [{ $size: "$updatedAts" }, 1], + }, + ], + }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.err_out" } }, "$$netIndex"], - }, - ], + }, }, deltaDropIn: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.drop_in", + $let: { + vars: { + first: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.drop_in", + }, }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { + $arrayElemAt: [ + "$net", + { + $subtract: [{ $size: "$net" }, 1], + }, + ], + }, + as: "iface", + in: "$$iface.drop_in", + }, + }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { + $arrayElemAt: [ + "$updatedAts", + { + $subtract: [{ $size: "$updatedAts" }, 1], + }, + ], + }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_in" } }, "$$netIndex"], - }, - ], + }, }, deltaDropOut: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.drop_out", + $let: { + vars: { + first: { + $arrayElemAt: [ + { + $map: { + input: { $arrayElemAt: ["$net", 0] }, + as: "iface", + in: "$$iface.drop_out", + }, }, + "$$netIndex", + ], + }, + last: { + $arrayElemAt: [ + { + $map: { + input: { + $arrayElemAt: [ + "$net", + { + $subtract: [{ $size: "$net" }, 1], + }, + ], + }, + as: "iface", + in: "$$iface.drop_out", + }, + }, + "$$netIndex", + ], + }, + tFirst: { $arrayElemAt: ["$updatedAts", 0] }, + tLast: { + $arrayElemAt: [ + "$updatedAts", + { + $subtract: [{ $size: "$updatedAts" }, 1], + }, + ], + }, + }, + in: { + $cond: [ + { $gt: [{ $subtract: ["$$tLast", "$$tFirst"] }, 0] }, + { + $divide: [{ $subtract: ["$$last", "$$first"] }, { $divide: [{ $subtract: ["$$tLast", "$$tFirst"] }, 1000] }], }, - "$$netIndex", + 0, ], }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.drop_out" } }, "$$netIndex"], - }, - ], - }, - deltaFifoIn: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.fifo_in", - }, - }, - "$$netIndex", - ], - }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_in" } }, "$$netIndex"], - }, - ], - }, - deltaFifoOut: { - $subtract: [ - { - $arrayElemAt: [ - { - $map: { - input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] }, - as: "iface", - in: "$$iface.fifo_out", - }, - }, - "$$netIndex", - ], - }, - { - $arrayElemAt: [{ $map: { input: { $arrayElemAt: ["$net", 0] }, as: "iface", in: "$$iface.fifo_out" } }, "$$netIndex"], - }, - ], + }, }, }, }, }, + firstUpdatedAt: 1, + lastUpdatedAt: 1, }, }, ], From ff31ee773fdd87bf22865ee1923a1d3594301e85 Mon Sep 17 00:00:00 2001 From: Owaise Date: Thu, 14 Aug 2025 19:35:12 +0530 Subject: [PATCH 11/11] Added a drop down so that we have choice to make about which network interface to choose. --- .../Components/Charts/Utils/chartUtils.jsx | 2 +- .../Components/NetworkStats/NetworkCharts.jsx | 29 ++++---- .../Details/Components/NetworkStats/index.jsx | 72 +++++++++++++++---- .../Details/Hooks/useHardwareUtils.jsx | 50 ++++++++++--- client/src/locales/en.json | 9 +++ 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/client/src/Components/Charts/Utils/chartUtils.jsx b/client/src/Components/Charts/Utils/chartUtils.jsx index ce15d27cd..e24881c1c 100644 --- a/client/src/Components/Charts/Utils/chartUtils.jsx +++ b/client/src/Components/Charts/Utils/chartUtils.jsx @@ -109,7 +109,7 @@ export const NetworkTick = ({ x, y, payload, index, formatter }) => { formatter = (value, space = false) => { if (typeof value !== "number") return value; // need to add space between value and unit - return `${(value / 1024).toFixed(2)}${space ? " " : ""}Kbps`; + return `${(value / 1024).toFixed(1)}${space ? " " : ""}Kbps`; }; } diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx index 98eced859..9157a3432 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/NetworkCharts.jsx @@ -15,7 +15,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { const theme = useTheme(); const { t } = useTranslation(); - const { formatBytesString } = useHardwareUtils(); + const { formatBytesPerSecondString, formatPacketsPerSecondString } = useHardwareUtils(); if (!ethernetData?.length) { return {t("noNetworkStatsAvailable")}; @@ -26,19 +26,19 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { type: "network-bytes", data: ethernetData, dataKeys: ["bytesPerSec"], - heading: t("bytesPerSecond"), + heading: t("dataReceived"), strokeColor: theme.palette.info.main, gradientStartColor: theme.palette.info.main, - yLabel: t("bytesPerSecond"), + yLabel: t("rate"), xTick: , - yTick: , + yTick: , toolTip: ( ), }, @@ -46,18 +46,19 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { type: "network-packets", data: ethernetData, dataKeys: ["packetsPerSec"], - heading: t("packetsPerSecond"), + heading: t("packetsReceivedRate"), strokeColor: theme.palette.success.main, gradientStartColor: theme.palette.success.main, - yLabel: t("packetsPerSecond"), + yLabel: t("rate"), xTick: , + yTick: , toolTip: ( Math.round(value).toLocaleString()} + formatter={formatPacketsPerSecondString} /> ), }, @@ -65,7 +66,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { type: "network-errors", data: ethernetData, dataKeys: ["errors"], - heading: t("errors"), + heading: t("networkErrors"), strokeColor: theme.palette.error.main, gradientStartColor: theme.palette.error.main, yLabel: t("errors"), @@ -74,7 +75,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { Math.round(value).toLocaleString()} /> @@ -84,7 +85,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { type: "network-drops", data: ethernetData, dataKeys: ["drops"], - heading: t("drops"), + heading: t("networkDrops"), strokeColor: theme.palette.warning.main, gradientStartColor: theme.palette.warning.main, yLabel: t("drops"), @@ -93,7 +94,7 @@ const NetworkCharts = ({ ethernetData, dateRange }) => { Math.round(value).toLocaleString()} /> diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx index 623720aba..227523439 100644 --- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx +++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx @@ -1,15 +1,21 @@ import PropTypes from "prop-types"; +import { useState, useEffect } from "react"; +import { FormControl, InputLabel, Select, MenuItem, Box } from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { useTheme } from "@emotion/react"; import NetworkStatBoxes from "./NetworkStatBoxes"; import NetworkCharts from "./NetworkCharts"; import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader"; -const getInterfaceName = (net) => { - const interfaceNames = ["eth0", "Ethernet", "en0"]; - const found = (net || []).find((iface) => interfaceNames.includes(iface.name)); - return found ? found.name : null; +const getAvailableInterfaces = (net) => { + return (net || []).map((iface) => iface.name).filter(Boolean); }; const getNetworkInterfaceData = (checks, ifaceName) => { + if (!ifaceName) return []; + + // Transform backend data structure for the selected interface + // Backend already calculates deltas, we just reshape the data return (checks || []) .map((check) => { const networkInterface = (check.net || []).find( @@ -28,21 +34,63 @@ const getNetworkInterfaceData = (checks, ifaceName) => { }; const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => { - const ifaceName = getInterfaceName(net); - const ethernetData = getNetworkInterfaceData(checks, ifaceName); + const { t } = useTranslation(); + const theme = useTheme(); + + const availableInterfaces = getAvailableInterfaces(net); + const [selectedInterface, setSelectedInterface] = useState(""); + + // Set default interface when data loads + useEffect(() => { + if (availableInterfaces.length > 0 && !selectedInterface) { + setSelectedInterface(availableInterfaces[0]); + } + }, [availableInterfaces, selectedInterface]); + + const ethernetData = getNetworkInterfaceData(checks, selectedInterface); return ( <> - + + {availableInterfaces.length > 0 && ( + + {t("networkInterface")} + + + )} + + { } }; - const formatBytesString = (bytes, space = false) => { + const formatBytesPerSecondString = (bytesPerSec, space = false) => { if ( - bytes === undefined || - bytes === null || - typeof bytes !== "number" || - bytes === 0 + bytesPerSec === undefined || + bytesPerSec === null || + typeof bytesPerSec !== "number" || + bytesPerSec === 0 ) { - return `0${space ? " " : ""}${t("gb")}`; + return `0${space ? " " : ""}B/s`; } - const GB = bytes / (1024 * 1024 * 1024); - const MB = bytes / (1024 * 1024); + const GB = bytesPerSec / (1024 * 1024 * 1024); + const MB = bytesPerSec / (1024 * 1024); + const KB = bytesPerSec / 1024; if (GB >= 1) { - return `${Number(GB.toFixed(2))}${space ? " " : ""}${t("gb")}`; + return `${Number(GB.toFixed(1))}${space ? " " : ""}GB/s`; + } else if (MB >= 1) { + return `${Number(MB.toFixed(1))}${space ? " " : ""}MB/s`; + } else if (KB >= 1) { + return `${Number(KB.toFixed(1))}${space ? " " : ""}KB/s`; } else { - return `${Number(MB.toFixed(2))}${space ? " " : ""}${t("mb")}`; + return `${Number(bytesPerSec.toFixed(1))}${space ? " " : ""}B/s`; + } + }; + + const formatPacketsPerSecondString = (packetsPerSec, space = false) => { + if ( + packetsPerSec === undefined || + packetsPerSec === null || + typeof packetsPerSec !== "number" || + packetsPerSec === 0 + ) { + return `0${space ? " " : ""}pps`; + } + + const M = packetsPerSec / (1000 * 1000); + const K = packetsPerSec / 1000; + + if (M >= 1) { + return `${Number(M.toFixed(1))}${space ? " " : ""}Mpps`; + } else if (K >= 1) { + return `${Number(K.toFixed(1))}${space ? " " : ""}Kpps`; + } else { + return `${Math.round(packetsPerSec)}${space ? " " : ""}pps`; } }; @@ -154,7 +181,8 @@ const useHardwareUtils = () => { decimalToPercentage, buildTemps, getDimensions, - formatBytesString, + formatBytesPerSecondString, + formatPacketsPerSecondString, }; }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 08c60c372..857825c9f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -205,6 +205,10 @@ "bytesPerSecond": "Bytes per second", "bytesReceived": "Bytes Received", "bytesSent": "Bytes Sent", + "dataReceived": "Data Received", + "dataSent": "Data Sent", + "dataRate": "Data Rate", + "rate": "Rate", "bulkImport": { "fallbackPage": "Import a file to upload a list of servers in bulk", "invalidFileType": "Invalid file type", @@ -341,6 +345,10 @@ "errors": "Errors", "errorsIn": "Errors In", "errorsOut": "Errors Out", + "networkErrors": "Network Errors", + "networkDrops": "Network Drops", + "networkInterface": "Network Interface", + "selectInterface": "Select Interface", "details": "Details", "displayName": "Display name", "distributedRightCategoryTitle": "Monitor", @@ -763,6 +771,7 @@ }, "packetsPerSecond": "Packets per second", "packetsReceived": "Packets Received", + "packetsReceivedRate": "Packets Received Rate", "packetsSent": "Packets Sent", "pause": "Pause", "pingMonitoring": "Ping monitoring",