From eefdb8d253a8b467afb0ddcdb93b3dc37d6676b5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 21:39:38 +0000 Subject: [PATCH 1/9] initial commit --- client/src/Pages/Infrastructure/Details/index.tsx | 5 +++++ .../Infrastructure/Details/{index.jsx => old.jsx} | 0 client/src/Routes/index.jsx | 10 ++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 client/src/Pages/Infrastructure/Details/index.tsx rename client/src/Pages/Infrastructure/Details/{index.jsx => old.jsx} (100%) diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx new file mode 100644 index 000000000..384f9c188 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -0,0 +1,5 @@ +const InfrastructureDetails = () => { + return null; +}; + +export default InfrastructureDetails; diff --git a/client/src/Pages/Infrastructure/Details/index.jsx b/client/src/Pages/Infrastructure/Details/old.jsx similarity index 100% rename from client/src/Pages/Infrastructure/Details/index.jsx rename to client/src/Pages/Infrastructure/Details/old.jsx diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx index 780663f68..f9b5cb8a1 100644 --- a/client/src/Routes/index.jsx +++ b/client/src/Routes/index.jsx @@ -27,7 +27,7 @@ import PageSpeedCreate from "../Pages/PageSpeed/Create/index.jsx"; // Infrastructure import Infrastructure from "../Pages/Infrastructure/Monitors"; import InfrastructureCreate from "../Pages/Infrastructure/Create/index.jsx"; -import InfrastructureDetails from "../Pages/Infrastructure/Details/index.jsx"; +import InfrastructureDetails from "../Pages/Infrastructure/Details/index"; // Server Status import ServerUnreachable from "../Pages/ServerUnreachable.jsx"; @@ -165,7 +165,13 @@ const Routes = () => { /> } + element={ + <> + + + + + } /> Date: Wed, 28 Jan 2026 22:00:33 +0000 Subject: [PATCH 2/9] headers --- .../Pages/Infrastructure/Details/index.tsx | 58 ++++++++++++++++++- client/src/Pages/Uptime/Details/index.tsx | 2 +- client/src/Types/Monitor.ts | 40 +++++++++++++ server/src/controllers/monitorController.ts | 4 +- server/src/service/business/monitorService.ts | 5 +- server/src/types/monitor.ts | 4 +- 6 files changed, 107 insertions(+), 6 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index 384f9c188..38b814410 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -1,5 +1,61 @@ +import { BasePage } 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 { useTheme } from "@mui/material"; +import { useMemo, useState } from "react"; +import { useParams } from "react-router-dom"; +import { useGet } from "@/Hooks/UseApi"; +import type { HardwareDetailsResponse } from "@/Types/Monitor"; +import { useIsAdmin } from "@/Hooks/useIsAdmin"; + const InfrastructureDetails = () => { - return null; + const theme = useTheme(); + const isAdmin = useIsAdmin(); + + const { monitorId } = useParams<{ monitorId: string }>(); + + const [dateRange, setDateRange] = useState("recent"); + + const monitorDetailsUrl = useMemo(() => { + if (!monitorId) { + return null; + } + const params = new URLSearchParams(); + params.append("dateRange", dateRange); + return `/monitors/hardware/details/${monitorId}?${params.toString()}`; + }, [monitorId, dateRange]); + + const { + data: monitorDetailsData, + isLoading: monitorIsLoading, + refetch: refetchMonitor, + } = useGet( + monitorDetailsUrl, + {}, + { refreshInterval: 10000, keepPreviousData: true } + ); + + console.log(monitorDetailsData); + + const monitor = monitorDetailsData?.monitor; + const monitorStats = monitorDetailsData?.monitorStats ?? null; + + return ( + + + + + ); }; export default InfrastructureDetails; diff --git a/client/src/Pages/Uptime/Details/index.tsx b/client/src/Pages/Uptime/Details/index.tsx index 1a6177ddc..f32ae1989 100644 --- a/client/src/Pages/Uptime/Details/index.tsx +++ b/client/src/Pages/Uptime/Details/index.tsx @@ -8,6 +8,7 @@ import { } from "@/Components/v2/monitors"; import { TrendingUp, AlertTriangle } from "lucide-react"; import { ChecksTable } from "@/Pages/Uptime/Details/Components/ChecksTable"; +import { MonitorStatBoxes } from "@/Components/v2/monitors"; import { useTheme } from "@mui/material/styles"; import { useIsAdmin } from "@/Hooks/useIsAdmin"; @@ -18,7 +19,6 @@ import { useGet } from "@/Hooks/UseApi"; import type { MonitorDetailsResponse } from "@/Types/Monitor"; import type { ChecksResponse } from "@/Types/Check"; import type { RootState } from "@/Types/state"; -import { MonitorStatBoxes } from "@/Components/v2/monitors"; import { formatDateWithTz } from "@/Utils/timeUtilsLegacy"; import { t } from "i18next"; diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index d4dcf5a38..aaafb9a11 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -107,3 +107,43 @@ export interface PageSpeedDetailsResponse { monitor: MonitorWithChecks; monitorStats: MonitorStats | null; } + +export interface HardwareDetailsResponse { + monitor: Monitor; + stats: { + aggregateData: { + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: Array<{ + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: Array<{ + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; + }>; + net: Array<{ + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; + }>; + }>; + }; + monitorStats: MonitorStats | null; +} diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 721565f59..df1945fcc 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -93,7 +93,7 @@ class MonitorController { const dateRange = optionalString(req?.query?.dateRange, "dateRange") || "recent"; const teamId = requireTeamId(req?.user?.teamId); - const monitor = await this.monitorService.getHardwareDetailsById({ + const data = await this.monitorService.getHardwareDetailsById({ teamId, monitorId, dateRange, @@ -102,7 +102,7 @@ class MonitorController { return res.status(200).json({ success: true, msg: "Hardware details retrieved successfully", - data: monitor, + data: data, }); } catch (error) { next(error); diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index b950c9a32..b824908b5 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -257,9 +257,12 @@ export class MonitorService implements IMonitorService { checks: checksData.checks, }; + const monitorStats = await this.monitorStatsRepository.findByMonitorId(monitor.id); + return { - ...monitor, + monitor, stats, + monitorStats, }; }; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index b2bf360dc..39fa9d9ac 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -75,7 +75,8 @@ export interface UptimeDetailsResult { monitorStats: import("./monitorStats.js").MonitorStats | null; } -export interface HardwareDetailsResult extends Monitor { +export interface HardwareDetailsResult { + monitor: Monitor; stats: { aggregateData: { totalChecks: number; @@ -111,6 +112,7 @@ export interface HardwareDetailsResult extends Monitor { }>; }>; }; + monitorStats: import("./monitorStats.js").MonitorStats | null; } export interface PageSpeedDetailsResult { From dcf5a779230196293c84c270bdbd5b9ca6c4430d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 22:38:02 +0000 Subject: [PATCH 3/9] 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" } } }, From cb392eaaba8d9f9eb815c7d8e50e92d4940d8e0d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 23:26:00 +0000 Subject: [PATCH 4/9] gauges, charts --- .../charts/HistogramInfrastructure.tsx | 201 ++++++++++++++++++ client/src/Components/v2/monitors/index.tsx | 1 + .../Details/Components/Charts.tsx | 98 +++++++++ .../Details/Components/Gauges.tsx | 110 ++++++++++ .../Details/Components/TabOverview.tsx | 28 ++- .../Pages/Infrastructure/Details/index.tsx | 9 +- client/src/Types/Check.ts | 12 +- client/src/Types/Monitor.ts | 78 ++++--- client/src/Utils/InfraUtils.ts | 1 - client/src/locales/en.json | 25 +++ server/src/db/models/Check.ts | 13 +- .../checks/MongoChecksRepistory.ts | 15 +- server/src/types/check.ts | 13 +- server/src/types/monitor.ts | 78 ++++--- 14 files changed, 596 insertions(+), 86 deletions(-) create mode 100644 client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/Charts.tsx create mode 100644 client/src/Pages/Infrastructure/Details/Components/Gauges.tsx diff --git a/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx new file mode 100644 index 000000000..86b6c3d47 --- /dev/null +++ b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx @@ -0,0 +1,201 @@ +import { BaseChart } from "@/Components/v2/design-elements"; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + ResponsiveContainer, +} from "recharts"; +import { Fragment, useId } from "react"; +import { XTick } from "@/Components/v2/monitors"; + +import { useTheme } from "@mui/material/styles"; +import type { HardwareCheckStats } from "@/Types/Monitor"; + +const AREA_COLORS = [ + // Blues + "#3182bd", // Deep blue + "#6baed6", // Medium blue + "#9ecae1", // Light blue + + // Greens + "#74c476", // Soft green + "#a1d99b", // Light green + "#c7e9c0", // Pale green + + // Oranges + "#fdae6b", // Warm orange + "#fdd0a2", // Light orange + "#feedde", // Pale orange + + // Purples + "#9467bd", // Lavender + "#a55194", // Deep magenta + "#c994c7", // Soft magenta + + // Reds + "#ff9896", // Soft red + "#de2d26", // Deep red + "#fc9272", // Medium red + + // Cyans/Teals + "#17becf", // Cyan + "#7fcdbb", // Teal + "#a1dab4", // Light teal + + // Yellows + "#fec44f", // Mustard + "#fee391", // Light yellow + "#ffffd4", // Pale yellow + + // Additional colors + "#e377c2", // Soft pink + "#bcbd22", // Olive + "#2ca02c", // Vibrant green +]; + +const createGradient = ({ + id, + startColor, + endColor, + startOpacity = 0.8, + endOpacity = 0, + direction = "vertical", +}: { + id: string; + startColor: string; + endColor: string; + startOpacity?: number; + endOpacity?: number; + direction?: "vertical" | "horizontal"; +}) => ( + + + + + + +); + +export const HistogramInfrastructure = ({ + range, + title, + type, + idx: _idx, + checks, + xKey, + yDomain, + dataKeys, + gradient = false, + gradientDirection = "vertical", + gradientStartColor, + gradientEndColor, + strokeColor, + fillColor, +}: { + range: string; + title: string; + type: string; + idx: number | null; + checks: HardwareCheckStats[]; + xKey: string; + yDomain?: number[]; + dataKeys: string[]; + gradient?: boolean; + gradientDirection?: "vertical" | "horizontal"; + gradientStartColor?: string; + gradientEndColor?: string; + strokeColor: string; + fillColor?: string; +}) => { + const theme = useTheme(); + const uniqueId = useId(); + let data = checks.reverse(); + + let avgTemps: { bucketDate: string; avg_temp: number | null }[] = []; + let tempYDomain: number[] = []; + if (type === "temp") { + avgTemps = data.map((check) => { + const temps = check.avgTemperature || []; + if (temps.length === 0) return { bucketDate: check._id, avg_temp: null }; + const totalTemp = temps.reduce((sum, temp) => sum + (temp || 0), 0); + const avgTemp = totalTemp / temps.length; + return { bucketDate: check?._id, avg_temp: avgTemp }; + }); + + const maxTemp: number = avgTemps.reduce((max, item) => { + return item.avg_temp && item.avg_temp > max ? item.avg_temp : max; + }, 0); + + tempYDomain = [0, Math.ceil((maxTemp * 1.3) / 10) * 10]; + } + + return ( + + + + ( + + )} + /> + + + + {dataKeys?.map((dataKey, index) => { + const gradientId = `gradient-${uniqueId}-${index}`; + return ( + + {gradient === true && + createGradient({ + id: gradientId, + startColor: gradientStartColor || AREA_COLORS[index], + endColor: gradientEndColor || "transparent", + direction: gradientDirection, + })} + + + ); + })} + + + + ); +}; diff --git a/client/src/Components/v2/monitors/index.tsx b/client/src/Components/v2/monitors/index.tsx index 4efaae65c..ce90f1b8c 100644 --- a/client/src/Components/v2/monitors/index.tsx +++ b/client/src/Components/v2/monitors/index.tsx @@ -9,3 +9,4 @@ export * from "./charts/PiePageSpeed"; export * from "./charts/PiePageSpeedLegend"; export * from "./charts/HistogramPageSpeedDetails"; export * from "./charts/HistogramPageSpeedDetailsTooltip"; +export * from "./charts/HistogramInfrastructure"; diff --git a/client/src/Pages/Infrastructure/Details/Components/Charts.tsx b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx new file mode 100644 index 000000000..cdeba0a77 --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx @@ -0,0 +1,98 @@ +import Grid from "@mui/material/Grid"; +import { HistogramInfrastructure } from "@/Components/v2/monitors"; + +import { useTranslation } from "react-i18next"; +import type { HardwareCheckStats } from "@/Types/Monitor"; +import { useMemo } from "react"; +import { useTheme } from "@mui/material/styles"; +import useMediaQuery from "@mui/material/useMediaQuery"; + +const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { + return [ + { + title: t("pages.infrastructure.charts.labels.memory"), + type: "memory", + dataKeys: ["avgMemoryUsage"], + strokeColor: theme.palette.primary.main, + gradientStartColor: theme.palette.primary.main, + yDomain: [0, 1], + idx: null, + }, + { + title: t("pages.infrastructure.charts.labels.cpu"), + type: "cpu", + dataKeys: ["avgCpuUsage"], + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + yDomain: [0, 1], + idx: null, + }, + { + title: t("pages.infrastructure.charts.labels.temp"), + type: "temp", + dataKeys: ["avg_temp"], + strokeColor: theme.palette.error.main, + gradientStartColor: theme.palette.error.main, + yDomain: [0, 150], + idx: null, + }, + ...(checks[0]?.disks?.map((_, idx) => ({ + title: t("pages.infrastructure.charts.labels.disk", { idx }), + type: "disk", + dataKeys: [`disks[${idx}].usagePercent`], + strokeColor: theme.palette.warning.main, + gradientStartColor: theme.palette.warning.main, + yDomain: [0, 1], + idx, + })) || []), + ]; +}; + +export const InfraDetailsCharts = ({ + checks, + range, +}: { + checks: HardwareCheckStats[]; + range: string; +}) => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation(); + const chartConfigs = useMemo( + () => getChartConfigs(theme, checks, t), + [theme, checks, t] + ); + + console.log(checks); + return ( + + {chartConfigs.map((config) => { + return ( + + + + ); + })} + + ); +}; diff --git a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx new file mode 100644 index 000000000..5df7ee7ac --- /dev/null +++ b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx @@ -0,0 +1,110 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { BaseChart, Gauge } from "@/Components/v2/design-elements"; + +import { useTranslation } from "react-i18next"; +import { getGbs, getFrequency } from "@/Utils/InfraUtils"; +import { useTheme } from "@mui/material"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import type { CheckSnapshot, GroupedCheck } from "@/Types/Check"; +import type { HardwareCheckStats } from "@/Types/Monitor"; + +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 ( + + + + + + {upperLabel} + {upperValue} + + + {lowerLabel} + {lowerValue} + + + ); +}; + +export const InfraDetailsGauges = ({ + snapshot, +}: { + snapshot: CheckSnapshot | undefined; +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + + if (!snapshot) { + return null; + } + + return ( + + + + {snapshot?.disk?.map((disk, idx) => { + return ( + + ); + })} + + ); +}; diff --git a/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx index c4cc6f0f1..7bb79ae6d 100644 --- a/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx @@ -1,10 +1,32 @@ +import Stack from "@mui/material/Stack"; +import { InfraDetailsGauges } from "@/Pages/Infrastructure/Details/Components/Gauges"; import { StatusBoxes } from "@/Pages/Infrastructure/Details/Components/StatusBoxes"; +import { InfraDetailsCharts } from "@/Pages/Infrastructure/Details/Components/Charts"; -import type { Monitor } from "@/Types/Monitor"; +import { useTheme } from "@mui/material"; +import type { HardwareStats, Monitor } from "@/Types/Monitor"; -export const TabOverview = ({ monitor }: { monitor: Monitor | undefined }) => { +export const TabOverview = ({ + monitor, + stats, +}: { + monitor: Monitor | undefined; + stats: HardwareStats | undefined; +}) => { + const theme = useTheme(); if (!monitor) { return null; } - return ; + + const checks = stats?.checks || []; + return ( + + + + + + ); }; diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index c24e5a394..5260c5d15 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -1,6 +1,5 @@ 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"; @@ -46,6 +45,7 @@ const InfrastructureDetails = () => { const monitor = monitorDetailsData?.monitor; const monitorStats = monitorDetailsData?.monitorStats ?? null; + const stats = monitorDetailsData?.stats; return ( @@ -68,7 +68,12 @@ const InfrastructureDetails = () => { - {selectedTab === 0 && } + {selectedTab === 0 && ( + + )} {selectedTab === 1 && } ); diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts index 6f10bea3a..8fc4e5664 100644 --- a/client/src/Types/Check.ts +++ b/client/src/Types/Check.ts @@ -16,6 +16,7 @@ export interface CheckCpuInfo { physical_core?: number; logical_core?: number; frequency?: number; + current_frequency?: number; temperature?: number[]; free_percent?: number; usage_percent?: number; @@ -43,11 +44,18 @@ export interface CheckCaptureInfo { export interface CheckDiskInfo { device?: string; mountpoint?: string; - read_speed_bytes?: number; - write_speed_bytes?: number; total_bytes?: number; free_bytes?: number; + used_bytes?: number; usage_percent?: number; + total_inodes?: number; + free_inodes?: number; + used_inodes?: number; + inodes_usage_percent?: number; + read_bytes?: number; + write_bytes?: number; + read_time?: number; + write_time?: number; } export interface CheckErrorInfo { diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index aaafb9a11..92af7ccd8 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -108,42 +108,50 @@ export interface PageSpeedDetailsResponse { monitorStats: MonitorStats | null; } +export interface HardwareDiskStats { + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; +} + +export interface HardwareNetStats { + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; +} + +export interface HardwareCheckStats { + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: HardwareDiskStats[]; + net: HardwareNetStats[]; +} + +export interface HardwareStats { + aggregateData: { + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: HardwareCheckStats[]; +} + export interface HardwareDetailsResponse { monitor: Monitor; - stats: { - aggregateData: { - totalChecks: number; - }; - upChecks: { - totalChecks: number; - }; - checks: Array<{ - _id: string; - avgCpuUsage: number; - avgMemoryUsage: number; - avgTemperature: number[]; - disks: Array<{ - name: string; - readSpeed: number; - writeSpeed: number; - totalBytes: number; - freeBytes: number; - usagePercent: number; - }>; - net: Array<{ - name: string; - bytesSentPerSecond: number; - deltaBytesRecv: number; - deltaPacketsSent: number; - deltaPacketsRecv: number; - deltaErrIn: number; - deltaErrOut: number; - deltaDropIn: number; - deltaDropOut: number; - deltaFifoIn: number; - deltaFifoOut: number; - }>; - }>; - }; + stats: HardwareStats; monitorStats: MonitorStats | null; } diff --git a/client/src/Utils/InfraUtils.ts b/client/src/Utils/InfraUtils.ts index 9fba65dbc..07212737e 100644 --- a/client/src/Utils/InfraUtils.ts +++ b/client/src/Utils/InfraUtils.ts @@ -13,7 +13,6 @@ export const getCores = (cores: number | undefined) => { }; 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`; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 0ba2f2651..cfea5299a 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -299,6 +299,31 @@ "memory": "Memory", "disk": "Disk", "os": "OS" + }, + "gauges": { + "cpu": { + "lowerLabel": "Max frequency", + "title": "CPU usage", + "upperLabel": "Current frequency" + }, + "disk": { + "lowerLabel": "Free", + "title": "Disk {{idx}} usage", + "upperLabel": "Used" + }, + "memory": { + "lowerLabel": "Free", + "title": "Memory usage", + "upperLabel": "Used" + } + }, + "charts": { + "labels": { + "memory": "Memory usage", + "cpu": "CPU usage", + "temp": "Temp", + "disk": "Disk usage" + } } } }, diff --git a/server/src/db/models/Check.ts b/server/src/db/models/Check.ts index 4d9c499de..7bd1cec6f 100644 --- a/server/src/db/models/Check.ts +++ b/server/src/db/models/Check.ts @@ -71,6 +71,7 @@ const cpuSchema = new Schema( physical_core: { type: Number, default: 0 }, logical_core: { type: Number, default: 0 }, frequency: { type: Number, default: 0 }, + current_frequency: { type: Number, default: 0 }, temperature: { type: [Number], default: [] }, free_percent: { type: Number, default: 0 }, usage_percent: { type: Number, default: 0 }, @@ -92,11 +93,18 @@ const diskSchema = new Schema( { device: { type: String, default: "" }, mountpoint: { type: String, default: "" }, - read_speed_bytes: { type: Number, default: 0 }, - write_speed_bytes: { type: Number, default: 0 }, total_bytes: { type: Number, default: 0 }, free_bytes: { type: Number, default: 0 }, + used_bytes: { type: Number, default: 0 }, usage_percent: { type: Number, default: 0 }, + total_inodes: { type: Number, default: 0 }, + free_inodes: { type: Number, default: 0 }, + used_inodes: { type: Number, default: 0 }, + inodes_usage_percent: { type: Number, default: 0 }, + read_bytes: { type: Number, default: 0 }, + write_bytes: { type: Number, default: 0 }, + read_time: { type: Number, default: 0 }, + write_time: { type: Number, default: 0 }, }, { _id: false } ); @@ -106,6 +114,7 @@ const hostSchema = new Schema( os: { type: String, default: "" }, platform: { type: String, default: "" }, kernel_version: { type: String, default: "" }, + pretty_name: { type: String, default: "" }, }, { _id: false } ); diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 1f65673f3..64e8dbbb1 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -117,11 +117,18 @@ class MongoChecksRepository implements IChecksRepository { (disks ?? []).map((disk) => ({ device: disk?.device ?? "", mountpoint: disk?.mountpoint ?? "", - read_speed_bytes: disk?.read_speed_bytes ?? 0, - write_speed_bytes: disk?.write_speed_bytes ?? 0, total_bytes: disk?.total_bytes ?? 0, free_bytes: disk?.free_bytes ?? 0, + used_bytes: disk?.used_bytes ?? 0, usage_percent: disk?.usage_percent ?? 0, + total_inodes: disk?.total_inodes ?? 0, + free_inodes: disk?.free_inodes ?? 0, + used_inodes: disk?.used_inodes ?? 0, + inodes_usage_percent: disk?.inodes_usage_percent ?? 0, + read_bytes: disk?.read_bytes ?? 0, + write_bytes: disk?.write_bytes ?? 0, + read_time: disk?.read_time ?? 0, + write_time: disk?.write_time ?? 0, })); const mapErrors = (errors?: CheckErrorInfo[]): CheckErrorInfo[] => @@ -628,8 +635,8 @@ class MongoChecksRepository implements IChecksRepository { as: "dIdx", in: { name: { $concat: ["disk", { $toString: "$$dIdx" }] }, - readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_speed_bytes", "$$dIdx"] } } } }, - writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_speed_bytes", "$$dIdx"] } } } }, + readSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.read_bytes", "$$dIdx"] } } } }, + writeSpeed: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.write_bytes", "$$dIdx"] } } } }, totalBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.total_bytes", "$$dIdx"] } } } }, freeBytes: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.free_bytes", "$$dIdx"] } } } }, usagePercent: { $avg: { $map: { input: "$disks", as: "dA", in: { $arrayElemAt: ["$$dA.usage_percent", "$$dIdx"] } } } }, diff --git a/server/src/types/check.ts b/server/src/types/check.ts index 8e223d7c8..757fb95e9 100644 --- a/server/src/types/check.ts +++ b/server/src/types/check.ts @@ -12,6 +12,7 @@ export interface CheckCpuInfo { physical_core?: number; logical_core?: number; frequency?: number; + current_frequency?: number; temperature?: number[]; free_percent?: number; usage_percent?: number; @@ -28,6 +29,7 @@ export interface CheckHostInfo { os?: string; platform?: string; kernel_version?: string; + pretty_name?: string; } export interface CheckCaptureInfo { @@ -38,11 +40,18 @@ export interface CheckCaptureInfo { export interface CheckDiskInfo { device?: string; mountpoint?: string; - read_speed_bytes?: number; - write_speed_bytes?: number; total_bytes?: number; free_bytes?: number; + used_bytes?: number; usage_percent?: number; + total_inodes?: number; + free_inodes?: number; + used_inodes?: number; + inodes_usage_percent?: number; + read_bytes?: number; + write_bytes?: number; + read_time?: number; + write_time?: number; } export interface CheckErrorInfo { diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index 39fa9d9ac..57185a735 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -75,43 +75,51 @@ export interface UptimeDetailsResult { monitorStats: import("./monitorStats.js").MonitorStats | null; } +export interface HardwareDiskStats { + name: string; + readSpeed: number; + writeSpeed: number; + totalBytes: number; + freeBytes: number; + usagePercent: number; +} + +export interface HardwareNetStats { + name: string; + bytesSentPerSecond: number; + deltaBytesRecv: number; + deltaPacketsSent: number; + deltaPacketsRecv: number; + deltaErrIn: number; + deltaErrOut: number; + deltaDropIn: number; + deltaDropOut: number; + deltaFifoIn: number; + deltaFifoOut: number; +} + +export interface HardwareCheckStats { + _id: string; + avgCpuUsage: number; + avgMemoryUsage: number; + avgTemperature: number[]; + disks: HardwareDiskStats[]; + net: HardwareNetStats[]; +} + +export interface HardwareStats { + aggregateData: { + totalChecks: number; + }; + upChecks: { + totalChecks: number; + }; + checks: HardwareCheckStats[]; +} + export interface HardwareDetailsResult { monitor: Monitor; - stats: { - aggregateData: { - totalChecks: number; - }; - upChecks: { - totalChecks: number; - }; - checks: Array<{ - _id: string; - avgCpuUsage: number; - avgMemoryUsage: number; - avgTemperature: number[]; - disks: Array<{ - name: string; - readSpeed: number; - writeSpeed: number; - totalBytes: number; - freeBytes: number; - usagePercent: number; - }>; - net: Array<{ - name: string; - bytesSentPerSecond: number; - deltaBytesRecv: number; - deltaPacketsSent: number; - deltaPacketsRecv: number; - deltaErrIn: number; - deltaErrOut: number; - deltaDropIn: number; - deltaDropOut: number; - deltaFifoIn: number; - deltaFifoOut: number; - }>; - }>; - }; + stats: HardwareStats; monitorStats: import("./monitorStats.js").MonitorStats | null; } From 9b871ffb52dd07f03f2e4e3e212b3ad43312858d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 23:29:04 +0000 Subject: [PATCH 5/9] grid fix --- client/src/Pages/Infrastructure/Details/Components/Charts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/Charts.tsx b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx index cdeba0a77..5d1de37be 100644 --- a/client/src/Pages/Infrastructure/Details/Components/Charts.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx @@ -1,4 +1,4 @@ -import Grid from "@mui/material/Grid"; +import Grid from "@mui/material/Grid2"; import { HistogramInfrastructure } from "@/Components/v2/monitors"; import { useTranslation } from "react-i18next"; From 353b97e7d34dd9e15a944a502f6d795a02a81dcd Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 28 Jan 2026 23:34:18 +0000 Subject: [PATCH 6/9] _id -> bucketDate --- .../Components/v2/monitors/charts/HistogramInfrastructure.tsx | 4 ++-- client/src/Types/Check.ts | 2 +- client/src/Types/Monitor.ts | 2 +- server/src/repositories/checks/MongoChecksRepistory.ts | 2 +- server/src/types/check.ts | 2 +- server/src/types/monitor.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx index 86b6c3d47..f5f122c93 100644 --- a/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx +++ b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx @@ -132,10 +132,10 @@ export const HistogramInfrastructure = ({ if (type === "temp") { avgTemps = data.map((check) => { const temps = check.avgTemperature || []; - if (temps.length === 0) return { bucketDate: check._id, avg_temp: null }; + if (temps.length === 0) return { bucketDate: check.bucketDate, avg_temp: null }; const totalTemp = temps.reduce((sum, temp) => sum + (temp || 0), 0); const avgTemp = totalTemp / temps.length; - return { bucketDate: check?._id, avg_temp: avgTemp }; + return { bucketDate: check.bucketDate, avg_temp: avgTemp }; }); const maxTemp: number = avgTemps.reduce((max, item) => { diff --git a/client/src/Types/Check.ts b/client/src/Types/Check.ts index 8fc4e5664..9b903207c 100644 --- a/client/src/Types/Check.ts +++ b/client/src/Types/Check.ts @@ -192,7 +192,7 @@ export interface HardwareChecksResult { totalChecks: number; }; checks: Array<{ - _id: string; + bucketDate: string; avgCpuUsage: number; avgMemoryUsage: number; avgTemperature: number[]; diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index 92af7ccd8..94b3d83d7 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -132,7 +132,7 @@ export interface HardwareNetStats { } export interface HardwareCheckStats { - _id: string; + bucketDate: string; avgCpuUsage: number; avgMemoryUsage: number; avgTemperature: number[]; diff --git a/server/src/repositories/checks/MongoChecksRepistory.ts b/server/src/repositories/checks/MongoChecksRepistory.ts index 64e8dbbb1..a36788e2c 100644 --- a/server/src/repositories/checks/MongoChecksRepistory.ts +++ b/server/src/repositories/checks/MongoChecksRepistory.ts @@ -529,7 +529,7 @@ class MongoChecksRepository implements IChecksRepository { }; const checks = (hardwareMetrics ?? []).map((metric) => ({ - _id: metric._id, + bucketDate: metric._id, avgCpuUsage: metric.avgCpuUsage ?? 0, avgMemoryUsage: metric.avgMemoryUsage ?? 0, avgTemperature: metric.avgTemperature ?? [], diff --git a/server/src/types/check.ts b/server/src/types/check.ts index 757fb95e9..c02b03099 100644 --- a/server/src/types/check.ts +++ b/server/src/types/check.ts @@ -136,7 +136,7 @@ export interface HardwareChecksResult { totalChecks: number; }; checks: Array<{ - _id: string; + bucketDate: string; avgCpuUsage: number; avgMemoryUsage: number; avgTemperature: number[]; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index 57185a735..62ced091d 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -99,7 +99,7 @@ export interface HardwareNetStats { } export interface HardwareCheckStats { - _id: string; + bucketDate: string; avgCpuUsage: number; avgMemoryUsage: number; avgTemperature: number[]; From 38ef9ccad360e3a56f2f582603626642fb797989 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 29 Jan 2026 00:01:22 +0000 Subject: [PATCH 7/9] network charts --- .../charts/HistogramInfrastructure.tsx | 13 ++- .../Details/Components/Charts.tsx | 7 +- .../Details/Components/ChartsNetwork.tsx | 107 ++++++++++++++++++ .../Details/Components/TabNetwork.tsx | 30 ++++- .../Details/Components/TabOverview.tsx | 4 +- .../Pages/Infrastructure/Details/index.tsx | 14 ++- client/src/locales/en.json | 4 +- 7 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx diff --git a/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx index f5f122c93..7ef82ba0c 100644 --- a/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx +++ b/client/src/Components/v2/monitors/charts/HistogramInfrastructure.tsx @@ -93,7 +93,7 @@ const createGradient = ({ ); export const HistogramInfrastructure = ({ - range, + dateRange, title, type, idx: _idx, @@ -107,8 +107,9 @@ export const HistogramInfrastructure = ({ gradientEndColor, strokeColor, fillColor, + yAxisFormatter, }: { - range: string; + dateRange: string; title: string; type: string; idx: number | null; @@ -122,6 +123,7 @@ export const HistogramInfrastructure = ({ gradientEndColor?: string; strokeColor: string; fillColor?: string; + yAxisFormatter?: (value: number) => string; }) => { const theme = useTheme(); const uniqueId = useId(); @@ -160,11 +162,14 @@ export const HistogramInfrastructure = ({ tick={(props) => ( )} /> - + { export const InfraDetailsCharts = ({ checks, - range, + dateRange, }: { checks: HardwareCheckStats[]; - range: string; + dateRange: string; }) => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); @@ -63,7 +63,6 @@ export const InfraDetailsCharts = ({ [theme, checks, t] ); - console.log(checks); return ( + `${(value / (1024 * 1024)).toFixed(2)} MB`; + +interface ChartConfig { + title: string; + type: string; + dataKeys: string[]; + strokeColor: string; + gradientStartColor: string; + idx: number | null; + interfaceName?: string; +} + +const getChartConfigs = ( + theme: any, + checks: HardwareCheckStats[], + t: any +): ChartConfig[] => { + const configs: ChartConfig[] = []; + + const netInterfaces = checks[0]?.net || []; + + netInterfaces.forEach((iface, idx) => { + configs.push( + { + title: t("pages.infrastructure.charts.labels.netBytesSent", { + name: iface.name, + }), + type: "netBytesSent", + dataKeys: [`net[${idx}].bytesSentPerSecond`], + strokeColor: theme.palette.primary.main, + gradientStartColor: theme.palette.primary.main, + idx, + interfaceName: iface.name, + }, + { + title: t("pages.infrastructure.charts.labels.netBytesRecv", { + name: iface.name, + }), + type: "netBytesRecv", + dataKeys: [`net[${idx}].deltaBytesRecv`], + strokeColor: theme.palette.success.main, + gradientStartColor: theme.palette.success.main, + idx, + interfaceName: iface.name, + } + ); + }); + + return configs; +}; + +export const InfraNetworkCharts = ({ + checks, + dateRange, +}: { + checks: HardwareCheckStats[]; + dateRange: string; +}) => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + const { t } = useTranslation(); + const chartConfigs = useMemo( + () => getChartConfigs(theme, checks, t), + [theme, checks, t] + ); + + return ( + + {chartConfigs.map((config) => { + return ( + + + + ); + })} + + ); +}; diff --git a/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx b/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx index 241a092d4..612040cb1 100644 --- a/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/TabNetwork.tsx @@ -1,3 +1,29 @@ -export const TabNetwork = () => { - return
Network Content
; +import Stack from "@mui/material/Stack"; +import { InfraNetworkCharts } from "./ChartsNetwork"; + +import { useTheme } from "@mui/material"; +import type { HardwareStats } from "@/Types/Monitor"; + +export const TabNetwork = ({ + stats, + dateRange, +}: { + stats: HardwareStats | undefined; + dateRange: string; +}) => { + const theme = useTheme(); + + if (!stats) { + return null; + } + + const checks = stats?.checks || []; + return ( + + + + ); }; diff --git a/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx index 7bb79ae6d..447a2cce6 100644 --- a/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/TabOverview.tsx @@ -9,9 +9,11 @@ import type { HardwareStats, Monitor } from "@/Types/Monitor"; export const TabOverview = ({ monitor, stats, + dateRange, }: { monitor: Monitor | undefined; stats: HardwareStats | undefined; + dateRange: string; }) => { const theme = useTheme(); if (!monitor) { @@ -25,7 +27,7 @@ export const TabOverview = ({ ); diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index 5260c5d15..31357d3c7 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -59,6 +59,12 @@ const InfrastructureDetails = () => { monitor={monitor} monitorStats={monitorStats} /> + { @@ -72,9 +78,15 @@ const InfrastructureDetails = () => { + )} + {selectedTab === 1 && ( + )} - {selectedTab === 1 && } ); }; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index cfea5299a..2c79562f0 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -322,7 +322,9 @@ "memory": "Memory usage", "cpu": "CPU usage", "temp": "Temp", - "disk": "Disk usage" + "disk": "Disk usage", + "netBytesSent": "{{name}} - Bytes Sent", + "netBytesRecv": "{{name}} - Bytes Received" } } } From 227dc9e7e822335762f2b6f4ec3ef1bedb019590 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 29 Jan 2026 00:04:51 +0000 Subject: [PATCH 8/9] y axis formatting --- .../src/Pages/Infrastructure/Details/Components/Charts.tsx | 7 +++++++ .../Infrastructure/Details/Components/ChartsNetwork.tsx | 3 +-- client/src/Pages/Infrastructure/Details/index.tsx | 2 -- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/Charts.tsx b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx index ac8011023..a293c310e 100644 --- a/client/src/Pages/Infrastructure/Details/Components/Charts.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/Charts.tsx @@ -6,6 +6,8 @@ import type { HardwareCheckStats } from "@/Types/Monitor"; import { useMemo } from "react"; import { useTheme } from "@mui/material/styles"; import useMediaQuery from "@mui/material/useMediaQuery"; +const formatPercent = (value: number) => `${(value * 100).toFixed(0)}%`; +const formatTemp = (value: number) => `${value.toFixed(0)}°C`; const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { return [ @@ -16,6 +18,7 @@ const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { strokeColor: theme.palette.primary.main, gradientStartColor: theme.palette.primary.main, yDomain: [0, 1], + yAxisFormatter: formatPercent, idx: null, }, { @@ -25,6 +28,7 @@ const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { strokeColor: theme.palette.success.main, gradientStartColor: theme.palette.success.main, yDomain: [0, 1], + yAxisFormatter: formatPercent, idx: null, }, { @@ -34,6 +38,7 @@ const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { strokeColor: theme.palette.error.main, gradientStartColor: theme.palette.error.main, yDomain: [0, 150], + yAxisFormatter: formatTemp, idx: null, }, ...(checks[0]?.disks?.map((_, idx) => ({ @@ -43,6 +48,7 @@ const getChartConfigs = (theme: any, checks: HardwareCheckStats[], t: any) => { strokeColor: theme.palette.warning.main, gradientStartColor: theme.palette.warning.main, yDomain: [0, 1], + yAxisFormatter: formatPercent, idx, })) || []), ]; @@ -88,6 +94,7 @@ export const InfraDetailsCharts = ({ gradientStartColor={config.gradientStartColor} gradientEndColor="#ffffff" strokeColor={config.strokeColor} + yAxisFormatter={config.yAxisFormatter} />
); diff --git a/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx b/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx index fb3ea845c..a911e00e3 100644 --- a/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/ChartsNetwork.tsx @@ -7,8 +7,7 @@ import { useMemo } from "react"; import { useTheme } from "@mui/material/styles"; import useMediaQuery from "@mui/material/useMediaQuery"; -const formatBytesToMB = (value: number) => - `${(value / (1024 * 1024)).toFixed(2)} MB`; +const formatBytesToMB = (value: number) => `${(value / (1024 * 1024)).toFixed(2)} MB`; interface ChartConfig { title: string; diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index 31357d3c7..d7f208072 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -41,8 +41,6 @@ const InfrastructureDetails = () => { { refreshInterval: 10000, keepPreviousData: true } ); - console.log(monitorDetailsData); - const monitor = monitorDetailsData?.monitor; const monitorStats = monitorDetailsData?.monitorStats ?? null; const stats = monitorDetailsData?.stats; From a6c291c8ef325dc71c3b54d133f49f3697d0ddd7 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 29 Jan 2026 00:07:11 +0000 Subject: [PATCH 9/9] build fix --- client/src/Pages/Infrastructure/Details/Components/Gauges.tsx | 3 +-- .../Pages/Infrastructure/Details/Components/StatusBoxes.tsx | 1 - client/src/Pages/Infrastructure/Details/index.tsx | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx index 5df7ee7ac..a72530db4 100644 --- a/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/Gauges.tsx @@ -6,8 +6,7 @@ import { useTranslation } from "react-i18next"; import { getGbs, getFrequency } from "@/Utils/InfraUtils"; import { useTheme } from "@mui/material"; import useMediaQuery from "@mui/material/useMediaQuery"; -import type { CheckSnapshot, GroupedCheck } from "@/Types/Check"; -import type { HardwareCheckStats } from "@/Types/Monitor"; +import type { CheckSnapshot } from "@/Types/Check"; const InfraDetailGauge = ({ title, diff --git a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx index 52c888a49..7b1d80c40 100644 --- a/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx +++ b/client/src/Pages/Infrastructure/Details/Components/StatusBoxes.tsx @@ -3,7 +3,6 @@ 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, diff --git a/client/src/Pages/Infrastructure/Details/index.tsx b/client/src/Pages/Infrastructure/Details/index.tsx index d7f208072..42be78a63 100644 --- a/client/src/Pages/Infrastructure/Details/index.tsx +++ b/client/src/Pages/Infrastructure/Details/index.tsx @@ -4,7 +4,6 @@ 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"; import { useParams } from "react-router-dom"; import { useGet } from "@/Hooks/UseApi"; @@ -14,7 +13,6 @@ import { useTranslation } from "react-i18next"; const InfrastructureDetails = () => { const { t } = useTranslation(); - const theme = useTheme(); const isAdmin = useIsAdmin(); const { monitorId } = useParams<{ monitorId: string }>();