diagnostics

This commit is contained in:
Alex Holliday
2026-02-06 18:46:06 +00:00
parent 597732e109
commit 8470631162
6 changed files with 206 additions and 97 deletions
@@ -1,3 +1,5 @@
import { BaseChart } from "@/Components/v2/design-elements";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -104,3 +106,50 @@ export const Gauge = ({
</Box>
);
};
export const DetailGauge = ({
title,
progress,
upperLabel,
upperValue,
lowerLabel,
lowerValue,
}: {
title: string;
progress: number;
upperLabel?: string;
upperValue?: string | number;
lowerLabel?: string;
lowerValue?: string | number;
}) => {
const theme = useTheme();
return (
<BaseChart
icon={null}
title={title}
maxWidth={225}
>
<Stack
alignItems={"center"}
mb={theme.spacing(4)}
gap={theme.spacing(4)}
>
<Gauge progress={progress} />
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{upperLabel}</Typography>
<Typography>{upperValue}</Typography>
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{lowerLabel}</Typography>
<Typography>{lowerValue}</Typography>
</Stack>
</BaseChart>
);
};
@@ -1,6 +1,5 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import { BaseChart, Gauge } from "@/Components/v2/design-elements";
import { DetailGauge } from "@/Components/v2/design-elements";
import { useTranslation } from "react-i18next";
import { getGbs, getFrequency } from "@/Utils/InfraUtils";
@@ -8,53 +7,6 @@ import { useTheme } from "@mui/material";
import useMediaQuery from "@mui/material/useMediaQuery";
import type { CheckSnapshot } from "@/Types/Check";
const InfraDetailGauge = ({
title,
progress,
upperLabel,
upperValue,
lowerLabel,
lowerValue,
}: {
title: string;
progress: number;
upperLabel?: string;
upperValue?: string | number;
lowerLabel?: string;
lowerValue?: string | number;
}) => {
const theme = useTheme();
return (
<BaseChart
icon={null}
title={title}
maxWidth={225}
>
<Stack
alignItems={"center"}
mb={theme.spacing(4)}
gap={theme.spacing(4)}
>
<Gauge progress={progress} />
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{upperLabel}</Typography>
<Typography>{upperValue}</Typography>
</Stack>
<Stack
direction={"row"}
justifyContent={"space-between"}
>
<Typography>{lowerLabel}</Typography>
<Typography>{lowerValue}</Typography>
</Stack>
</BaseChart>
);
};
export const InfraDetailsGauges = ({
snapshot,
}: {
@@ -74,7 +26,7 @@ export const InfraDetailsGauges = ({
spacing={theme.spacing(8)}
alignItems={"center"}
>
<InfraDetailGauge
<DetailGauge
title={t("pages.infrastructure.gauges.memory.title")}
progress={(snapshot?.memory?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.memory.upperLabel")}
@@ -82,7 +34,7 @@ export const InfraDetailsGauges = ({
lowerLabel={t("pages.infrastructure.gauges.memory.lowerLabel")}
lowerValue={getGbs(snapshot?.memory?.total_bytes || 0)}
/>
<InfraDetailGauge
<DetailGauge
title={t("pages.infrastructure.gauges.cpu.title")}
progress={(snapshot?.cpu?.usage_percent || 0) * 100}
upperLabel={t("pages.infrastructure.gauges.cpu.upperLabel")}
@@ -92,7 +44,7 @@ export const InfraDetailsGauges = ({
/>
{snapshot?.disk?.map((disk, idx) => {
return (
<InfraDetailGauge
<DetailGauge
key={disk?.device || 0 + idx}
// title={`Disk ${idx} usage`}
title={t("pages.infrastructure.gauges.disk.title", { idx })}
@@ -9,9 +9,9 @@ import { getPercentage, formatBytes } from "../../utils/utils.js";
import { useTranslation } from "react-i18next";
import { Box } from "@mui/material";
const BaseContainer = ({children}) => {
const theme = useTheme()
return(
const BaseContainer = ({ children }) => {
const theme = useTheme();
return (
<Box
sx={{
padding: theme.spacing(3),
@@ -22,13 +22,21 @@ const BaseContainer = ({children}) => {
[theme.breakpoints.down("md")]: {
width: `calc(50% - (1 * ${theme.spacing(8)} / 2))`,
},
}}>
{children}
}}
>
{children}
</Box>
);
};
const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => {
const InfrastructureStyleGauge = ({
value,
heading,
metricOne,
valueOne,
metricTwo,
valueTwo,
}) => {
const theme = useTheme();
const MetricRow = ({ label, value }) => (
@@ -39,40 +47,58 @@ const InfrastructureStyleGauge = ({ value, heading, metricOne, valueOne, metricT
gap={theme.spacing(2)}
>
<Typography>{label}</Typography>
<Typography sx={{
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.tertiary.main,
width: "40%",
mb: theme.spacing(2),
mt: theme.spacing(2),
pr: theme.spacing(2),
textAlign: "right",
}}>
<Typography
sx={{
borderRadius: theme.spacing(2),
backgroundColor: theme.palette.tertiary.main,
width: "40%",
mb: theme.spacing(2),
mt: theme.spacing(2),
pr: theme.spacing(2),
textAlign: "right",
}}
>
{value}
</Typography>
</Stack>
);
return(
return (
<BaseContainer>
<Stack direction="column" gap={theme.spacing(2)} alignItems="center">
<Stack
direction="column"
gap={theme.spacing(2)}
alignItems="center"
>
<Box
sx = {{
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
width: "100%",
}}
>
<CustomGauge progress={value} radius={100}/>
<Typography component="h2" sx={{fontWeight: 600}}>
<CustomGauge
progress={value}
radius={100}
/>
<Typography
component="h2"
sx={{ fontWeight: 600 }}
>
{heading}
</Typography>
</Typography>
</Box>
<Box sx={{ width:"100%", borderTop:`1px solid ${theme.palette.divider}`}}>
<MetricRow label={metricOne} value={valueOne} />
<Box sx={{ width: "100%", borderTop: `1px solid ${theme.palette.divider}` }}>
<MetricRow
label={metricOne}
value={valueOne}
/>
{metricTwo && valueTwo && (
<MetricRow label={metricTwo} value={valueTwo} />
<MetricRow
label={metricTwo}
value={valueTwo}
/>
)}
</Box>
</Stack>
@@ -147,16 +173,16 @@ Gauges.propTypes = {
};
InfrastructureStyleGauge.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]),
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]),
};
BaseContainer.propTypes = {
children: PropTypes.node.isRequired,
children: PropTypes.node.isRequired,
};
export default Gauges;
+84
View File
@@ -0,0 +1,84 @@
import Stack from "@mui/material/Stack";
import { DetailGauge } from "@/Components/v2/design-elements";
import prettyBytes from "pretty-bytes";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material";
import type { Diagnostics } from "@/Types/Diagnostics";
interface StatGaugesProps {
diagnostics: Diagnostics | null;
}
const getPercentage = (value: number, total: number) => {
if (!value || !total) return 0;
return (value / total) * 100;
};
const formatPerecentage = new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: 1,
maximumFractionDigits: 1,
});
export const StatGauges = ({ diagnostics }: StatGaugesProps) => {
const theme = useTheme();
const { t } = useTranslation();
if (!diagnostics) {
return null;
}
const heapTotalSize = getPercentage(
diagnostics?.v8HeapStats?.totalHeapSizeBytes,
diagnostics?.v8HeapStats?.heapSizeLimitBytes
);
const heapUsedSize = getPercentage(
diagnostics?.v8HeapStats?.usedHeapSizeBytes,
diagnostics?.v8HeapStats?.heapSizeLimitBytes
);
const actualHeapUsed = getPercentage(
diagnostics?.v8HeapStats?.usedHeapSizeBytes,
diagnostics?.v8HeapStats?.totalHeapSizeBytes
);
return (
<Stack
direction={{ xs: "column", md: "row" }}
gap={theme.spacing(8)}
>
<DetailGauge
title={t("pages.logs.diagnostics.gauges.heapAllocation")}
progress={heapTotalSize}
upperValue={formatPerecentage.format(heapTotalSize / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.total")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.heapSizeLimitBytes ?? 0)}
/>
<DetailGauge
title={t("pages.logs.diagnostics.gauges.heapUsage")}
progress={heapUsedSize}
upperLabel={t("pages.logs.diagnostics.gauges.availableMemoryPercentage")}
upperValue={formatPerecentage.format(heapUsedSize / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.used")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0)}
/>
<DetailGauge
title={t("pages.logs.diagnostics.gauges.heapUtilization")}
progress={actualHeapUsed}
upperLabel={t("pages.logs.diagnostics.gauges.allocatedPercentage")}
upperValue={formatPerecentage.format(actualHeapUsed / 100)}
lowerLabel={t("pages.logs.diagnostics.gauges.total")}
lowerValue={prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0)}
/>
<DetailGauge
title={t("pages.logs.diagnostics.gauges.instantCpuUsage")}
progress={diagnostics.cpuUsage?.usagePercentage ?? 0}
upperLabel={t("pages.logs.diagnostics.gauges.usedSPercentage")}
upperValue={formatPerecentage.format(
(diagnostics.cpuUsage?.usagePercentage ?? 0) / 100
)}
/>
</Stack>
);
};
+5 -9
View File
@@ -26,29 +26,25 @@ export const Stats = ({ diagnostics }: StatsProps) => {
>
<StatBox
title={t("pages.logs.diagnostics.stats.eventLoopDelay")}
subtitle={prettyMilliseconds(diagnostics.eventLoopDelayMs, {
subtitle={prettyMilliseconds(diagnostics.eventLoopDelayMs ?? 0, {
millisecondsDecimalDigits: 2,
})}
/>
<StatBox
title={t("pages.logs.diagnostics.stats.uptime")}
subtitle={prettyMilliseconds(diagnostics.uptimeMs, { hideSeconds: true })}
/>
<StatBox
title={t("pages.logs.diagnostics.stats.uptime")}
subtitle={prettyMilliseconds(diagnostics.uptimeMs, { hideSeconds: true })}
subtitle={prettyMilliseconds(diagnostics.uptimeMs ?? 0, { hideSeconds: true })}
/>
<StatBox
title={t("pages.logs.diagnostics.stats.usedHeapSize")}
subtitle={prettyBytes(diagnostics.v8HeapStats.usedHeapSizeBytes)}
subtitle={prettyBytes(diagnostics.v8HeapStats?.usedHeapSizeBytes ?? 0)}
/>
<StatBox
title={t("pages.logs.diagnostics.stats.totalHeapSize")}
subtitle={prettyBytes(diagnostics.v8HeapStats.totalHeapSizeBytes)}
subtitle={prettyBytes(diagnostics.v8HeapStats?.totalHeapSizeBytes ?? 0)}
/>
<StatBox
title={t("pages.logs.diagnostics.stats.osMemoryLimit")}
subtitle={prettyBytes(diagnostics.osStats.totalMemoryBytes)}
subtitle={prettyBytes(diagnostics.osStats?.totalMemoryBytes ?? 0)}
/>
</Stack>
);
+6 -4
View File
@@ -1,5 +1,6 @@
import Stack from "@mui/material/Stack";
import { Stats } from "@/Pages/Logs/Stats";
import { StatGauges } from "@/Pages/Logs/StatGauges";
import { useTheme } from "@mui/material";
import { useGet } from "@/Hooks/UseApi";
@@ -9,14 +10,15 @@ export const TabDiagnostics = () => {
const theme = useTheme();
const {
data: diagnostics,
isLoading,
error,
refetch,
} = useGet<Diagnostics>("/diagnostic/system");
isLoading: _isLoading,
error: _error,
refetch: _refetch,
} = useGet<Diagnostics>("/diagnostic/system", {}, { refreshInterval: 5000 });
return (
<Stack gap={theme.spacing(8)}>
<Stats diagnostics={diagnostics} />
<StatGauges diagnostics={diagnostics} />
</Stack>
);
};