mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-21 00:48:45 -05:00
Merge pull request #3220 from bluewave-labs/fix/infra-cleanup
fix: remove unused infra components
This commit is contained in:
@@ -127,7 +127,7 @@ export const HistogramInfrastructure = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const uniqueId = useId();
|
||||
let data = checks.reverse();
|
||||
const data = checks;
|
||||
|
||||
let avgTemps: { bucketDate: string; avg_temp: number | null }[] = [];
|
||||
let tempYDomain: number[] = [];
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
// Components
|
||||
import { Typography } from "@mui/material";
|
||||
import BaseContainer from "../BaseContainer/index.jsx";
|
||||
import AreaChart from "@/Components/v1/Charts/AreaChart/index.jsx";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
const InfraAreaChart = ({ config }) => {
|
||||
const theme = useTheme();
|
||||
const { getDimensions } = useHardwareUtils();
|
||||
return (
|
||||
<BaseContainer>
|
||||
<Typography
|
||||
component="h2"
|
||||
padding={theme.spacing(8)}
|
||||
>
|
||||
{config.heading}
|
||||
</Typography>
|
||||
<AreaChart
|
||||
height={getDimensions().areaChartHeight}
|
||||
data={config.data}
|
||||
dataKeys={config.dataKeys}
|
||||
xKey="_id"
|
||||
yDomain={config.yDomain}
|
||||
customTooltip={config.toolTip}
|
||||
xTick={config.xTick}
|
||||
yTick={config.yTick}
|
||||
strokeColor={config.strokeColor}
|
||||
gradient={true}
|
||||
gradientStartColor={config.gradientStartColor}
|
||||
gradientEndColor="#ffffff"
|
||||
/>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfraAreaChart;
|
||||
@@ -1,140 +0,0 @@
|
||||
// Components
|
||||
import { Stack } from "@mui/material";
|
||||
import InfraAreaChart from "./InfraAreaChart.jsx";
|
||||
import SkeletonLayout from "./skeleton.jsx";
|
||||
|
||||
// Utils
|
||||
import {
|
||||
PercentTick,
|
||||
TzTick,
|
||||
InfrastructureTooltip,
|
||||
TemperatureTooltip,
|
||||
} from "@/Components/v1/Charts/Utils/chartUtils.jsx";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const AreaChartBoxes = ({ shouldRender, monitor, dateRange }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { buildTemps } = useHardwareUtils();
|
||||
|
||||
if (!shouldRender) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
const { stats } = monitor ?? {};
|
||||
const { checks } = stats;
|
||||
|
||||
let latestCheck = checks[0];
|
||||
const { temps, tempKeys } = buildTemps(checks);
|
||||
|
||||
const configs = [
|
||||
{
|
||||
type: "memory",
|
||||
data: checks,
|
||||
dataKeys: ["avgMemoryUsage"],
|
||||
heading: t("memoryUsage"),
|
||||
strokeColor: theme.palette.accent.main, // CAIO_REVIEW
|
||||
gradientStartColor: theme.palette.accent.main, // CAIO_REVIEW
|
||||
yLabel: t("memoryUsage"),
|
||||
yDomain: [0, 1],
|
||||
yTick: <PercentTick />,
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.primary.main}
|
||||
yKey={"avgMemoryUsage"}
|
||||
yLabel={"Memory usage"}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "cpu",
|
||||
data: checks,
|
||||
dataKeys: ["avgCpuUsage"],
|
||||
heading: t("cpuUsage"),
|
||||
strokeColor: theme.palette.success.main,
|
||||
gradientStartColor: theme.palette.success.main,
|
||||
yLabel: t("cpuUsage"),
|
||||
yDomain: [0, 1],
|
||||
yTick: <PercentTick />,
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.success.main}
|
||||
yKey={"avgCpuUsage"}
|
||||
yLabel={"CPU usage"}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "temperature",
|
||||
data: temps,
|
||||
dataKeys: tempKeys,
|
||||
strokeColor: theme.palette.error.main,
|
||||
gradientStartColor: theme.palette.error.main,
|
||||
heading: t("cpuTemperature"),
|
||||
yLabel: "Temperature",
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
yDomain: [
|
||||
0,
|
||||
Math.max(Math.max(...temps.flatMap((t) => tempKeys.map((k) => t[k]))) * 1.1, 200),
|
||||
],
|
||||
toolTip: (
|
||||
<TemperatureTooltip
|
||||
keys={tempKeys}
|
||||
dotColor={theme.palette.error.main}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
),
|
||||
},
|
||||
...(latestCheck?.disks?.map((disk, idx) => ({
|
||||
type: "disk",
|
||||
data: checks,
|
||||
diskIndex: idx,
|
||||
dataKeys: [`disks[${idx}].usagePercent`],
|
||||
heading: `Disk${idx} usage`,
|
||||
strokeColor: theme.palette.warning.main,
|
||||
gradientStartColor: theme.palette.warning.main,
|
||||
yLabel: t("diskUsage"),
|
||||
yDomain: [0, 1],
|
||||
yTick: <PercentTick />,
|
||||
xTick: <TzTick dateRange={dateRange} />,
|
||||
toolTip: (
|
||||
<InfrastructureTooltip
|
||||
dotColor={theme.palette.warning.main}
|
||||
yKey={`disks.usagePercent`}
|
||||
yLabel={"Disc usage"}
|
||||
yIdx={idx}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
),
|
||||
})) || []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction={"row"}
|
||||
// height={chartContainerHeight} // FE team HELP! Possibly no longer needed?
|
||||
gap={theme.spacing(8)} // FE team HELP!
|
||||
flexWrap="wrap" // //FE team HELP! Better way to do this?
|
||||
sx={{
|
||||
"& > *": {
|
||||
flexBasis: `calc(50% - ${theme.spacing(8)})`,
|
||||
maxWidth: `calc(50% - ${theme.spacing(8)})`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{configs.map((config) => (
|
||||
<InfraAreaChart
|
||||
key={`${config.type}-${config.diskIndex ?? ""}`}
|
||||
config={config}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default AreaChartBoxes;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const SkeletonLayout = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<Skeleton
|
||||
height={"33vh"}
|
||||
sx={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<Skeleton
|
||||
height={"33vh"}
|
||||
sx={{ flex: 1 }}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Renders a base box with consistent styling
|
||||
* @param {Object} props - Component properties
|
||||
* @param {React.ReactNode} props.children - Child components to render inside the box
|
||||
* @param {Object} props.sx - Additional styling for the box
|
||||
* @returns {React.ReactElement} Styled box component
|
||||
*/
|
||||
|
||||
// Components
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const BaseContainer = ({ children, sx = {}, shouldExpand = false }) => {
|
||||
const theme = useTheme();
|
||||
const { getDimensions } = useHardwareUtils();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: `${theme.spacing(getDimensions().baseBoxPaddingVertical)} ${theme.spacing(getDimensions().baseBoxPaddingHorizontal)}`,
|
||||
minWidth: 200,
|
||||
width: shouldExpand ? "100%" : 225,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
BaseContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
sx: PropTypes.object,
|
||||
shouldExpand: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default BaseContainer;
|
||||
@@ -26,7 +26,9 @@ const getChartConfigs = (
|
||||
): ChartConfig[] => {
|
||||
const configs: ChartConfig[] = [];
|
||||
|
||||
const netInterfaces = checks[0]?.net || [];
|
||||
// Find the first check that has network data to get interface names
|
||||
const checkWithNet = checks.find((c) => c.net && c.net.length > 0);
|
||||
const netInterfaces = checkWithNet?.net || [];
|
||||
|
||||
netInterfaces.forEach((iface, idx) => {
|
||||
configs.push(
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
// Components
|
||||
import CustomGauge from "@/Components/v1/Charts/CustomGauge/index.jsx";
|
||||
import BaseContainer from "../BaseContainer/index.jsx";
|
||||
import { Stack, Typography, Box } from "@mui/material";
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Gauge = ({
|
||||
value,
|
||||
heading,
|
||||
metricOne,
|
||||
valueOne,
|
||||
metricTwo,
|
||||
valueTwo,
|
||||
metricThree,
|
||||
valueThree,
|
||||
metricFour,
|
||||
valueFour,
|
||||
shouldExpand = false,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const valueStyle = {
|
||||
borderRadius: theme.spacing(2),
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
minWidth: "40%",
|
||||
maxWidth: "60%",
|
||||
mb: theme.spacing(2),
|
||||
mt: theme.spacing(2),
|
||||
pr: theme.spacing(2),
|
||||
textAlign: "right",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseContainer shouldExpand={shouldExpand}>
|
||||
<Stack
|
||||
direction="column"
|
||||
gap={theme.spacing(2)}
|
||||
alignItems="center"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.gradient.color1,
|
||||
}}
|
||||
>
|
||||
<CustomGauge
|
||||
progress={value}
|
||||
radius={100}
|
||||
/>
|
||||
<Typography
|
||||
component="h2"
|
||||
sx={{ fontWeight: 600 }}
|
||||
>
|
||||
{heading}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
borderTop: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricOne}</Typography>
|
||||
<Typography sx={valueStyle}>{valueOne}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricTwo}</Typography>
|
||||
<Typography sx={valueStyle}>{valueTwo}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricThree}</Typography>
|
||||
<Typography sx={valueStyle}>{valueThree}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricFour}</Typography>
|
||||
<Typography sx={valueStyle}>{valueFour}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</BaseContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Gauge.propTypes = {
|
||||
value: PropTypes.number,
|
||||
heading: PropTypes.string,
|
||||
metricOne: PropTypes.string,
|
||||
valueOne: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
metricTwo: PropTypes.string,
|
||||
valueTwo: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
metricThree: PropTypes.string,
|
||||
valueThree: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
metricFour: PropTypes.string,
|
||||
valueFour: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
shouldExpand: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Gauge;
|
||||
@@ -1,112 +0,0 @@
|
||||
// Components
|
||||
import { Box } from "@mui/material";
|
||||
import Gauge from "./Gauge.jsx";
|
||||
import SkeletonLayout from "./skeleton.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
// Utils
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Gauges = ({ isLoading = false, monitor }) => {
|
||||
const { decimalToPercentage, formatBytes, formatDeviceName, formatMountpoint } =
|
||||
useHardwareUtils();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
const latestCheck = monitor?.recentChecks?.[0];
|
||||
const memoryUsagePercent = latestCheck?.memory?.usage_percent ?? 0;
|
||||
const memoryUsedBytes = latestCheck?.memory?.used_bytes ?? 0;
|
||||
const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0;
|
||||
const cpuUsagePercent = latestCheck?.cpu?.usage_percent ?? 0;
|
||||
const cpuPhysicalCores = latestCheck?.cpu?.physical_core ?? 0;
|
||||
const cpuFrequency = latestCheck?.cpu?.frequency ?? 0;
|
||||
|
||||
const gauges = [
|
||||
{
|
||||
type: "memory",
|
||||
value: decimalToPercentage(memoryUsagePercent),
|
||||
heading: t("memoryUsage"),
|
||||
metricOne: t("used"),
|
||||
valueOne: formatBytes(memoryUsedBytes, true),
|
||||
metricTwo: t("total"),
|
||||
valueTwo: formatBytes(memoryTotalBytes, true),
|
||||
},
|
||||
{
|
||||
type: "cpu",
|
||||
value: decimalToPercentage(cpuUsagePercent),
|
||||
heading: t("cpuUsage"),
|
||||
metricOne: t("cores"),
|
||||
valueOne: cpuPhysicalCores ?? 0,
|
||||
metricTwo: t("frequency"),
|
||||
valueTwo: `${(cpuFrequency / 1000).toFixed(2)} Ghz`,
|
||||
},
|
||||
...(latestCheck?.disk ?? [])
|
||||
.filter((disk) => {
|
||||
if (!monitor?.selectedDisks || monitor.selectedDisks.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return monitor.selectedDisks.includes(disk.mountpoint || disk.device);
|
||||
})
|
||||
.map((disk, idx) => ({
|
||||
type: "disk",
|
||||
diskIndex: idx,
|
||||
value: decimalToPercentage(disk.usage_percent),
|
||||
heading: `Disk${idx} usage`,
|
||||
metricOne: t("used"),
|
||||
valueOne: formatBytes(disk.total_bytes - disk.free_bytes, true),
|
||||
metricTwo: t("total"),
|
||||
valueTwo: formatBytes(disk.total_bytes, true),
|
||||
metricThree: t("device"),
|
||||
valueThree: formatDeviceName(disk.device),
|
||||
metricFour: t("mountedOn"),
|
||||
valueFour: formatMountpoint(disk.mountpoint),
|
||||
})),
|
||||
];
|
||||
|
||||
// Only expand gauges to fill row when there are 4 or more
|
||||
const shouldExpand = gauges.length >= 4;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: shouldExpand
|
||||
? "repeat(auto-fill, minmax(200px, 1fr))"
|
||||
: "repeat(auto-fill, 225px)",
|
||||
gap: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{gauges.map((gauge) => {
|
||||
return (
|
||||
<Gauge
|
||||
key={`${gauge.type}-${gauge.diskIndex ?? ""}`}
|
||||
value={gauge.value}
|
||||
heading={gauge.heading}
|
||||
metricOne={gauge.metricOne}
|
||||
valueOne={gauge.valueOne}
|
||||
metricTwo={gauge.metricTwo}
|
||||
valueTwo={gauge.valueTwo}
|
||||
metricThree={gauge.metricThree}
|
||||
valueThree={gauge.valueThree}
|
||||
metricFour={gauge.metricFour}
|
||||
valueFour={gauge.valueFour}
|
||||
shouldExpand={shouldExpand}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Gauges.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
monitor: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Gauges;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
{Array.from({ length: 3 }).map((_, idx) => {
|
||||
return (
|
||||
<Skeleton
|
||||
key={`gauge-${idx}`}
|
||||
variant="rectangular"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,132 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import InfraAreaChart from "../AreaChartBoxes/InfraAreaChart.jsx";
|
||||
|
||||
import {
|
||||
TzTick,
|
||||
InfrastructureTooltip,
|
||||
NetworkTick,
|
||||
} from "@/Components/v1/Charts/Utils/chartUtils.jsx";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
|
||||
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;
|
||||
@@ -1,81 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
|
||||
import StatBox from "@/Components/v1/StatBox/index.jsx";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
|
||||
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;
|
||||
@@ -1,101 +0,0 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Select from "@/Components/v1/Inputs/Select/index.jsx";
|
||||
import NetworkStatBoxes from "./NetworkStatBoxes.jsx";
|
||||
import NetworkCharts from "./NetworkCharts.jsx";
|
||||
import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
|
||||
|
||||
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 && (
|
||||
<Select
|
||||
name="networkInterface"
|
||||
label={t("networkInterface")}
|
||||
value={selectedInterface}
|
||||
onChange={(e) => setSelectedInterface(e.target.value)}
|
||||
items={availableInterfaces.map((interfaceName) => ({
|
||||
_id: interfaceName,
|
||||
name: interfaceName,
|
||||
}))}
|
||||
sx={{ minWidth: 200 }}
|
||||
/>
|
||||
)}
|
||||
<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;
|
||||
@@ -1,56 +0,0 @@
|
||||
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,115 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import StatusBoxes from "@/Components/v1/StatusBoxes/index.jsx";
|
||||
import StatBox from "@/Components/v1/StatBox/index.jsx";
|
||||
|
||||
//Utils
|
||||
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils.js";
|
||||
import { useHardwareUtils } from "../../Hooks/useHardwareUtils.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const InfraStatBoxes = ({ shouldRender, monitor }) => {
|
||||
// Utils
|
||||
const { formatBytes } = useHardwareUtils();
|
||||
const { determineState } = useMonitorUtils();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const latestCheck = monitor?.recentChecks?.[0];
|
||||
|
||||
// Get data from latest check
|
||||
const physicalCores = latestCheck?.cpu?.physical_core ?? 0;
|
||||
const logicalCores = latestCheck?.cpu?.logical_core ?? 0;
|
||||
const cpuFrequency = latestCheck?.cpu?.frequency ?? 0;
|
||||
const cpuTemperature =
|
||||
latestCheck?.cpu?.temperature?.length > 0
|
||||
? latestCheck.cpu.temperature.reduce((acc, curr) => acc + curr, 0) /
|
||||
latestCheck.cpu.temperature.length
|
||||
: 0;
|
||||
const memoryTotalBytes = latestCheck?.memory?.total_bytes ?? 0;
|
||||
const diskTotalBytes = latestCheck?.disk[0]?.total_bytes ?? 0;
|
||||
const os = latestCheck?.host?.os ?? undefined;
|
||||
const platform = latestCheck?.host?.platform ?? undefined;
|
||||
const osPlatform =
|
||||
typeof os === "undefined" && typeof platform === "undefined"
|
||||
? undefined
|
||||
: `${os} ${platform}`;
|
||||
|
||||
return (
|
||||
<StatusBoxes
|
||||
shouldRender={shouldRender}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<StatBox
|
||||
gradient={true}
|
||||
status={determineState(monitor)}
|
||||
heading={t("status")}
|
||||
subHeading={determineState(monitor)}
|
||||
/>
|
||||
<StatBox
|
||||
heading={t("cpuPhysical")}
|
||||
subHeading={
|
||||
<>
|
||||
{physicalCores}
|
||||
<Typography component="span">
|
||||
{physicalCores === 1 ? "core" : "cores"}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
key={2}
|
||||
heading={t("cpuLogical")}
|
||||
subHeading={
|
||||
<>
|
||||
{logicalCores}
|
||||
<Typography component="span">
|
||||
{logicalCores === 1 ? "core" : "cores"}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading={t("cpuFrequency")}
|
||||
subHeading={
|
||||
<>
|
||||
{(cpuFrequency / 1000).toFixed(2)}
|
||||
<Typography component="span">Ghz</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading={t("avgCpuTemperature")}
|
||||
subHeading={
|
||||
<>
|
||||
{cpuTemperature.toFixed(2)}
|
||||
<Typography component="span">°C</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatBox
|
||||
heading={t("memory")}
|
||||
subHeading={formatBytes(memoryTotalBytes)}
|
||||
/>
|
||||
<StatBox
|
||||
heading={t("disk")}
|
||||
subHeading={formatBytes(diskTotalBytes)}
|
||||
/>
|
||||
{/* <StatBox
|
||||
heading={t("uptime")}
|
||||
subHeading={
|
||||
<>
|
||||
{(uptimePercentage * 100).toFixed(2)}
|
||||
<Typography component="span">%</Typography>
|
||||
</>
|
||||
}
|
||||
/> */}
|
||||
<StatBox
|
||||
key={8}
|
||||
heading={t("os")}
|
||||
subHeading={osPlatform}
|
||||
/>
|
||||
</StatusBoxes>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfraStatBoxes;
|
||||
@@ -1,270 +0,0 @@
|
||||
import { Typography, Tooltip } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Constants
|
||||
const BASE_BOX_PADDING_VERTICAL = 4;
|
||||
const BASE_BOX_PADDING_HORIZONTAL = 8;
|
||||
const TYPOGRAPHY_PADDING = 8;
|
||||
const CHART_CONTAINER_HEIGHT = 300;
|
||||
|
||||
const useHardwareUtils = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getDimensions = () => {
|
||||
const totalTypographyPadding = parseInt(theme.spacing(TYPOGRAPHY_PADDING), 10) * 2;
|
||||
const totalChartContainerPadding =
|
||||
parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2;
|
||||
return {
|
||||
baseBoxPaddingVertical: BASE_BOX_PADDING_VERTICAL,
|
||||
baseBoxPaddingHorizontal: BASE_BOX_PADDING_HORIZONTAL,
|
||||
totalContainerPadding: parseInt(theme.spacing(BASE_BOX_PADDING_VERTICAL), 10) * 2,
|
||||
areaChartHeight:
|
||||
CHART_CONTAINER_HEIGHT - totalChartContainerPadding - totalTypographyPadding,
|
||||
};
|
||||
};
|
||||
|
||||
const formatBytes = (bytes, space = false) => {
|
||||
if (bytes === undefined || bytes === null)
|
||||
return (
|
||||
<>
|
||||
{0}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
);
|
||||
if (typeof bytes !== "number")
|
||||
return (
|
||||
<>
|
||||
{0}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
);
|
||||
if (bytes === 0)
|
||||
return (
|
||||
<>
|
||||
{0}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
const GB = bytes / (1024 * 1024 * 1024);
|
||||
const MB = bytes / (1024 * 1024);
|
||||
|
||||
if (GB >= 1) {
|
||||
return (
|
||||
<>
|
||||
{Number(GB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("gb")}</Typography>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{Number(MB.toFixed(2))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">{t("mb")}</Typography>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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`;
|
||||
}
|
||||
};
|
||||
|
||||
const formatDeviceName = (device) => {
|
||||
const deviceStr = String(device || "");
|
||||
|
||||
// Show full device path
|
||||
return (
|
||||
<Tooltip
|
||||
title={deviceStr}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
display: "inline-block",
|
||||
userSelect: "none",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
{deviceStr}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const formatMountpoint = (mountpoint) => {
|
||||
const mountpointStr = String(mountpoint || "");
|
||||
|
||||
if (!mountpointStr) {
|
||||
return (
|
||||
<Tooltip
|
||||
title="No mountpoint available"
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
display: "inline-block",
|
||||
userSelect: "none",
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
N/A
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
// Show full mountpoint path
|
||||
return (
|
||||
<Tooltip
|
||||
title={mountpointStr}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
sx={{
|
||||
cursor: "default",
|
||||
display: "inline-block",
|
||||
userSelect: "none",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: "100%",
|
||||
}}
|
||||
>
|
||||
{mountpointStr}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a decimal value to a percentage
|
||||
*
|
||||
* @function decimalToPercentage
|
||||
* @param {number} value - Decimal value to convert
|
||||
* @returns {number} Percentage representation
|
||||
*
|
||||
* @example
|
||||
* decimalToPercentage(0.75) // Returns 75
|
||||
* decimalToPercentage(null) // Returns 0
|
||||
*/
|
||||
const decimalToPercentage = (value) => {
|
||||
if (value === null || value === undefined) return 0;
|
||||
return value * 100;
|
||||
};
|
||||
|
||||
const buildTemps = (checks) => {
|
||||
let numCores = 1;
|
||||
if (checks === null) return { temps: [], tempKeys: [] };
|
||||
|
||||
for (const check of checks) {
|
||||
if (check?.avgTemperature?.length > numCores) {
|
||||
numCores = check.avgTemperature.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const temps = checks.map((check) => {
|
||||
// If there's no data, set the temperature to 0
|
||||
if (
|
||||
check?.avgTemperature?.length === 0 ||
|
||||
check?.avgTemperature === undefined ||
|
||||
check?.avgTemperature === null
|
||||
) {
|
||||
check.avgTemperature = Array(numCores).fill(0);
|
||||
}
|
||||
const res = check?.avgTemperature?.reduce(
|
||||
(acc, cur, idx) => {
|
||||
acc[`core${idx + 1}`] = cur;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
_id: check._id,
|
||||
}
|
||||
);
|
||||
return res;
|
||||
});
|
||||
if (temps.length === 0 || !temps[0]) {
|
||||
return { temps: [], tempKeys: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
tempKeys: Object.keys(temps[0] || {}).filter((key) => key !== "_id"),
|
||||
temps,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
formatBytes,
|
||||
formatDeviceName,
|
||||
formatMountpoint,
|
||||
decimalToPercentage,
|
||||
buildTemps,
|
||||
getDimensions,
|
||||
formatBytesPerSecondString,
|
||||
formatPacketsPerSecondString,
|
||||
};
|
||||
};
|
||||
|
||||
export { useHardwareUtils };
|
||||
@@ -1,142 +0,0 @@
|
||||
// Components
|
||||
import { Stack, Typography, Tab } from "@mui/material";
|
||||
import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx";
|
||||
import MonitorDetailsControlHeader from "@/Components/v1/MonitorDetailsControlHeader/index.jsx";
|
||||
import MonitorTimeFrameHeader from "@/Components/v1/MonitorTimeFrameHeader/index.jsx";
|
||||
import StatusBoxes from "./Components/StatusBoxes/index.jsx";
|
||||
import GaugeBoxes from "./Components/GaugeBoxes/index.jsx";
|
||||
import AreaChartBoxes from "./Components/AreaChartBoxes/index.jsx";
|
||||
import GenericFallback from "@/Components/v1/GenericFallback/index.jsx";
|
||||
import NetworkStats from "./Components/NetworkStats/index.jsx";
|
||||
import CustomTabList from "@/Components/v1/Tab/index.jsx";
|
||||
import TabContext from "@mui/lab/TabContext";
|
||||
|
||||
// Utils
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useIsAdmin } from "@/Hooks/useIsAdmin.js";
|
||||
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks.js";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
// Constants
|
||||
const BREADCRUMBS = [
|
||||
{ name: "infrastructure monitors", path: "/infrastructure" },
|
||||
{ name: "details", path: "" },
|
||||
];
|
||||
const InfrastructureDetails = () => {
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [trigger, setTrigger] = useState(false);
|
||||
const [tab, setTab] = useState("details");
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const { monitorId } = useParams();
|
||||
const { t } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({
|
||||
monitorId,
|
||||
dateRange,
|
||||
updateTrigger: trigger,
|
||||
});
|
||||
|
||||
const triggerUpdate = () => {
|
||||
setTrigger(!trigger);
|
||||
};
|
||||
|
||||
if (networkError === true) {
|
||||
return (
|
||||
<GenericFallback>
|
||||
<Typography
|
||||
variant="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{t("common.toasts.networkError")}
|
||||
</Typography>
|
||||
<Typography>{t("common.toasts.checkConnection")}</Typography>
|
||||
</GenericFallback>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoading && monitor?.stats?.checks?.length === 0) {
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorDetailsControlHeader
|
||||
path={"infrastructure"}
|
||||
isLoading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<GenericFallback>
|
||||
<Typography>{t("distributedUptimeDetailsNoMonitorHistory")}</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<MonitorDetailsControlHeader
|
||||
path={"infrastructure"}
|
||||
isLoading={isLoading}
|
||||
isAdmin={isAdmin}
|
||||
monitor={monitor}
|
||||
triggerUpdate={triggerUpdate}
|
||||
/>
|
||||
<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}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{tab === "network" && (
|
||||
<NetworkStats
|
||||
net={monitor?.recentChecks?.[0]?.net || []}
|
||||
isLoading={isLoading}
|
||||
checks={monitor?.stats?.checks}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
/>
|
||||
)}
|
||||
</TabContext>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfrastructureDetails;
|
||||
Reference in New Issue
Block a user