From dcf5a779230196293c84c270bdbd5b9ca6c4430d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 22:38:02 +0000 Subject: [PATCH] stat boxes --- .../Components/v2/design-elements/Tabs.tsx | 64 ++++++++++++++++ .../Components/v2/design-elements/index.tsx | 1 + .../Details/Components/StatusBoxes.tsx | 74 +++++++++++++++++++ .../Details/Components/TabNetwork.tsx | 3 + .../Details/Components/TabOverview.tsx | 10 +++ .../Pages/Infrastructure/Details/index.tsx | 18 ++++- client/src/Types/Check.ts | 1 + client/src/Utils/InfraUtils.ts | 55 ++++++++++++++ client/src/locales/en.json | 15 ++++ 9 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 client/src/Components/v2/design-elements/Tabs.tsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx create mode 100644 client/src/Utils/InfraUtils.ts diff --git a/client/src/Components/v2/design-elements/Tabs.tsx b/client/src/Components/v2/design-elements/Tabs.tsx new file mode 100644 index 000000000..149c0bd93 --- /dev/null +++ b/client/src/Components/v2/design-elements/Tabs.tsx @@ -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 ( + + {props.children} + + ); +}; + +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 ( + + ); +}; diff --git a/client/src/Components/v2/design-elements/index.tsx b/client/src/Components/v2/design-elements/index.tsx index 000dab1b2..33248a345 100644 --- a/client/src/Components/v2/design-elements/index.tsx +++ b/client/src/Components/v2/design-elements/index.tsx @@ -16,3 +16,4 @@ export * from "./Tooltip"; export * from "./StatBox"; export * from "./BaseChart"; export * from "./Gauge"; +export * from "./Tabs"; diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx new file mode 100644 index 000000000..52c888a49 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx @@ -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 ( + + + + + + + + + + ); +}; diff --git a/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx b/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx new file mode 100644 index 000000000..241a092d4 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx @@ -0,0 +1,3 @@ +export const TabNetwork = () => { + return
Network Content
; +}; diff --git a/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx new file mode 100644 index 000000000..c4cc6f0f1 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx @@ -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 ; +}; diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index 38b814410..c24e5a394 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -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("recent"); + const [selectedTab, setSelectedTab] = useState(0); const monitorDetailsUrl = useMemo(() => { if (!monitorId) { @@ -54,6 +59,17 @@ const InfrastructureDetails = () => { monitor={monitor} monitorStats={monitorStats} /> + { + setSelectedTab(value); + }} + > + + + + {selectedTab === 0 && } + {selectedTab === 1 && } ); }; diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts index 04e0356dc..6f10bea3a 100644 --- a/client/src/Types/Check.ts +++ b/client/src/Types/Check.ts @@ -32,6 +32,7 @@ export interface CheckHostInfo { os?: string; platform?: string; kernel_version?: string; + pretty_name?: string; } export interface CheckCaptureInfo { diff --git a/client/src/Utils/InfraUtils.ts b/client/src/Utils/InfraUtils.ts new file mode 100644 index 000000000..9fba65dbc --- /dev/null +++ b/client/src/Utils/InfraUtils.ts @@ -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[]): 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})`; +}; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index d015f8f46..0ba2f2651 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -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" } } },