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" && (
+
+ )}
+
);
};