mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-11 04:09:40 -06:00
fixed the minor changes for FE.
This commit is contained in:
@@ -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)}%`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <text x={x} y={y} textAnchor="end" fill="#888">{label}</text>;
|
||||
};
|
||||
// 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: <BytesTick /> },
|
||||
{ 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: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.info.main}
|
||||
yKey={"bytesPerSec"}
|
||||
yLabel={"Bytes per second"}
|
||||
dateRange={dateRange}
|
||||
formatter={getFormattedNetworkMetric}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
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: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"packetsPerSec"}
|
||||
yLabel={"Packets per second"}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => 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: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.error.main}
|
||||
yKey={"errors"}
|
||||
yLabel={"Errors"}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => 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: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={"drops"}
|
||||
yLabel={"Drops"}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => 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 (
|
||||
<text x={x} y={y} textAnchor="end" fill="#888">
|
||||
{formatYAxis(chartKey, value)}
|
||||
</text>
|
||||
);
|
||||
};
|
||||
|
||||
const chartConfigs = charts.map((chart) => ({
|
||||
data: eth0Data,
|
||||
dataKeys: [chart.key],
|
||||
heading: chart.title,
|
||||
strokeColor: chart.color,
|
||||
gradientStartColor: chart.color,
|
||||
yTick: chart.yTick || <CustomTick chartKey={chart.key} />,
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={chart.color}
|
||||
yKey={chart.key}
|
||||
yLabel={chart.title}
|
||||
dateRange={dateRange}
|
||||
formatter={getFormattedNetworkMetric}
|
||||
/>
|
||||
),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
{chartConfigs.map((config, idx) => (
|
||||
<Grid item xs={12} md={6} key={config.heading}>
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="h6" sx={{ color: textColor }} mb={2}>
|
||||
{config.heading}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
data={config.data}
|
||||
dataKeys={config.dataKeys}
|
||||
xKey="time"
|
||||
yTick={config.yTick}
|
||||
xTick={config.xTick}
|
||||
strokeColor={config.strokeColor}
|
||||
gradient
|
||||
gradientStartColor={config.gradientStartColor}
|
||||
gradientEndColor="#fff"
|
||||
height={200}
|
||||
customTooltip={config.toolTip}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
gap={theme.spacing(8)}
|
||||
flexWrap="wrap"
|
||||
sx={{
|
||||
"& > *": {
|
||||
flexBasis: `calc(50% - ${theme.spacing(8)})`,
|
||||
maxWidth: `calc(50% - ${theme.spacing(8)})`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{configs.map((config) => (
|
||||
<InfraAreaChart
|
||||
key={config.type}
|
||||
config={config}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default NetworkCharts;
|
||||
NetworkCharts.propTypes = {
|
||||
eth0Data: PropTypes.array.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default NetworkCharts;
|
||||
|
||||
@@ -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) => [
|
||||
<StatBox
|
||||
heading={`${INTERFACE_LABELS[iface.name] || iface.name} - Bytes Sent`}
|
||||
key={`${iface.name}-bytes-sent`}
|
||||
heading="Bytes Sent"
|
||||
subHeading={formatBytes(iface.bytes_sent)}
|
||||
icon={DataUsageIcon}
|
||||
iconProps={{ color: "action" }}
|
||||
/>
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-bytes-recv`}
|
||||
heading="Bytes Received"
|
||||
subHeading={formatBytes(iface.bytes_recv)}
|
||||
icon={DataUsageIcon}
|
||||
iconProps={{ color: "action", sx: { transform: "rotate(180deg)" } }}
|
||||
/>
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-sent`}
|
||||
heading="Packets Sent"
|
||||
subHeading={formatNumber(iface.packets_sent)}
|
||||
icon={NetworkCheckIcon}
|
||||
iconProps={{ color: "action" }}
|
||||
/>
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-recv`}
|
||||
heading="Packets Received"
|
||||
subHeading={formatNumber(iface.packets_recv)}
|
||||
icon={NetworkCheckIcon}
|
||||
iconProps={{ color: "action", sx: { transform: "rotate(180deg)" } }}
|
||||
/>
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-in`}
|
||||
heading="Errors In"
|
||||
subHeading={formatNumber(iface.err_in)}
|
||||
icon={ErrorOutlineIcon}
|
||||
iconProps={{ color: "error" }}
|
||||
/>
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-out`}
|
||||
heading="Errors Out"
|
||||
subHeading={formatNumber(iface.err_out)}
|
||||
icon={ErrorOutlineIcon}
|
||||
iconProps={{ color: "error" }}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
/>,
|
||||
])
|
||||
.flat()}
|
||||
</StatusBoxes>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<NetworkCharts
|
||||
eth0Data={filteredEth0Data}
|
||||
xAxisFormatter={xAxisFormatter}
|
||||
eth0Data={eth0Data}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user