remove unused

This commit is contained in:
Alex Holliday
2026-01-29 17:43:33 +00:00
parent a1973a6d2b
commit 363baef7b1
16 changed files with 4 additions and 1410 deletions
@@ -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;