From bf5d7f3c5322d49fe1c4780966aea013ebe6713a Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Sat, 26 Jul 2025 15:24:25 +0530 Subject: [PATCH 01/30] Adding Global Threshold Settings --- .../src/Pages/Infrastructure/Create/index.jsx | 74 +++++++---- .../Settings/SettingsGlobalThresholds.jsx | 117 ++++++++++++++++++ client/src/Pages/Settings/index.jsx | 11 ++ client/src/Validation/validation.js | 7 ++ client/src/locales/en.json | 4 + server/src/db/models/AppSettings.js | 8 +- server/src/validation/joi.js | 8 ++ 7 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 client/src/Pages/Settings/SettingsGlobalThresholds.jsx diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 0800bb500..fa6ac4fcb 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -3,7 +3,7 @@ import { useTheme } from "@emotion/react"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; - +import axios from "axios"; // Utility and Network import { infrastructureMonitorValidation } from "../../../Validation/validation"; import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; @@ -87,35 +87,59 @@ const CreateInfrastructureMonitor = () => { }); // Populate form fields if editing + // Populate global thresholds from /settings if creating + const { authToken } = useSelector((state) => state.auth); + useEffect(() => { - if (isCreate || !monitor) return; + const fetchGlobalThresholds = async () => { + if (!isCreate) return; - setInfrastructureMonitor({ - url: monitor.url.replace(/^https?:\/\//, ""), - name: monitor.name || "", - notifications: monitor.notifications, - interval: monitor.interval / MS_PER_MINUTE, - cpu: monitor.thresholds?.usage_cpu !== undefined, - usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "", + try { + const token = authToken; + if (!token) { + console.warn("No token found for fetching global thresholds."); + return; + } - memory: monitor.thresholds?.usage_memory !== undefined, - usage_memory: monitor.thresholds?.usage_memory - ? monitor.thresholds.usage_memory * 100 - : "", + const response = await axios.get(`${import.meta.env.VITE_APP_API_BASE_URL}/settings`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); - disk: monitor.thresholds?.usage_disk !== undefined, - usage_disk: monitor.thresholds?.usage_disk - ? monitor.thresholds.usage_disk * 100 - : "", + const globalThresholds = response.data?.data?.settings?.globalThresholds; - temperature: monitor.thresholds?.usage_temperature !== undefined, - usage_temperature: monitor.thresholds?.usage_temperature - ? monitor.thresholds.usage_temperature * 100 - : "", - secret: monitor.secret || "", - }); - setHttps(monitor.url.startsWith("https")); - }, [isCreate, monitor]); + if (!globalThresholds) { + console.warn("No global thresholds found in /settings response."); + return; + } + + setInfrastructureMonitor((prev) => ({ + ...prev, + cpu: globalThresholds.cpu != null, + usage_cpu: globalThresholds.cpu != null ? globalThresholds.cpu.toString() : "", + + memory: globalThresholds.memory != null, + usage_memory: + globalThresholds.memory != null ? globalThresholds.memory.toString() : "", + + disk: globalThresholds.disk != null, + usage_disk: + globalThresholds.disk != null ? globalThresholds.disk.toString() : "", + + temperature: globalThresholds.temperature != null, + usage_temperature: + globalThresholds.temperature != null + ? globalThresholds.temperature.toString() + : "", + })); + } catch (error) { + console.error("Failed to fetch global thresholds:", error); + } + }; + + fetchGlobalThresholds(); + }, [isCreate, user]); // Handlers const onSubmit = async (event) => { diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx new file mode 100644 index 000000000..eca752688 --- /dev/null +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -0,0 +1,117 @@ +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import ConfigBox from "../../Components/ConfigBox"; +import TextInput from "../../Components/Inputs/TextInput"; + +import { useTheme } from "@emotion/react"; +import { PropTypes } from "prop-types"; +import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; + +const SettingsGlobalThresholds = ({ + isAdmin, + HEADING_SX, + settingsData, + setSettingsData, +}) => { + const { t } = useTranslation(); // For language translation + const theme = useTheme(); // MUI theme access + + // Local state for thresholds + const [thresholds, setThresholds] = useState({ + cpu: "", + memory: "", + disk: "", + temperature: "", + }); + + // Load existing thresholds from settingsData on first render or when settingsData changes + useEffect(() => { + if (settingsData?.settings?.globalThresholds) { + setThresholds({ + cpu: settingsData.settings.globalThresholds.cpu || "", + memory: settingsData.settings.globalThresholds.memory || "", + disk: settingsData.settings.globalThresholds.disk || "", + temperature: settingsData.settings.globalThresholds.temperature || "", + }); + } + }, [settingsData]); + + // Handles input change and updates state & parent data + const handleChange = (e, min, max) => { + const { name, value } = e.target; + + // Allow empty or numeric value within range + if ( + value === "" || + (/^\d+$/.test(value) && Number(value) >= min && Number(value) <= max) + ) { + const updatedThresholds = { + ...thresholds, + [name]: value, + }; + + setThresholds(updatedThresholds); + + // Update settingsData without enabling/disabling metrics + setSettingsData({ + ...settingsData, + settings: { + ...settingsData.settings, + globalThresholds: updatedThresholds, + }, + }); + } + }; + + // Only render this section for admins + if (!isAdmin) return null; + + return ( + + {/* Header and description */} + + + {t("settingsPage.globalThresholds.title", "Global Thresholds")} + + + {t( + "settingsPage.globalThresholds.description", + "Configure global CPU, Memory, Disk, and Temperature thresholds." + )} + + + + {/* Threshold inputs */} + + {[ + ["CPU Threshold (%)", "cpu", 1, 100], + ["Memory Threshold (%)", "memory", 1, 100], + ["Disk Threshold (%)", "disk", 1, 100], + ["Temperature Threshold (°C)", "temperature", 1, 150], + ].map(([label, name, min, max]) => ( + handleChange(e, min, max)} + /> + ))} + + + ); +}; + +// Prop types +SettingsGlobalThresholds.propTypes = { + isAdmin: PropTypes.bool, + HEADING_SX: PropTypes.object, + settingsData: PropTypes.object, + setSettingsData: PropTypes.func, +}; + +export default SettingsGlobalThresholds; diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index dae6bd680..ab529eb68 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -8,6 +8,7 @@ import SettingsPagespeed from "./SettingsPagespeed"; import SettingsDemoMonitors from "./SettingsDemoMonitors"; import SettingsAbout from "./SettingsAbout"; import SettingsEmail from "./SettingsEmail"; +import SettingsGlobalThresholds from "./SettingsGlobalThresholds"; import Button from "@mui/material/Button"; // Utils import { settingsValidation } from "../../Validation/validation"; @@ -48,6 +49,7 @@ const Settings = () => { setIsApiKeySet, setIsEmailPasswordSet, }); + const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors(); const [isSaving, saveError, saveSettings] = useSaveSettings({ @@ -60,6 +62,7 @@ const Settings = () => { const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors(); const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats(); + // Setup const isAdmin = useIsAdmin(); const theme = useTheme(); @@ -149,6 +152,7 @@ const Settings = () => { error.details.forEach((err) => { newErrors[err.path[0]] = err.message; }); + setErrors(newErrors); } saveSettings(settingsData?.settings); @@ -190,6 +194,13 @@ const Settings = () => { handleChange={handleChange} errors={errors} /> + + { if (!dayjs(value).isValid()) { return helpers.error("any.invalid"); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 78d0f2aed..5c69ecd81 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -837,6 +837,10 @@ "title": "Display timezone" }, "title": "Settings", + "globalThresholds": { + "title": "Global Thresholds", + "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring." + }, "uiSettings": { "description": "Switch between light and dark mode, or change user interface language.", "labelLanguage": "Language", diff --git a/server/src/db/models/AppSettings.js b/server/src/db/models/AppSettings.js index cd63118e9..be7c856a4 100755 --- a/server/src/db/models/AppSettings.js +++ b/server/src/db/models/AppSettings.js @@ -65,7 +65,13 @@ const AppSettingsSchema = mongoose.Schema( type: Number, default: 1, }, - }, + globalThresholds: { + cpu: { type: Number }, + memory: { type: Number}, + disk: { type: Number}, + temperature: { type: Number}, + } +}, { timestamps: true, } diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 744b0ec74..51d8a94a2 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -416,8 +416,16 @@ const updateAppSettingsBodyValidation = joi.object({ systemEmailIgnoreTLS: joi.boolean(), systemEmailRequireTLS: joi.boolean(), systemEmailRejectUnauthorized: joi.boolean(), + + globalThresholds: joi.object({ + cpu: joi.number().min(1).max(100).allow("").optional(), + memory: joi.number().min(1).max(100).allow("").optional(), + disk: joi.number().min(1).max(100).allow("").optional(), + temperature: joi.number().min(1).max(150).allow("").optional(), + }).optional(), }); + //**************************************** // Status Page Validation //**************************************** From 8d105e636db84b64678dfcfd0662098542c1a0ee Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Tue, 29 Jul 2025 00:42:56 +0530 Subject: [PATCH 02/30] Updated Global THreshold Code Quality --- client/src/Hooks/monitorHooks.js | 26 +++++++ .../src/Pages/Infrastructure/Create/index.jsx | 72 +++++++------------ 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 4f0e9c049..6b8cb983b 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -300,6 +300,31 @@ const useCreateMonitor = () => { return [createMonitor, isLoading]; }; +const useFetchGlobalSettings = () => { + const [isLoading, setIsLoading] = useState(true); + const [globalSettings, setGlobalSettings] = useState(null); + + useEffect(() => { + const fetchGlobalSettings = async () => { + try { + const res = await networkService.getAppSettings(); + setGlobalSettings(res?.data); + setIsLoading(false); + } catch (error) { + console.error("Failed to fetch global settings:", error); + } finally { + setIsLoading(false); + } + }; + + fetchGlobalSettings(); + }, []); + + return [globalSettings, isLoading]; +}; + + + const useDeleteMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); @@ -499,6 +524,7 @@ export { useFetchUptimeMonitorById, useFetchHardwareMonitorById, useCreateMonitor, + useFetchGlobalSettings, useDeleteMonitor, useUpdateMonitor, usePauseMonitor, diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index fa6ac4fcb..151383cfb 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -11,7 +11,11 @@ import { capitalizeFirstLetter } from "../../../Utils/stringUtils"; import { useTranslation } from "react-i18next"; import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; import NotificationsConfig from "../../../Components/NotificationConfig"; -import { useUpdateMonitor, useCreateMonitor } from "../../../Hooks/monitorHooks"; +import { + useUpdateMonitor, + useCreateMonitor, + useFetchGlobalSettings, +} from "../../../Hooks/monitorHooks"; // MUI import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material"; @@ -65,6 +69,7 @@ const CreateInfrastructureMonitor = () => { useGetNotificationsByTeamId(); const [updateMonitor, isUpdating] = useUpdateMonitor(); const [createMonitor, isCreating] = useCreateMonitor(); + const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings(); // State const [errors, setErrors] = useState({}); @@ -87,59 +92,30 @@ const CreateInfrastructureMonitor = () => { }); // Populate form fields if editing - // Populate global thresholds from /settings if creating - const { authToken } = useSelector((state) => state.auth); useEffect(() => { - const fetchGlobalThresholds = async () => { - if (!isCreate) return; + if (!isCreate || globalSettingsLoading) return; + const globalThresholds = globalSettings?.data?.settings?.globalThresholds; - try { - const token = authToken; - if (!token) { - console.warn("No token found for fetching global thresholds."); - return; - } + setInfrastructureMonitor((prev) => ({ + ...prev, + cpu: globalThresholds.cpu != null, + usage_cpu: globalThresholds.cpu != null ? globalThresholds.cpu.toString() : "", - const response = await axios.get(`${import.meta.env.VITE_APP_API_BASE_URL}/settings`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + memory: globalThresholds.memory != null, + usage_memory: + globalThresholds.memory != null ? globalThresholds.memory.toString() : "", - const globalThresholds = response.data?.data?.settings?.globalThresholds; + disk: globalThresholds.disk != null, + usage_disk: globalThresholds.disk != null ? globalThresholds.disk.toString() : "", - if (!globalThresholds) { - console.warn("No global thresholds found in /settings response."); - return; - } - - setInfrastructureMonitor((prev) => ({ - ...prev, - cpu: globalThresholds.cpu != null, - usage_cpu: globalThresholds.cpu != null ? globalThresholds.cpu.toString() : "", - - memory: globalThresholds.memory != null, - usage_memory: - globalThresholds.memory != null ? globalThresholds.memory.toString() : "", - - disk: globalThresholds.disk != null, - usage_disk: - globalThresholds.disk != null ? globalThresholds.disk.toString() : "", - - temperature: globalThresholds.temperature != null, - usage_temperature: - globalThresholds.temperature != null - ? globalThresholds.temperature.toString() - : "", - })); - } catch (error) { - console.error("Failed to fetch global thresholds:", error); - } - }; - - fetchGlobalThresholds(); - }, [isCreate, user]); + temperature: globalThresholds.temperature != null, + usage_temperature: + globalThresholds.temperature != null + ? globalThresholds.temperature.toString() + : "", + })); + }, [isCreate, globalSettings]); // Handlers const onSubmit = async (event) => { From ad06544a649b436568088bc649941b2d4737bff6 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Tue, 29 Jul 2025 00:54:25 +0530 Subject: [PATCH 03/30] Removed Consecutive same statements --- client/src/Hooks/monitorHooks.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 6b8cb983b..767ef5af8 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -309,7 +309,6 @@ const useFetchGlobalSettings = () => { try { const res = await networkService.getAppSettings(); setGlobalSettings(res?.data); - setIsLoading(false); } catch (error) { console.error("Failed to fetch global settings:", error); } finally { From dbea93f9babf46562921c0b0471aaf203c86b05b Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Tue, 29 Jul 2025 21:35:30 +0530 Subject: [PATCH 04/30] Improved Code Quality --- client/src/Hooks/monitorHooks.js | 36 +++++++++---------- .../src/Pages/Infrastructure/Create/index.jsx | 27 ++++++++++---- .../Settings/SettingsGlobalThresholds.jsx | 20 ++++++----- server/src/db/models/AppSettings.js | 12 +++---- server/src/validation/joi.js | 15 ++++---- 5 files changed, 63 insertions(+), 47 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 767ef5af8..1d57ffd5f 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -301,29 +301,27 @@ const useCreateMonitor = () => { }; const useFetchGlobalSettings = () => { - const [isLoading, setIsLoading] = useState(true); - const [globalSettings, setGlobalSettings] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [globalSettings, setGlobalSettings] = useState(null); + useEffect(() => { + const fetchGlobalSettings = async () => { + try { + const res = await networkService.getAppSettings(); + setGlobalSettings(res?.data); + } catch (error) { + console.error("Failed to fetch global settings:", error); + createToast({ body: "Failed to load global settings" }); + } finally { + setIsLoading(false); + } + }; - useEffect(() => { - const fetchGlobalSettings = async () => { - try { - const res = await networkService.getAppSettings(); - setGlobalSettings(res?.data); - } catch (error) { - console.error("Failed to fetch global settings:", error); - } finally { - setIsLoading(false); - } - }; + fetchGlobalSettings(); + }, []); - fetchGlobalSettings(); - }, []); - - return [globalSettings, isLoading]; + return [globalSettings, isLoading]; }; - - const useDeleteMonitor = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 151383cfb..3159a3529 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -3,7 +3,6 @@ import { useTheme } from "@emotion/react"; import { useParams } from "react-router-dom"; import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import axios from "axios"; // Utility and Network import { infrastructureMonitorValidation } from "../../../Validation/validation"; import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks"; @@ -95,25 +94,41 @@ const CreateInfrastructureMonitor = () => { useEffect(() => { if (!isCreate || globalSettingsLoading) return; - const globalThresholds = globalSettings?.data?.settings?.globalThresholds; + const globalThresholds = globalSettings?.data?.settings?.globalThresholds || {}; + + // Define default empty string for all thresholds + const defaultThresholds = { + cpu: "", + memory: "", + disk: "", + temperature: "", + }; setInfrastructureMonitor((prev) => ({ ...prev, cpu: globalThresholds.cpu != null, - usage_cpu: globalThresholds.cpu != null ? globalThresholds.cpu.toString() : "", + usage_cpu: + globalThresholds.cpu != null + ? globalThresholds.cpu.toString() + : defaultThresholds.cpu, memory: globalThresholds.memory != null, usage_memory: - globalThresholds.memory != null ? globalThresholds.memory.toString() : "", + globalThresholds.memory != null + ? globalThresholds.memory.toString() + : defaultThresholds.memory, disk: globalThresholds.disk != null, - usage_disk: globalThresholds.disk != null ? globalThresholds.disk.toString() : "", + usage_disk: + globalThresholds.disk != null + ? globalThresholds.disk.toString() + : defaultThresholds.disk, temperature: globalThresholds.temperature != null, usage_temperature: globalThresholds.temperature != null ? globalThresholds.temperature.toString() - : "", + : defaultThresholds.temperature, })); }, [isCreate, globalSettings]); diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx index eca752688..cd1c50927 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -15,8 +15,8 @@ const SettingsGlobalThresholds = ({ settingsData, setSettingsData, }) => { - const { t } = useTranslation(); // For language translation - const theme = useTheme(); // MUI theme access + const { t } = useTranslation(); // For language translation + const theme = useTheme(); // MUI theme access // Local state for thresholds const [thresholds, setThresholds] = useState({ @@ -42,19 +42,18 @@ const SettingsGlobalThresholds = ({ const handleChange = (e, min, max) => { const { name, value } = e.target; - // Allow empty or numeric value within range - if ( + const numValue = parseFloat(value); + const isValidNumber = value === "" || - (/^\d+$/.test(value) && Number(value) >= min && Number(value) <= max) - ) { + (!isNaN(numValue) && isFinite(numValue) && numValue >= min && numValue <= max); + + if (isValidNumber) { const updatedThresholds = { ...thresholds, [name]: value, }; setThresholds(updatedThresholds); - - // Update settingsData without enabling/disabling metrics setSettingsData({ ...settingsData, settings: { @@ -72,7 +71,10 @@ const SettingsGlobalThresholds = ({ {/* Header and description */} - + {t("settingsPage.globalThresholds.title", "Global Thresholds")} diff --git a/server/src/db/models/AppSettings.js b/server/src/db/models/AppSettings.js index be7c856a4..2313d638c 100755 --- a/server/src/db/models/AppSettings.js +++ b/server/src/db/models/AppSettings.js @@ -66,12 +66,12 @@ const AppSettingsSchema = mongoose.Schema( default: 1, }, globalThresholds: { - cpu: { type: Number }, - memory: { type: Number}, - disk: { type: Number}, - temperature: { type: Number}, - } -}, + cpu: { type: Number }, + memory: { type: Number }, + disk: { type: Number }, + temperature: { type: Number }, + }, + }, { timestamps: true, } diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 51d8a94a2..f2c15205b 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -417,15 +417,16 @@ const updateAppSettingsBodyValidation = joi.object({ systemEmailRequireTLS: joi.boolean(), systemEmailRejectUnauthorized: joi.boolean(), - globalThresholds: joi.object({ - cpu: joi.number().min(1).max(100).allow("").optional(), - memory: joi.number().min(1).max(100).allow("").optional(), - disk: joi.number().min(1).max(100).allow("").optional(), - temperature: joi.number().min(1).max(150).allow("").optional(), - }).optional(), + globalThresholds: joi + .object({ + cpu: joi.number().min(1).max(100).allow("").optional(), + memory: joi.number().min(1).max(100).allow("").optional(), + disk: joi.number().min(1).max(100).allow("").optional(), + temperature: joi.number().min(1).max(150).allow("").optional(), + }) + .optional(), }); - //**************************************** // Status Page Validation //**************************************** From af010e138d93fd023faf57ca18583f1050892d93 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 00:01:27 +0530 Subject: [PATCH 05/30] Some changes in ui --- .../Settings/SettingsGlobalThresholds.jsx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx index cd1c50927..96f14e62d 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -26,17 +26,16 @@ const SettingsGlobalThresholds = ({ temperature: "", }); - // Load existing thresholds from settingsData on first render or when settingsData changes - useEffect(() => { - if (settingsData?.settings?.globalThresholds) { - setThresholds({ - cpu: settingsData.settings.globalThresholds.cpu || "", - memory: settingsData.settings.globalThresholds.memory || "", - disk: settingsData.settings.globalThresholds.disk || "", - temperature: settingsData.settings.globalThresholds.temperature || "", - }); - } - }, [settingsData]); + // Load existing thresholds from settingsData + + if (settingsData?.settings?.globalThresholds) { + setThresholds({ + cpu: settingsData.settings.globalThresholds.cpu || "", + memory: settingsData.settings.globalThresholds.memory || "", + disk: settingsData.settings.globalThresholds.disk || "", + temperature: settingsData.settings.globalThresholds.temperature || "", + }); + } // Handles input change and updates state & parent data const handleChange = (e, min, max) => { From da7d9c02c870856bf721a99d95df91493b189278 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 00:25:53 +0530 Subject: [PATCH 06/30] Used Parent State And Set Minor Changes --- client/src/Hooks/monitorHooks.js | 2 +- .../Settings/SettingsGlobalThresholds.jsx | 43 +++++-------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 1d57ffd5f..616a37877 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -302,7 +302,7 @@ const useCreateMonitor = () => { const useFetchGlobalSettings = () => { const [isLoading, setIsLoading] = useState(true); - const [globalSettings, setGlobalSettings] = useState(null); + const [globalSettings, setGlobalSettings] = useState(undefined); useEffect(() => { const fetchGlobalSettings = async () => { try { diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx index 96f14e62d..b4d2581d2 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -6,7 +6,6 @@ import TextInput from "../../Components/Inputs/TextInput"; import { useTheme } from "@emotion/react"; import { PropTypes } from "prop-types"; -import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; const SettingsGlobalThresholds = ({ @@ -18,26 +17,7 @@ const SettingsGlobalThresholds = ({ const { t } = useTranslation(); // For language translation const theme = useTheme(); // MUI theme access - // Local state for thresholds - const [thresholds, setThresholds] = useState({ - cpu: "", - memory: "", - disk: "", - temperature: "", - }); - - // Load existing thresholds from settingsData - - if (settingsData?.settings?.globalThresholds) { - setThresholds({ - cpu: settingsData.settings.globalThresholds.cpu || "", - memory: settingsData.settings.globalThresholds.memory || "", - disk: settingsData.settings.globalThresholds.disk || "", - temperature: settingsData.settings.globalThresholds.temperature || "", - }); - } - - // Handles input change and updates state & parent data + // Handles input change and updates parent state const handleChange = (e, min, max) => { const { name, value } = e.target; @@ -47,19 +27,16 @@ const SettingsGlobalThresholds = ({ (!isNaN(numValue) && isFinite(numValue) && numValue >= min && numValue <= max); if (isValidNumber) { - const updatedThresholds = { - ...thresholds, - [name]: value, - }; - - setThresholds(updatedThresholds); - setSettingsData({ - ...settingsData, + setSettingsData((prev) => ({ + ...prev, settings: { - ...settingsData.settings, - globalThresholds: updatedThresholds, + ...prev.settings, + globalThresholds: { + ...prev.settings?.globalThresholds, + [name]: value, + }, }, - }); + })); } }; @@ -98,7 +75,7 @@ const SettingsGlobalThresholds = ({ label={label} placeholder={`${min} - ${max}`} type="number" - value={thresholds[name]} + value={settingsData?.settings?.globalThresholds?.[name] || ""} onChange={(e) => handleChange(e, min, max)} /> ))} From ca02ed9d18b82709648b402db426038de573c348 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 29 Jul 2025 19:53:11 -0700 Subject: [PATCH 07/30] refactor invite module into class --- server/src/config/services.js | 5 +- server/src/db/mongo/MongoDB.js | 16 +-- server/src/db/mongo/modules/inviteModule.js | 133 +++++++------------ server/src/service/business/inviteService.js | 6 +- server/src/service/business/userService.js | 2 +- 5 files changed, 60 insertions(+), 102 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index c2ecae200..ae3a4d27b 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -38,7 +38,9 @@ import HardwareCheck from "../db/models/HardwareCheck.js"; import PageSpeedCheck from "../db/models/PageSpeedCheck.js"; import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; +import InviteToken from "../db/models/InviteToken.js"; +import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { @@ -52,7 +54,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); - const db = new MongoDB({ logger, envSettings, checkModule }); + const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); + const db = new MongoDB({ logger, envSettings, checkModule, inviteModule }); await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index ef7179d35..1126b5ca6 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -7,12 +7,6 @@ import AppSettings from "../models/AppSettings.js"; import * as userModule from "./modules/userModule.js"; -//**************************************** -// Invite Token Operations -//**************************************** - -import * as inviteModule from "./modules/inviteModule.js"; - //**************************************** // Recovery Operations //**************************************** @@ -35,12 +29,6 @@ import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; //**************************************** import * as hardwareCheckModule from "./modules/hardwareCheckModule.js"; -//**************************************** -// Checks -//**************************************** - -import * as checkModule from "./modules/checkModule.js"; - //**************************************** // Maintenance Window //**************************************** @@ -69,11 +57,11 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule }) { + constructor({ logger, envSettings, checkModule, inviteModule }) { this.logger = logger; this.envSettings = envSettings; Object.assign(this, userModule); - Object.assign(this, inviteModule); + this.inviteModule = inviteModule; Object.assign(this, recoveryModule); Object.assign(this, monitorModule); Object.assign(this, pageSpeedCheckModule); diff --git a/server/src/db/mongo/modules/inviteModule.js b/server/src/db/mongo/modules/inviteModule.js index 89d8ad8d7..444e51efd 100755 --- a/server/src/db/mongo/modules/inviteModule.js +++ b/server/src/db/mongo/modules/inviteModule.js @@ -1,89 +1,56 @@ -import InviteToken from "../../models/InviteToken.js"; -import crypto from "crypto"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "inviteModule"; -/** - * Request an invite token for a user. - * - * This function deletes any existing invite tokens for the user's email, - * generates a new token, saves it, and then returns the new token. - * - * @param {Object} userData - The user data. - * @param {string} userData.email - The user's email. - * @param {mongoose.Schema.Types.ObjectId} userData.teamId - The ID of the team. - * @param {Array} userData.role - The user's role(s). - * @param {Date} [userData.expiry=Date.now] - The expiry date of the token. Defaults to the current date and time. - * @returns {Promise} The invite token. - * @throws {Error} If there is an error. - */ -const requestInviteToken = async (userData) => { - try { - await InviteToken.deleteMany({ email: userData.email }); - userData.token = crypto.randomBytes(32).toString("hex"); - let inviteToken = new InviteToken(userData); - await inviteToken.save(); - return inviteToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestInviteToken"; - throw error; - } -}; -/** - * Retrieves an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteToken = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOne({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); +class InviteModule { + constructor({ InviteToken, crypto, stringService }) { + this.InviteToken = InviteToken; + this.crypto = crypto; + this.stringService = stringService; + } + + requestInviteToken = async (userData) => { + try { + await this.InviteToken.deleteMany({ email: userData.email }); + userData.token = this.crypto.randomBytes(32).toString("hex"); + let inviteToken = new this.InviteToken(userData); + await inviteToken.save(); + return inviteToken; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestInviteToken"; + throw error; } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteToken"; - throw error; - } -}; + }; -/** - * Retrieves and deletes an invite token - * - * This function searches for an invite token in the database and deletes it. - * If the invite token is not found, it throws an error. - * - * @param {string} token - The invite token to search for. - * @returns {Promise} The invite token data. - * @throws {Error} If the invite token is not found or there is another error. - */ -const getInviteTokenAndDelete = async (token) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const invite = await InviteToken.findOneAndDelete({ - token, - }); - if (invite === null) { - throw new Error(stringService.authInviteNotFound); + getInviteToken = async (token) => { + try { + const invite = await this.InviteToken.findOne({ + token, + }); + if (invite === null) { + throw new Error(this.stringService.authInviteNotFound); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteToken"; + throw error; } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteTokenAndDelete"; - throw error; - } -}; + }; + getInviteTokenAndDelete = async (token) => { + try { + const invite = await this.InviteToken.findOneAndDelete({ + token, + }); + if (invite === null) { + throw new Error(this.stringService.authInviteNotFound); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteTokenAndDelete"; + throw error; + } + }; +} -export { requestInviteToken, getInviteToken, getInviteTokenAndDelete }; +export default InviteModule; diff --git a/server/src/service/business/inviteService.js b/server/src/service/business/inviteService.js index 161045971..369ed195a 100644 --- a/server/src/service/business/inviteService.js +++ b/server/src/service/business/inviteService.js @@ -17,12 +17,12 @@ class InviteService { getInviteToken = async ({ invite, teamId }) => { invite.teamId = teamId; - const inviteToken = await this.db.requestInviteToken(invite); + const inviteToken = await this.db.inviteModule.requestInviteToken(invite); return inviteToken; }; sendInviteEmail = async ({ inviteRequest, firstName }) => { - const inviteToken = await this.db.requestInviteToken({ ...inviteRequest }); + const inviteToken = await this.db.inviteModule.requestInviteToken({ ...inviteRequest }); const { clientHost } = this.settingsService.getSettings(); const html = await this.emailService.buildEmail("employeeActivationTemplate", { @@ -36,7 +36,7 @@ class InviteService { }; verifyInviteToken = async ({ inviteToken }) => { - const invite = await this.db.getInviteToken(inviteToken); + const invite = await this.db.inviteModule.getInviteToken(inviteToken); return invite; }; } diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index d0d1a1205..5eb776376 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -31,7 +31,7 @@ class UserService { // If superAdmin exists, a token should be attached to all further register requests const superAdminExists = await this.db.checkSuperadmin(); if (superAdminExists) { - const invitedUser = await this.db.getInviteTokenAndDelete(user.inviteToken); + const invitedUser = await this.db.inviteModule.getInviteTokenAndDelete(user.inviteToken); user.role = invitedUser.role; user.teamId = invitedUser.teamId; } else { From e04ac77ddd11bf53fb456ddd525ec2fc183fc0be Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 16:39:41 +0530 Subject: [PATCH 08/30] Ran npm start format --- .../Settings/SettingsGlobalThresholds.jsx | 190 +++++++++--------- client/src/Pages/Settings/index.jsx | 5 +- client/src/Validation/validation.js | 15 +- client/src/locales/en.json | 4 +- 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx index b4d2581d2..acc101ab4 100644 --- a/client/src/Pages/Settings/SettingsGlobalThresholds.jsx +++ b/client/src/Pages/Settings/SettingsGlobalThresholds.jsx @@ -1,95 +1,95 @@ -import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; -import Typography from "@mui/material/Typography"; -import ConfigBox from "../../Components/ConfigBox"; -import TextInput from "../../Components/Inputs/TextInput"; - -import { useTheme } from "@emotion/react"; -import { PropTypes } from "prop-types"; -import { useTranslation } from "react-i18next"; - -const SettingsGlobalThresholds = ({ - isAdmin, - HEADING_SX, - settingsData, - setSettingsData, -}) => { - const { t } = useTranslation(); // For language translation - const theme = useTheme(); // MUI theme access - - // Handles input change and updates parent state - const handleChange = (e, min, max) => { - const { name, value } = e.target; - - const numValue = parseFloat(value); - const isValidNumber = - value === "" || - (!isNaN(numValue) && isFinite(numValue) && numValue >= min && numValue <= max); - - if (isValidNumber) { - setSettingsData((prev) => ({ - ...prev, - settings: { - ...prev.settings, - globalThresholds: { - ...prev.settings?.globalThresholds, - [name]: value, - }, - }, - })); - } - }; - - // Only render this section for admins - if (!isAdmin) return null; - - return ( - - {/* Header and description */} - - - {t("settingsPage.globalThresholds.title", "Global Thresholds")} - - - {t( - "settingsPage.globalThresholds.description", - "Configure global CPU, Memory, Disk, and Temperature thresholds." - )} - - - - {/* Threshold inputs */} - - {[ - ["CPU Threshold (%)", "cpu", 1, 100], - ["Memory Threshold (%)", "memory", 1, 100], - ["Disk Threshold (%)", "disk", 1, 100], - ["Temperature Threshold (°C)", "temperature", 1, 150], - ].map(([label, name, min, max]) => ( - handleChange(e, min, max)} - /> - ))} - - - ); -}; - -// Prop types -SettingsGlobalThresholds.propTypes = { - isAdmin: PropTypes.bool, - HEADING_SX: PropTypes.object, - settingsData: PropTypes.object, - setSettingsData: PropTypes.func, -}; - -export default SettingsGlobalThresholds; +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import ConfigBox from "../../Components/ConfigBox"; +import TextInput from "../../Components/Inputs/TextInput"; + +import { useTheme } from "@emotion/react"; +import { PropTypes } from "prop-types"; +import { useTranslation } from "react-i18next"; + +const SettingsGlobalThresholds = ({ + isAdmin, + HEADING_SX, + settingsData, + setSettingsData, +}) => { + const { t } = useTranslation(); // For language translation + const theme = useTheme(); // MUI theme access + + // Handles input change and updates parent state + const handleChange = (e, min, max) => { + const { name, value } = e.target; + + const numValue = parseFloat(value); + const isValidNumber = + value === "" || + (!isNaN(numValue) && isFinite(numValue) && numValue >= min && numValue <= max); + + if (isValidNumber) { + setSettingsData((prev) => ({ + ...prev, + settings: { + ...prev.settings, + globalThresholds: { + ...prev.settings?.globalThresholds, + [name]: value, + }, + }, + })); + } + }; + + // Only render this section for admins + if (!isAdmin) return null; + + return ( + + {/* Header and description */} + + + {t("settingsPage.globalThresholds.title", "Global Thresholds")} + + + {t( + "settingsPage.globalThresholds.description", + "Configure global CPU, Memory, Disk, and Temperature thresholds." + )} + + + + {/* Threshold inputs */} + + {[ + ["CPU Threshold (%)", "cpu", 1, 100], + ["Memory Threshold (%)", "memory", 1, 100], + ["Disk Threshold (%)", "disk", 1, 100], + ["Temperature Threshold (°C)", "temperature", 1, 150], + ].map(([label, name, min, max]) => ( + handleChange(e, min, max)} + /> + ))} + + + ); +}; + +// Prop types +SettingsGlobalThresholds.propTypes = { + isAdmin: PropTypes.bool, + HEADING_SX: PropTypes.object, + settingsData: PropTypes.object, + setSettingsData: PropTypes.func, +}; + +export default SettingsGlobalThresholds; diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx index ab529eb68..92f58b4c0 100644 --- a/client/src/Pages/Settings/index.jsx +++ b/client/src/Pages/Settings/index.jsx @@ -49,7 +49,7 @@ const Settings = () => { setIsApiKeySet, setIsEmailPasswordSet, }); - + const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors(); const [isSaving, saveError, saveSettings] = useSaveSettings({ @@ -62,7 +62,6 @@ const Settings = () => { const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors(); const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats(); - // Setup const isAdmin = useIsAdmin(); const theme = useTheme(); @@ -152,7 +151,7 @@ const Settings = () => { error.details.forEach((err) => { newErrors[err.path[0]] = err.message; }); - + setErrors(newErrors); } saveSettings(settingsData?.settings); diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js index 22ea042d7..9c6699d4b 100644 --- a/client/src/Validation/validation.js +++ b/client/src/Validation/validation.js @@ -302,15 +302,16 @@ const settingsValidation = joi.object({ systemEmailIgnoreTLS: joi.boolean(), systemEmailRequireTLS: joi.boolean(), systemEmailRejectUnauthorized: joi.boolean(), - globalThresholds: joi.object({ - cpu: joi.number().min(1).max(100).allow("").optional(), - memory: joi.number().min(1).max(100).allow("").optional(), - disk: joi.number().min(1).max(100).allow("").optional(), - temperature: joi.number().min(1).max(150).allow("").optional(), - }).optional(), + globalThresholds: joi + .object({ + cpu: joi.number().min(1).max(100).allow("").optional(), + memory: joi.number().min(1).max(100).allow("").optional(), + disk: joi.number().min(1).max(100).allow("").optional(), + temperature: joi.number().min(1).max(150).allow("").optional(), + }) + .optional(), }); - const dayjsValidator = (value, helpers) => { if (!dayjs(value).isValid()) { return helpers.error("any.invalid"); diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 5c69ecd81..86362d3c7 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -838,8 +838,8 @@ }, "title": "Settings", "globalThresholds": { - "title": "Global Thresholds", - "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring." + "title": "Global Thresholds", + "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring." }, "uiSettings": { "description": "Switch between light and dark mode, or change user interface language.", From d4ecb26ffe8b2de2f77f7e099a8379b4cf512266 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 18:30:04 +0530 Subject: [PATCH 09/30] Updated UseEffect --- client/src/Pages/Infrastructure/Create/index.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 3159a3529..9b8f4f767 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -93,8 +93,8 @@ const CreateInfrastructureMonitor = () => { // Populate form fields if editing useEffect(() => { - if (!isCreate || globalSettingsLoading) return; - const globalThresholds = globalSettings?.data?.settings?.globalThresholds || {}; + if (monitor == undefined && (!isCreate || globalSettingsLoading)) return; + const globalThresholds = globalSettings?.data?.settings?.globalThresholds; // Define default empty string for all thresholds const defaultThresholds = { @@ -106,6 +106,10 @@ const CreateInfrastructureMonitor = () => { setInfrastructureMonitor((prev) => ({ ...prev, + url: monitor != undefined ? monitor.url.replace(/^https?:\/\//, "") : "", + name: monitor != undefined ? monitor.name || "" : "", + notifications: monitor != undefined ? monitor.notifications : [], + interval: monitor != undefined ? monitor.interval / MS_PER_MINUTE : 0.25, cpu: globalThresholds.cpu != null, usage_cpu: globalThresholds.cpu != null @@ -130,7 +134,7 @@ const CreateInfrastructureMonitor = () => { ? globalThresholds.temperature.toString() : defaultThresholds.temperature, })); - }, [isCreate, globalSettings]); + }, [isCreate, globalSettings, monitor, globalSettingsLoading]); // Handlers const onSubmit = async (event) => { From 0ecbf48f5d78f1c0704149bbd1bc94cc425b6dac Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 19:18:24 +0530 Subject: [PATCH 10/30] client/src/Pages/Infrastructure/Create/index.jsx --- .../src/Pages/Infrastructure/Create/index.jsx | 73 ++++++++++--------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 9b8f4f767..be9cb6062 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -93,10 +93,7 @@ const CreateInfrastructureMonitor = () => { // Populate form fields if editing useEffect(() => { - if (monitor == undefined && (!isCreate || globalSettingsLoading)) return; const globalThresholds = globalSettings?.data?.settings?.globalThresholds; - - // Define default empty string for all thresholds const defaultThresholds = { cpu: "", memory: "", @@ -104,37 +101,45 @@ const CreateInfrastructureMonitor = () => { temperature: "", }; - setInfrastructureMonitor((prev) => ({ - ...prev, - url: monitor != undefined ? monitor.url.replace(/^https?:\/\//, "") : "", - name: monitor != undefined ? monitor.name || "" : "", - notifications: monitor != undefined ? monitor.notifications : [], - interval: monitor != undefined ? monitor.interval / MS_PER_MINUTE : 0.25, - cpu: globalThresholds.cpu != null, - usage_cpu: - globalThresholds.cpu != null - ? globalThresholds.cpu.toString() - : defaultThresholds.cpu, - - memory: globalThresholds.memory != null, - usage_memory: - globalThresholds.memory != null - ? globalThresholds.memory.toString() - : defaultThresholds.memory, - - disk: globalThresholds.disk != null, - usage_disk: - globalThresholds.disk != null - ? globalThresholds.disk.toString() - : defaultThresholds.disk, - - temperature: globalThresholds.temperature != null, - usage_temperature: - globalThresholds.temperature != null - ? globalThresholds.temperature.toString() - : defaultThresholds.temperature, - })); - }, [isCreate, globalSettings, monitor, globalSettingsLoading]); + if (isCreate) { + // If global settings are not loaded yet, use default thresholds + if (globalSettingsLoading) return; + // Create mode: use global thresholds + setInfrastructureMonitor((prev) => ({ + ...prev, + url: "", + name: "", + notifications: [], + interval: 0.25, + cpu: globalThresholds?.cpu != null, + usage_cpu: globalThresholds?.cpu?.toString() || defaultThresholds.cpu, + memory: globalThresholds?.memory != null, + usage_memory: globalThresholds?.memory?.toString() || defaultThresholds.memory, + disk: globalThresholds?.disk != null, + usage_disk: globalThresholds?.disk?.toString() || defaultThresholds.disk, + temperature: globalThresholds?.temperature != null, + usage_temperature: + globalThresholds?.temperature?.toString() || defaultThresholds.temperature, + })); + } else if (monitor) { + // Edit mode: use monitor only, ignore global thresholds + setInfrastructureMonitor((prev) => ({ + ...prev, + url: monitor.url?.replace(/^https?:\/\//, "") || "", + name: monitor.name || "", + notifications: monitor.notifications || [], + interval: monitor.interval / MS_PER_MINUTE, + cpu: !!monitor.thresholds?.usage_cpu ?? false, + usage_cpu: monitor.thresholds?.usage_cpu * 100?.toString() || "", + memory: !!monitor.thresholds?.usage_memory ?? false, + usage_memory: monitor.thresholds?.usage_memory * 100?.toString() || "", + disk: !!monitor.thresholds?.usage_disk ?? false, + usage_disk: monitor.thresholds?.usage_disk * 100?.toString() || "", + temperature: !!monitor.thresholds?.usage_temperature ?? false, + usage_temperature: monitor.thresholds?.usage_temperature * 100?.toString() || "", + })); + } + }, [isCreate, monitor, globalSettings, globalSettingsLoading]); // Handlers const onSubmit = async (event) => { From 5e46a9f2d51e4b93bf0e56b1dee5cceb72df3752 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 19:38:23 +0530 Subject: [PATCH 11/30] Pushed global thresholds inside create if branch --- client/src/Pages/Infrastructure/Create/index.jsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index be9cb6062..73a11c40f 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -93,18 +93,18 @@ const CreateInfrastructureMonitor = () => { // Populate form fields if editing useEffect(() => { - const globalThresholds = globalSettings?.data?.settings?.globalThresholds; - const defaultThresholds = { - cpu: "", - memory: "", - disk: "", - temperature: "", - }; - if (isCreate) { // If global settings are not loaded yet, use default thresholds if (globalSettingsLoading) return; // Create mode: use global thresholds + const globalThresholds = globalSettings?.data?.settings?.globalThresholds; + const defaultThresholds = { + cpu: "", + memory: "", + disk: "", + temperature: "", + }; + setInfrastructureMonitor((prev) => ({ ...prev, url: "", From 78a5b2e6667fc1f15986f4f9f318f856112f4397 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 09:18:16 -0700 Subject: [PATCH 12/30] fix crypto dependency --- server/src/service/business/userService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index 5eb776376..4e6a41ab0 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -36,7 +36,7 @@ class UserService { user.teamId = invitedUser.teamId; } else { // This is the first account, create JWT secret to use if one is not supplied by env - const jwtSecret = crypto.randomBytes(64).toString("hex"); + const jwtSecret = this.crypto.randomBytes(64).toString("hex"); await this.db.updateAppSettings({ jwtSecret }); } From 753a904c947cf99f0664ca64c64a08490558af40 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 10:20:33 -0700 Subject: [PATCH 13/30] refacotr status page module into class --- server/src/config/services.js | 7 +- .../src/controllers/statusPageController.js | 10 +- server/src/db/mongo/MongoDB.js | 9 +- .../src/db/mongo/modules/statusPageModule.js | 541 +++++++++--------- server/src/service/business/monitorService.js | 2 +- 5 files changed, 282 insertions(+), 287 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index ae3a4d27b..f5c9b3066 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -33,15 +33,19 @@ import jwt from "jsonwebtoken"; import crypto from "crypto"; // DB Modules +import { NormalizeData } from "../utils/dataUtils.js"; + import Check from "../db/models/Check.js"; import HardwareCheck from "../db/models/HardwareCheck.js"; import PageSpeedCheck from "../db/models/PageSpeedCheck.js"; import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; +import StatusPage from "../db/models/StatusPage.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; +import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -55,7 +59,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService // Create DB const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); - const db = new MongoDB({ logger, envSettings, checkModule, inviteModule }); + const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); + const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule }); await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); diff --git a/server/src/controllers/statusPageController.js b/server/src/controllers/statusPageController.js index 85211c7ff..1cbcb49a2 100755 --- a/server/src/controllers/statusPageController.js +++ b/server/src/controllers/statusPageController.js @@ -19,7 +19,7 @@ class StatusPageController extends BaseController { await imageValidation.validateAsync(req.file); const { _id, teamId } = req.user; - const statusPage = await this.db.createStatusPage({ + const statusPage = await this.db.statusPageModule.createStatusPage({ statusPageData: req.body, image: req.file, userId: _id, @@ -39,7 +39,7 @@ class StatusPageController extends BaseController { await createStatusPageBodyValidation.validateAsync(req.body); await imageValidation.validateAsync(req.file); - const statusPage = await this.db.updateStatusPage(req.body, req.file); + const statusPage = await this.db.statusPageModule.updateStatusPage(req.body, req.file); if (statusPage === null) { throw this.errorService.createNotFoundError(this.stringService.statusPageNotFound); } @@ -54,7 +54,7 @@ class StatusPageController extends BaseController { getStatusPage = this.asyncHandler( async (req, res) => { - const statusPage = await this.db.getStatusPage(); + const statusPage = await this.db.statusPageModule.getStatusPage(); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -69,7 +69,7 @@ class StatusPageController extends BaseController { await getStatusPageParamValidation.validateAsync(req.params); await getStatusPageQueryValidation.validateAsync(req.query); - const statusPage = await this.db.getStatusPageByUrl(req.params.url, req.query.type); + const statusPage = await this.db.statusPageModule.getStatusPageByUrl(req.params.url); return res.success({ msg: this.stringService.statusPageByUrl, data: statusPage, @@ -82,7 +82,7 @@ class StatusPageController extends BaseController { getStatusPagesByTeamId = this.asyncHandler( async (req, res) => { const teamId = req.user.teamId; - const statusPages = await this.db.getStatusPagesByTeamId(teamId); + const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId); return res.success({ msg: this.stringService.statusPageByTeamId, diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 1126b5ca6..9235892a8 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -44,11 +44,6 @@ import * as notificationModule from "./modules/notificationModule.js"; //**************************************** import * as settingsModule from "./modules/settingsModule.js"; -//**************************************** -// Status Page -//**************************************** -import * as statusPageModule from "./modules/statusPageModule.js"; - //**************************************** // Diagnostic //**************************************** @@ -57,7 +52,7 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule }) { + constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule }) { this.logger = logger; this.envSettings = envSettings; Object.assign(this, userModule); @@ -70,7 +65,7 @@ class MongoDB { Object.assign(this, maintenanceWindowModule); Object.assign(this, notificationModule); Object.assign(this, settingsModule); - Object.assign(this, statusPageModule); + this.statusPageModule = statusPageModule; Object.assign(this, diagnosticModule); } diff --git a/server/src/db/mongo/modules/statusPageModule.js b/server/src/db/mongo/modules/statusPageModule.js index e814c20f5..10710d0f7 100755 --- a/server/src/db/mongo/modules/statusPageModule.js +++ b/server/src/db/mongo/modules/statusPageModule.js @@ -1,292 +1,287 @@ -import StatusPage from "../../models/StatusPage.js"; -import { NormalizeData } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; +// import StatusPage from "../../models/StatusPage.js"; +// import { NormalizeData } from "../../../utils/dataUtils.js"; +// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; +// import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "statusPageModule"; -const createStatusPage = async ({ statusPageData, image, userId, teamId }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const statusPage = new StatusPage({ - ...statusPageData, - userId, - teamId, - }); - if (image) { - statusPage.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } - await statusPage.save(); - return statusPage; - } catch (error) { - if (error?.code === 11000) { - // Handle duplicate URL errors - error.status = 400; - error.message = stringService.statusPageUrlNotUnique; - } - error.service = SERVICE_NAME; - error.method = "createStatusPage"; - throw error; +class StatusPageModule { + constructor({ StatusPage, NormalizeData, stringService }) { + this.StatusPage = StatusPage; + this.NormalizeData = NormalizeData; + this.stringService = stringService; } -}; -const updateStatusPage = async (statusPageData, image) => { - try { - if (image) { - statusPageData.logo = { - data: image.buffer, - contentType: image.mimetype, - }; - } else { - statusPageData.logo = null; - } - - if (statusPageData.deleteSubmonitors === "true") { - statusPageData.subMonitors = []; - } - const statusPage = await StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { - new: true, - }); - - return statusPage; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateStatusPage"; - throw error; - } -}; - -const getStatusPageByUrl = async (url, type) => { - // TODO This is deprecated, can remove and have controller call getStatusPage - try { - return getStatusPage(url); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const getStatusPagesByTeamId = async (teamId) => { - try { - const statusPages = await StatusPage.find({ teamId }); - return statusPages; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPagesByTeamId"; - throw error; - } -}; - -const getStatusPage = async (url) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const preliminaryStatusPage = await StatusPage.findOne({ url }); - if (!preliminaryStatusPage) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + createStatusPage = async ({ statusPageData, image, userId, teamId }) => { + try { + const statusPage = new this.StatusPage({ + ...statusPageData, + userId, + teamId, + }); + if (image) { + statusPage.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } + await statusPage.save(); + return statusPage; + } catch (error) { + if (error?.code === 11000) { + // Handle duplicate URL errors + error.status = 400; + error.message = this.stringService.statusPageUrlNotUnique; + } + error.service = SERVICE_NAME; + error.method = "createStatusPage"; throw error; } + }; - if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { - const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = - preliminaryStatusPage; - return { - statusPage: { - _id, - color, - companyName, - isPublished, - logo, - originalMonitors, - showCharts, - showUptimePercentage, - timezone, - showAdminLoginLink, - url, - }, - monitors: [], - }; + updateStatusPage = async (statusPageData, image) => { + try { + if (image) { + statusPageData.logo = { + data: image.buffer, + contentType: image.mimetype, + }; + } else { + statusPageData.logo = null; + } + + if (statusPageData.deleteSubmonitors === "true") { + statusPageData.subMonitors = []; + } + const statusPage = await this.StatusPage.findOneAndUpdate({ url: statusPageData.url }, statusPageData, { + new: true, + }); + + return statusPage; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateStatusPage"; + throw error; } + }; - const statusPageQuery = await StatusPage.aggregate([ - { $match: { url: url } }, - { - $set: { - originalMonitors: "$monitors", - }, - }, - { - $lookup: { - from: "monitors", - localField: "monitors", - foreignField: "_id", - as: "monitors", - }, - }, - { - $unwind: { - path: "$monitors", - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: "maintenancewindows", - let: { monitorId: "$monitors._id" }, - pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], - as: "monitors.maintenanceWindows", - }, - }, - { - $lookup: { - from: "checks", - let: { monitorId: "$monitors._id" }, - pipeline: [ - { - $match: { - $expr: { $eq: ["$monitorId", "$$monitorId"] }, - }, - }, - { $sort: { createdAt: -1 } }, - { $limit: 25 }, - ], - as: "monitors.checks", - }, - }, - { - $addFields: { - "monitors.orderIndex": { - $indexOfArray: ["$originalMonitors", "$monitors._id"], - }, - "monitors.isMaintenance": { - $reduce: { - input: "$monitors.maintenanceWindows", - initialValue: false, - in: { - $or: [ - "$$value", - { - $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], - }, - ], - }, - }, - }, - }, - }, - { $match: { "monitors.orderIndex": { $ne: -1 } } }, - { $sort: { "monitors.orderIndex": 1 } }, + getStatusPageByUrl = async (url) => { + // TODO This is deprecated, can remove and have controller call getStatusPage + try { + return this.getStatusPage(url); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; + throw error; + } + }; - { - $group: { - _id: "$_id", - statusPage: { $first: "$$ROOT" }, - monitors: { $push: "$monitors" }, - }, - }, - { - $project: { + getStatusPagesByTeamId = async (teamId) => { + try { + const statusPages = await this.StatusPage.find({ teamId }); + return statusPages; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPagesByTeamId"; + throw error; + } + }; + + getStatusPage = async (url) => { + try { + const preliminaryStatusPage = await this.StatusPage.findOne({ url }); + if (!preliminaryStatusPage) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { + const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = + preliminaryStatusPage; + return { statusPage: { - _id: 1, - color: 1, - companyName: 1, - isPublished: 1, - logo: 1, - originalMonitors: 1, - showCharts: 1, - showUptimePercentage: 1, - timezone: 1, - showAdminLoginLink: 1, - url: 1, + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, }, - monitors: { - _id: 1, - userId: 1, - teamId: 1, - name: 1, - description: 1, - status: 1, - type: 1, - ignoreTlsErrors: 1, - jsonPath: 1, - expectedValue: 1, - matchMethod: 1, - url: 1, - port: 1, - isActive: 1, - interval: 1, - uptimePercentage: 1, - notifications: 1, - secret: 1, - thresholds: 1, - alertThreshold: 1, - cpuAlertThreshold: 1, - memoryAlertThreshold: 1, - diskAlertThreshold: 1, - tempAlertThreshold: 1, - checks: 1, - isMaintenance: 1, - createdAt: 1, - updatedAt: 1, + monitors: [], + }; + } + + const statusPageQuery = await this.StatusPage.aggregate([ + { $match: { url: url } }, + { + $set: { + originalMonitors: "$monitors", }, }, - }, - ]); - if (!statusPageQuery.length) { - const error = new Error(stringService.statusPageNotFound); - error.status = 404; + { + $lookup: { + from: "monitors", + localField: "monitors", + foreignField: "_id", + as: "monitors", + }, + }, + { + $unwind: { + path: "$monitors", + preserveNullAndEmptyArrays: true, + }, + }, + { + $lookup: { + from: "maintenancewindows", + let: { monitorId: "$monitors._id" }, + pipeline: [{ $match: { $expr: { $eq: ["$monitorId", "$$monitorId"] } } }], + as: "monitors.maintenanceWindows", + }, + }, + { + $lookup: { + from: "checks", + let: { monitorId: "$monitors._id" }, + pipeline: [ + { + $match: { + $expr: { $eq: ["$monitorId", "$$monitorId"] }, + }, + }, + { $sort: { createdAt: -1 } }, + { $limit: 25 }, + ], + as: "monitors.checks", + }, + }, + { + $addFields: { + "monitors.orderIndex": { + $indexOfArray: ["$originalMonitors", "$monitors._id"], + }, + "monitors.isMaintenance": { + $reduce: { + input: "$monitors.maintenanceWindows", + initialValue: false, + in: { + $or: [ + "$$value", + { + $and: [{ $eq: ["$$this.active", true] }, { $lte: ["$$this.start", "$$NOW"] }, { $gte: ["$$this.end", "$$NOW"] }], + }, + ], + }, + }, + }, + }, + }, + { $match: { "monitors.orderIndex": { $ne: -1 } } }, + { $sort: { "monitors.orderIndex": 1 } }, + + { + $group: { + _id: "$_id", + statusPage: { $first: "$$ROOT" }, + monitors: { $push: "$monitors" }, + }, + }, + { + $project: { + statusPage: { + _id: 1, + color: 1, + companyName: 1, + isPublished: 1, + logo: 1, + originalMonitors: 1, + showCharts: 1, + showUptimePercentage: 1, + timezone: 1, + showAdminLoginLink: 1, + url: 1, + }, + monitors: { + _id: 1, + userId: 1, + teamId: 1, + name: 1, + description: 1, + status: 1, + type: 1, + ignoreTlsErrors: 1, + jsonPath: 1, + expectedValue: 1, + matchMethod: 1, + url: 1, + port: 1, + isActive: 1, + interval: 1, + uptimePercentage: 1, + notifications: 1, + secret: 1, + thresholds: 1, + alertThreshold: 1, + cpuAlertThreshold: 1, + memoryAlertThreshold: 1, + diskAlertThreshold: 1, + tempAlertThreshold: 1, + checks: 1, + isMaintenance: 1, + createdAt: 1, + updatedAt: 1, + }, + }, + }, + ]); + if (!statusPageQuery.length) { + const error = new Error(this.stringService.statusPageNotFound); + error.status = 404; + throw error; + } + + const { statusPage, monitors } = statusPageQuery[0]; + + const normalizedMonitors = monitors.map((monitor) => { + return { + ...monitor, + checks: this.NormalizeData(monitor.checks, 10, 100), + }; + }); + + return { statusPage, monitors: normalizedMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getStatusPageByUrl"; throw error; } + }; - const { statusPage, monitors } = statusPageQuery[0]; + deleteStatusPage = async (url) => { + try { + await this.StatusPage.deleteOne({ url }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPage"; + throw error; + } + }; + deleteStatusPagesByMonitorId = async (monitorId) => { + try { + await this.StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteStatusPageByMonitorId"; + throw error; + } + }; +} - const normalizedMonitors = monitors.map((monitor) => { - return { - ...monitor, - checks: NormalizeData(monitor.checks, 10, 100), - }; - }); - - return { statusPage, monitors: normalizedMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getStatusPageByUrl"; - throw error; - } -}; - -const deleteStatusPage = async (url) => { - try { - await StatusPage.deleteOne({ url }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPage"; - throw error; - } -}; - -const deleteStatusPagesByMonitorId = async (monitorId) => { - try { - await StatusPage.deleteMany({ monitors: { $in: [monitorId] } }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteStatusPageByMonitorId"; - throw error; - } -}; - -export { - createStatusPage, - updateStatusPage, - getStatusPagesByTeamId, - getStatusPage, - getStatusPageByUrl, - deleteStatusPage, - deleteStatusPagesByMonitorId, -}; +export default StatusPageModule; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index 94be6984c..f90c21470 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -143,7 +143,7 @@ class MonitorService { await this.verifyTeamAccess({ teamId, monitorId }); const monitor = await this.db.deleteMonitor({ teamId, monitorId }); await this.jobQueue.deleteJob(monitor); - await this.db.deleteStatusPagesByMonitorId(monitor._id); + await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); return monitor; }; From 8adad64248902e85f0a0efb13ed2fa3deea0a8e5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 10:41:53 -0700 Subject: [PATCH 14/30] refactor module into class --- server/src/config/services.js | 9 +- server/src/db/mongo/MongoDB.js | 10 +- server/src/db/mongo/modules/userModule.js | 396 ++++++++------------- server/src/service/business/userService.js | 22 +- 4 files changed, 174 insertions(+), 263 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index f5c9b3066..8ac7c72e2 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -34,7 +34,10 @@ import crypto from "crypto"; // DB Modules import { NormalizeData } from "../utils/dataUtils.js"; +import { GenerateAvatarImage } from "../utils/imageProcessing.js"; +import { ParseBoolean } from "../utils/utils.js"; +// Models import Check from "../db/models/Check.js"; import HardwareCheck from "../db/models/HardwareCheck.js"; import PageSpeedCheck from "../db/models/PageSpeedCheck.js"; @@ -42,10 +45,12 @@ import Monitor from "../db/models/Monitor.js"; import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; import StatusPage from "../db/models/StatusPage.js"; +import Team from "../db/models/Team.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; +import UserModule from "../db/mongo/modules/userModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -60,7 +65,9 @@ export const initializeServices = async ({ logger, envSettings, settingsService const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User }); const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); - const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule }); + const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); + const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule }); + await db.connect(); const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 9235892a8..158f68f7c 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -1,12 +1,6 @@ import mongoose from "mongoose"; import AppSettings from "../models/AppSettings.js"; -//**************************************** -// User Operations -//**************************************** - -import * as userModule from "./modules/userModule.js"; - //**************************************** // Recovery Operations //**************************************** @@ -52,10 +46,10 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule }) { + constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule }) { this.logger = logger; this.envSettings = envSettings; - Object.assign(this, userModule); + this.userModule = userModule; this.inviteModule = inviteModule; Object.assign(this, recoveryModule); Object.assign(this, monitorModule); diff --git a/server/src/db/mongo/modules/userModule.js b/server/src/db/mongo/modules/userModule.js index 974f2d2ed..83b3b15f9 100755 --- a/server/src/db/mongo/modules/userModule.js +++ b/server/src/db/mongo/modules/userModule.js @@ -1,261 +1,171 @@ -import UserModel from "../../models/User.js"; -import TeamModel from "../../models/Team.js"; -import { GenerateAvatarImage } from "../../../utils/imageProcessing.js"; - -const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -import { ParseBoolean } from "../../../utils/utils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "userModule"; +const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key -const checkSuperadmin = async () => { - const superAdmin = await UserModel.findOne({ role: "superadmin" }); - if (superAdmin !== null) { - return true; +class UserModule { + constructor({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }) { + this.User = User; + this.Team = Team; + this.GenerateAvatarImage = GenerateAvatarImage; + this.ParseBoolean = ParseBoolean; + this.stringService = stringService; } - return false; -}; -/** - * Insert a User - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const insertUser = async (userData, imageFile, generateAvatarImage = GenerateAvatarImage) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - if (imageFile) { - // 1. Save the full size image - userData.profileImage = { - data: imageFile.buffer, - contentType: imageFile.mimetype, - }; + checkSuperadmin = async () => { + try { + const superAdmin = await this.User.findOne({ role: "superadmin" }); + if (superAdmin !== null) { + return true; + } + return false; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "checkSuperadmin"; + throw error; + } + }; - // 2. Get the avatar sized image - const avatar = await generateAvatarImage(imageFile); - userData.avatarImage = avatar; + insertUser = async (userData, imageFile) => { + try { + if (imageFile) { + // 1. Save the full size image + userData.profileImage = { + data: imageFile.buffer, + contentType: imageFile.mimetype, + }; + + // 2. Get the avatar sized image + const avatar = await this.GenerateAvatarImage(imageFile); + userData.avatarImage = avatar; + } + + // Handle creating team if superadmin + if (userData.role.includes("superadmin")) { + const team = new this.Team({ + email: userData.email, + }); + userData.teamId = team._id; + userData.checkTTL = 60 * 60 * 24 * 30; + await team.save(); + } + + const newUser = new this.User(userData); + await newUser.save(); + return await this.User.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find + } catch (error) { + if (error.code === DUPLICATE_KEY_CODE) { + error.message = this.stringService.dbUserExists; + } + error.service = SERVICE_NAME; + error.method = "insertUser"; + throw error; + } + }; + getUserByEmail = async (email) => { + try { + // Need the password to be able to compare, removed .select() + // We can strip the hash before returning the user + const user = await this.User.findOne({ email: email }).select("-profileImage"); + if (!user) { + throw new Error(this.stringService.dbUserNotFound); + } + return user; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUserByEmail"; + throw error; + } + }; + + updateUser = async ({ userId, user, file }) => { + if (!userId) { + throw new Error("No user in request"); } - // Handle creating team if superadmin - if (userData.role.includes("superadmin")) { - const team = new TeamModel({ - email: userData.email, - }); - userData.teamId = team._id; - userData.checkTTL = 60 * 60 * 24 * 30; - await team.save(); + try { + const candidateUser = { ...user }; + + if (this.ParseBoolean(candidateUser.deleteProfileImage) === true) { + candidateUser.profileImage = null; + candidateUser.avatarImage = null; + } else if (file) { + // 1. Save the full size image + candidateUser.profileImage = { + data: file.buffer, + contentType: file.mimetype, + }; + + // 2. Get the avatar sized image + const avatar = await this.GenerateAvatarImage(file); + candidateUser.avatarImage = avatar; + } + + const updatedUser = await this.User.findByIdAndUpdate( + userId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ) + .select("-password") + .select("-profileImage"); + return updatedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateUser"; + throw error; } - - const newUser = new UserModel(userData); - await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }).select("-password").select("-profileImage"); // .select() doesn't work with create, need to save then find - } catch (error) { - if (error.code === DUPLICATE_KEY_CODE) { - error.message = stringService.dbUserExists; + }; + deleteUser = async (userId) => { + try { + const deletedUser = await this.User.findByIdAndDelete(userId); + if (!deletedUser) { + throw new Error(this.stringService.dbUserNotFound); + } + return deletedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteUser"; + throw error; } - error.service = SERVICE_NAME; - error.method = "insertUser"; - throw error; - } -}; + }; -/** - * Get User by Email - * Gets a user by Email. Not sure if we'll ever need this except for login. - * If not needed except for login, we can move password comparison here - * Throws error if user not found - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUserByEmail = async (email) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - // Need the password to be able to compare, removed .select() - // We can strip the hash before returning the user - const user = await UserModel.findOne({ email: email }).select("-profileImage"); - if (!user) { - throw new Error(stringService.dbUserNotFound); + getAllUsers = async () => { + try { + const users = await this.User.find().select("-password").select("-profileImage"); + return users; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllUsers"; + throw error; } - return user; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserByEmail"; - throw error; - } -}; + }; -/** - * Update a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ + getUserById = async (roles, userId) => { + try { + if (!roles.includes("superadmin")) { + throw new Error("User is not a superadmin"); + } -const updateUser = async ({ userId, user, file }) => { - if (!userId) { - throw new Error("No user in request"); - } + const user = await this.User.findById(userId).select("-password").select("-profileImage"); + if (!user) { + throw new Error("User not found"); + } - try { - const candidateUser = { ...user }; - - if (ParseBoolean(candidateUser.deleteProfileImage) === true) { - candidateUser.profileImage = null; - candidateUser.avatarImage = null; - } else if (file) { - // 1. Save the full size image - candidateUser.profileImage = { - data: file.buffer, - contentType: file.mimetype, - }; - - // 2. Get the avatar sized image - const avatar = await GenerateAvatarImage(file); - candidateUser.avatarImage = avatar; + return user; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUserById"; + throw error; } + }; - // ****************************************** - // End handling profile image - // ****************************************** - - const updatedUser = await UserModel.findByIdAndUpdate( - userId, - candidateUser, - { new: true } // Returns updated user instead of pre-update user - ) - .select("-password") - .select("-profileImage"); - return updatedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateUser"; - throw error; - } -}; - -/** - * Delete a user by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteUser = async (userId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - - try { - const deletedUser = await UserModel.findByIdAndDelete(userId); - if (!deletedUser) { - throw new Error(stringService.dbUserNotFound); + editUserById = async (userId, user) => { + try { + await this.User.findByIdAndUpdate(userId, user, { new: true }).select("-password").select("-profileImage"); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editUserById"; + throw error; } - return deletedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteUser"; - throw error; - } -}; + }; +} -/** - * Delete a user by ID - * @async - * @param {string} teamId - * @returns {void} - * @throws {Error} - */ -const deleteTeam = async (teamId) => { - try { - await TeamModel.findByIdAndDelete(teamId); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteTeam"; - throw error; - } -}; - -const deleteAllOtherUsers = async () => { - try { - await UserModel.deleteMany({ role: { $ne: "superadmin" } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllOtherUsers"; - throw error; - } -}; - -const getAllUsers = async () => { - try { - const users = await UserModel.find().select("-password").select("-profileImage"); - return users; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllUsers"; - throw error; - } -}; - -const logoutUser = async (userId) => { - try { - await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "logoutUser"; - throw error; - } -}; - -const getUserById = async (roles, userId) => { - try { - if (!roles.includes("superadmin")) { - throw new Error("User is not a superadmin"); - } - - const user = await UserModel.findById(userId).select("-password").select("-profileImage"); - if (!user) { - throw new Error("User not found"); - } - - return user; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserById"; - throw error; - } -}; - -const editUserById = async (userId, user) => { - try { - await UserModel.findByIdAndUpdate(userId, user, { new: true }).select("-password").select("-profileImage"); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editUserById"; - throw error; - } -}; - -export { - checkSuperadmin, - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, - getUserById, - editUserById, -}; +export default UserModule; diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index 4e6a41ab0..04c14c477 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -29,7 +29,7 @@ class UserService { registerUser = async (user, file) => { // Create a new user // If superAdmin exists, a token should be attached to all further register requests - const superAdminExists = await this.db.checkSuperadmin(); + const superAdminExists = await this.db.userModule.checkSuperadmin(); if (superAdminExists) { const invitedUser = await this.db.inviteModule.getInviteTokenAndDelete(user.inviteToken); user.role = invitedUser.role; @@ -40,7 +40,7 @@ class UserService { await this.db.updateAppSettings({ jwtSecret }); } - const newUser = await this.db.insertUser({ ...user }, file); + const newUser = await this.db.userModule.insertUser({ ...user }, file); this.logger.debug({ message: "New user created", @@ -83,7 +83,7 @@ class UserService { loginUser = async (email, password) => { // Check if user exists - const user = await this.db.getUserByEmail(email); + const user = await this.db.userModule.getUserByEmail(email); // Compare password const match = await user.comparePassword(password); if (match !== true) { @@ -110,7 +110,7 @@ class UserService { // Add user email to body for DB operation updates.email = currentUser.email; // Get user - const user = await this.db.getUserByEmail(currentUser.email); + const user = await this.db.userModule.getUserByEmail(currentUser.email); // Compare passwords const match = await user.comparePassword(updates?.password); // If not a match, throw a 403 @@ -122,17 +122,17 @@ class UserService { updates.password = updates.newPassword; } - const updatedUser = await this.db.updateUser({ userId: currentUser?._id, user: updates, file: file }); + const updatedUser = await this.db.userModule.updateUser({ userId: currentUser?._id, user: updates, file: file }); return updatedUser; }; checkSuperadminExists = async () => { - const superAdminExists = await this.db.checkSuperadmin(); + const superAdminExists = await this.db.userModule.checkSuperadmin(); return superAdminExists; }; requestRecovery = async (email) => { - const user = await this.db.getUserByEmail(email); + const user = await this.db.userModule.getUserByEmail(email); const recoveryToken = await this.db.requestRecoveryToken(email); const name = user.firstName; const { clientHost } = this.settingsService.getSettings(); @@ -195,21 +195,21 @@ class UserService { )); } // 6. Delete the user by id - await this.db.deleteUser(userId); + await this.db.userModule.deleteUser(userId); }; getAllUsers = async () => { - const users = await this.db.getAllUsers(); + const users = await this.db.userModule.getAllUsers(); return users; }; getUserById = async (roles, userId) => { - const user = await this.db.getUserById(roles, userId); + const user = await this.db.userModule.getUserById(roles, userId); return user; }; editUserById = async (userId, user) => { - await this.db.editUserById(userId, user); + await this.db.userModule.editUserById(userId, user); }; } export default UserService; From 2d6fdcb1e2cced589f62ebd96e9fdba3331cf131 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 23:14:35 +0530 Subject: [PATCH 15/30] Made code readable --- .../src/Pages/Infrastructure/Create/index.jsx | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index 73a11c40f..d9817aae3 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -96,8 +96,9 @@ const CreateInfrastructureMonitor = () => { if (isCreate) { // If global settings are not loaded yet, use default thresholds if (globalSettingsLoading) return; + // Create mode: use global thresholds - const globalThresholds = globalSettings?.data?.settings?.globalThresholds; + const globalThresholds = globalSettings?.data?.settings?.globalThresholds || {}; const defaultThresholds = { cpu: "", memory: "", @@ -111,15 +112,26 @@ const CreateInfrastructureMonitor = () => { name: "", notifications: [], interval: 0.25, - cpu: globalThresholds?.cpu != null, - usage_cpu: globalThresholds?.cpu?.toString() || defaultThresholds.cpu, - memory: globalThresholds?.memory != null, - usage_memory: globalThresholds?.memory?.toString() || defaultThresholds.memory, - disk: globalThresholds?.disk != null, - usage_disk: globalThresholds?.disk?.toString() || defaultThresholds.disk, - temperature: globalThresholds?.temperature != null, + cpu: globalThresholds.cpu != null, + usage_cpu: + globalThresholds.cpu != null + ? globalThresholds.cpu.toString() + : defaultThresholds.cpu, + memory: globalThresholds.memory != null, + usage_memory: + globalThresholds.memory != null + ? globalThresholds.memory.toString() + : defaultThresholds.memory, + disk: globalThresholds.disk != null, + usage_disk: + globalThresholds.disk != null + ? globalThresholds.disk.toString() + : defaultThresholds.disk, + temperature: globalThresholds.temperature != null, usage_temperature: - globalThresholds?.temperature?.toString() || defaultThresholds.temperature, + globalThresholds.temperature != null + ? globalThresholds.temperature.toString() + : defaultThresholds.temperature, })); } else if (monitor) { // Edit mode: use monitor only, ignore global thresholds @@ -129,15 +141,30 @@ const CreateInfrastructureMonitor = () => { name: monitor.name || "", notifications: monitor.notifications || [], interval: monitor.interval / MS_PER_MINUTE, - cpu: !!monitor.thresholds?.usage_cpu ?? false, - usage_cpu: monitor.thresholds?.usage_cpu * 100?.toString() || "", - memory: !!monitor.thresholds?.usage_memory ?? false, - usage_memory: monitor.thresholds?.usage_memory * 100?.toString() || "", - disk: !!monitor.thresholds?.usage_disk ?? false, - usage_disk: monitor.thresholds?.usage_disk * 100?.toString() || "", - temperature: !!monitor.thresholds?.usage_temperature ?? false, - usage_temperature: monitor.thresholds?.usage_temperature * 100?.toString() || "", + cpu: monitor.thresholds?.usage_cpu != null, + usage_cpu: + monitor.thresholds?.usage_cpu != null + ? (monitor.thresholds.usage_cpu * 100).toString() + : "", + memory: monitor.thresholds?.usage_memory != null, + usage_memory: + monitor.thresholds?.usage_memory != null + ? (monitor.thresholds.usage_memory * 100).toString() + : "", + disk: monitor.thresholds?.usage_disk != null, + usage_disk: + monitor.thresholds?.usage_disk != null + ? (monitor.thresholds.usage_disk * 100).toString() + : "", + temperature: monitor.thresholds?.usage_temperature != null, + usage_temperature: + monitor.thresholds?.usage_temperature != null + ? (monitor.thresholds.usage_temperature * 100).toString() + : "", + secret: monitor.secret || "", })); + + setHttps(monitor.url?.startsWith("https")); } }, [isCreate, monitor, globalSettings, globalSettingsLoading]); From 8bacbcde893a3049f47d5ac4e7a0a2a74bd18f67 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Wed, 30 Jul 2025 23:35:51 +0530 Subject: [PATCH 16/30] Removed Unnecassary command --- client/src/Pages/Infrastructure/Create/index.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index d9817aae3..f2833d52f 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -164,7 +164,6 @@ const CreateInfrastructureMonitor = () => { secret: monitor.secret || "", })); - setHttps(monitor.url?.startsWith("https")); } }, [isCreate, monitor, globalSettings, globalSettingsLoading]); From 00f5eaf44d0c3e1823a9d3572ab30f300d1305c9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 11:27:29 -0700 Subject: [PATCH 17/30] module -> class --- server/src/config/services.js | 5 +- server/src/db/mongo/MongoDB.js | 9 +- .../db/mongo/modules/hardwareCheckModule.js | 84 ++++--------------- .../service/infrastructure/bufferService.js | 2 +- 4 files changed, 24 insertions(+), 76 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 8ac7c72e2..54f226051 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -51,7 +51,7 @@ import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; import UserModule from "../db/mongo/modules/userModule.js"; - +import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -66,7 +66,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService const inviteModule = new InviteModule({ InviteToken, crypto, stringService }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); - const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule }); + const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger }); + const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule }); await db.connect(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 158f68f7c..077aa5b73 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -18,11 +18,6 @@ import * as monitorModule from "./modules/monitorModule.js"; import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; -//**************************************** -// Hardware Checks -//**************************************** -import * as hardwareCheckModule from "./modules/hardwareCheckModule.js"; - //**************************************** // Maintenance Window //**************************************** @@ -46,7 +41,7 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule }) { + constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule }) { this.logger = logger; this.envSettings = envSettings; this.userModule = userModule; @@ -54,7 +49,7 @@ class MongoDB { Object.assign(this, recoveryModule); Object.assign(this, monitorModule); Object.assign(this, pageSpeedCheckModule); - Object.assign(this, hardwareCheckModule); + this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; Object.assign(this, maintenanceWindowModule); Object.assign(this, notificationModule); diff --git a/server/src/db/mongo/modules/hardwareCheckModule.js b/server/src/db/mongo/modules/hardwareCheckModule.js index 9cb228bc4..448e72fed 100755 --- a/server/src/db/mongo/modules/hardwareCheckModule.js +++ b/server/src/db/mongo/modules/hardwareCheckModule.js @@ -1,70 +1,22 @@ -import HardwareCheck from "../../models/HardwareCheck.js"; -import Monitor from "../../models/Monitor.js"; -import { logger } from "../../../utils/logger.js"; - const SERVICE_NAME = "hardwareCheckModule"; -const createHardwareCheck = async (hardwareCheckData) => { - try { - const { monitorId, status } = hardwareCheckData; - const n = (await HardwareCheck.countDocuments({ monitorId })) + 1; - const monitor = await Monitor.findById(monitorId); - if (!monitor) { - logger.error({ - message: "Monitor not found", - service: SERVICE_NAME, - method: "createHardwareCheck", - details: `monitor ID: ${monitorId}`, - }); - return null; +class HardwareCheckModule { + constructor({ HardwareCheck, Monitor, logger }) { + this.HardwareCheck = HardwareCheck; + this.Monitor = Monitor; + this.logger = logger; + } + + createHardwareChecks = async (hardwareChecks) => { + try { + await this.HardwareCheck.insertMany(hardwareChecks, { ordered: false }); + return true; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createHardwareChecks"; + throw error; } + }; +} - let newUptimePercentage; - if (monitor.uptimePercentage === undefined) { - newUptimePercentage = status === true ? 1 : 0; - } else { - newUptimePercentage = (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; - } - - await Monitor.findOneAndUpdate({ _id: monitorId }, { uptimePercentage: newUptimePercentage }); - - const hardwareCheck = await new HardwareCheck({ - ...hardwareCheckData, - }).save(); - return hardwareCheck; - } catch (error) { - logger.error({ - message: "Error creating hardware check", - service: SERVICE_NAME, - method: "createHardwareCheck", - stack: error.stack, - }); - error.service = SERVICE_NAME; - error.method = "createHardwareCheck"; - throw error; - } -}; - -const createHardwareChecks = async (hardwareChecks) => { - try { - await HardwareCheck.insertMany(hardwareChecks, { ordered: false }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createHardwareChecks"; - throw error; - } -}; - -const deleteHardwareChecksByMonitorId = async (monitorId) => { - try { - const result = await HardwareCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteHardwareChecksByMonitorId"; - throw error; - } -}; - -export { createHardwareCheck, createHardwareChecks, deleteHardwareChecksByMonitorId }; +export default HardwareCheckModule; diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js index e772c313d..b29cf533b 100755 --- a/server/src/service/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -23,7 +23,7 @@ class BufferService { this.OPERATION_MAP = { checks: this.db.checkModule.createChecks, pagespeedChecks: this.db.createPageSpeedChecks, - hardwareChecks: this.db.createHardwareChecks, + hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks, }; this.scheduleNextFlush(); From 585fb4f700eb803012550decef691eec7c7b7f8f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 11:36:19 -0700 Subject: [PATCH 18/30] module -> class --- server/src/config/services.js | 15 +- server/src/db/mongo/MongoDB.js | 4 +- .../mongo/modules/maintenanceWindowModule.js | 278 ++++++------------ .../business/maintenanceWindowService.js | 12 +- .../SuperSimpleQueueHelper.js | 2 +- 5 files changed, 119 insertions(+), 192 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 54f226051..4e8a6ad5b 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -46,12 +46,15 @@ import User from "../db/models/User.js"; import InviteToken from "../db/models/InviteToken.js"; import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; +import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; import UserModule from "../db/mongo/modules/userModule.js"; import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; +import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -67,7 +70,17 @@ export const initializeServices = async ({ logger, envSettings, settingsService const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService }); const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger }); - const db = new MongoDB({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule }); + const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); + const db = new MongoDB({ + logger, + envSettings, + checkModule, + inviteModule, + statusPageModule, + userModule, + hardwareCheckModule, + maintenanceWindowModule, + }); await db.connect(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 077aa5b73..cd79789a3 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -41,7 +41,7 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule }) { + constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule, maintenanceWindowModule }) { this.logger = logger; this.envSettings = envSettings; this.userModule = userModule; @@ -51,7 +51,7 @@ class MongoDB { Object.assign(this, pageSpeedCheckModule); this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; - Object.assign(this, maintenanceWindowModule); + this.maintenanceWindowModule = maintenanceWindowModule; Object.assign(this, notificationModule); Object.assign(this, settingsModule); this.statusPageModule = statusPageModule; diff --git a/server/src/db/mongo/modules/maintenanceWindowModule.js b/server/src/db/mongo/modules/maintenanceWindowModule.js index f7fea4636..61523c9f0 100755 --- a/server/src/db/mongo/modules/maintenanceWindowModule.js +++ b/server/src/db/mongo/modules/maintenanceWindowModule.js @@ -1,192 +1,106 @@ -import MaintenanceWindow from "../../models/MaintenanceWindow.js"; const SERVICE_NAME = "maintenanceWindowModule"; -/** - * Asynchronously creates a new MaintenanceWindow document and saves it to the database. - * If the maintenance window is a one-time event, the expiry field is set to the same value as the end field. - * @async - * @function createMaintenanceWindow - * @param {Object} maintenanceWindowData - The data for the new MaintenanceWindow document. - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowData.monitorId - The ID of the monitor. - * @param {Boolean} maintenanceWindowData.active - Indicates whether the maintenance window is active. - * @param {Boolean} maintenanceWindowData.oneTime - Indicates whether the maintenance window is a one-time event. - * @param {Date} maintenanceWindowData.start - The start date and time of the maintenance window. - * @param {Date} maintenanceWindowData.end - The end date and time of the maintenance window. - * @returns {Promise} The saved MaintenanceWindow document. - * @throws {Error} If there is an error saving the document. - */ -const createMaintenanceWindow = async (maintenanceWindowData) => { - try { - const maintenanceWindow = new MaintenanceWindow({ - ...maintenanceWindowData, - }); +class MaintenanceWindowModule { + constructor({ MaintenanceWindow }) { + this.MaintenanceWindow = MaintenanceWindow; + } + createMaintenanceWindow = async (maintenanceWindowData) => { + try { + const maintenanceWindow = new this.MaintenanceWindow({ + ...maintenanceWindowData, + }); - // If the maintenance window is a one time window, set the expiry to the end date - if (maintenanceWindowData.oneTime) { - maintenanceWindow.expiry = maintenanceWindowData.end; + // If the maintenance window is a one time window, set the expiry to the end date + if (maintenanceWindowData.oneTime) { + maintenanceWindow.expiry = maintenanceWindowData.end; + } + const result = await maintenanceWindow.save(); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMaintenanceWindow"; + throw error; } - const result = await maintenanceWindow.save(); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMaintenanceWindow"; - throw error; - } -}; - -const getMaintenanceWindowById = async ({ id, teamId }) => { - try { - const maintenanceWindow = await MaintenanceWindow.findOne({ - _id: id, - teamId: teamId, - }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific team ID. - * @async - * @function getMaintenanceWindowByUserId - * @param {String} teamId - The ID of the team. - * @param {Object} query - The request body. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - */ -const getMaintenanceWindowsByTeamId = async (teamId, query) => { - try { - let { active, page, rowsPerPage, field, order } = query || {}; - const maintenanceQuery = { teamId }; - - if (active !== undefined) maintenanceQuery.active = active; - - const maintenanceWindowCount = await MaintenanceWindow.countDocuments(maintenanceQuery); - - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; + }; + getMaintenanceWindowById = async ({ id, teamId }) => { + try { + const maintenanceWindow = await this.MaintenanceWindow.findOne({ + _id: id, + teamId: teamId, + }); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowById"; + throw error; } + }; - // Sorting - let sort = {}; - if (field !== undefined && order !== undefined) { - sort[field] = order === "asc" ? 1 : -1; + getMaintenanceWindowsByTeamId = async (teamId, query) => { + try { + let { active, page, rowsPerPage, field, order } = query || {}; + const maintenanceQuery = { teamId }; + + if (active !== undefined) maintenanceQuery.active = active; + + const maintenanceWindowCount = await this.MaintenanceWindow.countDocuments(maintenanceQuery); + + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + + // Sorting + let sort = {}; + if (field !== undefined && order !== undefined) { + sort[field] = order === "asc" ? 1 : -1; + } + + const maintenanceWindows = await this.MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort); + + return { maintenanceWindows, maintenanceWindowCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowByUserId"; + throw error; } + }; + getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { + try { + const maintenanceWindows = await this.MaintenanceWindow.find({ + monitorId: monitorId, + teamId: teamId, + }); + return maintenanceWindows; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowsByMonitorId"; + throw error; + } + }; - const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort); + deleteMaintenanceWindowById = async ({ id, teamId }) => { + try { + const maintenanceWindow = await this.MaintenanceWindow.findOneAndDelete({ _id: id, teamId }); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMaintenanceWindowById"; + throw error; + } + }; - return { maintenanceWindows, maintenanceWindowCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowByUserId"; - throw error; - } -}; + editMaintenanceWindowById = async ({ id, body }) => { + try { + const editedMaintenanceWindow = await this.MaintenanceWindow.findByIdAndUpdate(id, body, { new: true }); + return editedMaintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMaintenanceWindowById"; + throw error; + } + }; +} -/** - * Asynchronously retrieves all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function getMaintenanceWindowsByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of MaintenanceWindow documents. - * @throws {Error} If there is an error retrieving the documents. - */ -const getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { - try { - const maintenanceWindows = await MaintenanceWindow.find({ - monitorId: monitorId, - teamId: teamId, - }); - return maintenanceWindows; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowsByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes a MaintenanceWindow document by its ID. - * @async - * @function deleteMaintenanceWindowById - * @param {mongoose.Schema.Types.ObjectId} maintenanceWindowId - The ID of the MaintenanceWindow document to delete. - * @returns {Promise} The deleted MaintenanceWindow document. - * @throws {Error} If there is an error deleting the document. - */ -const deleteMaintenanceWindowById = async ({ id, teamId }) => { - try { - const maintenanceWindow = await MaintenanceWindow.findOneAndDelete({ _id: id, teamId }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowById"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific monitor ID. - * @async - * @function deleteMaintenanceWindowByMonitorId - * @param {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - */ -const deleteMaintenanceWindowByMonitorId = async (monitorId) => { - try { - const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByMonitorId"; - throw error; - } -}; - -/** - * Asynchronously deletes all MaintenanceWindow documents associated with a specific user ID. - * @async - * @function deleteMaintenanceWindowByUserId - * @param {String} userId - The ID of the user. - * @returns {Promise} The result of the delete operation. This object contains information about the operation, such as the number of documents deleted. - * @throws {Error} If there is an error deleting the documents. - * @example - */ -const deleteMaintenanceWindowByUserId = async (userId) => { - try { - const result = await MaintenanceWindow.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByUserId"; - throw error; - } -}; - -const editMaintenanceWindowById = async ({ id, body, teamId }) => { - try { - const editedMaintenanceWindow = await MaintenanceWindow.findByIdAndUpdate(id, body, { new: true }); - return editedMaintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMaintenanceWindowById"; - throw error; - } -}; - -export { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, -}; +export default MaintenanceWindowModule; diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js index ec0fa5345..1e8c09739 100644 --- a/server/src/service/business/maintenanceWindowService.js +++ b/server/src/service/business/maintenanceWindowService.js @@ -25,7 +25,7 @@ class MaintenanceWindowService { } const dbTransactions = monitorIds.map((monitorId) => { - return this.db.createMaintenanceWindow({ + return this.db.maintenanceWindowModule.createMaintenanceWindow({ teamId, monitorId, name: body.name, @@ -39,26 +39,26 @@ class MaintenanceWindowService { }; getMaintenanceWindowById = async ({ id, teamId }) => { - const maintenanceWindow = await this.db.getMaintenanceWindowById({ id, teamId }); + const maintenanceWindow = await this.db.maintenanceWindowModule.getMaintenanceWindowById({ id, teamId }); return maintenanceWindow; }; getMaintenanceWindowsByTeamId = async ({ teamId, query }) => { - const maintenanceWindows = await this.db.getMaintenanceWindowsByTeamId(teamId, query); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByTeamId(teamId, query); return maintenanceWindows; }; getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId({ monitorId, teamId }); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({ monitorId, teamId }); return maintenanceWindows; }; deleteMaintenanceWindow = async ({ id, teamId }) => { - await this.db.deleteMaintenanceWindowById({ id, teamId }); + await this.db.maintenanceWindowModule.deleteMaintenanceWindowById({ id, teamId }); }; editMaintenanceWindow = async ({ id, teamId, body }) => { - const editedMaintenanceWindow = await this.db.editMaintenanceWindowById({ id, body, teamId }); + const editedMaintenanceWindow = await this.db.maintenanceWindowModule.editMaintenanceWindowById({ id, body, teamId }); return editedMaintenanceWindow; }; } diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js index 639e5183b..ca2e5f642 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js @@ -67,7 +67,7 @@ class SuperSimpleQueueHelper { }; async isInMaintenanceWindow(monitorId) { - const maintenanceWindows = await this.db.getMaintenanceWindowsByMonitorId(monitorId); + const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId); // Check for active maintenance window: const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => { if (window.active) { From de742068a740dc5a7e4df7f97fa23b822d860db9 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 12:25:12 -0700 Subject: [PATCH 19/30] module -> class --- server/src/config/services.js | 23 +- server/src/controllers/monitorController.js | 4 +- .../src/controllers/notificationController.js | 2 +- server/src/db/mongo/MongoDB.js | 24 +- server/src/db/mongo/modules/monitorModule.js | 1207 +++++++---------- server/src/service/business/checkService.js | 6 +- .../business/maintenanceWindowService.js | 2 +- server/src/service/business/monitorService.js | 20 +- server/src/service/business/userService.js | 2 +- .../SuperSimpleQueue/SuperSimpleQueue.js | 2 +- .../service/infrastructure/statusService.js | 2 +- 11 files changed, 580 insertions(+), 714 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 4e8a6ad5b..44fb6491b 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -32,8 +32,11 @@ import mjml2html from "mjml"; import jwt from "jsonwebtoken"; import crypto from "crypto"; +import { fileURLToPath } from "url"; +import { ObjectId } from "mongodb"; + // DB Modules -import { NormalizeData } from "../utils/dataUtils.js"; +import { NormalizeData, NormalizeDataUptimeDetails } from "../utils/dataUtils.js"; import { GenerateAvatarImage } from "../utils/imageProcessing.js"; import { ParseBoolean } from "../utils/utils.js"; @@ -47,6 +50,7 @@ import InviteToken from "../db/models/InviteToken.js"; import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; +import MonitorStats from "../db/models/MonitorStats.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; @@ -54,7 +58,7 @@ import StatusPageModule from "../db/mongo/modules/statusPageModule.js"; import UserModule from "../db/mongo/modules/userModule.js"; import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; - +import MonitorModule from "../db/mongo/modules/monitorModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -71,6 +75,20 @@ export const initializeServices = async ({ logger, envSettings, settingsService const userModule = new UserModule({ User, Team, GenerateAvatarImage, ParseBoolean, stringService }); const hardwareCheckModule = new HardwareCheckModule({ HardwareCheck, Monitor, logger }); const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow }); + const monitorModule = new MonitorModule({ + Monitor, + MonitorStats, + Check, + PageSpeedCheck, + HardwareCheck, + stringService, + fs, + path, + fileURLToPath, + ObjectId, + NormalizeData, + NormalizeDataUptimeDetails, + }); const db = new MongoDB({ logger, envSettings, @@ -80,6 +98,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService userModule, hardwareCheckModule, maintenanceWindowModule, + monitorModule, }); await db.connect(); diff --git a/server/src/controllers/monitorController.js b/server/src/controllers/monitorController.js index 13890e5ea..a4f391087 100755 --- a/server/src/controllers/monitorController.js +++ b/server/src/controllers/monitorController.js @@ -32,7 +32,7 @@ class MonitorController extends BaseController { } async verifyTeamAccess(teamId, monitorId) { - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } @@ -150,7 +150,7 @@ class MonitorController extends BaseController { await getCertificateParamValidation.validateAsync(req.params); const { monitorId } = req.params; - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); const certificate = await fetchMonitorCertificate(sslChecker, monitor); return res.success({ diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/notificationController.js index 081968e3d..bffb8ee6d 100755 --- a/server/src/controllers/notificationController.js +++ b/server/src/controllers/notificationController.js @@ -158,7 +158,7 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index cd79789a3..f73db6163 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -6,23 +6,12 @@ import AppSettings from "../models/AppSettings.js"; //**************************************** import * as recoveryModule from "./modules/recoveryModule.js"; -//**************************************** -// Monitors -//**************************************** - -import * as monitorModule from "./modules/monitorModule.js"; - //**************************************** // Page Speed Checks //**************************************** import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; -//**************************************** -// Maintenance Window -//**************************************** -import * as maintenanceWindowModule from "./modules/maintenanceWindowModule.js"; - //**************************************** // Notifications //**************************************** @@ -41,7 +30,17 @@ import * as diagnosticModule from "./modules/diagnosticModule.js"; class MongoDB { static SERVICE_NAME = "MongoDB"; - constructor({ logger, envSettings, checkModule, inviteModule, statusPageModule, userModule, hardwareCheckModule, maintenanceWindowModule }) { + constructor({ + logger, + envSettings, + checkModule, + inviteModule, + statusPageModule, + userModule, + hardwareCheckModule, + maintenanceWindowModule, + monitorModule, + }) { this.logger = logger; this.envSettings = envSettings; this.userModule = userModule; @@ -52,6 +51,7 @@ class MongoDB { this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; + this.monitorModule = monitorModule; Object.assign(this, notificationModule); Object.assign(this, settingsModule); this.statusPageModule = statusPageModule; diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 6e2e80f49..332122df4 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -1,15 +1,14 @@ -import Monitor from "../../models/Monitor.js"; -import MonitorStats from "../../models/MonitorStats.js"; -import Check from "../../models/Check.js"; -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -import HardwareCheck from "../../models/HardwareCheck.js"; -import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js"; -import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; -import { ObjectId } from "mongodb"; +// import Monitor from "../../models/Monitor.js"; +// import MonitorStats from "../../models/MonitorStats.js"; +// import Check from "../../models/Check.js"; +// import PageSpeedCheck from "../../models/PageSpeedCheck.js"; +// import HardwareCheck from "../../models/HardwareCheck.js"; +// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; +// import StringService from "../../../service/system/stringService.js"; +// import fs from "fs"; +// import path from "path"; +// import { fileURLToPath } from "url"; +// import { ObjectId } from "mongodb"; import { buildUptimeDetailsPipeline, @@ -20,476 +19,351 @@ import { buildMonitorsWithChecksByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline, } from "./monitorModuleQueries.js"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); -const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); const SERVICE_NAME = "monitorModule"; -const CHECK_MODEL_LOOKUP = { - http: Check, - ping: Check, - docker: Check, - port: Check, - pagespeed: PageSpeedCheck, - hardware: HardwareCheck, -}; +class MonitorModule { + constructor({ + Monitor, + MonitorStats, + Check, + PageSpeedCheck, + HardwareCheck, + stringService, + fs, + path, + fileURLToPath, + ObjectId, + NormalizeData, + NormalizeDataUptimeDetails, + }) { + this.Monitor = Monitor; + this.MonitorStats = MonitorStats; + this.Check = Check; + this.PageSpeedCheck = PageSpeedCheck; + this.HardwareCheck = HardwareCheck; + this.stringService = stringService; + this.fs = fs; + this.path = path; + this.fileURLToPath = fileURLToPath; + this.ObjectId = ObjectId; + this.NormalizeData = NormalizeData; + this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails; -/** - * Get all monitors - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getAllMonitors = async (req, res) => { - try { - const monitors = await Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; + this.CHECK_MODEL_LOOKUP = { + http: Check, + ping: Check, + docker: Check, + port: Check, + pagespeed: PageSpeedCheck, + hardware: HardwareCheck, + }; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + + this.demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); } -}; -/** - * Function to calculate uptime duration based on the most recent check. - * @param {Array} checks Array of check objects. - * @returns {number} Uptime duration in ms. - */ -const calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; - - for (let i = checks.length - 1; i >= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; + // Helper + calculateUptimeDuration = (checks) => { + if (!checks || checks.length === 0) { + return 0; } - } + const latestCheck = new Date(checks[0].createdAt); + let latestDownCheck = 0; - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } + for (let i = checks.length - 1; i >= 0; i--) { + if (checks[i].status === false) { + latestDownCheck = new Date(checks[i].createdAt); + break; + } + } - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; -}; + // If no down check is found, uptime is from the last check to now + if (latestDownCheck === 0) { + return Date.now() - new Date(checks[checks.length - 1].createdAt); + } -/** - * Helper function to get duration since last check - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); -}; - -/** - * Helper function to get latestResponseTime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - return checks[0]?.responseTime ?? 0; -}; - -/** - * Helper function to get average response time - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ -const getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - - const validChecks = checks.filter((check) => typeof check.responseTime === "number"); - if (validChecks.length === 0) { - return 0; - } - const aggResponseTime = validChecks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / validChecks.length; -}; - -/** - * Helper function to get percentage 24h uptime - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; -}; - -/** - * Helper function to get all incidents - * @param {Array} checks Array of check objects. - * @returns {number} Timestamp of the most recent check. - */ - -const getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); -}; - -/** - * Get date range parameters - * @param {string} dateRange - 'day' | 'week' | 'month' | 'all' - * @returns {Object} Start and end dates - */ -const getDateRange = (dateRange) => { - const startDates = { - recent: new Date(new Date().setHours(new Date().getHours() - 2)), - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - all: new Date(0), - }; - return { - start: startDates[dateRange], - end: new Date(), - }; -}; - -/** - * Get checks for a monitor - * @param {string} monitorId - Monitor ID - * @param {Object} model - Check model to use - * @param {Object} dateRange - Date range parameters - * @param {number} sortOrder - Sort order (1 for ascending, -1 for descending) - * @returns {Promise} All checks and date-ranged checks - */ -const getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { - const indexSpec = { - monitorId: 1, - createdAt: sortOrder, // This will be 1 or -1 + // Otherwise the uptime is from the last check to the last down check + return latestCheck - latestDownCheck; }; - const [checksAll, checksForDateRange] = await Promise.all([ - model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), - model - .find({ - monitorId, - createdAt: { $gte: dateRange.start, $lte: dateRange.end }, - }) - .hint(indexSpec) - .lean(), - ]); + // Helper + getLastChecked = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + // Data is sorted newest->oldest, so last check is the most recent + return new Date() - new Date(checks[0].createdAt); + }; + getLatestResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } - return { checksAll, checksForDateRange }; -}; + return checks[0]?.responseTime ?? 0; + }; -/** - * Process checks for display - * @param {Array} checks - Checks to process - * @param {number} numToDisplay - Number of checks to display - * @param {boolean} normalize - Whether to normalize the data - * @returns {Array} Processed checks - */ -const processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { - let processedChecks = checks; - if (numToDisplay && checks.length > numToDisplay) { - const n = Math.ceil(checks.length / numToDisplay); - processedChecks = checks.filter((_, index) => index % n === 0); - } - return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; -}; + // Helper + getAverageResponseTime = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } -/** - * Get time-grouped checks based on date range - * @param {Array} checks Array of check objects - * @param {string} dateRange 'day' | 'week' | 'month' - * @returns {Object} Grouped checks by time period - */ -const groupChecksByTime = (checks, dateRange) => { - return checks.reduce((acc, check) => { - // Validate the date - const checkDate = new Date(check.createdAt); - if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + const validChecks = checks.filter((check) => typeof check.responseTime === "number"); + if (validChecks.length === 0) { + return 0; + } + const aggResponseTime = validChecks.reduce((sum, check) => { + return sum + check.responseTime; + }, 0); + return aggResponseTime / validChecks.length; + }; + + // Helper + getUptimePercentage = (checks) => { + if (!checks || checks.length === 0) { + return 0; + } + const upCount = checks.reduce((count, check) => { + return check.status === true ? count + 1 : count; + }, 0); + return (upCount / checks.length) * 100; + }; + + // Helper + getIncidents = (checks) => { + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + return checks.reduce((acc, check) => { + return check.status === false ? (acc += 1) : acc; + }, 0); + }; + + // Helper + getDateRange = (dateRange) => { + const startDates = { + recent: new Date(new Date().setHours(new Date().getHours() - 2)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + all: new Date(0), + }; + return { + start: startDates[dateRange], + end: new Date(), + }; + }; + + //Helper + getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => { + const indexSpec = { + monitorId: 1, + createdAt: sortOrder, // This will be 1 or -1 + }; + + const [checksAll, checksForDateRange] = await Promise.all([ + model.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(), + model + .find({ + monitorId, + createdAt: { $gte: dateRange.start, $lte: dateRange.end }, + }) + .hint(indexSpec) + .lean(), + ]); + + return { checksAll, checksForDateRange }; + }; + + // Helper + processChecksForDisplay = (normalizeData, checks, numToDisplay, normalize) => { + let processedChecks = checks; + if (numToDisplay && checks.length > numToDisplay) { + const n = Math.ceil(checks.length / numToDisplay); + processedChecks = checks.filter((_, index) => index % n === 0); + } + return normalize ? normalizeData(processedChecks, 1, 100) : processedChecks; + }; + + // Helper + groupChecksByTime = (checks, dateRange) => { + return checks.reduce((acc, check) => { + // Validate the date + const checkDate = new Date(check.createdAt); + if (Number.isNaN(checkDate.getTime()) || checkDate.getTime() === 0) { + return acc; + } + + const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; + + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); return acc; - } - - const time = dateRange === "day" ? checkDate.setMinutes(0, 0, 0) : checkDate.toISOString().split("T")[0]; - - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); -}; - -/** - * Calculate aggregate stats for a group of checks - * @param {Object} group Group of checks - * @returns {Object} Stats for the group - */ -const calculateGroupStats = (group) => { - const totalChecks = group.checks.length; - - const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); - - return { - time: group.time, - uptimePercentage: getUptimePercentage(group.checks), - totalChecks, - totalIncidents: group.checks.filter((check) => !check.status).length, - avgResponseTime: - checksWithResponseTime.length > 0 - ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length - : 0, + }, {}); }; -}; -/** - * Get uptime details by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getUptimeDetailsById = async ({ monitorId, dateRange, normalize }) => { - try { - const dates = getDateRange(dateRange); - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", + // Helper + calculateGroupStats = (group) => { + const totalChecks = group.checks.length; + + const checksWithResponseTime = group.checks.filter((check) => typeof check.responseTime === "number" && !Number.isNaN(check.responseTime)); + + return { + time: group.time, + uptimePercentage: this.getUptimePercentage(group.checks), + totalChecks, + totalIncidents: group.checks.filter((check) => !check.status).length, + avgResponseTime: + checksWithResponseTime.length > 0 + ? checksWithResponseTime.reduce((sum, check) => sum + check.responseTime, 0) / checksWithResponseTime.length + : 0, }; + }; - const dateString = formatLookup[dateRange]; - - const results = await Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); - - const monitorData = results[0]; - - monitorData.groupedUpChecks = NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); - - monitorData.groupedDownChecks = NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); - - const normalizedGroupChecks = NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); - - monitorData.groupedChecks = normalizedGroupChecks; - const monitorStats = await MonitorStats.findOne({ monitorId }); - return { monitorData, monitorStats }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUptimeDetailsById"; - throw error; - } -}; - -/** - * Get stats by monitor ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorStatsById = async ({ monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - // Get monitor, if we can't find it, abort with error - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); - } - - // Get query params - const sort = sortOrder === "asc" ? 1 : -1; - - // Get Checks for monitor in date range requested - const model = CHECK_MODEL_LOOKUP[monitor.type]; - const dates = getDateRange(dateRange); - const { checksAll, checksForDateRange } = await getMonitorChecks(monitorId, model, dates, sort); - - // Build monitor stats - const monitorStats = { - ...monitor.toObject(), - uptimeDuration: calculateUptimeDuration(checksAll), - lastChecked: getLastChecked(checksAll), - latestResponseTime: getLatestResponseTime(checksAll), - periodIncidents: getIncidents(checksForDateRange), - periodTotalChecks: checksForDateRange.length, - checks: processChecksForDisplay(NormalizeData, checksForDateRange, numToDisplay, normalize), - }; - - if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = getUptimePercentage(checksForDateRange); - const groupedChecks = groupChecksByTime(checksForDateRange, dateRange); - monitorStats.aggregateData = Object.values(groupedChecks).map(calculateGroupStats); - } - - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } -}; - -const getHardwareDetailsById = async ({ monitorId, dateRange }) => { - try { - const monitor = await Monitor.findById(monitorId); - const dates = getDateRange(dateRange); - const formatLookup = { - recent: "%Y-%m-%dT%H:%M:00Z", - day: "%Y-%m-%dT%H:00:00Z", - week: "%Y-%m-%dT%H:00:00Z", - month: "%Y-%m-%dT00:00:00Z", - }; - const dateString = formatLookup[dateRange]; - const hardwareStats = await HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); - - const monitorStats = { - ...monitor.toObject(), - stats: hardwareStats[0], - }; - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getHardwareDetailsById"; - throw error; - } -}; - -/** - * Get a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorById = async (monitorId) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - const error = new Error(stringService.getDbFindMonitorById(monitorId)); - error.status = 404; + getAllMonitors = async () => { + try { + const monitors = await this.Monitor.find(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllMonitors"; throw error; } + }; - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } -}; + getMonitorById = async (monitorId) => { + try { + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + const error = new Error(this.stringService.getDbFindMonitorById(monitorId)); + error.status = 404; + throw error; + } -const getMonitorsByIds = async (monitorIds) => { - try { - const objectIds = monitorIds.map((id) => new ObjectId(id)); - return await Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByIds"; - throw error; - } -}; - -const getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summaryResult = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); - const summary = summaryResult[0]; - - const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); - - const filteredMonitors = await Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ); - - const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { - if (!monitor.checks) { return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorById"; + throw error; } - monitor.checks = NormalizeData(monitor.checks, 10, 100); - return monitor; - }); + }; - return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; -}; - -const getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { - try { - const matchStage = { teamId: new ObjectId(teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; + getMonitorsByIds = async (monitorIds) => { + try { + const objectIds = monitorIds.map((id) => new this.ObjectId(id)); + return await this.Monitor.find({ _id: { $in: objectIds } }, { _id: 1, teamId: 1 }).lean(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByIds"; + throw error; } + }; + getUptimeDetailsById = async ({ monitorId, dateRange }) => { + try { + const dates = this.getDateRange(dateRange); + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; - if (explain === true) { - return Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + const dateString = formatLookup[dateRange]; + + const results = await this.Check.aggregate(buildUptimeDetailsPipeline(monitorId, dates, dateString)); + + const monitorData = results[0]; + + monitorData.groupedUpChecks = this.NormalizeDataUptimeDetails(monitorData.groupedUpChecks, 10, 100); + + monitorData.groupedDownChecks = this.NormalizeDataUptimeDetails(monitorData.groupedDownChecks, 10, 100); + + const normalizedGroupChecks = this.NormalizeDataUptimeDetails(monitorData.groupedChecks, 10, 100); + + monitorData.groupedChecks = normalizedGroupChecks; + const monitorStats = await this.MonitorStats.findOne({ monitorId }); + return { monitorData, monitorStats }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUptimeDetailsById"; + throw error; } + }; - const queryResult = await Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); - const { monitors, summary } = queryResult?.[0] ?? {}; - return { monitors, summary }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; - } -}; + getMonitorStatsById = async ({ monitorId, sortOrder, dateRange, numToDisplay, normalize }) => { + try { + // Get monitor, if we can't find it, abort with error + const monitor = await this.Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } -const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { - try { + // Get query params + const sort = sortOrder === "asc" ? 1 : -1; + + // Get Checks for monitor in date range requested + const model = this.CHECK_MODEL_LOOKUP[monitor.type]; + const dates = this.getDateRange(dateRange); + const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, model, dates, sort); + + // Build monitor stats + const monitorStats = { + ...monitor.toObject(), + uptimeDuration: this.calculateUptimeDuration(checksAll), + lastChecked: this.getLastChecked(checksAll), + latestResponseTime: this.getLatestResponseTime(checksAll), + periodIncidents: this.getIncidents(checksForDateRange), + periodTotalChecks: checksForDateRange.length, + checks: this.processChecksForDisplay(this.NormalizeData, checksForDateRange, numToDisplay, normalize), + }; + + if (monitor.type === "http" || monitor.type === "ping" || monitor.type === "docker" || monitor.type === "port") { + // HTTP/PING Specific stats + monitorStats.periodAvgResponseTime = this.getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = this.getUptimePercentage(checksForDateRange); + const groupedChecks = this.groupChecksByTime(checksForDateRange, dateRange); + monitorStats.aggregateData = Object.values(groupedChecks).map(this.calculateGroupStats); + } + + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorStatsById"; + throw error; + } + }; + + getHardwareDetailsById = async ({ monitorId, dateRange }) => { + try { + const monitor = await this.Monitor.findById(monitorId); + const dates = this.getDateRange(dateRange); + const formatLookup = { + recent: "%Y-%m-%dT%H:%M:00Z", + day: "%Y-%m-%dT%H:00:00Z", + week: "%Y-%m-%dT%H:00:00Z", + month: "%Y-%m-%dT00:00:00Z", + }; + const dateString = formatLookup[dateRange]; + const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString)); + + const monitorStats = { + ...monitor.toObject(), + stats: hardwareStats[0], + }; + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getHardwareDetailsById"; + throw error; + } + }; + + getMonitorsByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId }) => { limit = parseInt(limit); page = parseInt(page); rowsPerPage = parseInt(rowsPerPage); @@ -498,28 +372,18 @@ const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, f order = "asc"; } // Build match stage - const matchStage = { teamId: new ObjectId(teamId) }; + const matchStage = { teamId: new this.ObjectId(teamId) }; if (type !== undefined) { matchStage.type = Array.isArray(type) ? { $in: type } : type; } - if (explain === true) { - return Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - } + const summaryResult = await this.Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })); + const summary = summaryResult[0]; - const queryResult = await Monitor.aggregate( - buildMonitorsWithChecksByTeamIdPipeline({ + const monitors = await this.Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })); + + const filteredMonitors = await this.Monitor.aggregate( + buildFilteredMonitorsByTeamIdPipeline({ matchStage, filter, page, @@ -530,237 +394,220 @@ const getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, f type, }) ); - const monitors = queryResult[0]?.monitors; - const count = queryResult[0]?.count; - const normalizedFilteredMonitors = monitors.map((monitor) => { + + const normalizedFilteredMonitors = filteredMonitors.map((monitor) => { if (!monitor.checks) { return monitor; } - monitor.checks = NormalizeData(monitor.checks, 10, 100); + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); return monitor; }); - return { count, monitors: normalizedFilteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsWithChecksByTeamId"; - throw error; - } -}; -/** - * Create a monitor - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const createMonitor = async ({ body, teamId, userId }) => { - try { - const monitor = new Monitor({ ...body, teamId, userId }); - const saved = await monitor.save(); - return saved; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } -}; + return { summary, monitors, filteredMonitors: normalizedFilteredMonitors }; + }; -/** - * Create bulk monitors - * @async - * @param {Express.Request} req - * @returns {Promise} - * @throws {Error} - */ -const createBulkMonitors = async (req) => { - try { - const monitors = req.map((item) => new Monitor({ ...item, notifications: undefined })); - await Monitor.bulkSave(monitors); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createBulkMonitors"; - throw error; - } -}; + getMonitorsAndSummaryByTeamId = async ({ type, explain, teamId }) => { + try { + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } -/** - * Delete a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const deleteMonitor = async ({ teamId, monitorId }) => { - const stringService = ServiceRegistry.get(StringService.SERVICE_NAME); - try { - const deletedMonitor = await Monitor.findOneAndDelete({ _id: monitorId, teamId }); + if (explain === true) { + return this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); + } - if (!deletedMonitor) { - throw new Error(stringService.getDbFindMonitorById(monitorId)); + const queryResult = await this.Monitor.aggregate(buildMonitorsAndSummaryByTeamIdPipeline({ matchStage })); + const { monitors, summary } = queryResult?.[0] ?? {}; + return { monitors, summary }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsAndSummaryByTeamId"; + throw error; } + }; - return deletedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } -}; + getMonitorsWithChecksByTeamId = async ({ limit, type, page, rowsPerPage, filter, field, order, teamId, explain }) => { + try { + limit = parseInt(limit); + page = parseInt(page); + rowsPerPage = parseInt(rowsPerPage); + if (field === undefined) { + field = "name"; + order = "asc"; + } + // Build match stage + const matchStage = { teamId: new this.ObjectId(teamId) }; + if (type !== undefined) { + matchStage.type = Array.isArray(type) ? { $in: type } : type; + } -/** - * DELETE ALL MONITORS (TEMP) - */ + if (explain === true) { + return this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ).explain("executionStats"); + } -const deleteAllMonitors = async (teamId) => { - try { - const monitors = await Monitor.find({ teamId }); - const { deletedCount } = await Monitor.deleteMany({ teamId }); + const queryResult = await this.Monitor.aggregate( + buildMonitorsWithChecksByTeamIdPipeline({ + matchStage, + filter, + page, + rowsPerPage, + field, + order, + limit, + type, + }) + ); + const monitors = queryResult[0]?.monitors; + const count = queryResult[0]?.count; + const normalizedFilteredMonitors = monitors.map((monitor) => { + if (!monitor.checks) { + return monitor; + } + monitor.checks = this.NormalizeData(monitor.checks, 10, 100); + return monitor; + }); + return { count, monitors: normalizedFilteredMonitors }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsWithChecksByTeamId"; + throw error; + } + }; + createMonitor = async ({ body, teamId, userId }) => { + try { + const monitor = new this.Monitor({ ...body, teamId, userId }); + const saved = await monitor.save(); + return saved; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMonitor"; + throw error; + } + }; - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } -}; + createBulkMonitors = async (req) => { + try { + const monitors = req.map((item) => new this.Monitor({ ...item, notifications: undefined })); + await this.Monitor.bulkSave(monitors); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createBulkMonitors"; + throw error; + } + }; -/** - * Delete all monitors associated with a user ID - * @async - * @param {string} userId - The ID of the user whose monitors are to be deleted. - * @returns {Promise} A promise that resolves when the operation is complete. - */ -const deleteMonitorsByUserId = async (userId) => { - try { - const result = await Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } -}; + deleteMonitor = async ({ teamId, monitorId }) => { + try { + const deletedMonitor = await this.Monitor.findOneAndDelete({ _id: monitorId, teamId }); -/** - * Edit a monitor by ID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const editMonitor = async ({ monitorId, body }) => { - try { - const editedMonitor = await Monitor.findByIdAndUpdate(monitorId, body, { - new: true, - }); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } -}; + if (!deletedMonitor) { + throw new Error(this.stringService.getDbFindMonitorById(monitorId)); + } -const addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } -}; + return deletedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitor"; + throw error; + } + }; -const pauseMonitor = async ({ monitorId }) => { - try { - const monitor = await Monitor.findOneAndUpdate( - { _id: monitorId }, - [ - { - $set: { - isActive: { $not: "$isActive" }, - status: "$$REMOVE", + deleteAllMonitors = async (teamId) => { + try { + const monitors = await this.Monitor.find({ teamId }); + const { deletedCount } = await this.Monitor.deleteMany({ teamId }); + + return { monitors, deletedCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllMonitors"; + throw error; + } + }; + + deleteMonitorsByUserId = async (userId) => { + try { + const result = await this.Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitorsByUserId"; + throw error; + } + }; + + editMonitor = async ({ monitorId, body }) => { + try { + const editedMonitor = await this.Monitor.findByIdAndUpdate(monitorId, body, { + new: true, + }); + return editedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMonitor"; + throw error; + } + }; + + addDemoMonitors = async (userId, teamId) => { + try { + const demoMonitors = JSON.parse(this.fs.readFileSync(this.demoMonitorsPath, "utf8")); + + const demoMonitorsToInsert = demoMonitors.map((monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const insertedMonitors = await this.Monitor.insertMany(demoMonitorsToInsert); + return insertedMonitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "addDemoMonitors"; + throw error; + } + }; + + pauseMonitor = async ({ monitorId }) => { + try { + const monitor = await this.Monitor.findOneAndUpdate( + { _id: monitorId }, + [ + { + $set: { + isActive: { $not: "$isActive" }, + status: "$$REMOVE", + }, }, - }, - ], - { new: true } - ); + ], + { new: true } + ); - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "pauseMonitor"; - throw error; - } -}; + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "pauseMonitor"; + throw error; + } + }; +} -export { - getAllMonitors, - getMonitorStatsById, - getMonitorById, - getMonitorsByIds, - getMonitorsByTeamId, - getMonitorsAndSummaryByTeamId, - getMonitorsWithChecksByTeamId, - getUptimeDetailsById, - createMonitor, - createBulkMonitors, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, - getHardwareDetailsById, - pauseMonitor, -}; - -// Helper functions -export { - calculateUptimeDuration, - getLastChecked, - getLatestResponseTime, - getAverageResponseTime, - getUptimePercentage, - getIncidents, - getDateRange, - getMonitorChecks, - processChecksForDisplay, - groupChecksByTime, - calculateGroupStats, -}; - -// limit 25 -// page 1 -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 25 -// sort { name: 1 } -// filteredMonitors [] - -// limit 25 -// page NaN -// rowsPerPage 25 -// filter undefined -// field name -// order asc -// skip 0 -// sort { name: 1 } +export default MonitorModule; diff --git a/server/src/service/business/checkService.js b/server/src/service/business/checkService.js index c5a4df88d..7966fdbb4 100644 --- a/server/src/service/business/checkService.js +++ b/server/src/service/business/checkService.js @@ -23,7 +23,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); @@ -95,7 +95,7 @@ class CheckService { throw this.errorService.createBadRequestError("No monitor ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); } @@ -118,7 +118,7 @@ class CheckService { throw this.errorService.createBadRequestError("No team ID in request"); } - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor) { throw this.errorService.createNotFoundError("Monitor not found"); diff --git a/server/src/service/business/maintenanceWindowService.js b/server/src/service/business/maintenanceWindowService.js index 1e8c09739..9d52702e9 100644 --- a/server/src/service/business/maintenanceWindowService.js +++ b/server/src/service/business/maintenanceWindowService.js @@ -16,7 +16,7 @@ class MaintenanceWindowService { createMaintenanceWindow = async ({ teamId, body }) => { const monitorIds = body.monitors; - const monitors = await this.db.getMonitorsByIds(monitorIds); + const monitors = await this.db.monitorModule.getMonitorsByIds(monitorIds); const unauthorizedMonitors = monitors.filter((monitor) => !monitor.teamId.equals(teamId)); diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index f90c21470..e9254948a 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -20,20 +20,20 @@ class MonitorService { } verifyTeamAccess = async ({ teamId, monitorId }) => { - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); if (!monitor?.teamId?.equals(teamId)) { throw this.errorService.createAuthorizationError(); } }; getAllMonitors = async () => { - const monitors = await this.db.getAllMonitors(); + const monitors = await this.db.monitorModule.getAllMonitors(); return monitors; }; getUptimeDetailsById = async ({ teamId, monitorId, dateRange, normalize }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const data = await this.db.getUptimeDetailsById({ + const data = await this.db.monitorModule.getUptimeDetailsById({ monitorId, dateRange, normalize, @@ -44,7 +44,7 @@ class MonitorService { getMonitorStatsById = async ({ teamId, monitorId, limit, sortOrder, dateRange, numToDisplay, normalize }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitorStats = await this.db.getMonitorStatsById({ + const monitorStats = await this.db.monitorModule.getMonitorStatsById({ monitorId, limit, sortOrder, @@ -58,14 +58,14 @@ class MonitorService { getHardwareDetailsById = async ({ teamId, monitorId, dateRange }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.getHardwareDetailsById({ monitorId, dateRange }); + const monitor = await this.db.monitorModule.getHardwareDetailsById({ monitorId, dateRange }); return monitor; }; getMonitorById = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); return monitor; }; @@ -203,7 +203,7 @@ class MonitorService { }; getMonitorsByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order }) => { - const monitors = await this.db.getMonitorsByTeamId({ + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ limit, type, page, @@ -217,7 +217,7 @@ class MonitorService { }; getMonitorsAndSummaryByTeamId = async ({ teamId, type, explain }) => { - const result = await this.db.getMonitorsAndSummaryByTeamId({ + const result = await this.db.monitorModule.getMonitorsAndSummaryByTeamId({ type, explain, teamId, @@ -226,7 +226,7 @@ class MonitorService { }; getMonitorsWithChecksByTeamId = async ({ teamId, limit, type, page, rowsPerPage, filter, field, order, explain }) => { - const result = await this.db.getMonitorsWithChecksByTeamId({ + const result = await this.db.monitorModule.getMonitorsWithChecksByTeamId({ limit, type, page, @@ -241,7 +241,7 @@ class MonitorService { }; exportMonitorsToCSV = async ({ teamId }) => { - const monitors = await this.db.getMonitorsByTeamId({ teamId }); + const monitors = await this.db.monitorModule.getMonitorsByTeamId({ teamId }); if (!monitors || monitors.length === 0) { throw this.errorService.createNotFoundError("No monitors to export"); diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index 04c14c477..3ffa4c957 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -181,7 +181,7 @@ class UserService { } // 1. Find all the monitors associated with the team ID if superadmin - const result = await this.db.getMonitorsByTeamId({ + const result = await this.db.monitorModule.getMonitorsByTeamId({ teamId: teamId, }); diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js index f5661c9e6..60bcb0754 100644 --- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js +++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js @@ -33,7 +33,7 @@ class SuperSimpleQueue { this.scheduler.start(); this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); - const monitors = await this.db.getAllMonitors(); + const monitors = await this.db.monitorModule.getAllMonitors(); for (const monitor of monitors) { await this.addJob(monitor._id, monitor); } diff --git a/server/src/service/infrastructure/statusService.js b/server/src/service/infrastructure/statusService.js index 3e2330380..672eb55fc 100755 --- a/server/src/service/infrastructure/statusService.js +++ b/server/src/service/infrastructure/statusService.js @@ -122,7 +122,7 @@ class StatusService { this.insertCheck(networkResponse); try { const { monitorId, status, code } = networkResponse; - const monitor = await this.db.getMonitorById(monitorId); + const monitor = await this.db.monitorModule.getMonitorById(monitorId); // Update running stats this.updateRunningStats({ monitor, networkResponse }); From 16ebe4b1aba71ff3f8bd2b534dd93732663ae2a5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 15:06:19 -0700 Subject: [PATCH 20/30] module -> class --- server/src/config/services.js | 5 ++ server/src/db/mongo/MongoDB.js | 2 + .../db/mongo/modules/networkCheckModule.js | 66 ++++++++----------- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 44fb6491b..762a85d26 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -51,6 +51,7 @@ import StatusPage from "../db/models/StatusPage.js"; import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; import MonitorStats from "../db/models/MonitorStats.js"; +import NetworkCheck from "../db/models/NetworkCheck.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; @@ -59,6 +60,8 @@ import UserModule from "../db/mongo/modules/userModule.js"; import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; import MonitorModule from "../db/mongo/modules/monitorModule.js"; +import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; + export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); ServiceRegistry.instance = serviceRegistry; @@ -89,6 +92,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService NormalizeData, NormalizeDataUptimeDetails, }); + const networkCheckModule = new NetworkCheckModule({ NetworkCheck }); const db = new MongoDB({ logger, envSettings, @@ -99,6 +103,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService hardwareCheckModule, maintenanceWindowModule, monitorModule, + networkCheckModule, }); await db.connect(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index f73db6163..96f8062c3 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -40,6 +40,7 @@ class MongoDB { hardwareCheckModule, maintenanceWindowModule, monitorModule, + networkCheckModule, }) { this.logger = logger; this.envSettings = envSettings; @@ -56,6 +57,7 @@ class MongoDB { Object.assign(this, settingsModule); this.statusPageModule = statusPageModule; Object.assign(this, diagnosticModule); + this.networkCheckModule = networkCheckModule; } get serviceName() { diff --git a/server/src/db/mongo/modules/networkCheckModule.js b/server/src/db/mongo/modules/networkCheckModule.js index 12a86e73b..917e68440 100644 --- a/server/src/db/mongo/modules/networkCheckModule.js +++ b/server/src/db/mongo/modules/networkCheckModule.js @@ -1,44 +1,30 @@ -import NetworkCheck from "../../models/NetworkCheck.js"; - const SERVICE_NAME = "networkCheckModule"; -/** - * Creates and saves a new network check document to the database. - * @async - * @param {object} networkCheckData - The data for the new network check. This should conform to the NetworkCheckSchema. - * @param {string} networkCheckData.monitorId - The ID of the monitor associated with this check. - * @returns {Promise} A promise that resolves to the newly created network check document. - * @throws {Error} Throws an error if the database operation fails. - */ -const createNetworkCheck = async (networkCheckData) => { - try { - const networkCheck = await new NetworkCheck(networkCheckData); - await networkCheck.save(); - return networkCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNetworkCheck"; - throw error; +class NetworkCheckModule { + constructor({ NetworkCheck }) { + this.NetworkCheck = NetworkCheck; } -}; + createNetworkCheck = async (networkCheckData) => { + try { + const networkCheck = await new this.NetworkCheck(networkCheckData); + await networkCheck.save(); + return networkCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNetworkCheck"; + throw error; + } + }; + getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { + try { + const networkChecks = await this.NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit); + return networkChecks; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNetworkChecksByMonitorId"; + throw error; + } + }; +} -/** - * Retrieves a list of network checks for a specific monitor, sorted by most recent. - * @async - * @param {string} monitorId - The ID of the monitor to retrieve checks for. - * @param {number} [limit=100] - The maximum number of checks to return. Defaults to 100. - * @returns {Promise>} A promise that resolves to an array of network check documents. - * @throws {Error} Throws an error if the database operation fails. - */ -const getNetworkChecksByMonitorId = async (monitorId, limit = 100) => { - try { - const networkChecks = await NetworkCheck.find({ monitorId }).sort({ createdAt: -1 }).limit(limit); - return networkChecks; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNetworkChecksByMonitorId"; - throw error; - } -}; - -export { createNetworkCheck, getNetworkChecksByMonitorId }; +export default NetworkCheckModule; From 7bf96f6194a39f17e42137c884b00979d1cfd4b3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 15:14:36 -0700 Subject: [PATCH 21/30] module -> class --- server/src/config/services.js | 5 + .../src/controllers/notificationController.js | 14 +- server/src/db/mongo/MongoDB.js | 3 +- .../db/mongo/modules/notificationModule.js | 213 ++++++++---------- server/src/service/business/monitorService.js | 2 +- .../infrastructure/notificationService.js | 2 +- 6 files changed, 110 insertions(+), 129 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 762a85d26..2704baad0 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -52,6 +52,7 @@ import Team from "../db/models/Team.js"; import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; import MonitorStats from "../db/models/MonitorStats.js"; import NetworkCheck from "../db/models/NetworkCheck.js"; +import Notification from "../db/models/Notification.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; @@ -61,6 +62,7 @@ import HardwareCheckModule from "../db/mongo/modules/hardwareCheckModule.js"; import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule.js"; import MonitorModule from "../db/mongo/modules/monitorModule.js"; import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; +import NotificationModule from "../db/mongo/modules/notificationModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -93,6 +95,8 @@ export const initializeServices = async ({ logger, envSettings, settingsService NormalizeDataUptimeDetails, }); const networkCheckModule = new NetworkCheckModule({ NetworkCheck }); + const notificationModule = new NotificationModule({ Notification, Monitor }); + const db = new MongoDB({ logger, envSettings, @@ -104,6 +108,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService maintenanceWindowModule, monitorModule, networkCheckModule, + notificationModule, }); await db.connect(); diff --git a/server/src/controllers/notificationController.js b/server/src/controllers/notificationController.js index bffb8ee6d..e00dd5b6f 100755 --- a/server/src/controllers/notificationController.js +++ b/server/src/controllers/notificationController.js @@ -53,7 +53,7 @@ class NotificationController extends BaseController { body.userId = userId; body.teamId = teamId; - const notification = await this.db.createNotification(body); + const notification = await this.db.notificationModule.createNotification(body); return res.success({ msg: "Notification created successfully", data: notification, @@ -70,7 +70,7 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notifications = await this.db.getNotificationsByTeamId(teamId); + const notifications = await this.db.notificationModule.getNotificationsByTeamId(teamId); return res.success({ msg: "Notifications fetched successfully", @@ -88,12 +88,12 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } - await this.db.deleteNotificationById(req.params.id); + await this.db.notificationModule.deleteNotificationById(req.params.id); return res.success({ msg: "Notification deleted successfully", }); @@ -104,7 +104,7 @@ class NotificationController extends BaseController { getNotificationById = this.asyncHandler( async (req, res) => { - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); const teamId = req?.user?.teamId; if (!teamId) { @@ -134,13 +134,13 @@ class NotificationController extends BaseController { throw this.errorService.createBadRequestError("Team ID is required"); } - const notification = await this.db.getNotificationById(req.params.id); + const notification = await this.db.notificationModule.getNotificationById(req.params.id); if (!notification.teamId.equals(teamId)) { throw this.errorService.createAuthorizationError(); } - const editedNotification = await this.db.editNotification(req.params.id, req.body); + const editedNotification = await this.db.notificationModule.editNotification(req.params.id, req.body); return res.success({ msg: "Notification updated successfully", data: editedNotification, diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 96f8062c3..2e13e0a42 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -41,6 +41,7 @@ class MongoDB { maintenanceWindowModule, monitorModule, networkCheckModule, + notificationModule, }) { this.logger = logger; this.envSettings = envSettings; @@ -53,7 +54,7 @@ class MongoDB { this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; this.monitorModule = monitorModule; - Object.assign(this, notificationModule); + this.notificationModule = notificationModule; Object.assign(this, settingsModule); this.statusPageModule = statusPageModule; Object.assign(this, diagnosticModule); diff --git a/server/src/db/mongo/modules/notificationModule.js b/server/src/db/mongo/modules/notificationModule.js index 2348a6321..3c6edb54a 100755 --- a/server/src/db/mongo/modules/notificationModule.js +++ b/server/src/db/mongo/modules/notificationModule.js @@ -1,125 +1,100 @@ -import Notification from "../../models/Notification.js"; -import Monitor from "../../models/Monitor.js"; +// import Notification from "../../models/Notification.js"; +// import Monitor from "../../models/Monitor.js"; const SERVICE_NAME = "notificationModule"; -/** - * Creates a new notification. - * @param {Object} notificationData - The data for the new notification. - * @param {mongoose.Types.ObjectId} notificationData.monitorId - The ID of the monitor. - * @param {string} notificationData.type - The type of the notification (e.g., "email", "sms"). - * @param {string} [notificationData.address] - The address for the notification (if applicable). - * @param {string} [notificationData.phone] - The phone number for the notification (if applicable). - * @returns {Promise} The created notification. - * @throws Will throw an error if the notification cannot be created. - */ -const createNotification = async (notificationData) => { - try { - const notification = await new Notification({ ...notificationData }).save(); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNotification"; - throw error; - } -}; -const getNotificationsByTeamId = async (teamId) => { - try { - const notifications = await Notification.find({ teamId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByTeamId"; - throw error; +class NotificationModule { + constructor({ Notification, Monitor }) { + this.Notification = Notification; + this.Monitor = Monitor; } -}; - -const getNotificationsByIds = async (notificationIds) => { - try { - const notifications = await Notification.find({ _id: { $in: notificationIds } }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByIds"; - throw error; - } -}; - -/** - * Retrieves notifications by monitor ID. - * @param {mongoose.Types.ObjectId} monitorId - The ID of the monitor. - * @returns {Promise>} An array of notifications. - * @throws Will throw an error if the notifications cannot be retrieved. - */ -const getNotificationsByMonitorId = async (monitorId) => { - try { - const notifications = await Notification.find({ monitorId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByMonitorId"; - throw error; - } -}; - -const deleteNotificationsByMonitorId = async (monitorId) => { - try { - const result = await Notification.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationsByMonitorId"; - throw error; - } -}; - -const deleteNotificationById = async (id) => { - try { - const notification = await Notification.findById(id); - if (!notification) { - throw new Error("Notification not found"); + createNotification = async (notificationData) => { + try { + const notification = await new this.Notification({ ...notificationData }).save(); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNotification"; + throw error; } + }; + getNotificationsByTeamId = async (teamId) => { + try { + const notifications = await this.Notification.find({ teamId }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByTeamId"; + throw error; + } + }; + getNotificationsByIds = async (notificationIds) => { + try { + const notifications = await this.Notification.find({ _id: { $in: notificationIds } }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByIds"; + throw error; + } + }; + getNotificationsByMonitorId = async (monitorId) => { + try { + const notifications = await this.Notification.find({ monitorId }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByMonitorId"; + throw error; + } + }; + deleteNotificationsByMonitorId = async (monitorId) => { + try { + const result = await this.Notification.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteNotificationsByMonitorId"; + throw error; + } + }; + deleteNotificationById = async (id) => { + try { + const notification = await this.Notification.findById(id); + if (!notification) { + throw new Error("Notification not found"); + } - const result = await Notification.findByIdAndDelete(id); - await Monitor.updateMany({ notifications: id }, { $pull: { notifications: id } }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationById"; - throw error; - } -}; + const result = await this.Notification.findByIdAndDelete(id); + await this.Monitor.updateMany({ notifications: id }, { $pull: { notifications: id } }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteNotificationById"; + throw error; + } + }; + getNotificationById = async (id) => { + try { + const notification = await this.Notification.findById(id); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationById"; + throw error; + } + }; + editNotification = async (id, notificationData) => { + try { + const notification = await this.Notification.findByIdAndUpdate(id, notificationData, { + new: true, + }); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editNotification"; + throw error; + } + }; +} -const getNotificationById = async (id) => { - try { - const notification = await Notification.findById(id); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationById"; - throw error; - } -}; - -const editNotification = async (id, notificationData) => { - try { - const notification = await Notification.findByIdAndUpdate(id, notificationData, { - new: true, - }); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editNotification"; - throw error; - } -}; - -export { - createNotification, - getNotificationsByTeamId, - getNotificationsByIds, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, - deleteNotificationById, - getNotificationById, - editNotification, -}; +export default NotificationModule; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index e9254948a..f9e5a8875 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -155,7 +155,7 @@ class MonitorService { await this.jobQueue.deleteJob(monitor); await this.db.checkModule.deleteChecks(monitor._id); await this.db.deletePageSpeedChecksByMonitorId(monitor._id); - await this.db.deleteNotificationsByMonitorId(monitor._id); + await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); } catch (error) { this.logger.warn({ message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`, diff --git a/server/src/service/infrastructure/notificationService.js b/server/src/service/infrastructure/notificationService.js index 6af4fd20a..8c34519f3 100644 --- a/server/src/service/infrastructure/notificationService.js +++ b/server/src/service/infrastructure/notificationService.js @@ -81,7 +81,7 @@ class NotificationService { } async notifyAll({ notificationIDs, subject, html, content }) { - const notifications = await this.db.getNotificationsByIds(notificationIDs); + const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs); // Map each notification to a test promise const promises = notifications.map(async (notification) => { From b5f4c041658a50aa927ec79ebee26b088146d58f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 15:21:22 -0700 Subject: [PATCH 22/30] module->class --- server/src/config/services.js | 3 + server/src/db/mongo/MongoDB.js | 3 +- .../db/mongo/modules/pageSpeedCheckModule.js | 80 +++++++------------ server/src/service/business/monitorService.js | 2 +- .../service/infrastructure/bufferService.js | 2 +- 5 files changed, 34 insertions(+), 56 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 2704baad0..93ad0ce46 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -63,6 +63,7 @@ import MaintenanceWindowModule from "../db/mongo/modules/maintenanceWindowModule import MonitorModule from "../db/mongo/modules/monitorModule.js"; import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; import NotificationModule from "../db/mongo/modules/notificationModule.js"; +import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -96,6 +97,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService }); const networkCheckModule = new NetworkCheckModule({ NetworkCheck }); const notificationModule = new NotificationModule({ Notification, Monitor }); + const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck }); const db = new MongoDB({ logger, @@ -109,6 +111,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService monitorModule, networkCheckModule, notificationModule, + pageSpeedCheckModule, }); await db.connect(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 2e13e0a42..de6ca0c4e 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -42,6 +42,7 @@ class MongoDB { monitorModule, networkCheckModule, notificationModule, + pageSpeedCheckModule, }) { this.logger = logger; this.envSettings = envSettings; @@ -49,7 +50,7 @@ class MongoDB { this.inviteModule = inviteModule; Object.assign(this, recoveryModule); Object.assign(this, monitorModule); - Object.assign(this, pageSpeedCheckModule); + this.pageSpeedCheckModule = pageSpeedCheckModule; this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; diff --git a/server/src/db/mongo/modules/pageSpeedCheckModule.js b/server/src/db/mongo/modules/pageSpeedCheckModule.js index 9169a0836..b2a3eb71d 100755 --- a/server/src/db/mongo/modules/pageSpeedCheckModule.js +++ b/server/src/db/mongo/modules/pageSpeedCheckModule.js @@ -1,57 +1,31 @@ -import PageSpeedCheck from "../../models/PageSpeedCheck.js"; +// import PageSpeedCheck from "../../models/PageSpeedCheck.js"; const SERVICE_NAME = "pageSpeedCheckModule"; -/** - * Create a PageSpeed check for a monitor - * @async - * @param {Object} pageSpeedCheckData - * @param {string} pageSpeedCheckData.monitorId - * @param {number} pageSpeedCheckData.accessibility - * @param {number} pageSpeedCheckData.bestPractices - * @param {number} pageSpeedCheckData.seo - * @param {number} pageSpeedCheckData.performance - * @returns {Promise} - * @throws {Error} - */ -const createPageSpeedCheck = async (pageSpeedCheckData) => { - try { - const pageSpeedCheck = await new PageSpeedCheck({ - ...pageSpeedCheckData, - }).save(); - return pageSpeedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } -}; -const createPageSpeedChecks = async (pageSpeedChecks) => { - try { - await PageSpeedCheck.insertMany(pageSpeedChecks, { ordered: false }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } -}; -/** - * Delete all PageSpeed checks for a monitor - * @async - * @param {string} monitorId - * @returns {number} - * @throws {Error} - */ - -const deletePageSpeedChecksByMonitorId = async (monitorId) => { - try { - const result = await PageSpeedCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deletePageSpeedChecksByMonitorId"; - throw error; +class PageSpeedCheckModule { + constructor({ PageSpeedCheck }) { + this.PageSpeedCheck = PageSpeedCheck; } -}; -export { createPageSpeedCheck, createPageSpeedChecks, deletePageSpeedChecksByMonitorId }; + createPageSpeedChecks = async (pageSpeedChecks) => { + try { + await this.PageSpeedCheck.insertMany(pageSpeedChecks, { ordered: false }); + return true; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createPageSpeedCheck"; + throw error; + } + }; + + deletePageSpeedChecksByMonitorId = async (monitorId) => { + try { + const result = await this.PageSpeedCheck.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deletePageSpeedChecksByMonitorId"; + throw error; + } + }; +} +export default PageSpeedCheckModule; diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index f9e5a8875..c52b5cadf 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -154,7 +154,7 @@ class MonitorService { try { await this.jobQueue.deleteJob(monitor); await this.db.checkModule.deleteChecks(monitor._id); - await this.db.deletePageSpeedChecksByMonitorId(monitor._id); + await this.db.pageSpeedCheckModule.deletePageSpeedChecksByMonitorId(monitor._id); await this.db.notificationsModule.deleteNotificationsByMonitorId(monitor._id); } catch (error) { this.logger.warn({ diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js index b29cf533b..46da6bef3 100755 --- a/server/src/service/infrastructure/bufferService.js +++ b/server/src/service/infrastructure/bufferService.js @@ -22,7 +22,7 @@ class BufferService { }; this.OPERATION_MAP = { checks: this.db.checkModule.createChecks, - pagespeedChecks: this.db.createPageSpeedChecks, + pagespeedChecks: this.db.pageSpeedCheckModule.createPageSpeedChecks, hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks, }; From 48cf8a5e73c4c256eb699ddd5622ff402287509e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 15:34:03 -0700 Subject: [PATCH 23/30] module -> class --- server/src/config/services.js | 4 + server/src/db/mongo/MongoDB.js | 3 +- server/src/db/mongo/modules/recoveryModule.js | 152 +++++++++--------- server/src/service/business/userService.js | 6 +- 4 files changed, 86 insertions(+), 79 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 93ad0ce46..21608fd59 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -53,6 +53,7 @@ import MaintenanceWindow from "../db/models/MaintenanceWindow.js"; import MonitorStats from "../db/models/MonitorStats.js"; import NetworkCheck from "../db/models/NetworkCheck.js"; import Notification from "../db/models/Notification.js"; +import RecoveryToken from "../db/models/RecoveryToken.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; @@ -64,6 +65,7 @@ import MonitorModule from "../db/mongo/modules/monitorModule.js"; import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; import NotificationModule from "../db/mongo/modules/notificationModule.js"; import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js"; +import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -98,6 +100,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const networkCheckModule = new NetworkCheckModule({ NetworkCheck }); const notificationModule = new NotificationModule({ Notification, Monitor }); const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck }); + const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService }); const db = new MongoDB({ logger, @@ -112,6 +115,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService networkCheckModule, notificationModule, pageSpeedCheckModule, + recoveryModule, }); await db.connect(); diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index de6ca0c4e..49d54fc48 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -43,12 +43,13 @@ class MongoDB { networkCheckModule, notificationModule, pageSpeedCheckModule, + recoveryModule, }) { this.logger = logger; this.envSettings = envSettings; this.userModule = userModule; this.inviteModule = inviteModule; - Object.assign(this, recoveryModule); + this.recoveryModule = recoveryModule; Object.assign(this, monitorModule); this.pageSpeedCheckModule = pageSpeedCheckModule; this.hardwareCheckModule = hardwareCheckModule; diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/mongo/modules/recoveryModule.js index 2678f49c4..81e4a5a97 100755 --- a/server/src/db/mongo/modules/recoveryModule.js +++ b/server/src/db/mongo/modules/recoveryModule.js @@ -1,86 +1,88 @@ -import UserModel from "../../models/User.js"; -import RecoveryToken from "../../models/RecoveryToken.js"; -import crypto from "crypto"; -import serviceRegistry from "../../../service/system/serviceRegistry.js"; -import StringService from "../../../service/system/stringService.js"; +// import UserModel from "../../models/User.js"; +// import RecoveryToken from "../../models/RecoveryToken.js"; +// import crypto from "crypto"; +// import serviceRegistry from "../../../service/system/serviceRegistry.js"; +// import StringService from "../../../service/system/stringService.js"; const SERVICE_NAME = "recoveryModule"; -/** - * Request a recovery token - * @async - * @param {string} email - * @returns {Promise} - * @throws {Error} - */ -const requestRecoveryToken = async (email) => { - try { - // Delete any existing tokens - await RecoveryToken.deleteMany({ email }); - let recoveryToken = new RecoveryToken({ - email, - token: crypto.randomBytes(32).toString("hex"), - }); - await recoveryToken.save(); - return recoveryToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestRecoveryToken"; - throw error; +class RecoveryModule { + constructor({ User, RecoveryToken, crypto, stringService }) { + this.User = User; + this.RecoveryToken = RecoveryToken; + this.crypto = crypto; + this.stringService = stringService; } -}; -const validateRecoveryToken = async (candidateToken) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const recoveryToken = await RecoveryToken.findOne({ - token: candidateToken, - }); - if (recoveryToken !== null) { + requestRecoveryToken = async (email) => { + try { + // Delete any existing tokens + await this.RecoveryToken.deleteMany({ email }); + let recoveryToken = new this.RecoveryToken({ + email, + token: this.crypto.randomBytes(32).toString("hex"), + }); + await recoveryToken.save(); return recoveryToken; - } else { - throw new Error(stringService.dbTokenNotFound); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestRecoveryToken"; + throw error; } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "validateRecoveryToken"; - throw error; - } -}; - -const resetPassword = async (password, candidateToken) => { - const stringService = serviceRegistry.get(StringService.SERVICE_NAME); - try { - const newPassword = password; - - // Validate token again - const recoveryToken = await validateRecoveryToken(candidateToken); - const user = await UserModel.findOne({ email: recoveryToken.email }); - - if (user === null) { - throw new Error(stringService.dbUserNotFound); + }; + validateRecoveryToken = async (candidateToken) => { + try { + const recoveryToken = await this.RecoveryToken.findOne({ + token: candidateToken, + }); + if (recoveryToken !== null) { + return recoveryToken; + } else { + throw new Error(this.stringService.dbTokenNotFound); + } + } catch (error) { + error.service = SERVICE_NAME; + error.method = "validateRecoveryToken"; + throw error; } + }; - const match = await user.comparePassword(newPassword); - if (match === true) { - throw new Error(stringService.dbResetPasswordBadMatch); + resetPassword = async (password, candidateToken) => { + try { + const newPassword = password; + + // Validate token again + const recoveryToken = await this.validateRecoveryToken(candidateToken); + const user = await this.User.findOne({ email: recoveryToken.email }); + + if (user === null) { + console.log("WTF2"); + throw new Error(this.stringService.dbUserNotFound); + } + + const match = await user.comparePassword(newPassword); + + if (match === true) { + console.log("WTF"); + throw new Error("Password cannot be the same as the old password"); + } + + user.password = newPassword; + await user.save(); + await this.RecoveryToken.deleteMany({ email: recoveryToken.email }); + // Fetch the user again without the password + const userWithoutPassword = await this.User.findOne({ + email: recoveryToken.email, + }) + .select("-password") + .select("-profileImage"); + return userWithoutPassword; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "resetPassword"; + throw error; } + }; +} - user.password = newPassword; - await user.save(); - await RecoveryToken.deleteMany({ email: recoveryToken.email }); - // Fetch the user again without the password - const userWithoutPassword = await UserModel.findOne({ - email: recoveryToken.email, - }) - .select("-password") - .select("-profileImage"); - return userWithoutPassword; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "resetPassword"; - throw error; - } -}; - -export { requestRecoveryToken, validateRecoveryToken, resetPassword }; +export default RecoveryModule; diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index 3ffa4c957..d439a942b 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -133,7 +133,7 @@ class UserService { requestRecovery = async (email) => { const user = await this.db.userModule.getUserByEmail(email); - const recoveryToken = await this.db.requestRecoveryToken(email); + const recoveryToken = await this.db.recoveryModule.requestRecoveryToken(email); const name = user.firstName; const { clientHost } = this.settingsService.getSettings(); const url = `${clientHost}/set-new-password/${recoveryToken.token}`; @@ -148,11 +148,11 @@ class UserService { }; validateRecovery = async (recoveryToken) => { - await this.db.validateRecoveryToken(recoveryToken); + await this.db.recoveryModule.validateRecoveryToken(recoveryToken); }; resetPassword = async (password, recoveryToken) => { - const user = await this.db.resetPassword(password, recoveryToken); + const user = await this.db.recoveryModule.resetPassword(password, recoveryToken); const appSettings = await this.settingsService.getSettings(); const token = this.issueToken(user._doc, appSettings); return { user, token }; From 54d3392cf153d8536846e605c50828666b99bb9a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 30 Jul 2025 15:35:16 -0700 Subject: [PATCH 24/30] remove commented out imports --- server/src/db/mongo/modules/recoveryModule.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/mongo/modules/recoveryModule.js index 81e4a5a97..d8d5b25c4 100755 --- a/server/src/db/mongo/modules/recoveryModule.js +++ b/server/src/db/mongo/modules/recoveryModule.js @@ -1,9 +1,3 @@ -// import UserModel from "../../models/User.js"; -// import RecoveryToken from "../../models/RecoveryToken.js"; -// import crypto from "crypto"; -// import serviceRegistry from "../../../service/system/serviceRegistry.js"; -// import StringService from "../../../service/system/stringService.js"; - const SERVICE_NAME = "recoveryModule"; class RecoveryModule { From 73a6277feb1e24e35918f9c4bcbafaf1dc3c4a50 Mon Sep 17 00:00:00 2001 From: singh-kanwarpreet Date: Thu, 31 Jul 2025 16:04:41 +0530 Subject: [PATCH 25/30] Refactored Code --- .../src/Pages/Infrastructure/Create/index.jsx | 83 ++++++++----------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx index f2833d52f..b03342ef0 100644 --- a/client/src/Pages/Infrastructure/Create/index.jsx +++ b/client/src/Pages/Infrastructure/Create/index.jsx @@ -94,76 +94,59 @@ const CreateInfrastructureMonitor = () => { useEffect(() => { if (isCreate) { - // If global settings are not loaded yet, use default thresholds if (globalSettingsLoading) return; - // Create mode: use global thresholds - const globalThresholds = globalSettings?.data?.settings?.globalThresholds || {}; - const defaultThresholds = { - cpu: "", - memory: "", - disk: "", - temperature: "", - }; + const gt = globalSettings?.data?.settings?.globalThresholds || {}; - setInfrastructureMonitor((prev) => ({ - ...prev, + setHttps(false); + + setInfrastructureMonitor({ url: "", name: "", notifications: [], interval: 0.25, - cpu: globalThresholds.cpu != null, - usage_cpu: - globalThresholds.cpu != null - ? globalThresholds.cpu.toString() - : defaultThresholds.cpu, - memory: globalThresholds.memory != null, - usage_memory: - globalThresholds.memory != null - ? globalThresholds.memory.toString() - : defaultThresholds.memory, - disk: globalThresholds.disk != null, - usage_disk: - globalThresholds.disk != null - ? globalThresholds.disk.toString() - : defaultThresholds.disk, - temperature: globalThresholds.temperature != null, - usage_temperature: - globalThresholds.temperature != null - ? globalThresholds.temperature.toString() - : defaultThresholds.temperature, - })); + cpu: gt.cpu !== undefined, + usage_cpu: gt.cpu !== undefined ? gt.cpu.toString() : "", + memory: gt.memory !== undefined, + usage_memory: gt.memory !== undefined ? gt.memory.toString() : "", + disk: gt.disk !== undefined, + usage_disk: gt.disk !== undefined ? gt.disk.toString() : "", + temperature: gt.temperature !== undefined, + usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "", + secret: "", + }); } else if (monitor) { - // Edit mode: use monitor only, ignore global thresholds - setInfrastructureMonitor((prev) => ({ - ...prev, - url: monitor.url?.replace(/^https?:\/\//, "") || "", + const { thresholds = {} } = monitor; + + setHttps(monitor.url.startsWith("https")); + + setInfrastructureMonitor({ + url: monitor.url.replace(/^https?:\/\//, ""), name: monitor.name || "", notifications: monitor.notifications || [], interval: monitor.interval / MS_PER_MINUTE, - cpu: monitor.thresholds?.usage_cpu != null, + cpu: thresholds.usage_cpu !== undefined, usage_cpu: - monitor.thresholds?.usage_cpu != null - ? (monitor.thresholds.usage_cpu * 100).toString() + thresholds.usage_cpu !== undefined + ? (thresholds.usage_cpu * 100).toString() : "", - memory: monitor.thresholds?.usage_memory != null, + memory: thresholds.usage_memory !== undefined, usage_memory: - monitor.thresholds?.usage_memory != null - ? (monitor.thresholds.usage_memory * 100).toString() + thresholds.usage_memory !== undefined + ? (thresholds.usage_memory * 100).toString() : "", - disk: monitor.thresholds?.usage_disk != null, + disk: thresholds.usage_disk !== undefined, usage_disk: - monitor.thresholds?.usage_disk != null - ? (monitor.thresholds.usage_disk * 100).toString() + thresholds.usage_disk !== undefined + ? (thresholds.usage_disk * 100).toString() : "", - temperature: monitor.thresholds?.usage_temperature != null, + temperature: thresholds.usage_temperature !== undefined, usage_temperature: - monitor.thresholds?.usage_temperature != null - ? (monitor.thresholds.usage_temperature * 100).toString() + thresholds.usage_temperature !== undefined + ? (thresholds.usage_temperature * 100).toString() : "", secret: monitor.secret || "", - })); - + }); } }, [isCreate, monitor, globalSettings, globalSettingsLoading]); From 927e8a88b83ac7ffc98b7fd0136534a4f67d4cce Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 09:18:12 -0700 Subject: [PATCH 26/30] remove console log --- server/src/db/mongo/modules/recoveryModule.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/db/mongo/modules/recoveryModule.js b/server/src/db/mongo/modules/recoveryModule.js index d8d5b25c4..cc22306a3 100755 --- a/server/src/db/mongo/modules/recoveryModule.js +++ b/server/src/db/mongo/modules/recoveryModule.js @@ -50,14 +50,12 @@ class RecoveryModule { const user = await this.User.findOne({ email: recoveryToken.email }); if (user === null) { - console.log("WTF2"); throw new Error(this.stringService.dbUserNotFound); } const match = await user.comparePassword(newPassword); if (match === true) { - console.log("WTF"); throw new Error("Password cannot be the same as the old password"); } From 7a190f7c69e86920683f67eca3275f219efb6ec1 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 11:57:55 -0700 Subject: [PATCH 27/30] module -> class --- server/src/config/services.js | 4 ++ server/src/controllers/settingsController.js | 2 +- server/src/db/mongo/MongoDB.js | 4 +- server/src/db/mongo/modules/settingsModule.js | 63 +++++++++---------- server/src/service/business/userService.js | 2 +- 5 files changed, 37 insertions(+), 38 deletions(-) diff --git a/server/src/config/services.js b/server/src/config/services.js index 21608fd59..4fb9424cc 100644 --- a/server/src/config/services.js +++ b/server/src/config/services.js @@ -54,6 +54,7 @@ import MonitorStats from "../db/models/MonitorStats.js"; import NetworkCheck from "../db/models/NetworkCheck.js"; import Notification from "../db/models/Notification.js"; import RecoveryToken from "../db/models/RecoveryToken.js"; +import AppSettings from "../db/models/AppSettings.js"; import InviteModule from "../db/mongo/modules/inviteModule.js"; import CheckModule from "../db/mongo/modules/checkModule.js"; @@ -66,6 +67,7 @@ import NetworkCheckModule from "../db/mongo/modules/networkCheckModule.js"; import NotificationModule from "../db/mongo/modules/notificationModule.js"; import PageSpeedCheckModule from "../db/mongo/modules/pageSpeedCheckModule.js"; import RecoveryModule from "../db/mongo/modules/recoveryModule.js"; +import SettingsModule from "../db/mongo/modules/settingsModule.js"; export const initializeServices = async ({ logger, envSettings, settingsService }) => { const serviceRegistry = new ServiceRegistry({ logger }); @@ -101,6 +103,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService const notificationModule = new NotificationModule({ Notification, Monitor }); const pageSpeedCheckModule = new PageSpeedCheckModule({ PageSpeedCheck }); const recoveryModule = new RecoveryModule({ User, RecoveryToken, crypto, stringService }); + const settingsModule = new SettingsModule({ AppSettings }); const db = new MongoDB({ logger, @@ -116,6 +119,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService notificationModule, pageSpeedCheckModule, recoveryModule, + settingsModule, }); await db.connect(); diff --git a/server/src/controllers/settingsController.js b/server/src/controllers/settingsController.js index c66024025..7da724d23 100755 --- a/server/src/controllers/settingsController.js +++ b/server/src/controllers/settingsController.js @@ -55,7 +55,7 @@ class SettingsController extends BaseController { async (req, res) => { await updateAppSettingsBodyValidation.validateAsync(req.body); - const updatedSettings = await this.db.updateAppSettings(req.body); + const updatedSettings = await this.db.settingsModule.updateAppSettings(req.body); const returnSettings = this.buildAppSettings(updatedSettings); return res.success({ msg: this.stringService.updateAppSettings, diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 49d54fc48..2e98a0335 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -44,20 +44,20 @@ class MongoDB { notificationModule, pageSpeedCheckModule, recoveryModule, + settingsModule, }) { this.logger = logger; this.envSettings = envSettings; this.userModule = userModule; this.inviteModule = inviteModule; this.recoveryModule = recoveryModule; - Object.assign(this, monitorModule); this.pageSpeedCheckModule = pageSpeedCheckModule; this.hardwareCheckModule = hardwareCheckModule; this.checkModule = checkModule; this.maintenanceWindowModule = maintenanceWindowModule; this.monitorModule = monitorModule; this.notificationModule = notificationModule; - Object.assign(this, settingsModule); + this.settingsModule = settingsModule; this.statusPageModule = statusPageModule; Object.assign(this, diagnosticModule); this.networkCheckModule = networkCheckModule; diff --git a/server/src/db/mongo/modules/settingsModule.js b/server/src/db/mongo/modules/settingsModule.js index df325d2fa..37b851bf2 100755 --- a/server/src/db/mongo/modules/settingsModule.js +++ b/server/src/db/mongo/modules/settingsModule.js @@ -1,41 +1,36 @@ -import AppSettings from "../../models/AppSettings.js"; +// import AppSettings from "../../models/AppSettings.js"; const SERVICE_NAME = "SettingsModule"; -const getAppSettings = async () => { - try { - const settings = AppSettings.findOne(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getSettings"; - throw error; +class SettingsModule { + constructor({ AppSettings }) { + this.AppSettings = AppSettings; } -}; -const updateAppSettings = async (newSettings) => { - try { - const update = { $set: { ...newSettings } }; + updateAppSettings = async (newSettings) => { + try { + const update = { $set: { ...newSettings } }; - if (newSettings.pagespeedApiKey === "") { - update.$unset = { pagespeedApiKey: "" }; - delete update.$set.pagespeedApiKey; + if (newSettings.pagespeedApiKey === "") { + update.$unset = { pagespeedApiKey: "" }; + delete update.$set.pagespeedApiKey; + } + + if (newSettings.systemEmailPassword === "") { + update.$unset = { systemEmailPassword: "" }; + delete update.$set.systemEmailPassword; + } + + await this.AppSettings.findOneAndUpdate({}, update, { + upsert: true, + }); + const settings = await this.AppSettings.findOne().select("-__v -_id -createdAt -updatedAt -singleton").lean(); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateAppSettings"; + throw error; } + }; +} - if (newSettings.systemEmailPassword === "") { - update.$unset = { systemEmailPassword: "" }; - delete update.$set.systemEmailPassword; - } - - await AppSettings.findOneAndUpdate({}, update, { - upsert: true, - }); - const settings = await AppSettings.findOne().select("-__v -_id -createdAt -updatedAt -singleton").lean(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateAppSettings"; - throw error; - } -}; - -export { getAppSettings, updateAppSettings }; +export default SettingsModule; diff --git a/server/src/service/business/userService.js b/server/src/service/business/userService.js index d439a942b..0a965bced 100644 --- a/server/src/service/business/userService.js +++ b/server/src/service/business/userService.js @@ -37,7 +37,7 @@ class UserService { } else { // This is the first account, create JWT secret to use if one is not supplied by env const jwtSecret = this.crypto.randomBytes(64).toString("hex"); - await this.db.updateAppSettings({ jwtSecret }); + await this.db.settingsModule.updateAppSettings({ jwtSecret }); } const newUser = await this.db.userModule.insertUser({ ...user }, file); From ce49270cf8c00476c032553af62fd57235636e67 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 12:00:27 -0700 Subject: [PATCH 28/30] remove unused diagnostic db module --- server/src/db/mongo/MongoDB.js | 27 ----------- .../src/db/mongo/modules/diagnosticModule.js | 48 ------------------- 2 files changed, 75 deletions(-) delete mode 100755 server/src/db/mongo/modules/diagnosticModule.js diff --git a/server/src/db/mongo/MongoDB.js b/server/src/db/mongo/MongoDB.js index 2e98a0335..0c6274320 100755 --- a/server/src/db/mongo/MongoDB.js +++ b/server/src/db/mongo/MongoDB.js @@ -1,32 +1,6 @@ import mongoose from "mongoose"; import AppSettings from "../models/AppSettings.js"; -//**************************************** -// Recovery Operations -//**************************************** -import * as recoveryModule from "./modules/recoveryModule.js"; - -//**************************************** -// Page Speed Checks -//**************************************** - -import * as pageSpeedCheckModule from "./modules/pageSpeedCheckModule.js"; - -//**************************************** -// Notifications -//**************************************** -import * as notificationModule from "./modules/notificationModule.js"; - -//**************************************** -// AppSettings -//**************************************** -import * as settingsModule from "./modules/settingsModule.js"; - -//**************************************** -// Diagnostic -//**************************************** -import * as diagnosticModule from "./modules/diagnosticModule.js"; - class MongoDB { static SERVICE_NAME = "MongoDB"; @@ -59,7 +33,6 @@ class MongoDB { this.notificationModule = notificationModule; this.settingsModule = settingsModule; this.statusPageModule = statusPageModule; - Object.assign(this, diagnosticModule); this.networkCheckModule = networkCheckModule; } diff --git a/server/src/db/mongo/modules/diagnosticModule.js b/server/src/db/mongo/modules/diagnosticModule.js deleted file mode 100755 index 8717720a3..000000000 --- a/server/src/db/mongo/modules/diagnosticModule.js +++ /dev/null @@ -1,48 +0,0 @@ -import Monitor from "../../models/Monitor.js"; -import { ObjectId } from "mongodb"; - -const SERVICE_NAME = "diagnosticModule"; -import { buildMonitorSummaryByTeamIdPipeline, buildMonitorsByTeamIdPipeline, buildFilteredMonitorsByTeamIdPipeline } from "./monitorModuleQueries.js"; - -const getMonitorsByTeamIdExecutionStats = async (req) => { - try { - let { limit, type, page, rowsPerPage, filter, field, order } = req.query; - limit = parseInt(limit); - page = parseInt(page); - rowsPerPage = parseInt(rowsPerPage); - if (field === undefined) { - field = "name"; - order = "asc"; - } - // Build match stage - const matchStage = { teamId: new ObjectId(req.params.teamId) }; - if (type !== undefined) { - matchStage.type = Array.isArray(type) ? { $in: type } : type; - } - - const summary = await Monitor.aggregate(buildMonitorSummaryByTeamIdPipeline({ matchStage })).explain("executionStats"); - - const monitors = await Monitor.aggregate(buildMonitorsByTeamIdPipeline({ matchStage, field, order })).explain("executionStats"); - - const filteredMonitors = await Monitor.aggregate( - buildFilteredMonitorsByTeamIdPipeline({ - matchStage, - filter, - page, - rowsPerPage, - field, - order, - limit, - type, - }) - ).explain("executionStats"); - - return { summary, monitors, filteredMonitors }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorSummaryByTeamIdExecutionStats"; - throw error; - } -}; - -export { getMonitorsByTeamIdExecutionStats }; From 0779e5ea15122f16fddf4b880930e14c6c647d4b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 12:02:52 -0700 Subject: [PATCH 29/30] remove commented out imports --- server/src/db/mongo/modules/monitorModule.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js index 332122df4..76f055f94 100755 --- a/server/src/db/mongo/modules/monitorModule.js +++ b/server/src/db/mongo/modules/monitorModule.js @@ -1,15 +1,3 @@ -// import Monitor from "../../models/Monitor.js"; -// import MonitorStats from "../../models/MonitorStats.js"; -// import Check from "../../models/Check.js"; -// import PageSpeedCheck from "../../models/PageSpeedCheck.js"; -// import HardwareCheck from "../../models/HardwareCheck.js"; -// import ServiceRegistry from "../../../service/system/serviceRegistry.js"; -// import StringService from "../../../service/system/stringService.js"; -// import fs from "fs"; -// import path from "path"; -// import { fileURLToPath } from "url"; -// import { ObjectId } from "mongodb"; - import { buildUptimeDetailsPipeline, buildHardwareDetailsPipeline, From a86e9ff480d085b2b2f384a721e89a49fb4582ea Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 31 Jul 2025 13:24:32 -0700 Subject: [PATCH 30/30] fix references --- server/src/service/business/monitorService.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/service/business/monitorService.js b/server/src/service/business/monitorService.js index c52b5cadf..32f7d1ebe 100644 --- a/server/src/service/business/monitorService.js +++ b/server/src/service/business/monitorService.js @@ -71,7 +71,7 @@ class MonitorService { }; createMonitor = async ({ teamId, userId, body }) => { - const monitor = await this.db.createMonitor({ + const monitor = await this.db.monitorModule.createMonitor({ body, teamId, userId, @@ -122,7 +122,7 @@ class MonitorService { await createMonitorsBodyValidation.validateAsync(enrichedData); - const monitors = await this.db.createBulkMonitors(enrichedData); + const monitors = await this.db.monitorModule.createBulkMonitors(enrichedData); await Promise.all( monitors.map(async (monitor) => { @@ -141,14 +141,14 @@ class MonitorService { deleteMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.deleteMonitor({ teamId, monitorId }); + const monitor = await this.db.monitorModule.deleteMonitor({ teamId, monitorId }); await this.jobQueue.deleteJob(monitor); await this.db.statusPageModule.deleteStatusPagesByMonitorId(monitor._id); return monitor; }; deleteAllMonitors = async ({ teamId }) => { - const { monitors, deletedCount } = await this.db.deleteAllMonitors(teamId); + const { monitors, deletedCount } = await this.db.monitorModule.deleteAllMonitors(teamId); await Promise.all( monitors.map(async (monitor) => { try { @@ -171,19 +171,19 @@ class MonitorService { editMonitor = async ({ teamId, monitorId, body }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const editedMonitor = await this.db.editMonitor({ monitorId, body }); + const editedMonitor = await this.db.monitorModule.editMonitor({ monitorId, body }); await this.jobQueue.updateJob(editedMonitor); }; pauseMonitor = async ({ teamId, monitorId }) => { await this.verifyTeamAccess({ teamId, monitorId }); - const monitor = await this.db.pauseMonitor({ monitorId }); + const monitor = await this.db.monitorModule.pauseMonitor({ monitorId }); monitor.isActive === true ? await this.jobQueue.resumeJob(monitor._id, monitor) : await this.jobQueue.pauseJob(monitor); return monitor; }; addDemoMonitors = async ({ userId, teamId }) => { - const demoMonitors = await this.db.addDemoMonitors(userId, teamId); + const demoMonitors = await this.db.monitorModuleaddDemoMonitors(userId, teamId); await Promise.all(demoMonitors.map((monitor) => this.jobQueue.addJob(monitor._id, monitor))); return demoMonitors; };