Merge pull request #2741 from Owaiseimdad/Network-tab-implementation-FE
Network tab implementation fe
This commit is contained in:
10 files changed
+899
-75
No files matched your search
@@ -88,7 +88,51 @@ PercentTick.propTypes = {
|
||||
*/
|
||||
const getFormattedPercentage = (value) => {
|
||||
if (typeof value !== "number") return value;
|
||||
return `${(value * 100).toFixed(2)}.%`;
|
||||
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, formatter }) => {
|
||||
const theme = useTheme();
|
||||
if (index === 0) return null;
|
||||
|
||||
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(1)}${space ? " " : ""}Kbps`;
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
x={x - 20}
|
||||
y={y}
|
||||
textAnchor="middle"
|
||||
fill={theme.palette.primary.contrastTextTertiary}
|
||||
fontSize={11}
|
||||
fontWeight={400}
|
||||
>
|
||||
{formatter(payload?.value, true)}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkTick.propTypes = {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
index: PropTypes.number,
|
||||
formatter: PropTypes.func,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,6 +156,7 @@ export const InfrastructureTooltip = ({
|
||||
yLabel,
|
||||
dotColor,
|
||||
dateRange,
|
||||
formatter = getFormattedPercentage,
|
||||
}) => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const theme = useTheme();
|
||||
@@ -166,8 +211,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])}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import InfraAreaChart from "../../../../../Pages/Infrastructure/Details/Components/AreaChartBoxes/InfraAreaChart";
|
||||
|
||||
import {
|
||||
TzTick,
|
||||
InfrastructureTooltip,
|
||||
NetworkTick,
|
||||
} from "../../../../../Components/Charts/Utils/chartUtils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
|
||||
|
||||
const NetworkCharts = ({ ethernetData, dateRange }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { formatBytesPerSecondString, formatPacketsPerSecondString } = useHardwareUtils();
|
||||
|
||||
if (!ethernetData?.length) {
|
||||
return <Typography>{t("noNetworkStatsAvailable")}</Typography>;
|
||||
}
|
||||
|
||||
const configs = [
|
||||
{
|
||||
type: "network-bytes",
|
||||
data: ethernetData,
|
||||
dataKeys: ["bytesPerSec"],
|
||||
heading: t("dataReceived"),
|
||||
strokeColor: theme.palette.info.main,
|
||||
gradientStartColor: theme.palette.info.main,
|
||||
yLabel: t("rate"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
yTick: <NetworkTick formatter={formatBytesPerSecondString} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.info.main}
|
||||
yKey={"bytesPerSec"}
|
||||
yLabel={t("dataRate") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={formatBytesPerSecondString}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "network-packets",
|
||||
data: ethernetData,
|
||||
dataKeys: ["packetsPerSec"],
|
||||
heading: t("packetsReceivedRate"),
|
||||
strokeColor: theme.palette.success.main,
|
||||
gradientStartColor: theme.palette.success.main,
|
||||
yLabel: t("rate"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
yTick: <NetworkTick formatter={formatPacketsPerSecondString} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"packetsPerSec"}
|
||||
yLabel={t("packetsPerSecond") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={formatPacketsPerSecondString}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "network-errors",
|
||||
data: ethernetData,
|
||||
dataKeys: ["errors"],
|
||||
heading: t("networkErrors"),
|
||||
strokeColor: theme.palette.error.main,
|
||||
gradientStartColor: theme.palette.error.main,
|
||||
yLabel: t("errors"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.error.main}
|
||||
yKey={"errors"}
|
||||
yLabel={t("errors") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => Math.round(value).toLocaleString()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "network-drops",
|
||||
data: ethernetData,
|
||||
dataKeys: ["drops"],
|
||||
heading: t("networkDrops"),
|
||||
strokeColor: theme.palette.warning.main,
|
||||
gradientStartColor: theme.palette.warning.main,
|
||||
yLabel: t("drops"),
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={"drops"}
|
||||
yLabel={t("drops") + ": "}
|
||||
dateRange={dateRange}
|
||||
formatter={(value) => Math.round(value).toLocaleString()}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkCharts.propTypes = {
|
||||
ethernetData: PropTypes.array.isRequired,
|
||||
dateRange: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default NetworkCharts;
|
||||
@@ -0,0 +1,81 @@
|
||||
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 formatNumber(num) {
|
||||
return num != null ? num.toLocaleString() : "0";
|
||||
}
|
||||
|
||||
const NetworkStatBoxes = ({ shouldRender, net, ifaceName }) => {
|
||||
const { t } = useTranslation();
|
||||
const { formatBytes } = useHardwareUtils();
|
||||
|
||||
const filtered = net?.filter((iface) => iface.name === ifaceName) || [];
|
||||
|
||||
if (!net?.length) {
|
||||
return <Typography>{t("noNetworkStatsAvailable")}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusBoxes
|
||||
shouldRender={shouldRender}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
{filtered
|
||||
.map((iface) => [
|
||||
<StatBox
|
||||
key={`${iface.name}-bytes-sent`}
|
||||
heading={t("bytesSent")}
|
||||
subHeading={formatBytes(iface.bytes_sent)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-bytes-recv`}
|
||||
heading={t("bytesReceived")}
|
||||
subHeading={formatBytes(iface.bytes_recv)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-sent`}
|
||||
heading={t("packetsSent")}
|
||||
subHeading={formatNumber(iface.packets_sent)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-packets-recv`}
|
||||
heading={t("packetsReceived")}
|
||||
subHeading={formatNumber(iface.packets_recv)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-in`}
|
||||
heading={t("errorsIn")}
|
||||
subHeading={formatNumber(iface.err_in)}
|
||||
/>,
|
||||
<StatBox
|
||||
key={`${iface.name}-err-out`}
|
||||
heading={t("errorsOut")}
|
||||
subHeading={formatNumber(iface.err_out)}
|
||||
/>,
|
||||
])
|
||||
.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,
|
||||
})
|
||||
),
|
||||
ifaceName: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default NetworkStatBoxes;
|
||||
@@ -0,0 +1,110 @@
|
||||
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 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(
|
||||
(iface) => iface.name === ifaceName
|
||||
);
|
||||
if (!networkInterface) return null;
|
||||
return {
|
||||
_id: check._id,
|
||||
bytesPerSec: networkInterface.deltaBytesRecv,
|
||||
packetsPerSec: networkInterface.deltaPacketsRecv,
|
||||
errors: networkInterface.deltaErrOut ?? 0,
|
||||
drops: networkInterface.deltaDropOut ?? 0,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => {
|
||||
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 (
|
||||
<>
|
||||
<NetworkStatBoxes
|
||||
shouldRender={!isLoading}
|
||||
net={net}
|
||||
ifaceName={selectedInterface}
|
||||
/>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
{availableInterfaces.length > 0 && (
|
||||
<FormControl
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ minWidth: 200 }}
|
||||
>
|
||||
<InputLabel>{t("networkInterface")}</InputLabel>
|
||||
<Select
|
||||
value={selectedInterface}
|
||||
onChange={(e) => setSelectedInterface(e.target.value)}
|
||||
label={t("networkInterface")}
|
||||
>
|
||||
{availableInterfaces.map((interfaceName) => (
|
||||
<MenuItem
|
||||
key={interfaceName}
|
||||
value={interfaceName}
|
||||
>
|
||||
{interfaceName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<MonitorTimeFrameHeader
|
||||
isLoading={isLoading}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
</Box>
|
||||
<NetworkCharts
|
||||
ethernetData={ethernetData}
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -57,7 +57,7 @@ const useHardwareUtils = () => {
|
||||
if (GB >= 1) {
|
||||
return (
|
||||
<>
|
||||
{Number(GB.toFixed(0))}
|
||||
{Number(GB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
@@ -65,7 +65,7 @@ const useHardwareUtils = () => {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{Number(MB.toFixed(0))}
|
||||
{Number(MB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("mb")}</Typography>
|
||||
</>
|
||||
@@ -73,6 +73,53 @@ const useHardwareUtils = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatBytesPerSecondString = (bytesPerSec, space = false) => {
|
||||
if (
|
||||
bytesPerSec === undefined ||
|
||||
bytesPerSec === null ||
|
||||
typeof bytesPerSec !== "number" ||
|
||||
bytesPerSec === 0
|
||||
) {
|
||||
return `0${space ? " " : ""}B/s`;
|
||||
}
|
||||
|
||||
const GB = bytesPerSec / (1024 * 1024 * 1024);
|
||||
const MB = bytesPerSec / (1024 * 1024);
|
||||
const KB = bytesPerSec / 1024;
|
||||
|
||||
if (GB >= 1) {
|
||||
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(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`;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a decimal value to a percentage
|
||||
*
|
||||
@@ -134,6 +181,8 @@ const useHardwareUtils = () => {
|
||||
decimalToPercentage,
|
||||
buildTemps,
|
||||
getDimensions,
|
||||
formatBytesPerSecondString,
|
||||
formatPacketsPerSecondString,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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,10 @@ 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 +89,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={t("details")}
|
||||
value="details"
|
||||
/>
|
||||
<Tab
|
||||
label={t("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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -202,6 +202,13 @@
|
||||
"avgCpuTemperature": "Average CPU Temperature",
|
||||
"bar": "Bar",
|
||||
"basicInformation": "Basic Information",
|
||||
"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",
|
||||
@@ -334,6 +341,15 @@
|
||||
},
|
||||
"disk": "Disk",
|
||||
"diskUsage": "Disk Usage",
|
||||
"drops": "Drops",
|
||||
"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",
|
||||
"distributedStatusHeaderText": "Real-time, real-device coverage",
|
||||
@@ -639,7 +655,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",
|
||||
@@ -751,6 +769,10 @@
|
||||
"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",
|
||||
"packetsReceivedRate": "Packets Received Rate",
|
||||
"packetsSent": "Packets Sent",
|
||||
"pause": "Pause",
|
||||
"pingMonitoring": "Ping monitoring",
|
||||
"pingMonitoringDescription": "Check whether your server is available or not.",
|
||||
|
||||
@@ -330,6 +330,7 @@ class MonitorModule {
|
||||
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 +338,15 @@ 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];
|
||||
|
||||
return {
|
||||
...monitor.toObject(),
|
||||
stats: hardwareStats[0],
|
||||
stats,
|
||||
};
|
||||
return monitorStats;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getHardwareDetailsById";
|
||||
|
||||
@@ -263,6 +263,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
|
||||
net: {
|
||||
$push: "$net",
|
||||
},
|
||||
updatedAts: {
|
||||
$push: "$updatedAt",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -378,90 +381,383 @@ 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"],
|
||||
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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
avgBytesRecv: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$net",
|
||||
as: "netArray",
|
||||
in: {
|
||||
$arrayElemAt: ["$$netArray.bytes_recv", "$$netIndex"],
|
||||
deltaBytesRecv: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
avgPacketsSent: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$net",
|
||||
as: "netArray",
|
||||
in: {
|
||||
$arrayElemAt: ["$$netArray.packets_sent", "$$netIndex"],
|
||||
deltaPacketsSent: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
avgPacketsRecv: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$net",
|
||||
as: "netArray",
|
||||
in: {
|
||||
$arrayElemAt: ["$$netArray.packets_recv", "$$netIndex"],
|
||||
deltaPacketsRecv: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
avgErrIn: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$net",
|
||||
as: "netArray",
|
||||
in: {
|
||||
$arrayElemAt: ["$$netArray.err_in", "$$netIndex"],
|
||||
deltaErrIn: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
avgErrOut: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$net",
|
||||
as: "netArray",
|
||||
in: {
|
||||
$arrayElemAt: ["$$netArray.err_out", "$$netIndex"],
|
||||
deltaErrOut: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
deltaDropIn: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
deltaDropOut: {
|
||||
$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] }],
|
||||
},
|
||||
0,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
firstUpdatedAt: 1,
|
||||
lastUpdatedAt: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user