mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-08 18:59:43 -06:00
Network tab first implementation
This commit is contained in:
@@ -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 <text x={x} y={y} textAnchor="end" fill="#888">{label}</text>;
|
||||
};
|
||||
|
||||
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: <BytesTick /> },
|
||||
{ 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 (
|
||||
<Grid container spacing={3}>
|
||||
{charts.map((chart) => (
|
||||
<Grid item xs={12} md={6} key={chart.key}>
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="h6" sx={{ color: textColor }} mb={2}>
|
||||
{chart.title}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
data={eth0Data}
|
||||
dataKeys={[chart.key]}
|
||||
xKey="time"
|
||||
yTick={chart.yTick || null}
|
||||
xTick={<TzTick dateRange={dateRange} />}
|
||||
strokeColor={chart.color}
|
||||
gradient
|
||||
gradientStartColor={chart.color}
|
||||
gradientEndColor="#fff"
|
||||
height={200}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkCharts;
|
||||
@@ -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 <Typography>No network stats available.</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusBoxes shouldRender={shouldRender} flexWrap="wrap">
|
||||
{filtered.map((iface) => (
|
||||
<>
|
||||
<StatBox
|
||||
heading={`${INTERFACE_LABELS[iface.name] || iface.name} - Bytes Sent`}
|
||||
subHeading={formatBytes(iface.bytes_sent)}
|
||||
icon={DataUsageIcon}
|
||||
iconProps={{ color: "action" }}
|
||||
/>
|
||||
<StatBox
|
||||
heading="Bytes Received"
|
||||
subHeading={formatBytes(iface.bytes_recv)}
|
||||
icon={DataUsageIcon}
|
||||
iconProps={{ color: "action", sx: { transform: "rotate(180deg)" } }}
|
||||
/>
|
||||
<StatBox
|
||||
heading="Packets Sent"
|
||||
subHeading={formatNumber(iface.packets_sent)}
|
||||
icon={NetworkCheckIcon}
|
||||
iconProps={{ color: "action" }}
|
||||
/>
|
||||
<StatBox
|
||||
heading="Packets Received"
|
||||
subHeading={formatNumber(iface.packets_recv)}
|
||||
icon={NetworkCheckIcon}
|
||||
iconProps={{ color: "action", sx: { transform: "rotate(180deg)" } }}
|
||||
/>
|
||||
<StatBox
|
||||
heading="Errors In"
|
||||
subHeading={formatNumber(iface.err_in)}
|
||||
icon={ErrorOutlineIcon}
|
||||
iconProps={{ color: "error" }}
|
||||
/>
|
||||
<StatBox
|
||||
heading="Errors Out"
|
||||
subHeading={formatNumber(iface.err_out)}
|
||||
icon={ErrorOutlineIcon}
|
||||
iconProps={{ color: "error" }}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</StatusBoxes>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkStatBoxes;
|
||||
@@ -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 (
|
||||
<>
|
||||
<NetworkStatBoxes shouldRender={!isLoading} net={net} />
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<NetworkCharts eth0Data={filteredEth0Data} xAxisFormatter={xAxisFormatter} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
} from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={180}
|
||||
height={32}
|
||||
/>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Bytes Sent</TableCell>
|
||||
<TableCell>Bytes Received</TableCell>
|
||||
<TableCell>Packets Sent</TableCell>
|
||||
<TableCell>Packets Received</TableCell>
|
||||
<TableCell>Errors In</TableCell>
|
||||
<TableCell>Errors Out</TableCell>
|
||||
<TableCell>Drops In</TableCell>
|
||||
<TableCell>Drops Out</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Array.from({ length: 5 }).map((_, idx) => (
|
||||
<TableRow key={idx}>
|
||||
{Array.from({ length: 9 }).map((__, colIdx) => (
|
||||
<TableCell key={colIdx}>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={80}
|
||||
height={24}
|
||||
/>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -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}
|
||||
/>
|
||||
<StatusBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<GaugeBoxes
|
||||
isLoading={isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<AreaChartBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<TabContext value={tab}>
|
||||
<CustomTabList
|
||||
value={tab}
|
||||
onChange={(e, v) => setTab(v)}
|
||||
>
|
||||
<Tab
|
||||
label="Details"
|
||||
value="details"
|
||||
/>
|
||||
<Tab
|
||||
label="Network"
|
||||
value="network"
|
||||
/>
|
||||
</CustomTabList>
|
||||
{tab === "details" && (
|
||||
<>
|
||||
<StatusBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<GaugeBoxes
|
||||
isLoading={isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
<AreaChartBoxes
|
||||
shouldRender={!isLoading}
|
||||
monitor={monitor}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{tab === "network" && (
|
||||
<NetworkStats
|
||||
net={monitor?.stats?.aggregateData?.latestCheck?.net || []}
|
||||
isLoading={isLoading}
|
||||
checks={monitor?.stats?.checks}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
)}
|
||||
</TabContext>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user