From a9e9097a1fdcbb3d9e845d40a38017afc0ae8abb Mon Sep 17 00:00:00 2001 From: mannilakash Date: Wed, 4 Mar 2026 12:46:56 -0500 Subject: [PATCH] fix: refactoring changes and adding type migration to statusPageType --- client/src/Pages/CreateMonitor/index.tsx | 10 +- client/src/Pages/StatusPage/Create/index.tsx | 4 +- .../Components/InfrastructureMetrics.tsx | 238 +++++++++++------- .../Status/Components/MonitorsList.tsx | 156 +++++++----- client/src/locales/en.json | 28 +-- .../0005_migrateStatusPageTypeToArray.ts | 45 ++++ server/src/db/migration/index.ts | 2 + 7 files changed, 307 insertions(+), 176 deletions(-) create mode 100644 server/src/db/migration/0005_migrateStatusPageTypeToArray.ts diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index b80cddaaa..a2601e302 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -302,35 +302,35 @@ const CreateMonitorPage = () => { > { }} > - {monitor.name} + {`${monitor.name} (${t(`pages.common.monitors.monitorTypes.option${monitor?.type.charAt(0).toUpperCase() + monitor?.type.slice(1)}`)})`} { diff --git a/client/src/Pages/StatusPage/Status/Components/InfrastructureMetrics.tsx b/client/src/Pages/StatusPage/Status/Components/InfrastructureMetrics.tsx index 46614a5ba..3dafdadc5 100644 --- a/client/src/Pages/StatusPage/Status/Components/InfrastructureMetrics.tsx +++ b/client/src/Pages/StatusPage/Status/Components/InfrastructureMetrics.tsx @@ -5,6 +5,9 @@ import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; import prettyBytes from "pretty-bytes"; import type { Monitor } from "@/Types/Monitor"; +import Grid from "@mui/material/Grid"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import Box from "@mui/material/Box"; interface StatusPageMonitor extends Monitor { checks?: Monitor["recentChecks"]; @@ -13,34 +16,62 @@ interface StatusPageMonitor extends Monitor { infrastructureDisk?: number; } -interface MetricItemProps { +interface MetricDetailRowProps { label: string; - progress: number; - upperLabel?: string; - upperValue?: string; - lowerLabel?: string; - lowerValue?: string; + value: string; } -const MetricItem = ({ - label, - progress, - upperLabel, - upperValue, - lowerLabel, - lowerValue, -}: MetricItemProps) => { +const MetricDetailRow = ({ label, value }: MetricDetailRowProps) => { const theme = useTheme(); return ( - + {label} + + {value} + + ); +}; + +interface MetricItemProps { + label: string; + progress: number; + details?: MetricDetailRowProps[]; +} + +const MetricItem = ({ label, progress, details }: MetricItemProps) => { + const theme = useTheme(); + const isSmall = useMediaQuery(theme.breakpoints.down("md")); + return ( + + {label} - - {(upperLabel || lowerLabel) && ( - + {details && details.length > 0 && ( + - {upperLabel && ( - - - {upperLabel} - - {upperValue} - - )} - {lowerLabel && ( - - - {lowerLabel} - - {lowerValue} - - )} - + {details.map((detail) => ( + + ))} + )} - + ); }; @@ -99,54 +108,93 @@ export const InfrastructureMetrics = ({ monitor }: { monitor: StatusPageMonitor return ( {t("pages.statusPages.monitorsList.noData")} ); } - const cpuUsage = (latestCheck?.cpu?.usage_percent || 0) * 100; - const memoryUsage = (latestCheck?.memory?.usage_percent || 0) * 100; - const memoryUsed = latestCheck?.memory?.used_bytes || 0; - const memoryTotal = latestCheck?.memory?.total_bytes || 0; - - const disks = latestCheck?.disk ?? []; - const totalDiskUsage = disks.reduce((acc, disk) => acc + (disk?.usage_percent || 0), 0); - const diskCount = disks.length || 1; - const diskUsage = (totalDiskUsage / diskCount) * 100; - const diskUsed = disks.reduce((acc, disk) => acc + (disk?.used_bytes || 0), 0); - const diskTotal = disks.reduce((acc, disk) => acc + (disk?.total_bytes || 0), 0); + const metrics = [ + { + key: t("pages.statusPages.monitorsList.infrastructure.cpu"), + labelKey: t("pages.statusPages.monitorsList.infrastructure.cpu"), + hasData: latestCheck.cpu && typeof latestCheck.cpu.usage_percent === "number", + progress: latestCheck.cpu?.usage_percent ? latestCheck.cpu.usage_percent * 100 : 0, + details: [ + { + label: t("pages.statusPages.monitorsList.infrastructure.usage"), + value: `${((latestCheck.cpu?.usage_percent || 0) * 100).toFixed(2)}%`, + }, + ], + }, + { + key: t("pages.statusPages.monitorsList.infrastructure.memoryText"), + labelKey: t("pages.statusPages.monitorsList.infrastructure.memory"), + hasData: + latestCheck.memory && + typeof latestCheck.memory.usage_percent === "number" && + typeof latestCheck.memory.used_bytes === "number" && + typeof latestCheck.memory.total_bytes === "number", + progress: latestCheck.memory?.usage_percent + ? latestCheck.memory.usage_percent * 100 + : 0, + details: [ + { + label: t("pages.statusPages.monitorsList.infrastructure.used"), + value: prettyBytes(latestCheck.memory?.used_bytes || 0), + }, + { + label: t("pages.statusPages.monitorsList.infrastructure.total"), + value: prettyBytes(latestCheck.memory?.total_bytes || 0), + }, + ], + }, + { + key: t("pages.statusPages.monitorsList.infrastructure.disk"), + labelKey: t("pages.statusPages.monitorsList.infrastructure.disk"), + hasData: latestCheck.disk && latestCheck.disk.length > 0, + progress: latestCheck.disk + ? (latestCheck.disk.reduce((acc, disk) => acc + (disk?.usage_percent || 0), 0) / + latestCheck.disk.length) * + 100 + : 0, + details: [ + { + label: t("pages.statusPages.monitorsList.infrastructure.used"), + value: prettyBytes( + latestCheck?.disk?.reduce((acc, disk) => acc + (disk?.used_bytes || 0), 0) ?? + 0 + ), + }, + { + label: t("pages.statusPages.monitorsList.infrastructure.total"), + value: prettyBytes( + latestCheck?.disk?.reduce((acc, disk) => acc + (disk?.total_bytes || 0), 0) ?? + 0 + ), + }, + ], + }, + ]; return ( - - - - - + {metrics.map( + ({ key, labelKey, hasData, progress, details }) => + hasData && ( + + ) + )} + ); }; diff --git a/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx index 7e053c018..3cda8888e 100644 --- a/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx +++ b/client/src/Pages/StatusPage/Status/Components/MonitorsList.tsx @@ -7,7 +7,7 @@ import { StatusLabel, BaseBox } from "@/Components/design-elements"; import { SwitchComponent } from "@/Components/inputs"; import { InfrastructureMetrics } from "@/Pages/StatusPage/Status/Components/InfrastructureMetrics"; -import { useTheme } from "@mui/material/styles"; +import { useTheme, type Theme } from "@mui/material/styles"; import { useSelector } from "react-redux"; import { useState } from "react"; import type { Monitor } from "@/Types/Monitor"; @@ -23,6 +23,87 @@ interface MonitorsListProps { monitors: StatusPageMonitor[]; } +const getMonitorBadgeStyles = (monitorType: string, theme: Theme) => { + // for future: specific badge styles can be defined here based on monitor type + const badgeConfig: Record = { + hardware: { + color: theme.palette.background.paper, + bg: theme.palette.info.light, + }, + http: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + ping: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + port: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + docker: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + pagespeed: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + game: { + color: theme.palette.background.paper, + bg: theme.palette.success.light, + }, + }; + + const config = badgeConfig[monitorType] ?? badgeConfig.http; + return { + backgroundColor: config.bg, + color: config.color, + padding: `${theme.spacing(1)} ${theme.spacing(2)}`, + borderRadius: theme.shape.borderRadius, + }; +}; + +// Extracted component for monitor content below the header row +const MonitorContent = ({ + monitor, + statusPage, + chartType, +}: { + monitor: Monitor & { checks?: Monitor["recentChecks"] }; + statusPage: StatusPage; + chartType: string; +}) => { + const theme = useTheme(); + const isInfrastructureMonitor = monitor?.type === "hardware"; + + if (isInfrastructureMonitor) { + if (statusPage.showInfrastructure === false) { + return null; + } + return ; + } + + if (statusPage.showCharts === false) { + return null; + } + + return ( + + {chartType === "histogram" ? ( + + ) : ( + + )} + + ); +}; + export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => { const theme = useTheme(); const { t } = useTranslation(); @@ -55,7 +136,6 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => { )} {monitors?.map((monitor) => { - const isInfrastructureMonitor = monitor?.type === "hardware"; return ( { - {t(`pages.statusPages.monitorsList.badge.${monitor?.type}`)} + {t( + `pages.common.monitors.monitorTypes.option${monitor?.type.charAt(0).toUpperCase() + monitor?.type.slice(1)}` + )} {showURL && monitor?.url && ( {monitor.url} @@ -115,48 +190,11 @@ export const MonitorsList = ({ statusPage, monitors }: MonitorsListProps) => { - - {!isInfrastructureMonitor && ( - <> - {statusPage.showCharts !== false && ( - - {chartType === "histogram" ? ( - - ) : ( - - )} - - )} - - )} - - {isInfrastructureMonitor && statusPage.showInfrastructure !== false && ( - - - - - )} + ); })} diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 16b159398..4909030cd 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -407,6 +407,17 @@ "paused": "paused", "total": "total", "up": "up" + }, + "monitorTypes": { + "optionDocker": "Docker", + "optionGame": "Game", + "optionHttp": "HTTP(S)", + "optionPing": "Ping", + "optionPort": "Port", + "optionPagespeed": "PageSpeed", + "optionHardware": "Infrastructure", + "optionGrpc": "gRPC", + "optionWebSocket": "WebSocket" } } }, @@ -534,19 +545,12 @@ }, "type": { "description": "Select the type of check to perform", - "optionDocker": "Docker", "optionDockerDescription": "Use Docker to monitor if a container is running.", - "optionGame": "Game", "optionGameDescription": "Monitor if a specific game server is online.", - "optionGrpc": "gRPC", "optionGrpcDescription": "Monitor gRPC services using the standard Health Checking Protocol.", - "optionWebSocket": "WebSocket", "optionWebSocketDescription": "Monitor WebSocket endpoints for connection health and response time.", - "optionHttp": "HTTP(S)", "optionHttpDescription": "Use HTTP(S) to monitor your website or API endpoint.", - "optionPing": "Ping", "optionPingDescription": "Use ICMP Ping to monitor if a server is online.", - "optionPort": "Port", "optionPortDescription": "Monitor if a specific port on a server is open.", "title": "Type" }, @@ -1184,6 +1188,7 @@ "title": "Infrastructure", "cpu": "CPU", "memory": "RAM", + "memoryText": "Memory", "disk": "Disk", "usage": "Usage", "used": "Used", @@ -1192,15 +1197,6 @@ "uptime": { "title": "Uptime", "responseTime": "Response time" - }, - "badge": { - "http": "HTTP", - "ping": "Ping", - "port": "Port", - "docker": "Docker", - "pagespeed": "PageSpeed", - "hardware": "Infrastructure", - "game": "Game" } }, "statusBar": { diff --git a/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts new file mode 100644 index 000000000..0e2d99ea8 --- /dev/null +++ b/server/src/db/migration/0005_migrateStatusPageTypeToArray.ts @@ -0,0 +1,45 @@ +import mongoose from "mongoose"; +import { logger } from "@/utils/logger.js"; +import StatusPageModel from "../models/StatusPage.js"; + +/** + * Migration: Convert StatusPage `type` field from string to array of strings + * + * Old format: { type: "uptime" } + * New format: { type: ["uptime"] } + */ +export async function migrateStatusPageTypeToArray(): Promise { + const SERVICE_NAME = "Migration:MigrateStatusPageTypeToArray"; + + try { + logger.info({ service: SERVICE_NAME, message: "Starting migration of StatusPage type field from string to array" }); + + const db = mongoose.connection.db; + if (!db) { + throw new Error("Database connection is not initialized"); + } + + // Find all status pages where `type` is a string (not yet migrated) + const result = await StatusPageModel.updateMany({ type: { $type: "string" } }, [ + { + $set: { + type: ["$type"], + }, + }, + ]); + + if (result.modifiedCount === 0) { + logger.info({ service: SERVICE_NAME, message: "No StatusPage documents needed migration" }); + return; + } + + logger.info({ + service: SERVICE_NAME, + message: `Migration complete. Converted ${result.modifiedCount} StatusPage document(s) from string type to array type`, + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error({ service: SERVICE_NAME, message: `Error during StatusPage type migration: ${errorMessage}` }); + throw error; + } +} diff --git a/server/src/db/migration/index.ts b/server/src/db/migration/index.ts index 3b7b45ada..61f16ed4d 100644 --- a/server/src/db/migration/index.ts +++ b/server/src/db/migration/index.ts @@ -3,6 +3,7 @@ import { convertChecksToTimeSeries } from "./0002_convertChecksToTimeSeries.js"; import { cleanupDuplicateMonitorStats } from "./0003_cleanupDuplicateMonitorStats.js"; import { fixInfrastructureThresholds } from "./0004_fixInfrastructureThresholds.js"; import MigrationModel from "../models/Migration.js"; +import { migrateStatusPageTypeToArray } from "./0005_migrateStatusPageTypeToArray.js"; type MigrationEntry = { name: string; @@ -14,6 +15,7 @@ const migrations: MigrationEntry[] = [ { name: "0002_convertChecksToTimeSeries", execute: convertChecksToTimeSeries }, { name: "0003_cleanupDuplicateMonitorStats", execute: cleanupDuplicateMonitorStats }, { name: "0004_fixInfrastructureThresholds", execute: fixInfrastructureThresholds }, + { name: "0005_migrateStatusPageTypeToArray", execute: migrateStatusPageTypeToArray }, ]; const runMigrations = async (logger?: { info: Function; error: Function }) => {