diff --git a/client/src/Components/Fallback/FallbackBackground.jsx b/client/src/Components/Fallback/FallbackBackground.jsx
index 00ecafb4e..fe46f4064 100644
--- a/client/src/Components/Fallback/FallbackBackground.jsx
+++ b/client/src/Components/Fallback/FallbackBackground.jsx
@@ -1,7 +1,6 @@
import { useTheme } from "@emotion/react";
import Box from "@mui/material/Box";
import Background from "../../assets/Images/background-grid.svg?react";
-import SkeletonDark from "../../assets/Images/create-placeholder-dark.svg?react";
import OutputAnimation from "../../assets/Animations/output.gif";
import DarkmodeOutput from "../../assets/Animations/darkmodeOutput.gif";
import { useSelector } from "react-redux";
@@ -13,7 +12,7 @@ const FallbackBackground = () => {
{
setIsLoading(true);
const updatedFields = {
name: monitor.name,
+ statusWindowSize: monitor.statusWindowSize,
+ statusWindowThreshold: monitor.statusWindowThreshold,
description: monitor.description,
interval: monitor.interval,
notifications: monitor.notifications,
diff --git a/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
new file mode 100644
index 000000000..5a6f5e981
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/CustomAlertsSection.jsx
@@ -0,0 +1,87 @@
+import ConfigBox from "../../../../Components/ConfigBox";
+import { Box, Stack, Typography } from "@mui/material";
+import { CustomThreshold } from "../Components/CustomThreshold";
+import { capitalizeFirstLetter } from "../../../../Utils/stringUtils";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PropTypes from "prop-types";
+const CustomAlertsSection = ({
+ errors,
+ onChange,
+ infrastructureMonitor,
+ handleCheckboxChange,
+}) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+
+ const METRICS = ["cpu", "memory", "disk", "temperature"];
+ const METRIC_PREFIX = "usage_";
+ const hasAlertError = (errors) => {
+ return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0;
+ };
+ const getAlertError = (errors) => {
+ const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX));
+ return errorKey ? errors[errorKey] : null;
+ };
+ return (
+
+
+
+ {t("infrastructureCustomizeAlerts")}
+
+
+ {t("infrastructureAlertNotificationDescription")}
+
+
+
+ {METRICS.map((metric) => {
+ return (
+
+ );
+ })}
+
+ {hasAlertError(errors) && (
+
+ {getAlertError(errors)}
+
+ )}
+
+
+ );
+};
+
+CustomAlertsSection.propTypes = {
+ errors: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ infrastructureMonitor: PropTypes.object.isRequired,
+ handleCheckboxChange: PropTypes.func.isRequired,
+};
+
+export default CustomAlertsSection;
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
new file mode 100644
index 000000000..becda3e3a
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorActionButtons.jsx
@@ -0,0 +1,78 @@
+import { useState } from "react";
+import { Box, Button } from "@mui/material";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
+import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
+import Dialog from "../../../../Components/Dialog";
+import PropTypes from "prop-types";
+
+const MonitorActionButtons = ({ monitor, isBusy, handlePause, handleRemove }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+
+ );
+};
+
+MonitorActionButtons.propTypes = {
+ monitor: PropTypes.object.isRequired,
+ isBusy: PropTypes.bool.isRequired,
+ handlePause: PropTypes.func.isRequired,
+ handleRemove: PropTypes.func.isRequired,
+};
+
+export default MonitorActionButtons;
diff --git a/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
new file mode 100644
index 000000000..2ed48bf64
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/Components/MonitorStatusHeader.jsx
@@ -0,0 +1,73 @@
+import { Box, Stack, Tooltip, Typography } from "@mui/material";
+import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
+import { useTheme } from "@emotion/react";
+import { useTranslation } from "react-i18next";
+import PulseDot from "../../../../Components/Animated/PulseDot";
+import PropTypes from "prop-types";
+const MonitorStatusHeader = ({ monitor, infrastructureMonitor }) => {
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
+ return (
+
+
+
+
+
+
+
+ {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
+
+
+ {t("editing")}
+
+
+ );
+};
+
+MonitorStatusHeader.propTypes = {
+ monitor: PropTypes.object.isRequired,
+ infrastructureMonitor: PropTypes.object.isRequired,
+};
+
+export default MonitorStatusHeader;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
new file mode 100644
index 000000000..fa8a6ebe9
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureMonitorForm.jsx
@@ -0,0 +1,90 @@
+import { useState } from "react";
+const useInfrastructureMonitorForm = () => {
+ const [infrastructureMonitor, setInfrastructureMonitor] = useState({
+ url: "",
+ name: "",
+ notifications: [],
+ notify_email: false,
+ interval: 0.25,
+ cpu: false,
+ usage_cpu: "",
+ memory: false,
+ usage_memory: "",
+ disk: false,
+ usage_disk: "",
+ temperature: false,
+ usage_temperature: "",
+ secret: "",
+ });
+
+ const onChangeForm = (name, value) => {
+ setInfrastructureMonitor({
+ ...infrastructureMonitor,
+ [name]: value,
+ });
+ };
+ const handleCheckboxChange = (event) => {
+ setInfrastructureMonitor({
+ ...infrastructureMonitor,
+ [event.target.name]: event.target.checked,
+ });
+ };
+ const initializeInfrastructureMonitorForCreate = (globalSettings) => {
+ const gt = globalSettings?.data?.settings?.globalThresholds || {};
+ setInfrastructureMonitor({
+ url: "",
+ name: "",
+ notifications: [],
+ interval: 0.25,
+ 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: "",
+ });
+ };
+
+ const initializeInfrastructureMonitorForUpdate = (monitor) => {
+ const MS_PER_MINUTE = 60000;
+ const { thresholds = {} } = monitor;
+ setInfrastructureMonitor({
+ url: monitor.url.replace(/^https?:\/\//, ""),
+ name: monitor.name || "",
+ notifications: monitor.notifications || [],
+ interval: monitor.interval / MS_PER_MINUTE,
+ cpu: thresholds.usage_cpu !== undefined,
+ usage_cpu:
+ thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
+ memory: thresholds.usage_memory !== undefined,
+ usage_memory:
+ thresholds.usage_memory !== undefined
+ ? (thresholds.usage_memory * 100).toString()
+ : "",
+ disk: thresholds.usage_disk !== undefined,
+ usage_disk:
+ thresholds.usage_disk !== undefined
+ ? (thresholds.usage_disk * 100).toString()
+ : "",
+ temperature: thresholds.usage_temperature !== undefined,
+ usage_temperature:
+ thresholds.usage_temperature !== undefined
+ ? (thresholds.usage_temperature * 100).toString()
+ : "",
+ secret: monitor.secret || "",
+ });
+ };
+ return {
+ infrastructureMonitor,
+ setInfrastructureMonitor,
+ onChangeForm,
+ handleCheckboxChange,
+ initializeInfrastructureMonitorForCreate,
+ initializeInfrastructureMonitorForUpdate,
+ };
+};
+
+export default useInfrastructureMonitorForm;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
new file mode 100644
index 000000000..da564ae7c
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useInfrastructureSubmit.jsx
@@ -0,0 +1,80 @@
+import { useCreateMonitor, useUpdateMonitor } from "../../../../Hooks/monitorHooks";
+const useInfrastructureSubmit = () => {
+ const [createMonitor, isCreating] = useCreateMonitor();
+ const [updateMonitor, isUpdating] = useUpdateMonitor();
+ const buildForm = (infrastructureMonitor, https) => {
+ const MS_PER_MINUTE = 60000;
+
+ let form = {
+ url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
+ name:
+ infrastructureMonitor.name === ""
+ ? infrastructureMonitor.url
+ : infrastructureMonitor.name,
+ interval: infrastructureMonitor.interval * MS_PER_MINUTE,
+ cpu: infrastructureMonitor.cpu,
+ ...(infrastructureMonitor.cpu
+ ? { usage_cpu: infrastructureMonitor.usage_cpu }
+ : {}),
+ memory: infrastructureMonitor.memory,
+ ...(infrastructureMonitor.memory
+ ? { usage_memory: infrastructureMonitor.usage_memory }
+ : {}),
+ disk: infrastructureMonitor.disk,
+ ...(infrastructureMonitor.disk
+ ? { usage_disk: infrastructureMonitor.usage_disk }
+ : {}),
+ temperature: infrastructureMonitor.temperature,
+ ...(infrastructureMonitor.temperature
+ ? { usage_temperature: infrastructureMonitor.usage_temperature }
+ : {}),
+ secret: infrastructureMonitor.secret,
+ };
+ return form;
+ };
+ const submitInfrastructureForm = async (
+ infrastructureMonitor,
+ form,
+ isCreate,
+ monitorId
+ ) => {
+ const {
+ cpu,
+ usage_cpu,
+ memory,
+ usage_memory,
+ disk,
+ usage_disk,
+ temperature,
+ usage_temperature,
+ ...rest
+ } = form;
+
+ const thresholds = {
+ ...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
+ ...(memory ? { usage_memory: usage_memory / 100 } : {}),
+ ...(disk ? { usage_disk: usage_disk / 100 } : {}),
+ ...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
+ };
+
+ const finalForm = {
+ ...(isCreate ? {} : { _id: monitorId }),
+ ...rest,
+ description: form.name,
+ type: "hardware",
+ notifications: infrastructureMonitor.notifications,
+ thresholds,
+ };
+ // Handle create or update
+ isCreate
+ ? await createMonitor({ monitor: finalForm, redirect: "/infrastructure" })
+ : await updateMonitor({ monitor: finalForm, redirect: "/infrastructure" });
+ };
+ return {
+ buildForm,
+ submitInfrastructureForm,
+ isCreating,
+ isUpdating,
+ };
+};
+export default useInfrastructureSubmit;
diff --git a/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
new file mode 100644
index 000000000..080f441c4
--- /dev/null
+++ b/client/src/Pages/Infrastructure/Create/hooks/useValidateInfrastructureForm.jsx
@@ -0,0 +1,37 @@
+import { useState } from "react";
+import { infrastructureMonitorValidation } from "../../../../Validation/validation";
+import { createToast } from "../../../../Utils/toastUtils";
+const useValidateInfrastructureForm = () => {
+ const [errors, setErrors] = useState({});
+
+ const validateField = (name, value) => {
+ const { error } = infrastructureMonitorValidation.validate(
+ { [name]: value },
+ { abortEarly: false }
+ );
+ setErrors((prev) => ({
+ ...prev,
+ ...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
+ }));
+ };
+
+ const validateForm = (form) => {
+ const { error } = infrastructureMonitorValidation.validate(form, {
+ abortEarly: false,
+ });
+
+ if (error) {
+ const newErrors = {};
+ error.details.forEach((err) => {
+ newErrors[err.path[0]] = err.message;
+ });
+ console.log(newErrors);
+ setErrors(newErrors);
+ createToast({ body: "Please check the form for errors." });
+ return error;
+ }
+ return null;
+ };
+ return { errors, validateField, validateForm };
+};
+export default useValidateInfrastructureForm;
diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
index ae95cce33..28007c538 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -1,40 +1,33 @@
//Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import ConfigBox from "../../../Components/ConfigBox";
-import Dialog from "../../../Components/Dialog";
import FieldWrapper from "../../../Components/Inputs/FieldWrapper";
import Link from "../../../Components/Link";
-import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
-import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
-import PulseDot from "../../../Components/Animated/PulseDot";
import Select from "../../../Components/Inputs/Select";
import TextInput from "../../../Components/Inputs/TextInput";
-import { Box, Stack, Tooltip, Typography, Button, ButtonGroup } from "@mui/material";
-import { CustomThreshold } from "./Components/CustomThreshold";
+import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
-import { createToast } from "../../../Utils/toastUtils";
+import MonitorStatusHeader from "./Components/MonitorStatusHeader";
+import MonitorActionButtons from "./Components/MonitorActionButtons";
+import CustomAlertsSection from "./Components/CustomAlertsSection";
// Utils
import NotificationsConfig from "../../../Components/NotificationConfig";
-import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
-import { infrastructureMonitorValidation } from "../../../Validation/validation";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
-import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
import { useParams } from "react-router-dom";
-import { useSelector } from "react-redux";
import { useState, useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import {
- useCreateMonitor,
useDeleteMonitor,
useFetchGlobalSettings,
useFetchHardwareMonitorById,
usePauseMonitor,
- useUpdateMonitor,
} from "../../../Hooks/monitorHooks";
+import useInfrastructureMonitorForm from "./hooks/useInfrastructureMonitorForm";
+import useValidateInfrastructureForm from "./hooks/useValidateInfrastructureForm";
+import useInfrastructureSubmit from "./hooks/useInfrastructureSubmit";
const CreateInfrastructureMonitor = () => {
- const { user } = useSelector((state) => state.auth);
const { monitorId } = useParams();
const isCreate = typeof monitorId === "undefined";
@@ -42,39 +35,29 @@ const CreateInfrastructureMonitor = () => {
const { t } = useTranslation();
// State
- const [errors, setErrors] = useState({});
const [https, setHttps] = useState(false);
- const [isOpen, setIsOpen] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
- const [infrastructureMonitor, setInfrastructureMonitor] = useState({
- url: "",
- name: "",
- notifications: [],
- notify_email: false,
- interval: 0.25,
- cpu: false,
- usage_cpu: "",
- memory: false,
- usage_memory: "",
- disk: false,
- usage_disk: "",
- temperature: false,
- usage_temperature: "",
- secret: "",
- });
// Fetch monitor details if editing
- const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
const [monitor, isLoading] = useFetchHardwareMonitorById({
monitorId,
updateTrigger,
});
- const [createMonitor, isCreating] = useCreateMonitor();
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [globalSettings, globalSettingsLoading] = useFetchGlobalSettings();
const [notifications, notificationsAreLoading] = useGetNotificationsByTeamId();
const [pauseMonitor, isPausing] = usePauseMonitor();
- const [updateMonitor, isUpdating] = useUpdateMonitor();
+ const {
+ infrastructureMonitor,
+ setInfrastructureMonitor,
+ onChangeForm,
+ handleCheckboxChange,
+ initializeInfrastructureMonitorForCreate,
+ initializeInfrastructureMonitorForUpdate,
+ } = useInfrastructureMonitorForm();
+ const { errors, validateField, validateForm } = useValidateInfrastructureForm();
+ const { buildForm, submitInfrastructureForm, isCreating, isUpdating } =
+ useInfrastructureSubmit();
const FREQUENCIES = [
{ _id: 0.25, name: t("time.fifteenSeconds") },
@@ -93,157 +76,27 @@ const CreateInfrastructureMonitor = () => {
{ name: "Configure", path: `/infrastructure/configure/${monitorId}` },
]),
];
- const METRICS = ["cpu", "memory", "disk", "temperature"];
- const METRIC_PREFIX = "usage_";
- const MS_PER_MINUTE = 60000;
-
- const hasAlertError = (errors) => {
- return Object.keys(errors).filter((k) => k.startsWith(METRIC_PREFIX)).length > 0;
- };
-
- const getAlertError = (errors) => {
- const errorKey = Object.keys(errors).find((key) => key.startsWith(METRIC_PREFIX));
- return errorKey ? errors[errorKey] : null;
- };
-
// Populate form fields if editing
useEffect(() => {
if (isCreate) {
if (globalSettingsLoading) return;
-
- const gt = globalSettings?.data?.settings?.globalThresholds || {};
-
setHttps(false);
-
- setInfrastructureMonitor({
- url: "",
- name: "",
- notifications: [],
- interval: 0.25,
- 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: "",
- });
+ initializeInfrastructureMonitorForCreate(globalSettings);
} else if (monitor) {
- 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: thresholds.usage_cpu !== undefined,
- usage_cpu:
- thresholds.usage_cpu !== undefined
- ? (thresholds.usage_cpu * 100).toString()
- : "",
- memory: thresholds.usage_memory !== undefined,
- usage_memory:
- thresholds.usage_memory !== undefined
- ? (thresholds.usage_memory * 100).toString()
- : "",
- disk: thresholds.usage_disk !== undefined,
- usage_disk:
- thresholds.usage_disk !== undefined
- ? (thresholds.usage_disk * 100).toString()
- : "",
- temperature: thresholds.usage_temperature !== undefined,
- usage_temperature:
- thresholds.usage_temperature !== undefined
- ? (thresholds.usage_temperature * 100).toString()
- : "",
- secret: monitor.secret || "",
- });
+ initializeInfrastructureMonitorForUpdate(monitor);
}
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
// Handlers
const onSubmit = async (event) => {
event.preventDefault();
-
- // Build the form
- let form = {
- url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
- name:
- infrastructureMonitor.name === ""
- ? infrastructureMonitor.url
- : infrastructureMonitor.name,
- interval: infrastructureMonitor.interval * MS_PER_MINUTE,
- cpu: infrastructureMonitor.cpu,
- ...(infrastructureMonitor.cpu
- ? { usage_cpu: infrastructureMonitor.usage_cpu }
- : {}),
- memory: infrastructureMonitor.memory,
- ...(infrastructureMonitor.memory
- ? { usage_memory: infrastructureMonitor.usage_memory }
- : {}),
- disk: infrastructureMonitor.disk,
- ...(infrastructureMonitor.disk
- ? { usage_disk: infrastructureMonitor.usage_disk }
- : {}),
- temperature: infrastructureMonitor.temperature,
- ...(infrastructureMonitor.temperature
- ? { usage_temperature: infrastructureMonitor.usage_temperature }
- : {}),
- secret: infrastructureMonitor.secret,
- };
-
- const { error } = infrastructureMonitorValidation.validate(form, {
- abortEarly: false,
- });
-
+ const form = buildForm(infrastructureMonitor, https);
+ const error = validateForm(form);
if (error) {
- const newErrors = {};
- error.details.forEach((err) => {
- newErrors[err.path[0]] = err.message;
- });
- console.log(newErrors);
- setErrors(newErrors);
- createToast({ body: "Please check the form for errors." });
return;
}
-
- // Build the thresholds for the form
- const {
- cpu,
- usage_cpu,
- memory,
- usage_memory,
- disk,
- usage_disk,
- temperature,
- usage_temperature,
- ...rest
- } = form;
-
- const thresholds = {
- ...(cpu ? { usage_cpu: usage_cpu / 100 } : {}),
- ...(memory ? { usage_memory: usage_memory / 100 } : {}),
- ...(disk ? { usage_disk: usage_disk / 100 } : {}),
- ...(temperature ? { usage_temperature: usage_temperature / 100 } : {}),
- };
-
- form = {
- ...(isCreate ? {} : { _id: monitorId }),
- ...rest,
- description: form.name,
- type: "hardware",
- notifications: infrastructureMonitor.notifications,
- thresholds,
- };
-
- // Handle create or update
- isCreate
- ? await createMonitor({ monitor: form, redirect: "/infrastructure" })
- : await updateMonitor({ monitor: form, redirect: "/infrastructure" });
+ submitInfrastructureForm(infrastructureMonitor, form, isCreate, monitorId);
};
const triggerUpdate = () => {
@@ -252,28 +105,8 @@ const CreateInfrastructureMonitor = () => {
const onChange = (event) => {
const { value, name } = event.target;
- setInfrastructureMonitor({
- ...infrastructureMonitor,
- [name]: value,
- });
-
- const { error } = infrastructureMonitorValidation.validate(
- { [name]: value },
- { abortEarly: false }
- );
- setErrors((prev) => ({
- ...prev,
- ...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
- }));
- };
-
- const handleCheckboxChange = (event) => {
- const { name } = event.target;
- const { checked } = event.target;
- setInfrastructureMonitor({
- ...infrastructureMonitor,
- [name]: checked,
- });
+ onChangeForm(name, value);
+ validateField(name, value);
};
const handlePause = async () => {
@@ -336,105 +169,19 @@ const CreateInfrastructureMonitor = () => {
)}
{!isCreate && (
-
-
-
-
-
-
-
- {infrastructureMonitor.url?.replace(/^https?:\/\//, "") || "..."}
-
-
- {t("editing")}
-
-
+
)}
{!isCreate && (
-
-
-
-
+
)}
@@ -534,58 +281,12 @@ const CreateInfrastructureMonitor = () => {
setNotifications={infrastructureMonitor.notifications}
/>
-
-
-
- {t("infrastructureCustomizeAlerts")}
-
-
- {t("infrastructureAlertNotificationDescription")}
-
-
-
- {METRICS.map((metric) => {
- return (
-
- );
- })}
- {/* Error text */}
- {hasAlertError(errors) && (
-
- {getAlertError(errors)}
-
- )}
-
-
+
{
- {!isCreate && (
-
);
};
diff --git a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
index 227523439..41d2b0a87 100644
--- a/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
+++ b/client/src/Pages/Infrastructure/Details/Components/NetworkStats/index.jsx
@@ -1,8 +1,9 @@
import PropTypes from "prop-types";
import { useState, useEffect } from "react";
-import { FormControl, InputLabel, Select, MenuItem, Box } from "@mui/material";
+import { Box } from "@mui/material";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
+import Select from "../../../../../Components/Inputs/Select";
import NetworkStatBoxes from "./NetworkStatBoxes";
import NetworkCharts from "./NetworkCharts";
import MonitorTimeFrameHeader from "../../../../../Components/MonitorTimeFrameHeader";
@@ -63,27 +64,17 @@ const Network = ({ net, checks, isLoading, dateRange, setDateRange }) => {
gap={theme.spacing(4)}
>
{availableInterfaces.length > 0 && (
- setSelectedInterface(e.target.value)}
+ items={availableInterfaces.map((interfaceName) => ({
+ _id: interfaceName,
+ name: interfaceName,
+ }))}
sx={{ minWidth: 200 }}
- >
- {t("networkInterface")}
-
-
+ />
)}
{
+ const getValueById = (config, id) => {
+ const item = config.find((config) => config._id === id);
+ return item ? (item.value ? item.value : item.name) : null;
+ };
+
+ const getIdByValue = (config, name) => {
+ const item = config.find((config) => {
+ if (config.value) {
+ return config.value === name;
+ } else {
+ return config.name === name;
+ }
+ });
+ return item ? item._id : null;
+ };
+
+ return (
+
+
+
+
+ {t("createMonitorPage.incidentConfigTitle")}
+
+
+ {t("createMonitorPage.incidentConfigDescription")}
+
+
+
+
+
+
+
({
"& .MuiSelect-icon": {
color: theme.palette.primary.contrastTextSecondary, // Dropdown + color
},
+ "& .MuiSelect-select": {
+ display: "flex",
+ alignItems: "center",
+ },
"&:hover": {
backgroundColor: theme.palette.primary.main, // Background on hover
},
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index de248f87b..26e390ff4 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -115,6 +115,16 @@ const monitorValidation = joi.object({
_id: joi.string(),
userId: joi.string(),
teamId: joi.string(),
+ statusWindowSize: joi.number().min(1).max(20).default(5).messages({
+ "number.base": "Status window size must be a number.",
+ "number.min": "Status window size must be at least 1.",
+ "number.max": "Status window size must be at most 20.",
+ }),
+ statusWindowThreshold: joi.number().min(1).max(100).default(60).messages({
+ "number.base": "Incident percentage must be a number.",
+ "number.min": "Incident percentage must be at least 1.",
+ "number.max": "Incident percentage must be at most 100.",
+ }),
url: joi.when("type", {
is: "docker",
then: joi
@@ -150,6 +160,11 @@ const monitorValidation = joi.object({
// can be replaced by a shortest alternative
// (?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+
"(?:" +
+ // Single hostname without dots (like localhost)
+ "[a-z0-9\\u00a1-\\uffff][a-z0-9\\u00a1-\\uffff_-]{0,62}" +
+ "|" +
+ // Domain with dots
+ "(?:" +
"(?:" +
"[a-z0-9\\u00a1-\\uffff]" +
"[a-z0-9\\u00a1-\\uffff_-]{0,62}" +
@@ -159,6 +174,7 @@ const monitorValidation = joi.object({
// TLD identifier name, may end with dot
"(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" +
")" +
+ ")" +
// port number (optional)
"(?::\\d{2,5})?" +
// resource path (optional)
@@ -439,21 +455,35 @@ const notificationValidation = joi.object({
}),
address: joi.when("type", {
- is: "email",
- then: joi
- .string()
- .email({ tlds: { allow: false } })
- .required()
- .messages({
- "string.empty": "E-mail address cannot be empty",
- "any.required": "E-mail address is required",
- "string.email": "Please enter a valid e-mail address",
- }),
- otherwise: joi.string().uri().required().messages({
- "string.empty": "Webhook URL cannot be empty",
- "any.required": "Webhook URL is required",
- "string.uri": "Please enter a valid Webhook URL",
- }),
+ switch: [
+ {
+ is: "email",
+ then: joi
+ .string()
+ .email({ tlds: { allow: false } })
+ .required()
+ .messages({
+ "string.empty": "E-mail address cannot be empty",
+ "any.required": "E-mail address is required",
+ "string.email": "Please enter a valid e-mail address",
+ }),
+ },
+ {
+ is: "pager_duty",
+ then: joi.string().required().messages({
+ "string.empty": "PagerDuty routing key cannot be empty",
+ "any.required": "PagerDuty routing key is required",
+ }),
+ },
+ {
+ is: joi.valid("webhook", "slack", "discord"),
+ then: joi.string().uri().required().messages({
+ "string.empty": "Webhook URL cannot be empty",
+ "any.required": "Webhook URL is required",
+ "string.uri": "Please enter a valid Webhook URL",
+ }),
+ },
+ ],
}),
});
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 857825c9f..3656da661 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -1,18 +1,4 @@
{
- "ClickUpload": "Click to upload",
- "DeleteAccountButton": "Remove account",
- "DeleteAccountTitle": "Remove account",
- "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
- "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
- "DeleteWarningTitle": "Really remove this account?",
- "DragandDrop": "drag and drop",
- "EmailDescriptionText": "This is your current email address — it cannot be changed.",
- "FirstName": "First name",
- "LastName": "Last name",
- "MaxSize": "Maximum Size",
- "PhotoDescriptionText": "This photo will be displayed in your profile page.",
- "SupportedFormats": "Supported formats",
- "YourPhoto": "Profile photo",
"aboutus": "About Us",
"access": "Access",
"actions": "Actions",
@@ -202,13 +188,6 @@
"avgCpuTemperature": "Average CPU Temperature",
"bar": "Bar",
"basicInformation": "Basic Information",
- "bytesPerSecond": "Bytes per second",
- "bytesReceived": "Bytes Received",
- "bytesSent": "Bytes Sent",
- "dataReceived": "Data Received",
- "dataSent": "Data Sent",
- "dataRate": "Data Rate",
- "rate": "Rate",
"bulkImport": {
"fallbackPage": "Import a file to upload a list of servers in bulk",
"invalidFileType": "Invalid file type",
@@ -222,17 +201,21 @@
"uploadSuccess": "Monitors created successfully!",
"validationFailed": "Validation failed"
},
+ "bytesPerSecond": "Bytes per second",
+ "bytesReceived": "Bytes Received",
+ "bytesSent": "Bytes Sent",
"cancel": "Cancel",
"checkFormError": "Please check the form for errors.",
"checkFrequency": "Check frequency",
- "chooseGame": "Choose game",
"checkHooks": {
"failureResolveAll": "Failed to resolve all incidents.",
"failureResolveMonitor": "Failed to resolve monitor incidents.",
"failureResolveOne": "Failed to resolve incident."
},
"checkingEvery": "Checking every",
+ "chooseGame": "Choose game",
"city": "CITY",
+ "ClickUpload": "Click to upload",
"common": {
"appName": "Checkmate",
"buttons": {
@@ -262,6 +245,12 @@
"createMaintenance": "Create maintenance",
"createMaintenanceWindow": "Create maintenance window",
"createMonitor": "Create monitor",
+ "createMonitorPage": {
+ "incidentConfigDescription": "A sliding window is used to determine when a monitor goes down. The status of a monitor will only change when the percentage of checks in the sliding window meet the specified value.",
+ "incidentConfigStatusWindowLabel": "How many checks should be in the sliding window?",
+ "incidentConfigStatusWindowThresholdLabel": "What percentage of checks in the sliding window fail/succeed before monitor status changes?",
+ "incidentConfigTitle": "Incidents"
+ },
"createNew": "Create new",
"createNotifications": {
"dialogDeleteConfirm": "Delete",
@@ -311,13 +300,22 @@
}
},
"createYour": "Create your",
+ "dataRate": "Data Rate",
+ "dataReceived": "Data Received",
+ "dataSent": "Data Sent",
"date&Time": "Date & Time",
"delete": "Delete",
+ "DeleteAccountButton": "Remove account",
+ "DeleteAccountTitle": "Remove account",
+ "DeleteAccountWarning": "Removing your account means you won't be able to sign in again and all your data will be removed. This isn't reversible.",
+ "DeleteDescriptionText": "This will remove the account and all associated data from the server. This isn't reversible.",
"deleteDialogDescription": "Once deleted, this monitor cannot be retrieved.",
"deleteDialogTitle": "Do you really want to delete this monitor?",
"deleteStatusPage": "Do you want to delete this status page?",
"deleteStatusPageConfirm": "Yes, delete status page",
"deleteStatusPageDescription": "Once deleted, your status page cannot be retrieved.",
+ "DeleteWarningTitle": "Really remove this account?",
+ "details": "Details",
"diagnosticsPage": {
"diagnosticDescription": "System diagnostics",
"gauges": {
@@ -341,15 +339,6 @@
},
"disk": "Disk",
"diskUsage": "Disk Usage",
- "drops": "Drops",
- "errors": "Errors",
- "errorsIn": "Errors In",
- "errorsOut": "Errors Out",
- "networkErrors": "Network Errors",
- "networkDrops": "Network Drops",
- "networkInterface": "Network Interface",
- "selectInterface": "Select Interface",
- "details": "Details",
"displayName": "Display name",
"distributedRightCategoryTitle": "Monitor",
"distributedStatusHeaderText": "Real-time, real-device coverage",
@@ -395,6 +384,8 @@
"distributedUptimeStatusUptLogo": "Upt Logo",
"dockerContainerMonitoring": "Docker container monitoring",
"dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
+ "DragandDrop": "drag and drop",
+ "drops": "Drops",
"duration": "Duration",
"edit": "Edit",
"editing": "Editing...",
@@ -417,6 +408,7 @@
"validationErrors": "Validation errors"
}
},
+ "EmailDescriptionText": "This is your current email address — it cannot be changed.",
"emailSent": "Email sent successfully",
"errorInvalidFieldId": "Invalid field ID provided",
"errorInvalidTypeId": "Invalid notification type provided",
@@ -434,6 +426,9 @@
}
}
},
+ "errors": "Errors",
+ "errorsIn": "Errors In",
+ "errorsOut": "Errors Out",
"expectedValue": "Expected value",
"export": {
"failed": "Failed to export monitors",
@@ -442,9 +437,12 @@
},
"failedToSendEmail": "Failed to send email",
"features": "Features",
+ "FirstName": "First name",
"frequency": "Frequency",
"friendlyNameInput": "Friendly name",
"friendlyNamePlaceholder": "Maintenance at __ : __ for ___ minutes",
+ "gameServerMonitoring": "Game Server Monitoring",
+ "gameServerMonitoringDescription": "Check whether your game server is running or not",
"gb": "GB",
"general": {
"noOptionsFound": "No {{unit}} found"
@@ -520,6 +518,7 @@
"invalidFileFormat": "Unsupported file format!",
"invalidFileSize": "File size is too large!",
"inviteNoTokenFound": "No invite token found",
+ "LastName": "Last name",
"loginHere": "Login here",
"logsPage": {
"description": "System logs - last 1000 lines",
@@ -571,6 +570,7 @@
"regexPlaceholder": "^(success|ok)$",
"text": "Match Method"
},
+ "MaxSize": "Maximum Size",
"mb": "MB",
"mem": "Mem",
"memory": "Memory",
@@ -632,6 +632,11 @@
"namePlaceholder": "My Container",
"placeholder": "abcd1234"
},
+ "game": {
+ "label": "URL to monitor",
+ "namePlaceholder": "localhost:5173",
+ "placeholder": "localhost"
+ },
"http": {
"label": "URL to monitor",
"namePlaceholder": "Google",
@@ -646,16 +651,14 @@
"label": "URL to monitor",
"namePlaceholder": "Localhost:5173",
"placeholder": "localhost"
- },
- "game": {
- "label": "URL to monitor",
- "namePlaceholder": "localhost:5173",
- "placeholder": "localhost"
}
},
"ms": "ms",
"navControls": "Controls",
"network": "Network",
+ "networkDrops": "Network Drops",
+ "networkErrors": "Network Errors",
+ "networkInterface": "Network Interface",
"nextWindow": "Next window",
"noNetworkStatsAvailable": "No network stats available.",
"notFoundButton": "Go to the main dashboard",
@@ -739,6 +742,10 @@
"notifySMS": "Notify via SMS (coming soon)",
"now": "Now",
"os": "OS",
+ "packetsPerSecond": "Packets per second",
+ "packetsReceived": "Packets Received",
+ "packetsReceivedRate": "Packets Received Rate",
+ "packetsSent": "Packets Sent",
"pageSpeed": {
"fallback": {
"actionButton": "Let's create your first PageSpeed monitor!",
@@ -769,17 +776,12 @@
"passwordRequirements": "New password must contain at least 8 characters and must have at least one uppercase letter, one lowercase letter, one number and one special character.",
"saving": "Saving..."
},
- "packetsPerSecond": "Packets per second",
- "packetsReceived": "Packets Received",
- "packetsReceivedRate": "Packets Received Rate",
- "packetsSent": "Packets Sent",
"pause": "Pause",
+ "PhotoDescriptionText": "This photo will be displayed in your profile page.",
"pingMonitoring": "Ping monitoring",
"pingMonitoringDescription": "Check whether your server is available or not.",
"portMonitoring": "Port monitoring",
"portMonitoringDescription": "Check whether your port is open or not.",
- "gameServerMonitoring": "Game Server Monitoring",
- "gameServerMonitoringDescription": "Check whether your game server is running or not",
"portToMonitor": "Port to monitor",
"publicLink": "Public link",
"publicURL": "Public URL",
@@ -814,6 +816,7 @@
"refreshButton": "Refresh",
"title": "Queue"
},
+ "rate": "Rate",
"remove": "Remove",
"removeLogo": "Remove Logo",
"repeat": "Repeat",
@@ -829,6 +832,7 @@
},
"save": "Save",
"selectAll": "Select all",
+ "selectInterface": "Select Interface",
"sendTestNotifications": "Send test notifications",
"seperateEmails": "You can separate multiple emails with a comma",
"settingsAppearance": "Appearance",
@@ -873,6 +877,10 @@
"title": "Email",
"toastEmailRequiredFieldsError": "Email address, host, port and password are required"
},
+ "globalThresholds": {
+ "description": "Configure global CPU, Memory, Disk, and Temperature thresholds. If a value is provided, it will automatically be enabled for monitoring.",
+ "title": "Global Thresholds"
+ },
"pageSpeedSettings": {
"description": "Enter your Google PageSpeed API key to enable Google PageSpeed monitoring. Click Reset to update the key.",
"labelApiKey": "PageSpeed API key",
@@ -905,10 +913,6 @@
"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",
@@ -985,6 +989,7 @@
"statusPageStatusNotPublic": "This status page is not public.",
"statusPageStatusServiceStatus": "Service status",
"submit": "Submit",
+ "SupportedFormats": "Supported formats",
"teamPanel": {
"cancel": "Cancel",
"email": "Email",
@@ -1040,10 +1045,10 @@
"uptimeCreateJsonPathQuery": "for query language documentation.",
"uptimeGeneralInstructions": {
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect to get the full container ID.",
+ "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type.",
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
- "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
- "game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
+ "port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard."
},
"uptimeMonitor": {
"fallback": {
@@ -1066,5 +1071,6 @@
"websiteMonitoring": "Website monitoring",
"websiteMonitoringDescription": "Use HTTP(s) to monitor your website or API endpoint.",
"whenNewIncident": "When there is a new incident,",
- "window": "window"
+ "window": "window",
+ "YourPhoto": "Profile photo"
}
diff --git a/client/vite.config.js b/client/vite.config.js
index df686d751..b0d2b7d04 100644
--- a/client/vite.config.js
+++ b/client/vite.config.js
@@ -5,7 +5,7 @@ import { execSync } from "child_process";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
- let version = "3.0-beta";
+ let version = "3.1-beta";
return {
base: "/",
diff --git a/server/package-lock.json b/server/package-lock.json
index 3084a5361..b761c4d79 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -21,6 +21,7 @@
"express": "^4.19.2",
"express-rate-limit": "8.0.1",
"gamedig": "^5.3.1",
+ "got": "14.4.7",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
@@ -1137,6 +1138,12 @@
"hasInstallScript": true,
"license": "Apache-2.0"
},
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "license": "MIT"
+ },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -1159,12 +1166,12 @@
"license": "BSD-3-Clause"
},
"node_modules/@sindresorhus/is": {
- "version": "5.6.0",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
- "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz",
+ "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==",
"license": "MIT",
"engines": {
- "node": ">=14.16"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
@@ -1821,21 +1828,21 @@
}
},
"node_modules/cacheable-request": {
- "version": "10.2.14",
- "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
- "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz",
+ "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==",
"license": "MIT",
"dependencies": {
- "@types/http-cache-semantics": "^4.0.2",
- "get-stream": "^6.0.1",
+ "@types/http-cache-semantics": "^4.0.4",
+ "get-stream": "^9.0.1",
"http-cache-semantics": "^4.1.1",
- "keyv": "^4.5.3",
+ "keyv": "^4.5.4",
"mimic-response": "^4.0.0",
- "normalize-url": "^8.0.0",
+ "normalize-url": "^8.0.1",
"responselike": "^3.0.0"
},
"engines": {
- "node": ">=14.16"
+ "node": ">=18"
}
},
"node_modules/call-bind-apply-helpers": {
@@ -3750,12 +3757,12 @@
}
},
"node_modules/form-data-encoder": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
- "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz",
+ "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==",
"license": "MIT",
"engines": {
- "node": ">= 14.17"
+ "node": ">= 18"
}
},
"node_modules/formdata-polyfill": {
@@ -3840,6 +3847,82 @@
"node": ">=16.20.0"
}
},
+ "node_modules/gamedig/node_modules/@sindresorhus/is": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
+ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/gamedig/node_modules/cacheable-request": {
+ "version": "10.2.14",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
+ "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "^4.0.2",
+ "get-stream": "^6.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "keyv": "^4.5.3",
+ "mimic-response": "^4.0.0",
+ "normalize-url": "^8.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/gamedig/node_modules/form-data-encoder": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
+ "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.17"
+ }
+ },
+ "node_modules/gamedig/node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gamedig/node_modules/got": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
+ "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^5.2.0",
+ "@szmarczak/http-timer": "^5.0.1",
+ "cacheable-lookup": "^7.0.0",
+ "cacheable-request": "^10.2.8",
+ "decompress-response": "^6.0.0",
+ "form-data-encoder": "^2.1.2",
+ "get-stream": "^6.0.1",
+ "http2-wrapper": "^2.1.10",
+ "lowercase-keys": "^3.0.0",
+ "p-cancelable": "^3.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
"node_modules/gamedig/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -3852,6 +3935,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/gamedig/node_modules/p-cancelable": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+ "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
"node_modules/gaxios": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
@@ -3930,12 +4022,28 @@
}
},
"node_modules/get-stream": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-stream/node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4024,28 +4132,40 @@
}
},
"node_modules/got": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
- "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "version": "14.4.7",
+ "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz",
+ "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==",
"license": "MIT",
"dependencies": {
- "@sindresorhus/is": "^5.2.0",
+ "@sindresorhus/is": "^7.0.1",
"@szmarczak/http-timer": "^5.0.1",
"cacheable-lookup": "^7.0.0",
- "cacheable-request": "^10.2.8",
+ "cacheable-request": "^12.0.1",
"decompress-response": "^6.0.0",
- "form-data-encoder": "^2.1.2",
- "get-stream": "^6.0.1",
- "http2-wrapper": "^2.1.10",
+ "form-data-encoder": "^4.0.2",
+ "http2-wrapper": "^2.2.1",
"lowercase-keys": "^3.0.0",
- "p-cancelable": "^3.0.0",
- "responselike": "^3.0.0"
+ "p-cancelable": "^4.0.1",
+ "responselike": "^3.0.0",
+ "type-fest": "^4.26.1"
},
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/got/node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
- "url": "https://github.com/sindresorhus/got?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/handlebars": {
@@ -6350,12 +6470,12 @@
}
},
"node_modules/p-cancelable": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
- "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz",
+ "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==",
"license": "MIT",
"engines": {
- "node": ">=12.20"
+ "node": ">=14.16"
}
},
"node_modules/p-limit": {
diff --git a/server/package.json b/server/package.json
index 929fab58a..98738aece 100755
--- a/server/package.json
+++ b/server/package.json
@@ -28,6 +28,7 @@
"express": "^4.19.2",
"express-rate-limit": "8.0.1",
"gamedig": "^5.3.1",
+ "got": "14.4.7",
"handlebars": "^4.7.8",
"helmet": "^8.0.0",
"ioredis": "^5.4.2",
diff --git a/server/src/config/services.js b/server/src/config/services.js
index 73220128f..08c2525f3 100644
--- a/server/src/config/services.js
+++ b/server/src/config/services.js
@@ -19,8 +19,10 @@ import MaintenanceWindowService from "../service/business/maintenanceWindowServi
import MonitorService from "../service/business/monitorService.js";
import papaparse from "papaparse";
import axios from "axios";
+import got from "got";
import ping from "ping";
import http from "http";
+import https from "https";
import Docker from "dockerode";
import net from "net";
import fs from "fs";
@@ -32,6 +34,8 @@ import mjml2html from "mjml";
import jwt from "jsonwebtoken";
import crypto from "crypto";
import { games } from "gamedig";
+import jmespath from "jmespath";
+import { GameDig } from "gamedig";
import { fileURLToPath } from "url";
import { ObjectId } from "mongodb";
@@ -43,8 +47,6 @@ 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";
import Monitor from "../db/models/Monitor.js";
import User from "../db/models/User.js";
import InviteToken from "../db/models/InviteToken.js";
@@ -52,7 +54,6 @@ 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 Notification from "../db/models/Notification.js";
import RecoveryToken from "../db/models/RecoveryToken.js";
import AppSettings from "../db/models/AppSettings.js";
@@ -61,12 +62,9 @@ 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";
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";
import SettingsModule from "../db/mongo/modules/settingsModule.js";
@@ -80,18 +78,15 @@ export const initializeServices = async ({ logger, envSettings, settingsService
const stringService = new StringService(translationService);
// Create DB
- const checkModule = new CheckModule({ logger, Check, HardwareCheck, PageSpeedCheck, Monitor, User });
+ const checkModule = new CheckModule({ logger, Check, Monitor, User });
const inviteModule = new InviteModule({ InviteToken, crypto, stringService });
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, stringService });
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,
@@ -100,9 +95,7 @@ export const initializeServices = async ({ logger, envSettings, settingsService
NormalizeData,
NormalizeDataUptimeDetails,
});
- 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 settingsModule = new SettingsModule({ AppSettings });
@@ -113,19 +106,29 @@ export const initializeServices = async ({ logger, envSettings, settingsService
inviteModule,
statusPageModule,
userModule,
- hardwareCheckModule,
maintenanceWindowModule,
monitorModule,
- networkCheckModule,
notificationModule,
- pageSpeedCheckModule,
recoveryModule,
settingsModule,
});
await db.connect();
- const networkService = new NetworkService(axios, ping, logger, http, Docker, net, stringService, settingsService);
+ const networkService = new NetworkService({
+ axios,
+ got,
+ https,
+ jmespath,
+ GameDig,
+ ping,
+ logger,
+ http,
+ Docker,
+ net,
+ stringService,
+ settingsService,
+ });
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
const bufferService = new BufferService({ db, logger, envSettings });
const statusService = new StatusService({ db, logger, buffer: bufferService });
diff --git a/server/src/db/models/Check.js b/server/src/db/models/Check.js
index 8bb6cd028..42a718bd0 100755
--- a/server/src/db/models/Check.js
+++ b/server/src/db/models/Check.js
@@ -1,99 +1,176 @@
import mongoose from "mongoose";
-const BaseCheckSchema = mongoose.Schema({
- /**
- * Reference to the associated Monitor document.
- *
- * @type {mongoose.Schema.Types.ObjectId}
- */
- monitorId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Monitor",
- immutable: true,
- index: true,
- },
-
- teamId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "Team",
- immutable: true,
- index: true,
- },
- /**
- * Status of the check (true for up, false for down).
- *
- * @type {Boolean}
- */
- status: {
- type: Boolean,
- index: true,
- },
- /**
- * Response time of the check in milliseconds.
- *
- * @type {Number}
- */
- responseTime: {
- type: Number,
- },
- /**
- * HTTP status code received during the check.
- *
- * @type {Number}
- */
- statusCode: {
- type: Number,
- index: true,
- },
- /**
- * Message or description of the check result.
- *
- * @type {String}
- */
- message: {
- type: String,
- },
- /**
- * Expiry date of the check, auto-calculated to expire after 30 days.
- *
- * @type {Date}
- */
-
- expiry: {
- type: Date,
- default: Date.now,
- expires: 60 * 60 * 24 * 30, // 30 days
- },
- /**
- * Acknowledgment of the check.
- *
- * @type {Boolean}
- */
- ack: {
- type: Boolean,
- default: false,
- },
- /**
- * Resolution date of the check (when the check was resolved).
- *
- * @type {Date}
- */
- ackAt: {
- type: Date,
- },
+const cpuSchema = mongoose.Schema({
+ physical_core: { type: Number, default: 0 },
+ logical_core: { type: Number, default: 0 },
+ frequency: { type: Number, default: 0 },
+ temperature: { type: [Number], default: [] },
+ free_percent: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
});
-/**
- * Check Schema for MongoDB collection.
- *
- * Represents a check associated with a monitor, storing information
- * about the status and response of a particular check event.
- */
-const CheckSchema = mongoose.Schema({ ...BaseCheckSchema.obj }, { timestamps: true });
+const memorySchema = mongoose.Schema({
+ total_bytes: { type: Number, default: 0 },
+ available_bytes: { type: Number, default: 0 },
+ used_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+});
+
+const diskSchema = mongoose.Schema({
+ read_speed_bytes: { type: Number, default: 0 },
+ write_speed_bytes: { type: Number, default: 0 },
+ total_bytes: { type: Number, default: 0 },
+ free_bytes: { type: Number, default: 0 },
+ usage_percent: { type: Number, default: 0 },
+});
+
+const hostSchema = mongoose.Schema({
+ os: { type: String, default: "" },
+ platform: { type: String, default: "" },
+ kernel_version: { type: String, default: "" },
+});
+
+const errorSchema = mongoose.Schema({
+ metric: { type: [String], default: [] },
+ err: { type: String, default: "" },
+});
+
+const captureSchema = mongoose.Schema({
+ version: { type: String, default: "" },
+ mode: { type: String, default: "" },
+});
+
+const networkInterfaceSchema = mongoose.Schema({
+ name: { type: String },
+ bytes_sent: { type: Number, default: 0 },
+ bytes_recv: { type: Number, default: 0 },
+ packets_sent: { type: Number, default: 0 },
+ packets_recv: { type: Number, default: 0 },
+ err_in: { type: Number, default: 0 },
+ err_out: { type: Number, default: 0 },
+ drop_in: { type: Number, default: 0 },
+ drop_out: { type: Number, default: 0 },
+ fifo_in: { type: Number, default: 0 },
+ fifo_out: { type: Number, default: 0 },
+});
+
+const CheckSchema = new mongoose.Schema(
+ {
+ // Common fields
+ monitorId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Monitor",
+ immutable: true,
+ index: true,
+ },
+
+ teamId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "Team",
+ immutable: true,
+ index: true,
+ },
+ type: {
+ type: String,
+ enum: ["http", "hardware", "pagespeed", "distributed"],
+ required: true,
+ index: true,
+ },
+
+ status: {
+ type: Boolean,
+ index: true,
+ },
+
+ responseTime: {
+ type: Number,
+ },
+
+ timings: {
+ type: Object,
+ default: {},
+ },
+
+ statusCode: {
+ type: Number,
+ index: true,
+ },
+
+ message: {
+ type: String,
+ },
+
+ expiry: {
+ type: Date,
+ default: Date.now,
+ expires: 60 * 60 * 24 * 30, // 30 days
+ },
+
+ ack: {
+ type: Boolean,
+ default: false,
+ },
+
+ ackAt: {
+ type: Date,
+ },
+
+ // Hardware fields
+ cpu: {
+ type: cpuSchema,
+ default: () => ({}),
+ },
+ memory: {
+ type: memorySchema,
+ default: () => ({}),
+ },
+ disk: {
+ type: [diskSchema],
+ default: () => [],
+ },
+ host: {
+ type: hostSchema,
+ default: () => ({}),
+ },
+
+ errors: {
+ type: [errorSchema],
+ default: () => [],
+ },
+
+ capture: {
+ type: captureSchema,
+ default: () => ({}),
+ },
+
+ net: {
+ type: [networkInterfaceSchema],
+ default: () => [],
+ },
+
+ // PageSpeed fields
+ accessibility: {
+ type: Number,
+ },
+ bestPractices: {
+ type: Number,
+ },
+ seo: {
+ type: Number,
+ },
+ performance: {
+ type: Number,
+ },
+ audits: {
+ type: Object,
+ },
+ },
+ { timestamps: true }
+);
+
CheckSchema.index({ updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: 1 });
CheckSchema.index({ monitorId: 1, updatedAt: -1 });
CheckSchema.index({ teamId: 1, updatedAt: -1 });
export default mongoose.model("Check", CheckSchema);
-export { BaseCheckSchema };
diff --git a/server/src/db/models/HardwareCheck.js b/server/src/db/models/HardwareCheck.js
deleted file mode 100755
index 5b7d56213..000000000
--- a/server/src/db/models/HardwareCheck.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-const cpuSchema = mongoose.Schema({
- physical_core: { type: Number, default: 0 },
- logical_core: { type: Number, default: 0 },
- frequency: { type: Number, default: 0 },
- temperature: { type: [Number], default: [] },
- free_percent: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const memorySchema = mongoose.Schema({
- total_bytes: { type: Number, default: 0 },
- available_bytes: { type: Number, default: 0 },
- used_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const diskSchema = mongoose.Schema({
- read_speed_bytes: { type: Number, default: 0 },
- write_speed_bytes: { type: Number, default: 0 },
- total_bytes: { type: Number, default: 0 },
- free_bytes: { type: Number, default: 0 },
- usage_percent: { type: Number, default: 0 },
-});
-
-const hostSchema = mongoose.Schema({
- os: { type: String, default: "" },
- platform: { type: String, default: "" },
- kernel_version: { type: String, default: "" },
-});
-
-const errorSchema = mongoose.Schema({
- metric: { type: [String], default: [] },
- err: { type: String, default: "" },
-});
-
-const captureSchema = mongoose.Schema({
- version: { type: String, default: "" },
- mode: { type: String, default: "" },
-});
-
-const networkInterfaceSchema = mongoose.Schema({
- name: { type: String },
- bytes_sent: { type: Number, default: 0 },
- bytes_recv: { type: Number, default: 0 },
- packets_sent: { type: Number, default: 0 },
- packets_recv: { type: Number, default: 0 },
- err_in: { type: Number, default: 0 },
- err_out: { type: Number, default: 0 },
- drop_in: { type: Number, default: 0 },
- drop_out: { type: Number, default: 0 },
- fifo_in: { type: Number, default: 0 },
- fifo_out: { type: Number, default: 0 },
-});
-
-const HardwareCheckSchema = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- cpu: {
- type: cpuSchema,
- default: () => ({}),
- },
- memory: {
- type: memorySchema,
- default: () => ({}),
- },
- disk: {
- type: [diskSchema],
- default: () => [],
- },
- host: {
- type: hostSchema,
- default: () => ({}),
- },
-
- errors: {
- type: [errorSchema],
- default: () => [],
- },
-
- capture: {
- type: captureSchema,
- default: () => ({}),
- },
-
- net: {
- type: [networkInterfaceSchema],
- default: () => [],
- required: false,
- },
- },
- { timestamps: true }
-);
-
-HardwareCheckSchema.index({ createdAt: 1 });
-HardwareCheckSchema.index({ monitorId: 1, createdAt: 1 });
-HardwareCheckSchema.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("HardwareCheck", HardwareCheckSchema);
diff --git a/server/src/db/models/Monitor.js b/server/src/db/models/Monitor.js
index 1ee467999..0ad0e0a78 100755
--- a/server/src/db/models/Monitor.js
+++ b/server/src/db/models/Monitor.js
@@ -1,6 +1,4 @@
import mongoose from "mongoose";
-import HardwareCheck from "./HardwareCheck.js";
-import PageSpeedCheck from "./PageSpeedCheck.js";
import Check from "./Check.js";
import MonitorStats from "./MonitorStats.js";
import StatusPage from "./StatusPage.js";
@@ -30,6 +28,18 @@ const MonitorSchema = mongoose.Schema(
type: Boolean,
default: undefined,
},
+ statusWindow: {
+ type: [Boolean],
+ default: [],
+ },
+ statusWindowSize: {
+ type: Number,
+ default: 5,
+ },
+ statusWindowThreshold: {
+ type: Number,
+ default: 0.6,
+ },
type: {
type: String,
required: true,
@@ -133,13 +143,7 @@ MonitorSchema.pre("findOneAndDelete", async function (next) {
throw new Error("Monitor not found");
}
- if (doc?.type === "pagespeed") {
- await PageSpeedCheck.deleteMany({ monitorId: doc._id });
- } else if (doc?.type === "hardware") {
- await HardwareCheck.deleteMany({ monitorId: doc._id });
- } else {
- await Check.deleteMany({ monitorId: doc._id });
- }
+ await Check.deleteMany({ monitorId: doc._id });
// Deal with status pages
await StatusPage.updateMany({ monitors: doc?._id }, { $pull: { monitors: doc?._id } });
@@ -156,13 +160,7 @@ MonitorSchema.pre("deleteMany", async function (next) {
const monitors = await this.model.find(filter).select(["_id", "type"]).lean();
for (const monitor of monitors) {
- if (monitor.type === "pagespeed") {
- await PageSpeedCheck.deleteMany({ monitorId: monitor._id });
- } else if (monitor.type === "hardware") {
- await HardwareCheck.deleteMany({ monitorId: monitor._id });
- } else {
- await Check.deleteMany({ monitorId: monitor._id });
- }
+ await Check.deleteMany({ monitorId: monitor._id });
await StatusPage.updateMany({ monitors: monitor._id }, { $pull: { monitors: monitor._id } });
await MonitorStats.deleteMany({ monitorId: monitor._id.toString() });
}
diff --git a/server/src/db/models/MonitorStats.js b/server/src/db/models/MonitorStats.js
index c28811b33..c081c8a4f 100755
--- a/server/src/db/models/MonitorStats.js
+++ b/server/src/db/models/MonitorStats.js
@@ -40,10 +40,6 @@ const MonitorStatsSchema = new mongoose.Schema(
type: Number,
default: 0,
},
- uptBurnt: {
- type: mongoose.Schema.Types.Decimal128,
- required: false,
- },
},
{ timestamps: true }
);
diff --git a/server/src/db/models/NetworkCheck.js b/server/src/db/models/NetworkCheck.js
deleted file mode 100644
index eca69eef9..000000000
--- a/server/src/db/models/NetworkCheck.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-
-const networkInterfaceSchema = mongoose.Schema({
- name: { type: String, required: true },
- bytes_sent: { type: Number, default: 0 },
- bytes_recv: { type: Number, default: 0 },
- packets_sent: { type: Number, default: 0 },
- packets_recv: { type: Number, default: 0 },
- err_in: { type: Number, default: 0 },
- err_out: { type: Number, default: 0 },
- drop_in: { type: Number, default: 0 },
- drop_out: { type: Number, default: 0 },
- fifo_in: { type: Number, default: 0 },
- fifo_out: { type: Number, default: 0 },
-});
-
-const captureSchema = mongoose.Schema({
- version: { type: String, default: "" },
- mode: { type: String, default: "" },
-});
-
-const NetworkCheckSchema = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- data: {
- type: [networkInterfaceSchema],
- default: () => [],
- },
- capture: {
- type: captureSchema,
- default: () => ({}),
- },
- errors: {
- type: mongoose.Schema.Types.Mixed,
- default: null,
- },
- },
- { timestamps: true }
-);
-
-NetworkCheckSchema.index({ createdAt: 1 });
-NetworkCheckSchema.index({ monitorId: 1, createdAt: 1 });
-NetworkCheckSchema.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("NetworkCheck", NetworkCheckSchema);
diff --git a/server/src/db/models/PageSpeedCheck.js b/server/src/db/models/PageSpeedCheck.js
deleted file mode 100755
index 67c7c375e..000000000
--- a/server/src/db/models/PageSpeedCheck.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import mongoose from "mongoose";
-import { BaseCheckSchema } from "./Check.js";
-const AuditSchema = mongoose.Schema({
- id: { type: String, required: true },
- title: { type: String, required: true },
- description: { type: String, required: true },
- score: { type: Number, required: true },
- scoreDisplayMode: { type: String, required: true },
- displayValue: { type: String, required: true },
- numericValue: { type: Number, required: true },
- numericUnit: { type: String, required: true },
-});
-
-const AuditsSchema = mongoose.Schema({
- cls: {
- type: AuditSchema,
- required: true,
- },
- si: {
- type: AuditSchema,
- required: true,
- },
- fcp: {
- type: AuditSchema,
- required: true,
- },
- lcp: {
- type: AuditSchema,
- required: true,
- },
- tbt: {
- type: AuditSchema,
- required: true,
- },
-});
-
-/**
- * Mongoose schema for storing metrics from Google Lighthouse.
- * @typedef {Object} PageSpeedCheck
- * @property {mongoose.Schema.Types.ObjectId} monitorId - Reference to the Monitor model.
- * @property {number} accessibility - Accessibility score.
- * @property {number} bestPractices - Best practices score.
- * @property {number} seo - SEO score.
- * @property {number} performance - Performance score.
- */
-
-const PageSpeedCheck = mongoose.Schema(
- {
- ...BaseCheckSchema.obj,
- accessibility: {
- type: Number,
- required: true,
- },
- bestPractices: {
- type: Number,
- required: true,
- },
- seo: {
- type: Number,
- required: true,
- },
- performance: {
- type: Number,
- required: true,
- },
- audits: {
- type: AuditsSchema,
- required: true,
- },
- },
- { timestamps: true }
-);
-
-/**
- * Mongoose model for storing metrics from Google Lighthouse.
- * @typedef {mongoose.Model} LighthouseMetricsModel
- */
-
-PageSpeedCheck.index({ createdAt: 1 });
-PageSpeedCheck.index({ monitorId: 1, createdAt: 1 });
-PageSpeedCheck.index({ monitorId: 1, createdAt: -1 });
-
-export default mongoose.model("PageSpeedCheck", PageSpeedCheck);
diff --git a/server/src/db/mongo/modules/checkModule.js b/server/src/db/mongo/modules/checkModule.js
index a606a593b..dfca02bed 100755
--- a/server/src/db/mongo/modules/checkModule.js
+++ b/server/src/db/mongo/modules/checkModule.js
@@ -31,7 +31,7 @@ class CheckModule {
}
};
- getChecksByMonitor = async ({ monitorId, type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
+ getChecksByMonitor = async ({ monitorId, sortOrder, dateRange, filter, ack, page, rowsPerPage, status }) => {
try {
status = status === "true" ? true : status === "false" ? false : undefined;
page = parseInt(page);
@@ -79,19 +79,7 @@ class CheckModule {
skip = page * rowsPerPage;
}
- const checkModels = {
- http: this.Check,
- ping: this.Check,
- docker: this.Check,
- port: this.Check,
- pagespeed: this.PageSpeedCheck,
- hardware: this.HardwareCheck,
- game: this.Check,
- };
-
- const Model = checkModels[type];
-
- const checks = await Model.aggregate([
+ const checks = await this.Check.aggregate([
{ $match: matchStage },
{ $sort: { createdAt: sortOrder } },
{
@@ -166,18 +154,6 @@ class CheckModule {
const aggregatePipeline = [
{ $match: matchStage },
- {
- $unionWith: {
- coll: "hardwarechecks",
- pipeline: [{ $match: matchStage }],
- },
- },
- {
- $unionWith: {
- coll: "pagespeedchecks",
- pipeline: [{ $match: matchStage }],
- },
- },
{ $sort: { createdAt: sortOrder } },
{
diff --git a/server/src/db/mongo/modules/hardwareCheckModule.js b/server/src/db/mongo/modules/hardwareCheckModule.js
deleted file mode 100755
index 448e72fed..000000000
--- a/server/src/db/mongo/modules/hardwareCheckModule.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const SERVICE_NAME = "hardwareCheckModule";
-
-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;
- }
- };
-}
-
-export default HardwareCheckModule;
diff --git a/server/src/db/mongo/modules/monitorModule.js b/server/src/db/mongo/modules/monitorModule.js
index 0e301cdcf..8beb9316c 100755
--- a/server/src/db/mongo/modules/monitorModule.js
+++ b/server/src/db/mongo/modules/monitorModule.js
@@ -11,25 +11,10 @@ import {
const SERVICE_NAME = "monitorModule";
class MonitorModule {
- constructor({
- Monitor,
- MonitorStats,
- Check,
- PageSpeedCheck,
- HardwareCheck,
- stringService,
- fs,
- path,
- fileURLToPath,
- ObjectId,
- NormalizeData,
- NormalizeDataUptimeDetails,
- }) {
+ constructor({ Monitor, MonitorStats, Check, 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;
@@ -38,15 +23,6 @@ class MonitorModule {
this.NormalizeData = NormalizeData;
this.NormalizeDataUptimeDetails = NormalizeDataUptimeDetails;
- 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);
@@ -146,19 +122,18 @@ class MonitorModule {
};
//Helper
- getMonitorChecks = async (monitorId, model, dateRange, sortOrder) => {
+ getMonitorChecks = async (monitorId, dateRange, sortOrder) => {
const indexSpec = {
monitorId: 1,
- createdAt: sortOrder, // This will be 1 or -1
+ updatedAt: 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 },
- })
+ this.Check.find({ monitorId }).sort({ createdAt: sortOrder }).hint(indexSpec).lean(),
+ this.Check.find({
+ monitorId,
+ createdAt: { $gte: dateRange.start, $lte: dateRange.end },
+ })
.hint(indexSpec)
.lean(),
]);
@@ -295,9 +270,8 @@ class MonitorModule {
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);
+ const { checksAll, checksForDateRange } = await this.getMonitorChecks(monitorId, dates, sort);
// Build monitor stats
const monitorStats = {
@@ -339,7 +313,7 @@ class MonitorModule {
};
const dateString = formatLookup[dateRange];
- const hardwareStats = await this.HardwareCheck.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
+ const hardwareStats = await this.Check.aggregate(buildHardwareDetailsPipeline(monitor, dates, dateString));
const stats = hardwareStats[0];
diff --git a/server/src/db/mongo/modules/monitorModuleQueries.js b/server/src/db/mongo/modules/monitorModuleQueries.js
index 7753066fc..32aed1529 100755
--- a/server/src/db/mongo/modules/monitorModuleQueries.js
+++ b/server/src/db/mongo/modules/monitorModuleQueries.js
@@ -5,12 +5,12 @@ const buildUptimeDetailsPipeline = (monitorId, dates, dateString) => {
{
$match: {
monitorId: new ObjectId(monitorId),
- createdAt: { $gte: dates.start, $lte: dates.end },
+ updatedAt: { $gte: dates.start, $lte: dates.end },
},
},
{
$sort: {
- createdAt: 1,
+ updatedAt: 1,
},
},
{
@@ -173,6 +173,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
{
$match: {
monitorId: monitor._id,
+ type: "hardware",
createdAt: { $gte: dates.start, $lte: dates.end },
},
},
@@ -218,14 +219,14 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
{
$project: {
diskCount: {
- $size: "$disk",
+ $size: { $ifNull: ["$disk", []] },
},
- netCount: { $size: "$net" },
+ netCount: { $size: { $ifNull: ["$net", []] } },
},
},
{
$lookup: {
- from: "hardwarechecks",
+ from: "checks",
let: {
diskCount: "$diskCount",
netCount: "$netCount",
@@ -381,7 +382,7 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
},
net: {
$map: {
- input: { $range: [0, { $size: { $arrayElemAt: ["$net", 0] } }] },
+ input: { $range: [0, { $size: { $ifNull: [{ $arrayElemAt: ["$net", 0] }, []] } }] },
as: "netIndex",
in: {
name: {
@@ -409,7 +410,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$arrayElemAt: [
{
$map: {
- input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
as: "iface",
in: "$$iface.bytes_sent",
},
@@ -418,7 +421,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
],
},
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
- tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
},
in: {
$cond: [
@@ -444,7 +449,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$arrayElemAt: [
{
$map: {
- input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
as: "iface",
in: "$$iface.bytes_recv",
},
@@ -453,7 +460,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
],
},
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
- tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
},
in: {
$cond: [
@@ -479,7 +488,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$arrayElemAt: [
{
$map: {
- input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
as: "iface",
in: "$$iface.packets_sent",
},
@@ -488,7 +499,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
],
},
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
- tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
},
in: {
$cond: [
@@ -522,9 +535,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$map: {
input: {
$arrayElemAt: [
- "$net",
+ { $ifNull: ["$net", []] },
{
- $subtract: [{ $size: "$net" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
},
],
},
@@ -538,9 +551,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
tLast: {
$arrayElemAt: [
- "$updatedAts",
+ { $ifNull: ["$updatedAts", []] },
{
- $subtract: [{ $size: "$updatedAts" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
},
],
},
@@ -566,7 +579,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$arrayElemAt: [
{
$map: {
- input: { $arrayElemAt: ["$net", { $subtract: [{ $size: "$net" }, 1] }] },
+ input: {
+ $arrayElemAt: [{ $ifNull: ["$net", []] }, { $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1] }],
+ },
as: "iface",
in: "$$iface.err_in",
},
@@ -575,7 +590,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
],
},
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
- tLast: { $arrayElemAt: ["$updatedAts", { $subtract: [{ $size: "$updatedAts" }, 1] }] },
+ tLast: {
+ $arrayElemAt: [{ $ifNull: ["$updatedAts", []] }, { $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1] }],
+ },
},
in: {
$cond: [
@@ -609,9 +626,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$map: {
input: {
$arrayElemAt: [
- "$net",
+ { $ifNull: ["$net", []] },
{
- $subtract: [{ $size: "$net" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
},
],
},
@@ -625,9 +642,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
tLast: {
$arrayElemAt: [
- "$updatedAts",
+ { $ifNull: ["$updatedAts", []] },
{
- $subtract: [{ $size: "$updatedAts" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
},
],
},
@@ -664,9 +681,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$map: {
input: {
$arrayElemAt: [
- "$net",
+ { $ifNull: ["$net", []] },
{
- $subtract: [{ $size: "$net" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
},
],
},
@@ -680,9 +697,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
tLast: {
$arrayElemAt: [
- "$updatedAts",
+ { $ifNull: ["$updatedAts", []] },
{
- $subtract: [{ $size: "$updatedAts" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
},
],
},
@@ -719,9 +736,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
$map: {
input: {
$arrayElemAt: [
- "$net",
+ { $ifNull: ["$net", []] },
{
- $subtract: [{ $size: "$net" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$net", []] } }, 1],
},
],
},
@@ -735,9 +752,9 @@ const buildHardwareDetailsPipeline = (monitor, dates, dateString) => {
tFirst: { $arrayElemAt: ["$updatedAts", 0] },
tLast: {
$arrayElemAt: [
- "$updatedAts",
+ { $ifNull: ["$updatedAts", []] },
{
- $subtract: [{ $size: "$updatedAts" }, 1],
+ $subtract: [{ $size: { $ifNull: ["$updatedAts", []] } }, 1],
},
],
},
@@ -970,12 +987,7 @@ const buildMonitorsWithChecksByTeamIdPipeline = ({ matchStage, filter, page, row
// Add checks
if (limit) {
- let checksCollection = "checks";
- if (type === "pagespeed") {
- checksCollection = "pagespeedchecks";
- } else if (type === "hardware") {
- checksCollection = "hardwarechecks";
- }
+ const checksCollection = "checks";
monitorsPipeline.push({
$lookup: {
from: checksCollection,
@@ -1040,12 +1052,8 @@ const buildFilteredMonitorsByTeamIdPipeline = ({ matchStage, filter, page, rowsP
// Add checks
if (limit) {
- let checksCollection = "checks";
- if (type === "pagespeed") {
- checksCollection = "pagespeedchecks";
- } else if (type === "hardware") {
- checksCollection = "hardwarechecks";
- }
+ const checksCollection = "checks";
+
pipeline.push({
$lookup: {
from: checksCollection,
@@ -1159,46 +1167,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
},
]
: []),
- ...(limit
- ? [
- {
- $lookup: {
- from: "pagespeedchecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
- },
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "pagespeedchecks",
- },
- },
- ]
- : []),
- ...(limit
- ? [
- {
- $lookup: {
- from: "hardwarechecks",
- let: { monitorId: "$_id" },
- pipeline: [
- {
- $match: {
- $expr: { $eq: ["$monitorId", "$$monitorId"] },
- },
- },
- { $sort: { createdAt: -1 } },
- ...(limit ? [{ $limit: limit }] : []),
- ],
- as: "hardwarechecks",
- },
- },
- ]
- : []),
{
$addFields: {
@@ -1209,14 +1177,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
case: { $in: ["$type", ["http", "ping", "docker", "port", "game"]] },
then: "$standardchecks",
},
- {
- case: { $eq: ["$type", "pagespeed"] },
- then: "$pagespeedchecks",
- },
- {
- case: { $eq: ["$type", "hardware"] },
- then: "$hardwarechecks",
- },
],
default: [],
},
@@ -1226,8 +1186,6 @@ const buildGetMonitorsByTeamIdPipeline = (req) => {
{
$project: {
standardchecks: 0,
- pagespeedchecks: 0,
- hardwarechecks: 0,
},
},
],
diff --git a/server/src/db/mongo/modules/networkCheckModule.js b/server/src/db/mongo/modules/networkCheckModule.js
deleted file mode 100644
index 917e68440..000000000
--- a/server/src/db/mongo/modules/networkCheckModule.js
+++ /dev/null
@@ -1,30 +0,0 @@
-const SERVICE_NAME = "networkCheckModule";
-
-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;
- }
- };
-}
-
-export default NetworkCheckModule;
diff --git a/server/src/db/mongo/modules/pageSpeedCheckModule.js b/server/src/db/mongo/modules/pageSpeedCheckModule.js
deleted file mode 100755
index b2a3eb71d..000000000
--- a/server/src/db/mongo/modules/pageSpeedCheckModule.js
+++ /dev/null
@@ -1,31 +0,0 @@
-// import PageSpeedCheck from "../../models/PageSpeedCheck.js";
-const SERVICE_NAME = "pageSpeedCheckModule";
-
-class PageSpeedCheckModule {
- constructor({ PageSpeedCheck }) {
- this.PageSpeedCheck = PageSpeedCheck;
- }
-
- 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/checkService.js b/server/src/service/business/checkService.js
index 7966fdbb4..9beb43326 100644
--- a/server/src/service/business/checkService.js
+++ b/server/src/service/business/checkService.js
@@ -33,10 +33,9 @@ class CheckService {
throw this.errorService.createAuthorizationError();
}
- let { type, sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
+ let { sortOrder, dateRange, filter, ack, page, rowsPerPage, status } = query;
const result = await this.db.checkModule.getChecksByMonitor({
monitorId,
- type,
sortOrder,
dateRange,
filter,
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
index 60bcb0754..47d73c0df 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js
@@ -54,6 +54,7 @@ class SuperSimpleQueue {
id: monitorId.toString(),
template: "monitor-job",
repeat: monitor.interval,
+ active: monitor.isActive,
data: monitor.toObject(),
});
};
diff --git a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
index ca2e5f642..ac8da9169 100644
--- a/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
+++ b/server/src/service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js
@@ -2,6 +2,16 @@ const SERVICE_NAME = "JobQueueHelper";
class SuperSimpleQueueHelper {
static SERVICE_NAME = SERVICE_NAME;
+
+ /**
+ * @param {{
+ * db: import("../database").Database,
+ * logger: import("../logger").Logger,
+ * networkService: import("../networkService").NetworkService,
+ * statusService: import("../statusService").StatusService,
+ * notificationService: import("../notificationService").NotificationService
+ * }}
+ */
constructor({ db, logger, networkService, statusService, notificationService }) {
this.db = db;
this.logger = logger;
@@ -31,7 +41,8 @@ class SuperSimpleQueueHelper {
});
return;
}
- const networkResponse = await this.networkService.getStatus(monitor);
+ const networkResponse = await this.networkService.requestStatus(monitor);
+
if (!networkResponse) {
throw new Error("No network response");
}
diff --git a/server/src/service/infrastructure/bufferService.js b/server/src/service/infrastructure/bufferService.js
index dc86597b2..ed48cc654 100755
--- a/server/src/service/infrastructure/bufferService.js
+++ b/server/src/service/infrastructure/bufferService.js
@@ -1,32 +1,13 @@
const SERVICE_NAME = "BufferService";
-const TYPE_MAP = {
- http: "checks",
- ping: "checks",
- port: "checks",
- docker: "checks",
- pagespeed: "pagespeedChecks",
- hardware: "hardwareChecks",
- game: "checks",
-};
class BufferService {
static SERVICE_NAME = SERVICE_NAME;
constructor({ db, logger, envSettings }) {
- this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 5000 : 1000 * 60 * 1; // 1 minute
+ this.BUFFER_TIMEOUT = envSettings.nodeEnv === "development" ? 1000 : 1000 * 60 * 1; // 1 minute
this.db = db;
this.logger = logger;
this.SERVICE_NAME = SERVICE_NAME;
- this.buffers = {
- checks: [],
- pagespeedChecks: [],
- hardwareChecks: [],
- };
- this.OPERATION_MAP = {
- checks: this.db.checkModule.createChecks,
- pagespeedChecks: this.db.pageSpeedCheckModule.createPageSpeedChecks,
- hardwareChecks: this.db.hardwareCheckModule.createHardwareChecks,
- };
-
+ this.buffer = [];
this.scheduleNextFlush();
this.logger.info({
message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`,
@@ -39,9 +20,9 @@ class BufferService {
return BufferService.SERVICE_NAME;
}
- addToBuffer({ check, type }) {
+ addToBuffer({ check }) {
try {
- this.buffers[TYPE_MAP[type]].push(check);
+ this.buffer.push(check);
} catch (error) {
this.logger.error({
message: error.message,
@@ -55,7 +36,7 @@ class BufferService {
scheduleNextFlush() {
this.bufferTimer = setTimeout(async () => {
try {
- await this.flushBuffers();
+ await this.flushBuffer();
} catch (error) {
this.logger.error({
message: `Error in flush cycle: ${error.message}`,
@@ -69,35 +50,24 @@ class BufferService {
}
}, this.BUFFER_TIMEOUT);
}
- async flushBuffers() {
- let items = 0;
- for (const [bufferName, buffer] of Object.entries(this.buffers)) {
- items += buffer.length;
- const operation = this.OPERATION_MAP[bufferName];
- if (!operation) {
- this.logger.error({
- message: `No operation found for ${bufferName}`,
- service: this.SERVICE_NAME,
- method: "flushBuffers",
- });
- continue;
- }
- try {
- await operation(buffer);
- } catch (error) {
- this.logger.error({
- message: error.message,
- service: this.SERVICE_NAME,
- method: "flushBuffers",
- stack: error.stack,
- });
- }
- this.buffers[bufferName] = [];
+ async flushBuffer() {
+ let items = this.buffer.length;
+
+ try {
+ await this.db.checkModule.createChecks(this.buffer);
+ } catch (error) {
+ this.logger.error({
+ message: error.message,
+ service: this.SERVICE_NAME,
+ method: "flushBuffer",
+ stack: error.stack,
+ });
}
+ this.buffer = [];
this.logger.debug({
message: `Flushed ${items} items`,
service: this.SERVICE_NAME,
- method: "flushBuffers",
+ method: "flushBuffer",
});
items = 0;
}
diff --git a/server/src/service/infrastructure/networkService.js b/server/src/service/infrastructure/networkService.js
old mode 100755
new mode 100644
index 8f904bf56..c90096d7c
--- a/server/src/service/infrastructure/networkService.js
+++ b/server/src/service/infrastructure/networkService.js
@@ -1,23 +1,9 @@
-import jmespath from "jmespath";
-import https from "https";
-import { GameDig } from "gamedig";
-
const SERVICE_NAME = "NetworkService";
-const UPROCK_ENDPOINT = "https://api.uprock.com/checkmate/push";
-/**
- * Constructs a new NetworkService instance.
- *
- * @param {Object} axios - The axios instance for HTTP requests.
- * @param {Object} ping - The ping utility for network checks.
- * @param {Object} logger - The logger instance for logging.
- * @param {Object} http - The HTTP utility for network operations.
- * @param {Object} net - The net utility for network operations.
- */
class NetworkService {
static SERVICE_NAME = SERVICE_NAME;
- constructor(axios, ping, logger, http, Docker, net, stringService, settingsService) {
+ constructor({ axios, got, https, jmespath, GameDig, ping, logger, http, Docker, net, stringService, settingsService }) {
this.TYPE_PING = "ping";
this.TYPE_HTTP = "http";
this.TYPE_PAGESPEED = "pagespeed";
@@ -29,6 +15,10 @@ class NetworkService {
this.NETWORK_ERROR = 5000;
this.PING_ERROR = 5001;
this.axios = axios;
+ this.got = got;
+ this.https = https;
+ this.jmespath = jmespath;
+ this.GameDig = GameDig;
this.ping = ping;
this.logger = logger;
this.http = http;
@@ -38,151 +28,134 @@ class NetworkService {
this.settingsService = settingsService;
}
- get serviceName() {
- return NetworkService.SERVICE_NAME;
- }
-
- /**
- * Times the execution of an asynchronous operation.
- *
- * @param {Function} operation - The asynchronous operation to be timed.
- * @returns {Promise