From 2bbaf7d1eb110cfb6f66175f19f0549212e4cf1f Mon Sep 17 00:00:00 2001 From: mannilakash Date: Tue, 7 Apr 2026 07:02:44 -0400 Subject: [PATCH 1/7] feat: adding mobile/desktop pagespeed strategy --- .../design-elements/MonitorStatus.tsx | 11 ++++- .../design-elements/StrategyBadge.tsx | 38 +++++++++++++++++ .../src/Components/design-elements/index.tsx | 1 + client/src/Hooks/useMonitorForm.ts | 2 + client/src/Pages/CreateMonitor/index.tsx | 41 +++++++++++++++++++ .../Components/PageSpeedMonitorsTable.tsx | 17 +++++++- client/src/Types/Monitor.ts | 5 +++ client/src/Validation/monitor.ts | 2 + client/src/locales/en.json | 9 ++++ server/openapi.json | 11 +++++ server/src/db/migration/timescaledb/index.ts | 2 + server/src/db/models/Monitor.ts | 7 +++- .../monitors/MongoMonitorsRepository.ts | 2 + .../monitors/TimescaleMonitorsRepository.ts | 11 +++-- .../network/PageSpeedProvider.ts | 7 ++-- server/src/types/monitor.ts | 5 +++ server/src/validation/monitorValidation.ts | 5 ++- 17 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 client/src/Components/design-elements/StrategyBadge.tsx diff --git a/client/src/Components/design-elements/MonitorStatus.tsx b/client/src/Components/design-elements/MonitorStatus.tsx index 04c62de47..4520356a4 100644 --- a/client/src/Components/design-elements/MonitorStatus.tsx +++ b/client/src/Components/design-elements/MonitorStatus.tsx @@ -1,12 +1,13 @@ import type { Monitor } from "@/Types/Monitor"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import { PulseDot, Dot } from "@/Components/design-elements"; +import { PulseDot, Dot, StrategyBadge } from "@/Components/design-elements"; import { getStatusColor, formatUrl } from "@/Utils/MonitorUtils"; import { useTheme } from "@mui/material/styles"; import prettyMilliseconds from "pretty-ms"; import { typographyLevels } from "@/Utils/Theme/Palette"; import useMediaQuery from "@mui/material/useMediaQuery"; +import { LAYOUT } from "@/Utils/Theme/constants"; export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => { const theme = useTheme(); const isSmall = useMediaQuery(theme.breakpoints.down("md")); @@ -28,7 +29,7 @@ export const MonitorStatus = ({ monitor }: { monitor: Monitor }) => { { > {formatUrl(monitor?.url)} + {monitor.type === "pagespeed" && monitor.strategy && ( + <> + + + + )} {!isSmall && ( <> diff --git a/client/src/Components/design-elements/StrategyBadge.tsx b/client/src/Components/design-elements/StrategyBadge.tsx new file mode 100644 index 000000000..1414f8b6c --- /dev/null +++ b/client/src/Components/design-elements/StrategyBadge.tsx @@ -0,0 +1,38 @@ +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; +import type { PageSpeedStrategy } from "@/Types/Monitor"; +import { SPACING } from "@/Utils/Theme/constants"; + +interface StrategyBadgeProps { + strategy: PageSpeedStrategy | undefined; + variant?: "caption" | "body2"; +} + +export const StrategyBadge = ({ + strategy, + variant = "body2", +}: StrategyBadgeProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + + if (!strategy) { + return null; + } + + return ( + + {t(`pages.pageSpeed.strategy.${strategy}`)} + + ); +}; diff --git a/client/src/Components/design-elements/index.tsx b/client/src/Components/design-elements/index.tsx index ed6a885f6..45114116b 100644 --- a/client/src/Components/design-elements/index.tsx +++ b/client/src/Components/design-elements/index.tsx @@ -21,3 +21,4 @@ export * from "./SplitBox"; export * from "./TextLink"; export * from "./OfflineBanner"; export * from "./Avatar"; +export * from "./StrategyBadge"; diff --git a/client/src/Hooks/useMonitorForm.ts b/client/src/Hooks/useMonitorForm.ts index 963409fc8..ac13854b3 100644 --- a/client/src/Hooks/useMonitorForm.ts +++ b/client/src/Hooks/useMonitorForm.ts @@ -1,5 +1,6 @@ import { useMemo } from "react"; import { monitorSchema, type MonitorFormData } from "@/Validation/monitor"; +import { DefaultPageSpeedStrategy } from "@/Types/Monitor"; import type { Monitor, MonitorType } from "@/Types/Monitor"; interface UseMonitorFormOptions { @@ -88,6 +89,7 @@ export const useMonitorForm = ({ ...base, type: "pagespeed", url: data?.url || "", + strategy: data?.strategy ?? DefaultPageSpeedStrategy, }; break; case "hardware": diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index 15b76eab3..911e37189 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -37,6 +37,7 @@ import { type MonitorType, type GamesMap, supportsGeoCheck, + DefaultPageSpeedStrategy, } from "@/Types/Monitor"; import type { Notification } from "@/Types/Notification"; import type { MonitorFormData } from "@/Validation/monitor"; @@ -51,6 +52,7 @@ interface GeneralSettingsConfig { showSecret: boolean; showGrpcServiceName: boolean; showIgnoreTls: boolean; + showStrategy: boolean; } const getGeneralSettingsConfig = ( @@ -68,6 +70,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, ping: { urlLabel: t("pages.createMonitor.form.general.option.host.label"), @@ -79,6 +82,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, docker: { urlLabel: t("pages.createMonitor.form.general.option.container.label"), @@ -90,6 +94,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, port: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), @@ -101,6 +106,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, game: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), @@ -112,6 +118,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, grpc: { urlLabel: t("pages.createMonitor.form.general.option.host.label"), @@ -123,6 +130,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: true, showIgnoreTls: true, + showStrategy: false, }, pagespeed: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), @@ -134,6 +142,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: true, }, hardware: { urlLabel: t("pages.createMonitor.form.general.option.url.label"), @@ -145,6 +154,7 @@ const getGeneralSettingsConfig = ( showSecret: true, showGrpcServiceName: false, showIgnoreTls: false, + showStrategy: false, }, websocket: { urlLabel: t("pages.createMonitor.form.general.option.wsUrl.label"), @@ -156,6 +166,7 @@ const getGeneralSettingsConfig = ( showSecret: false, showGrpcServiceName: false, showIgnoreTls: true, + showStrategy: false, }, }; return configs[type] || configs.http; @@ -382,6 +393,36 @@ const CreateMonitorPage = () => { /> )} + {/* Strategy field - only for pagespeed type */} + {generalSettingsConfig.showStrategy && ( + ( + + )} + /> + )} + {/* Port field - only for port and game types */} {generalSettingsConfig.showPort && ( ), render: (row) => { - return row.name; + return ( + + {row.name} + + + ); }, }, { diff --git a/client/src/Types/Monitor.ts b/client/src/Types/Monitor.ts index 053b517d1..dc9aabaf4 100644 --- a/client/src/Types/Monitor.ts +++ b/client/src/Types/Monitor.ts @@ -38,6 +38,10 @@ export type MonitorStatus = (typeof MonitorStatuses)[number]; export type MonitorMatchMethod = "equal" | "include" | "regex" | ""; +export const PageSpeedStrategies = ["desktop", "mobile"] as const; +export type PageSpeedStrategy = (typeof PageSpeedStrategies)[number]; +export const DefaultPageSpeedStrategy: PageSpeedStrategy = "desktop"; + export interface Monitor { id: string; userId: string; @@ -72,6 +76,7 @@ export interface Monitor { selectedDisks: string[]; gameId?: string; grpcServiceName?: string; + strategy?: PageSpeedStrategy; group: string | null; geoCheckEnabled?: boolean; geoCheckLocations?: GeoContinent[]; diff --git a/client/src/Validation/monitor.ts b/client/src/Validation/monitor.ts index 9acffe6fe..88df71101 100644 --- a/client/src/Validation/monitor.ts +++ b/client/src/Validation/monitor.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { GeoContinents } from "@/Types/GeoCheck"; +import { PageSpeedStrategies } from "@/Types/Monitor"; // URL schema with custom error message const urlSchema = z.url({ message: "Please enter a valid URL" }); @@ -89,6 +90,7 @@ const grpcSchema = baseSchema.extend({ const pagespeedSchema = baseSchema.extend({ type: z.literal("pagespeed"), url: urlSchema, + strategy: z.enum(PageSpeedStrategies), }); // Hardware/Infrastructure monitor schema diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 83ca4fa33..013858adc 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -503,6 +503,11 @@ "wsUrl": { "label": "WebSocket URL", "placeholder": "wss://example.com/socket" + }, + "strategy": { + "label": "Strategy", + "desktop": "Desktop", + "mobile": "Mobile" } }, "title": "General settings", @@ -968,6 +973,10 @@ } }, "pageSpeed": { + "strategy": { + "desktop": "Desktop", + "mobile": "Mobile" + }, "charts": { "common": { "cls": "Cumulative Layout Shift (CLS)", diff --git a/server/openapi.json b/server/openapi.json index 35d109693..6a44c19f2 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -3631,6 +3631,12 @@ "type": "string", "default": "" }, + "strategy": { + "type": "string", + "enum": ["desktop", "mobile"], + "default": "desktop", + "description": "PageSpeed analysis strategy" + }, "selectedDisks": { "type": "array", "items": { @@ -3737,6 +3743,11 @@ "grpcServiceName": { "type": "string" }, + "strategy": { + "type": "string", + "enum": ["desktop", "mobile"], + "description": "PageSpeed analysis strategy" + }, "selectedDisks": { "type": "array", "items": { diff --git a/server/src/db/migration/timescaledb/index.ts b/server/src/db/migration/timescaledb/index.ts index 14084ebf3..9a76d093a 100644 --- a/server/src/db/migration/timescaledb/index.ts +++ b/server/src/db/migration/timescaledb/index.ts @@ -21,6 +21,7 @@ import { createStatusPages, dropStatusPages } from "./0018_create_status_pages.j import { createAppSettings, dropAppSettings } from "./0019_create_app_settings.js"; import { createContinuousAggregates, dropContinuousAggregates } from "./0020_create_continuous_aggregates.js"; import { createRetentionCompression, dropRetentionCompression } from "./0021_create_retention_compression.js"; +import { addStrategyToMonitors, dropStrategyFromMonitors } from "./0022_add_strategy_to_monitors.js"; const SERVICE_NAME = "TimescaleDB Migrations"; @@ -52,6 +53,7 @@ const migrations: MigrationEntry[] = [ { name: "0019_create_app_settings", up: createAppSettings, down: dropAppSettings }, { name: "0020_create_continuous_aggregates", up: createContinuousAggregates, down: dropContinuousAggregates }, { name: "0021_create_retention_compression", up: createRetentionCompression, down: dropRetentionCompression }, + { name: "0022_add_strategy_to_monitors", up: addStrategyToMonitors, down: dropStrategyFromMonitors }, ]; const ensureMigrationsTable = async (pool: Pool) => { diff --git a/server/src/db/models/Monitor.ts b/server/src/db/models/Monitor.ts index 036aeadad..a20c34c09 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/db/models/Monitor.ts @@ -1,6 +1,6 @@ import { Schema, model, Types } from "mongoose"; import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js"; -import { MonitorTypes, MonitorStatuses } from "@/types/monitor.js"; +import { MonitorTypes, MonitorStatuses, PageSpeedStrategies, DefaultPageSpeedStrategy } from "@/types/monitor.js"; import type { CheckAudits, CheckCaptureInfo, @@ -330,6 +330,11 @@ const MonitorSchema = new Schema( type: String, default: "", }, + strategy: { + type: String, + enum: PageSpeedStrategies, + default: DefaultPageSpeedStrategy, + }, group: { type: String, trim: true, diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index b2d759448..aed881006 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -386,6 +386,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { selectedDisks: doc.selectedDisks ?? [], gameId: doc.gameId ?? undefined, grpcServiceName: doc.grpcServiceName ?? undefined, + strategy: doc.strategy ?? undefined, group: doc.group ?? null, recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)), geoCheckEnabled: doc.geoCheckEnabled ?? false, @@ -445,6 +446,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { selectedDisks: doc.selectedDisks ?? [], gameId: doc.gameId ?? undefined, grpcServiceName: doc.grpcServiceName ?? undefined, + strategy: doc.strategy ?? undefined, group: doc.group ?? null, recentChecks: (doc.recentChecks ?? []).map((check: CheckSnapshotDocument) => this.toCheckSnapshot(check)), geoCheckEnabled: doc.geoCheckEnabled ?? false, diff --git a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts index a94d38504..af663680a 100644 --- a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts +++ b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts @@ -1,5 +1,6 @@ import type { Pool } from "pg"; import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent } from "@/types/monitor.js"; +import { DefaultPageSpeedStrategy } from "@/types/monitor.js"; import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; @@ -36,6 +37,7 @@ interface MonitorRow { selected_disks: string[] | null; game_id: string | null; grpc_service_name: string | null; + strategy: string | null; monitor_group: string | null; geo_check_enabled: boolean; geo_check_locations: GeoContinent[] | null; @@ -49,7 +51,7 @@ const MONITOR_COLUMNS = `id, user_id, team_id, name, description, type, status, interval_ms, is_active, status_window, status_window_size, status_window_threshold, uptime_percentage, cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter, disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks, - game_id, grpc_service_name, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms, + game_id, grpc_service_name, strategy, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms, created_at, updated_at`; export class TimescaleMonitorsRepository implements IMonitorsRepository { @@ -62,8 +64,8 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { interval_ms, is_active, status_window, status_window_size, status_window_threshold, cpu_alert_threshold, cpu_alert_counter, memory_alert_threshold, memory_alert_counter, disk_alert_threshold, disk_alert_counter, temp_alert_threshold, temp_alert_counter, selected_disks, - game_id, grpc_service_name, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms) - VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34) + game_id, grpc_service_name, strategy, monitor_group, geo_check_enabled, geo_check_locations, geo_check_interval_ms) + VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35) RETURNING ${MONITOR_COLUMNS}`, [ userId, @@ -96,6 +98,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { monitor.selectedDisks ?? [], monitor.gameId ?? null, monitor.grpcServiceName ?? null, + monitor.strategy ?? DefaultPageSpeedStrategy, monitor.group ?? null, monitor.geoCheckEnabled ?? false, monitor.geoCheckLocations ?? [], @@ -594,6 +597,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { ["selectedDisks", "selected_disks"], ["gameId", "game_id"], ["grpcServiceName", "grpc_service_name"], + ["strategy", "strategy"], ["group", "monitor_group"], ["geoCheckEnabled", "geo_check_enabled"], ["geoCheckLocations", "geo_check_locations"], @@ -1033,6 +1037,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { selectedDisks: row.selected_disks ?? [], gameId: row.game_id ?? undefined, grpcServiceName: row.grpc_service_name ?? undefined, + strategy: (row.strategy as Monitor["strategy"]) ?? undefined, group: row.monitor_group, geoCheckEnabled: row.geo_check_enabled, geoCheckLocations: row.geo_check_locations ?? [], diff --git a/server/src/service/infrastructure/network/PageSpeedProvider.ts b/server/src/service/infrastructure/network/PageSpeedProvider.ts index 121fe9bf8..0d8560868 100644 --- a/server/src/service/infrastructure/network/PageSpeedProvider.ts +++ b/server/src/service/infrastructure/network/PageSpeedProvider.ts @@ -1,6 +1,6 @@ import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js"; import { MonitorStatusResponse, PageSpeedStatusPayload } from "@/types/network.js"; -import { Monitor, MonitorType } from "@/types/monitor.js"; +import { Monitor, MonitorType, DefaultPageSpeedStrategy } from "@/types/monitor.js"; import { HttpProvider } from "@/service/infrastructure/network/HttpProvider.js"; import { ISettingsService } from "@/service/system/settingsService.js"; import { ILogger } from "@/utils/logger.js"; @@ -19,14 +19,15 @@ export class PageSpeedProvider implements IStatusProvider> { - const { url } = monitor; + const { url, strategy } = monitor; try { if (!url) throw new Error("URL is required for PageSpeed monitor"); const dbSettings = await this.settingsService.getDBSettings(); const apiKey = dbSettings?.pagespeedApiKey; + const resolvedStrategy = strategy ?? DefaultPageSpeedStrategy; let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent( url - )}&category=seo&category=accessibility&category=best-practices&category=performance`; + )}&strategy=${encodeURIComponent(resolvedStrategy)}&category=seo&category=accessibility&category=best-practices&category=performance`; if (apiKey) { pageSpeedUrl += `&key=${apiKey}`; diff --git a/server/src/types/monitor.ts b/server/src/types/monitor.ts index f29ce75d7..4e4309fe7 100644 --- a/server/src/types/monitor.ts +++ b/server/src/types/monitor.ts @@ -6,6 +6,10 @@ export type { GeoContinent } from "@/types/geoCheck.js"; export const MonitorTypes = ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] as const; export type MonitorType = (typeof MonitorTypes)[number]; +export const PageSpeedStrategies = ["desktop", "mobile"] as const; +export type PageSpeedStrategy = (typeof PageSpeedStrategies)[number]; +export const DefaultPageSpeedStrategy: PageSpeedStrategy = "desktop"; + export const GeoCheckSupportedTypes: readonly MonitorType[] = ["http", "ping"] as const; export const supportsGeoCheck = (type: MonitorType): boolean => GeoCheckSupportedTypes.includes(type); @@ -49,6 +53,7 @@ export interface Monitor { selectedDisks: string[]; gameId?: string; grpcServiceName?: string; + strategy?: PageSpeedStrategy; group: string | null; geoCheckEnabled?: boolean; geoCheckLocations?: GeoContinent[]; diff --git a/server/src/validation/monitorValidation.ts b/server/src/validation/monitorValidation.ts index df000ecef..2685c03f2 100644 --- a/server/src/validation/monitorValidation.ts +++ b/server/src/validation/monitorValidation.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { booleanCoercion } from "./shared.js"; import { GeoContinents } from "@/types/geoCheck.js"; -import { MonitorMatchMethods, MonitorTypes } from "@/types/monitor.js"; +import { MonitorMatchMethods, MonitorTypes, PageSpeedStrategies, DefaultPageSpeedStrategy } from "@/types/monitor.js"; export const getMonitorByIdParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required"), @@ -73,6 +73,7 @@ export const createMonitorBodyValidation = z.object({ matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).default(""), + strategy: z.enum(PageSpeedStrategies).default(DefaultPageSpeedStrategy), selectedDisks: z.array(z.string()).optional(), group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(), geoCheckEnabled: z.boolean().optional(), @@ -102,6 +103,7 @@ export const editMonitorBodyValidation = z.object({ tempAlertThreshold: z.number().optional(), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).optional(), + strategy: z.enum(PageSpeedStrategies).optional(), selectedDisks: z.array(z.string()).optional(), group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(), geoCheckEnabled: z.boolean().optional(), @@ -156,6 +158,7 @@ const importedMonitorSchema = z.object({ selectedDisks: z.array(z.string()).default([]), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).default(""), + strategy: z.enum(PageSpeedStrategies).default(DefaultPageSpeedStrategy), group: z.union([z.string().max(50).trim(), z.null()]).default(null), geoCheckEnabled: z.boolean().default(false), geoCheckLocations: z.array(z.enum(GeoContinents)).default([]), From f7514bc9389ce21e3e880450d4bba9560a2c9edc Mon Sep 17 00:00:00 2001 From: mannilakash Date: Tue, 7 Apr 2026 07:14:17 -0400 Subject: [PATCH 2/7] fix: format errors --- client/src/Components/design-elements/StrategyBadge.tsx | 5 +---- client/src/Pages/CreateMonitor/index.tsx | 8 ++------ .../Monitors/Components/PageSpeedMonitorsTable.tsx | 7 ++++++- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/client/src/Components/design-elements/StrategyBadge.tsx b/client/src/Components/design-elements/StrategyBadge.tsx index 1414f8b6c..9ef3381c2 100644 --- a/client/src/Components/design-elements/StrategyBadge.tsx +++ b/client/src/Components/design-elements/StrategyBadge.tsx @@ -9,10 +9,7 @@ interface StrategyBadgeProps { variant?: "caption" | "body2"; } -export const StrategyBadge = ({ - strategy, - variant = "body2", -}: StrategyBadgeProps) => { +export const StrategyBadge = ({ strategy, variant = "body2" }: StrategyBadgeProps) => { const theme = useTheme(); const { t } = useTranslation(); diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index 911e37189..9c8efa002 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -409,14 +409,10 @@ const CreateMonitorPage = () => { error={!!fieldState.error} > - {t( - "pages.createMonitor.form.general.option.strategy.desktop" - )} + {t("pages.createMonitor.form.general.option.strategy.desktop")} - {t( - "pages.createMonitor.form.general.option.strategy.mobile" - )} + {t("pages.createMonitor.form.general.option.strategy.mobile")} )} diff --git a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx index 0c097293e..9e9c6e89b 100644 --- a/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx +++ b/client/src/Pages/PageSpeed/Monitors/Components/PageSpeedMonitorsTable.tsx @@ -1,7 +1,12 @@ import Stack from "@mui/material/Stack"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; -import { Table, Pagination, StatusLabel, StrategyBadge } from "@/Components/design-elements"; +import { + Table, + Pagination, + StatusLabel, + StrategyBadge, +} from "@/Components/design-elements"; import { LAYOUT } from "@/Utils/Theme/constants"; import { HistogramPageSpeed } from "@/Components/monitors"; import { ArrowDown, ArrowUp } from "lucide-react"; From 5791bdd5e7261fcd4be48774d1bae8b5bd530a12 Mon Sep 17 00:00:00 2001 From: mannilakash Date: Tue, 7 Apr 2026 07:25:35 -0400 Subject: [PATCH 3/7] fix: adding timescaledb changes --- .../timescaledb/0022_add_strategy_to_monitors.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts diff --git a/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts b/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts new file mode 100644 index 000000000..1adda12bc --- /dev/null +++ b/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts @@ -0,0 +1,15 @@ +import type { Pool } from "pg"; + +export const addStrategyToMonitors = async (pool: Pool) => { + await pool.query(` + ALTER TABLE monitors + ADD COLUMN IF NOT EXISTS strategy TEXT DEFAULT 'desktop'; + `); +}; + +export const dropStrategyFromMonitors = async (pool: Pool) => { + await pool.query(` + ALTER TABLE monitors + DROP COLUMN IF EXISTS strategy; + `); +}; From 99a5aa17221f705e045450ee1f1b1bc2a7568e6f Mon Sep 17 00:00:00 2001 From: mannilakash Date: Mon, 13 Apr 2026 08:29:12 -0400 Subject: [PATCH 4/7] fix: badge and timescaleDB changes --- .../src/Components/design-elements/StrategyBadge.tsx | 9 +++------ client/src/Pages/CreateMonitor/index.tsx | 1 - .../timescaledb/0022_add_strategy_to_monitors.ts | 2 +- server/src/db/models/Monitor.ts | 3 +-- .../monitors/TimescaleMonitorsRepository.ts | 10 +++++----- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/client/src/Components/design-elements/StrategyBadge.tsx b/client/src/Components/design-elements/StrategyBadge.tsx index 9ef3381c2..cb06d17c1 100644 --- a/client/src/Components/design-elements/StrategyBadge.tsx +++ b/client/src/Components/design-elements/StrategyBadge.tsx @@ -22,12 +22,9 @@ export const StrategyBadge = ({ strategy, variant = "body2" }: StrategyBadgeProp variant={variant} px={SPACING.XXL} py={SPACING.SM} - textTransform="capitalize" - sx={{ - borderRadius: theme.shape.borderRadius, - backgroundColor: theme.palette.secondary.light, - color: theme.palette.text.primary, - }} + borderRadius={theme.shape.borderRadius} + bgcolor={theme.palette.secondary.light} + color={theme.palette.text.primary} > {t(`pages.pageSpeed.strategy.${strategy}`)} diff --git a/client/src/Pages/CreateMonitor/index.tsx b/client/src/Pages/CreateMonitor/index.tsx index 9c8efa002..744f63bf5 100644 --- a/client/src/Pages/CreateMonitor/index.tsx +++ b/client/src/Pages/CreateMonitor/index.tsx @@ -405,7 +405,6 @@ const CreateMonitorPage = () => { fieldLabel={t( "pages.createMonitor.form.general.option.strategy.label" )} - disabled={isEditMode} error={!!fieldState.error} > diff --git a/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts b/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts index 1adda12bc..06d48cc75 100644 --- a/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts +++ b/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts @@ -3,7 +3,7 @@ import type { Pool } from "pg"; export const addStrategyToMonitors = async (pool: Pool) => { await pool.query(` ALTER TABLE monitors - ADD COLUMN IF NOT EXISTS strategy TEXT DEFAULT 'desktop'; + ADD COLUMN IF NOT EXISTS strategy TEXT DEFAULT NULL; `); }; diff --git a/server/src/db/models/Monitor.ts b/server/src/db/models/Monitor.ts index a20c34c09..40866d191 100644 --- a/server/src/db/models/Monitor.ts +++ b/server/src/db/models/Monitor.ts @@ -1,6 +1,6 @@ import { Schema, model, Types } from "mongoose"; import type { Monitor, MonitorMatchMethod, CheckSnapshot } from "@/types/monitor.js"; -import { MonitorTypes, MonitorStatuses, PageSpeedStrategies, DefaultPageSpeedStrategy } from "@/types/monitor.js"; +import { MonitorTypes, MonitorStatuses, PageSpeedStrategies } from "@/types/monitor.js"; import type { CheckAudits, CheckCaptureInfo, @@ -333,7 +333,6 @@ const MonitorSchema = new Schema( strategy: { type: String, enum: PageSpeedStrategies, - default: DefaultPageSpeedStrategy, }, group: { type: String, diff --git a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts index af663680a..60c382cf1 100644 --- a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts +++ b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts @@ -1,6 +1,6 @@ import type { Pool } from "pg"; -import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent } from "@/types/monitor.js"; -import { DefaultPageSpeedStrategy } from "@/types/monitor.js"; +import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent, PageSpeedStrategy } from "@/types/monitor.js"; + import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; @@ -37,7 +37,7 @@ interface MonitorRow { selected_disks: string[] | null; game_id: string | null; grpc_service_name: string | null; - strategy: string | null; + strategy: PageSpeedStrategy | null; monitor_group: string | null; geo_check_enabled: boolean; geo_check_locations: GeoContinent[] | null; @@ -98,7 +98,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { monitor.selectedDisks ?? [], monitor.gameId ?? null, monitor.grpcServiceName ?? null, - monitor.strategy ?? DefaultPageSpeedStrategy, + monitor.strategy ?? null, monitor.group ?? null, monitor.geoCheckEnabled ?? false, monitor.geoCheckLocations ?? [], @@ -1037,7 +1037,7 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { selectedDisks: row.selected_disks ?? [], gameId: row.game_id ?? undefined, grpcServiceName: row.grpc_service_name ?? undefined, - strategy: (row.strategy as Monitor["strategy"]) ?? undefined, + strategy: row.strategy ?? undefined, group: row.monitor_group, geoCheckEnabled: row.geo_check_enabled, geoCheckLocations: row.geo_check_locations ?? [], From ac51dfeab70ca52e502149ddb7bc027b11adfcd7 Mon Sep 17 00:00:00 2001 From: mannilakash Date: Mon, 13 Apr 2026 13:17:24 -0400 Subject: [PATCH 5/7] fix: removing default value for strategy - monitors --- server/src/validation/monitorValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/validation/monitorValidation.ts b/server/src/validation/monitorValidation.ts index 2685c03f2..f229927d2 100644 --- a/server/src/validation/monitorValidation.ts +++ b/server/src/validation/monitorValidation.ts @@ -73,7 +73,7 @@ export const createMonitorBodyValidation = z.object({ matchMethod: z.union([z.enum(MonitorMatchMethods), z.literal("")]).optional(), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).default(""), - strategy: z.enum(PageSpeedStrategies).default(DefaultPageSpeedStrategy), + strategy: z.enum(PageSpeedStrategies).optional(), selectedDisks: z.array(z.string()).optional(), group: z.union([z.string().max(50).trim(), z.null(), z.literal("")]).optional(), geoCheckEnabled: z.boolean().optional(), From 6b63091d89162e2ff62f05b42f8526f5906416d1 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 11 May 2026 11:28:39 -0700 Subject: [PATCH 6/7] tighten validation, add tests --- .../0022_add_strategy_to_monitors.ts | 15 --- server/src/validation/monitorValidation.ts | 23 ++++- .../unit/validation/monitorValidation.test.ts | 93 +++++++++++++++++++ 3 files changed, 111 insertions(+), 20 deletions(-) delete mode 100644 server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts diff --git a/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts b/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts deleted file mode 100644 index 06d48cc75..000000000 --- a/server/src/db/migration/timescaledb/0022_add_strategy_to_monitors.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Pool } from "pg"; - -export const addStrategyToMonitors = async (pool: Pool) => { - await pool.query(` - ALTER TABLE monitors - ADD COLUMN IF NOT EXISTS strategy TEXT DEFAULT NULL; - `); -}; - -export const dropStrategyFromMonitors = async (pool: Pool) => { - await pool.query(` - ALTER TABLE monitors - DROP COLUMN IF EXISTS strategy; - `); -}; diff --git a/server/src/validation/monitorValidation.ts b/server/src/validation/monitorValidation.ts index 1889aa415..69d1c2c2a 100644 --- a/server/src/validation/monitorValidation.ts +++ b/server/src/validation/monitorValidation.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { booleanCoercion, dnsHostnameRegex, dnsServerValidation } from "./shared.js"; import { GeoContinents } from "@/types/geoCheck.js"; -import { DefaultPageSpeedStrategy, DnsRecordTypes, MonitorMatchMethods, MonitorStatuses, MonitorTypes, PageSpeedStrategies } from "@/types/monitor.js"; +import { DnsRecordTypes, MonitorMatchMethods, MonitorStatuses, MonitorTypes, PageSpeedStrategies } from "@/types/monitor.js"; export const getMonitorByIdParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required"), @@ -49,6 +49,16 @@ const refineDnsHostname = (body: { type?: string; url?: string }, ctx: z.Refinem } }; +const refineStrategyType = (body: { type?: string; strategy?: string }, ctx: z.RefinementCtx) => { + if (body.strategy !== undefined && body.type !== undefined && body.type !== "pagespeed") { + ctx.addIssue({ + code: "custom", + path: ["strategy"], + message: "Strategy is only valid for pagespeed monitors", + }); + } +}; + export const createMonitorBodyValidation = z .object({ _id: z.string().optional(), @@ -83,7 +93,8 @@ export const createMonitorBodyValidation = z dnsServer: dnsServerValidation.optional(), dnsRecordType: z.enum(DnsRecordTypes).optional(), }) - .superRefine(refineDnsHostname); + .superRefine(refineDnsHostname) + .superRefine(refineStrategyType); export const editMonitorBodyValidation = z .object({ @@ -117,7 +128,8 @@ export const editMonitorBodyValidation = z dnsServer: dnsServerValidation.optional(), dnsRecordType: z.enum(DnsRecordTypes).optional(), }) - .superRefine(refineDnsHostname); + .superRefine(refineDnsHostname) + .superRefine(refineStrategyType); export const pauseMonitorParamValidation = z.object({ monitorId: z.string().min(1, "Monitor ID is required"), @@ -175,7 +187,7 @@ const importedMonitorSchema = z selectedDisks: z.array(z.string()).default([]), gameId: z.union([z.string(), z.literal("")]).optional(), grpcServiceName: z.union([z.string(), z.literal("")]).default(""), - strategy: z.enum(PageSpeedStrategies).default(DefaultPageSpeedStrategy), + strategy: z.enum(PageSpeedStrategies).optional(), group: z.union([z.string().max(50).trim(), z.null()]).default(null), geoCheckEnabled: z.boolean().default(false), geoCheckLocations: z.array(z.enum(GeoContinents)).default([]), @@ -185,7 +197,8 @@ const importedMonitorSchema = z createdAt: z.string().optional(), updatedAt: z.string().optional(), }) - .superRefine(refineDnsHostname); + .superRefine(refineDnsHostname) + .superRefine(refineStrategyType); export const importMonitorsBodyValidation = z.object({ monitors: z.array(importedMonitorSchema).min(1, "At least one monitor is required"), diff --git a/server/test/unit/validation/monitorValidation.test.ts b/server/test/unit/validation/monitorValidation.test.ts index 62fa91625..057ff6ae1 100644 --- a/server/test/unit/validation/monitorValidation.test.ts +++ b/server/test/unit/validation/monitorValidation.test.ts @@ -129,3 +129,96 @@ describe("monitorValidation — DNS fields", () => { }); }); }); + +describe("monitorValidation — strategy gating", () => { + describe("createMonitorBodyValidation", () => { + it("accepts strategy on pagespeed monitors", () => { + const parsed = createMonitorBodyValidation.parse({ + name: "PS check", + type: "pagespeed", + url: "https://example.com", + strategy: "mobile", + }); + expect(parsed.strategy).toBe("mobile"); + }); + + it("accepts pagespeed monitors without a strategy", () => { + const parsed = createMonitorBodyValidation.parse({ + name: "PS check", + type: "pagespeed", + url: "https://example.com", + }); + expect(parsed.strategy).toBeUndefined(); + }); + + it("rejects strategy on non-pagespeed monitor types", () => { + for (const type of ["http", "ping", "docker", "port", "game", "grpc", "websocket", "dns", "hardware"] as const) { + expect(() => + createMonitorBodyValidation.parse({ + name: "wrong-type check", + type, + url: type === "dns" ? "example.com" : "https://example.com", + strategy: "desktop", + }) + ).toThrow(); + } + }); + + it("allows non-pagespeed monitors without a strategy", () => { + const parsed = createMonitorBodyValidation.parse({ + name: "HTTP check", + type: "http", + url: "https://example.com", + }); + expect(parsed.strategy).toBeUndefined(); + }); + }); + + describe("editMonitorBodyValidation", () => { + it("rejects strategy when type is explicitly non-pagespeed", () => { + expect(() => + editMonitorBodyValidation.parse({ + type: "http", + strategy: "mobile", + }) + ).toThrow(); + }); + + it("allows strategy when type is omitted (partial edit)", () => { + const parsed = editMonitorBodyValidation.parse({ + strategy: "mobile", + }); + expect(parsed.strategy).toBe("mobile"); + }); + }); + + describe("importMonitorsBodyValidation", () => { + it("does not inject a default strategy on imported HTTP monitors", () => { + const parsed = importMonitorsBodyValidation.parse({ + monitors: [ + { + name: "Imported HTTP", + type: "http", + url: "https://example.com", + }, + ], + }); + expect(parsed.monitors[0].strategy).toBeUndefined(); + }); + + it("rejects strategy on imported non-pagespeed monitors", () => { + expect(() => + importMonitorsBodyValidation.parse({ + monitors: [ + { + name: "Imported HTTP", + type: "http", + url: "https://example.com", + strategy: "desktop", + }, + ], + }) + ).toThrow(); + }); + }); +}); From 4b888c1ed1185c0c9052e9675128f2e2f77e78b1 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 11 May 2026 11:30:58 -0700 Subject: [PATCH 7/7] bump dependencies --- server/openapi.json | 69 +++++++++++++++++++---- server/package-lock.json | 119 ++++++++++++++++++++++----------------- server/package.json | 2 +- 3 files changed, 125 insertions(+), 65 deletions(-) diff --git a/server/openapi.json b/server/openapi.json index f62062332..26c58fa29 100644 --- a/server/openapi.json +++ b/server/openapi.json @@ -346,7 +346,7 @@ }, "type": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "url": { "type": "string" @@ -436,6 +436,13 @@ "geoCheckInterval": { "type": "number" }, + "dnsServer": { + "type": "string" + }, + "dnsRecordType": { + "type": "string", + "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] + }, "teamId": { "type": "string" }, @@ -2981,7 +2988,7 @@ { "schema": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "required": false, "name": "type", @@ -3416,7 +3423,7 @@ { "schema": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "required": false, "name": "type", @@ -4664,7 +4671,8 @@ "format": "date-time" } }, - "required": ["monitors", "name", "duration", "durationUnit", "start", "end", "repeat"] + "required": ["monitors", "name", "duration", "durationUnit", "start", "end", "repeat"], + "additionalProperties": false } } } @@ -5208,7 +5216,8 @@ "type": "string", "enum": ["seconds", "minutes", "hours", "days"] } - } + }, + "additionalProperties": false } } } @@ -5439,13 +5448,13 @@ "anyOf": [ { "type": "string", - "enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, { "type": "array", "items": { "type": "string", - "enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] } } ] @@ -5648,13 +5657,13 @@ "anyOf": [ { "type": "string", - "enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, { "type": "array", "items": { "type": "string", - "enum": ["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] } } ] @@ -6862,7 +6871,7 @@ }, "type": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "statusWindowSize": { "type": "number", @@ -6975,6 +6984,10 @@ ], "default": "" }, + "strategy": { + "type": "string", + "enum": ["desktop", "mobile"] + }, "selectedDisks": { "type": "array", "items": { @@ -7009,6 +7022,14 @@ "geoCheckInterval": { "type": "number", "minimum": 300000 + }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] } }, "required": ["name", "type", "url"] @@ -7496,7 +7517,7 @@ }, "type": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "ignoreTlsErrors": { "type": "boolean", @@ -7630,6 +7651,10 @@ ], "default": "" }, + "strategy": { + "type": "string", + "enum": ["desktop", "mobile"] + }, "group": { "anyOf": [ { @@ -7659,6 +7684,14 @@ "minimum": 300000, "default": 300000 }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] + }, "createdAt": { "type": "string" }, @@ -8034,7 +8067,7 @@ }, "type": { "type": "string", - "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "unknown"] + "enum": ["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket", "dns", "unknown"] }, "url": { "type": "string" @@ -8153,6 +8186,10 @@ } ] }, + "strategy": { + "type": "string", + "enum": ["desktop", "mobile"] + }, "selectedDisks": { "type": "array", "items": { @@ -8187,6 +8224,14 @@ "geoCheckInterval": { "type": "number", "minimum": 300000 + }, + "dnsServer": { + "type": "string", + "minLength": 1 + }, + "dnsRecordType": { + "type": "string", + "enum": ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] } } } diff --git a/server/package-lock.json b/server/package-lock.json index 52f37fde8..9010cfb9e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -21,7 +21,7 @@ "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", - "express-rate-limit": "8.3.2", + "express-rate-limit": "^8.5.1", "gamedig": "5.2.0", "got": "14.4.7", "handlebars": "^4.7.8", @@ -811,14 +811,15 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", - "integrity": "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.22.tgz", + "integrity": "sha512-PMYKKtJd70IsSG0yHrdAbxBr+ZWBKLvzFZfD3/urxgf6hXVMzuU5M+3MJ5G67RpOmLBu1fAUN65SbWuKUCOlAA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.1", - "fast-xml-parser": "5.5.8", + "@nodable/entities": "2.1.0", + "@smithy/types": "^4.14.1", + "fast-xml-parser": "5.7.2", "tslib": "^2.6.2" }, "engines": { @@ -865,7 +866,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -1485,7 +1485,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1530,7 +1529,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3193,6 +3191,19 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@nodable/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3757,9 +3768,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", - "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4244,7 +4255,6 @@ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -4405,7 +4415,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4643,7 +4652,6 @@ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", @@ -5257,7 +5265,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5836,7 +5843,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6730,7 +6736,6 @@ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz", "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==", "license": "MIT", - "peer": true, "dependencies": { "cssnano-preset-default": "^7.0.9", "lilconfig": "^3.1.3" @@ -7495,7 +7500,6 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7857,7 +7861,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7900,12 +7903,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.1.tgz", + "integrity": "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -7990,9 +7993,9 @@ "license": "MIT" }, "node_modules/fast-xml-builder": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", - "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", + "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", "dev": true, "funding": [ { @@ -8002,13 +8005,14 @@ ], "license": "MIT", "dependencies": { - "path-expression-matcher": "^1.1.3" + "path-expression-matcher": "^1.5.0", + "xml-naming": "^0.1.0" } }, "node_modules/fast-xml-parser": { - "version": "5.5.8", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", - "integrity": "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.2.tgz", + "integrity": "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w==", "dev": true, "funding": [ { @@ -8018,9 +8022,10 @@ ], "license": "MIT", "dependencies": { - "fast-xml-builder": "^1.1.4", - "path-expression-matcher": "^1.2.0", - "strnum": "^2.2.0" + "@nodable/entities": "^2.1.0", + "fast-xml-builder": "^1.1.5", + "path-expression-matcher": "^1.5.0", + "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" @@ -9097,9 +9102,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -9526,7 +9531,6 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -11543,9 +11547,9 @@ } }, "node_modules/mongoose": { - "version": "8.19.2", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.19.2.tgz", - "integrity": "sha512-ww2T4dBV+suCbOfG5YPwj9pLCfUVyj8FEA1D3Ux1HHqutpLxGyOYEPU06iPRBW4cKr3PJfOSYsIpHWPTkz5zig==", + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.1.tgz", + "integrity": "sha512-gHSPD8qEwRmiXapK17hEnFWZdcFENMegHTcw5XIIg2+7R8eXQvdwSiMpD/A2oG8tKzFLLHyRXd8/eaDPAVwZgQ==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -12154,9 +12158,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.1.tgz", - "integrity": "sha512-d7gQQmLvAKXKXE2GeP9apIGbMYKz88zWdsn/BN2HRWVQsDFdUY36WSLTY0Jvd4HWi7Fb30gQ62oAOzdgJA6fZw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", + "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", "dev": true, "funding": [ { @@ -12371,7 +12375,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14210,9 +14213,9 @@ } }, "node_modules/strnum": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", - "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", "dev": true, "funding": [ { @@ -14394,7 +14397,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14563,7 +14565,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -14727,7 +14728,6 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15390,6 +15390,22 @@ "node": ">=18" } }, + "node_modules/xml-naming": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", + "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/xmlbuilder": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", @@ -15562,7 +15578,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/server/package.json b/server/package.json index ea581be1a..958cb1eff 100755 --- a/server/package.json +++ b/server/package.json @@ -41,7 +41,7 @@ "dockerode": "4.0.6", "dotenv": "^16.4.5", "express": "^4.19.2", - "express-rate-limit": "8.3.2", + "express-rate-limit": "^8.5.1", "gamedig": "5.2.0", "got": "14.4.7", "handlebars": "^4.7.8",