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"
}
}
},