stat boxes

This commit is contained in:
Alex Holliday committed 2026-01-28 22:38:02 +00:00
1 parent ec03addba6
commit dcf5a77923
9 files changed
+240 -1

No files matched your search

@@ -0,0 +1,64 @@
import MuiTabs from "@mui/material/Tabs";
import type { TabsProps } from "@mui/material/Tabs";
import { useTheme } from "@mui/material/styles";
interface CustomTabsProps extends TabsProps {}
export const Tabs = (props: CustomTabsProps) => {
const theme = useTheme();
return (
<MuiTabs
sx={{
minHeight: 34,
borderBottom: `1px solid ${theme.palette.divider}`,
"& .MuiTabs-indicator": {
backgroundColor: theme.palette.primary.main,
height: 2,
bottom: 0,
},
"& .MuiTabs-flexContainer": {
gap: theme.spacing(16),
},
}}
{...props}
>
{props.children}
</MuiTabs>
);
};
import MuiTab from "@mui/material/Tab";
import type { TabProps } from "@mui/material/Tab";
interface CustomTabProps extends TabProps {}
export const Tab = (props: CustomTabProps) => {
const theme = useTheme();
return (
<MuiTab
disableRipple
iconPosition="start"
sx={{
textTransform: "none",
fontSize: 14,
fontWeight: 500,
minHeight: 34,
padding: theme.spacing(1, 0),
paddingBottom: 0,
minWidth: "auto",
alignItems: "flex-start",
color: theme.palette.text.secondary,
"&.Mui-selected": {
color: theme.palette.primary.main,
fontWeight: 600,
},
"&:hover": {
color: theme.palette.text.secondary,
},
"& .MuiTab-iconWrapper": {
marginRight: theme.spacing(2),
marginBottom: 0,
},
}}
{...props}
/>
);
};
@@ -16,3 +16,4 @@ export * from "./Tooltip";
export * from "./StatBox";
export * from "./BaseChart";
export * from "./Gauge";
export * from "./Tabs";
@@ -0,0 +1,74 @@
import Stack from "@mui/material/Stack";
import { StatBox } from "@/Components/v2/design-elements";
import { useTheme } from "@mui/material";
import { useTranslation } from "react-i18next";
import prettyMilliseconds from "pretty-ms";
import type { Monitor } from "@/Types/Monitor";
import {
getAvgTemp,
getCores,
getFrequency,
getGbs,
getDiskTotalGbs,
getOsAndPlatform,
} from "@/Utils/InfraUtils";
export const StatusBoxes = ({ monitor }: { monitor: Monitor }) => {
const { t } = useTranslation();
const theme = useTheme();
const latestCheck = monitor?.recentChecks?.[0];
// Get data from latest check
const physicalCores = getCores(latestCheck?.cpu?.physical_core);
const logicalCores = getCores(latestCheck?.cpu?.logical_core);
const cpuFrequency = getFrequency(latestCheck?.cpu?.frequency);
const cpuTemps = latestCheck?.cpu?.temperature ?? [];
const cpuTemperature = getAvgTemp(cpuTemps);
const memoryTotalBytes = getGbs(latestCheck?.memory?.total_bytes);
const diskTotalBytes = getDiskTotalGbs(latestCheck?.disk);
const os = getOsAndPlatform(latestCheck?.host);
const platform = latestCheck?.host?.platform ?? undefined;
const osPlatform =
typeof os === "undefined" && typeof platform === "undefined"
? undefined
: `${os} ${platform}`;
return (
<Stack
direction="row"
gap={theme.spacing(8)}
flexWrap={"wrap"}
>
<StatBox
title={t("pages.infrastructure.statBoxes.cpuPhysical")}
subtitle={physicalCores.toString()}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.cpuLogical")}
subtitle={logicalCores.toString()}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.cpuFrequency")}
subtitle={cpuFrequency}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.avgCpuTemperature")}
subtitle={cpuTemperature}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.memory")}
subtitle={memoryTotalBytes.toString()}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.disk")}
subtitle={diskTotalBytes.toString()}
/>
<StatBox
title={t("pages.infrastructure.statBoxes.os")}
subtitle={osPlatform || "N/A"}
/>
</Stack>
);
};
@@ -0,0 +1,3 @@
export const TabNetwork = () => {
return <div>Network Content</div>;
};
@@ -0,0 +1,10 @@
import { StatusBoxes } from "@/Pages/Infrastructure/Details/Components/StatusBoxes";
import type { Monitor } from "@/Types/Monitor";
export const TabOverview = ({ monitor }: { monitor: Monitor | undefined }) => {
if (!monitor) {
return null;
}
return <StatusBoxes monitor={monitor} />;
};
@@ -1,7 +1,9 @@
import { BasePage } from "@/Components/v2/design-elements";
import { BasePage, Tab, Tabs } from "@/Components/v2/design-elements";
import { HeaderMonitorControls, HeaderTimeRange } from "@/Components/v2/common";
import Stack from "@mui/material/Stack";
import { MonitorStatBoxes } from "@/Components/v2/monitors";
import { TabNetwork } from "@/Pages/Infrastructure/Details/Components/TabNetwork";
import { TabOverview } from "@/Pages/Infrastructure/Details/Components/TabOverview";
import { useTheme } from "@mui/material";
import { useMemo, useState } from "react";
@@ -9,14 +11,17 @@ import { useParams } from "react-router-dom";
import { useGet } from "@/Hooks/UseApi";
import type { HardwareDetailsResponse } from "@/Types/Monitor";
import { useIsAdmin } from "@/Hooks/useIsAdmin";
import { useTranslation } from "react-i18next";
const InfrastructureDetails = () => {
const { t } = useTranslation();
const theme = useTheme();
const isAdmin = useIsAdmin();
const { monitorId } = useParams<{ monitorId: string }>();
const [dateRange, setDateRange] = useState<string>("recent");
const [selectedTab, setSelectedTab] = useState<number>(0);
const monitorDetailsUrl = useMemo(() => {
if (!monitorId) {
@@ -54,6 +59,17 @@ const InfrastructureDetails = () => {
monitor={monitor}
monitorStats={monitorStats}
/>
<Tabs
value={selectedTab}
onChange={(_e, value) => {
setSelectedTab(value);
}}
>
<Tab label={t("pages.infrastructure.tabs.labels.overview")} />
<Tab label={t("pages.infrastructure.tabs.labels.network")} />
</Tabs>
{selectedTab === 0 && <TabOverview monitor={monitor} />}
{selectedTab === 1 && <TabNetwork />}
</BasePage>
);
};
+1
View File
@@ -32,6 +32,7 @@ export interface CheckHostInfo {
os?: string;
platform?: string;
kernel_version?: string;
pretty_name?: string;
}
export interface CheckCaptureInfo {
+55
View File
@@ -0,0 +1,55 @@
import type { CheckDiskInfo, CheckHostInfo } from "@/Types/Check";
export const getFrequency = (frequency: number | undefined): string => {
if (!frequency) return "N/A";
const ghz = (frequency / 1000).toFixed(2);
return `${ghz} GHz`;
};
export const getCores = (cores: number | undefined) => {
if (!cores) return "N/A";
if (cores === 1) return `${cores} core`;
return `${cores} cores`;
};
export const getAvgTemp = (temps: number[]): string => {
console.log(temps);
if (!temps || temps.length === 0) return "N/A";
const avgTemp = temps.reduce((a, b) => a + b, 0) / temps.length;
return `${avgTemp?.toFixed(2)} °C`;
};
export const getGbs = (bytes: number | undefined): string => {
if (!bytes) {
return "N/A";
}
if (bytes === 0) {
return "0 GB";
}
const GB = bytes / (1024 * 1024 * 1024);
const MB = bytes / (1024 * 1024);
if (GB >= 1) {
return GB.toFixed(2) + " GB";
} else {
return MB.toFixed(2) + " MB";
}
};
export const getDiskTotalGbs = (disk?: Partial<CheckDiskInfo>[]): string => {
if (!disk) {
return getGbs(0);
}
const totalBytes = disk?.reduce((acc, disk) => acc + (disk.total_bytes || 0), 0) || 0;
return getGbs(totalBytes);
};
export const getOsAndPlatform = (hostInfo: CheckHostInfo | undefined): string => {
if (!hostInfo) {
return "N/A";
}
const os = hostInfo?.pretty_name || hostInfo?.os || "N/A";
const platform = hostInfo?.platform || "N/A";
return `${os} (${platform})`;
};
+15
View File
@@ -284,6 +284,21 @@
"disk": "Disk",
"memory": "Memory"
}
},
"tabs": {
"labels": {
"overview": "Overview",
"network": "Network"
}
},
"statBoxes": {
"cpuPhysical": "CPU (Physical)",
"cpuLogical": "CPU (Logical)",
"cpuFrequency": "CPU Frequency",
"avgCpuTemperature": "Average CPU Temperature",
"memory": "Memory",
"disk": "Disk",
"os": "OS"
}
}
},