From 04ab4e23db941b46aae2000dd79ae4a113257b98 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 9 Jun 2025 12:56:05 +0800 Subject: [PATCH] add notificaions channel to Uptime Configure --- client/src/Pages/Uptime/Configure/index.jsx | 909 +++++++++----------- 1 file changed, 414 insertions(+), 495 deletions(-) diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx index f75e53e84..9667728cb 100644 --- a/client/src/Pages/Uptime/Configure/index.jsx +++ b/client/src/Pages/Uptime/Configure/index.jsx @@ -1,7 +1,7 @@ import { useNavigate, useParams } from "react-router"; import { useTheme } from "@emotion/react"; import { useDispatch, useSelector } from "react-redux"; -import { useEffect, useState } from "react"; +import { useState, useEffect } from "react"; import { Box, Stack, @@ -13,31 +13,26 @@ import { } from "@mui/material"; import { monitorValidation } from "../../../Validation/validation"; import { createToast } from "../../../Utils/toastUtils"; -import { logger } from "../../../Utils/Logger"; import { useTranslation } from "react-i18next"; import ConfigBox from "../../../Components/ConfigBox"; import { updateUptimeMonitor, - pauseUptimeMonitor, - getUptimeMonitorById, deleteUptimeMonitor, } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice"; import TextInput from "../../../Components/Inputs/TextInput"; import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments"; -import PauseIcon from "../../../assets/icons/pause-icon.svg?react"; -import ResumeIcon from "../../../assets/icons/resume-icon.svg?react"; import Select from "../../../Components/Inputs/Select"; -import Checkbox from "../../../Components/Inputs/Checkbox"; import Breadcrumbs from "../../../Components/Breadcrumbs"; import PulseDot from "../../../Components/Animated/PulseDot"; -import SkeletonLayout from "./skeleton"; import "./index.css"; import Dialog from "../../../Components/Dialog"; -import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/Components/NotificationIntegrationModal"; import { usePauseMonitor } from "../../../Hooks/useMonitorControls"; import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined"; import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; import { useMonitorUtils } from "../../../Hooks/useMonitorUtils"; +import { useFetchUptimeMonitorById } from "../../../Hooks/useFetchUptimeMonitorById"; +import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications"; +import NotificationsConfig from "../../../Components/NotificationConfig"; /** * Parses a URL string and returns a URL object. @@ -58,22 +53,41 @@ const parseUrl = (url) => { * @component */ const Configure = () => { + const { monitorId } = useParams(); + + // Local state + const [form, setForm] = useState({ + ignoreTlsErrors: false, + interval: 60000, + matchMethod: "equal", + expectedValue: "", + jsonPath: "", + notifications: [], + port: "", + type: "http", + }); + const [useAdvancedMatching, setUseAdvancedMatching] = useState(false); + const [updateTrigger, setUpdateTrigger] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const [errors, setErrors] = useState({}); + + const triggerUpdate = () => { + setUpdateTrigger(!updateTrigger); + }; + + // Network + const [monitor, isLoading, error] = useFetchUptimeMonitorById(monitorId, updateTrigger); + const [notifications, notificationsAreLoading, notificationsError] = + useGetNotificationsByTeamId(); + const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({ + monitorId: monitor?._id, + triggerUpdate, + }); + const MS_PER_MINUTE = 60000; const navigate = useNavigate(); const theme = useTheme(); const dispatch = useDispatch(); - const { user } = useSelector((state) => state.auth); - const { isLoading } = useSelector((state) => state.uptimeMonitors); - const [monitor, setMonitor] = useState({}); - const [errors, setErrors] = useState({}); - const { monitorId } = useParams(); - const idMap = { - "monitor-url": "url", - "monitor-name": "name", - "monitor-checks-http": "type", - "monitor-checks-ping": "type", - "notify-email-default": "notification-email", - }; const matchMethodOptions = [ { _id: "equal", name: "Equal" }, @@ -81,106 +95,28 @@ const Configure = () => { { _id: "regex", name: "Regex" }, ]; + const frequencies = [ + { _id: 1, name: "1 minute" }, + { _id: 2, name: "2 minutes" }, + { _id: 3, name: "3 minutes" }, + { _id: 4, name: "4 minutes" }, + { _id: 5, name: "5 minutes" }, + ]; + const expectedValuePlaceholders = { regex: "^(success|ok)$", equal: "success", include: "ok", }; - const [trigger, setTrigger] = useState(false); - const triggerUpdate = () => { - setTrigger(!trigger); - }; - const [pauseMonitor, isPausing, error] = usePauseMonitor({ - monitorId: monitor?._id, - triggerUpdate, - }); - - useEffect(() => { - const fetchMonitor = async () => { - try { - const action = await dispatch(getUptimeMonitorById({ monitorId })); - if (getUptimeMonitorById.fulfilled.match(action)) { - const monitor = action.payload.data; - setMonitor(monitor); - } else if (getUptimeMonitorById.rejected.match(action)) { - throw new Error(action.error.message); - } - } catch (error) { - logger.error("Error fetching monitor of id: " + monitorId); - navigate("/not-found", { replace: true }); - } - }; - fetchMonitor(); - }, [monitorId, navigate, trigger]); - - const handleChange = (event, name) => { - let { checked, value, id } = event.target; - if (!name) name = idMap[id]; - - if (name.includes("notification-")) { - name = name.replace("notification-", ""); - let hasNotif = monitor.notifications.some( - (notification) => notification.type === name - ); - setMonitor((prev) => { - const notifs = [...prev.notifications]; - if (hasNotif) { - return { - ...prev, - notifications: notifs.filter((notif) => notif.type !== name), - }; - } else { - return { - ...prev, - notifications: [ - ...notifs, - name === "email" - ? { type: name, address: value } - : // TODO - phone number - { type: name, phone: value }, - ], - }; - } - }); - } else { - if (name === "interval") { - value = value * MS_PER_MINUTE; - } - if (name === "ignoreTlsErrors") { - value = checked; - } - setMonitor((prev) => ({ - ...prev, - [name]: value, - })); - - const validation = monitorValidation.validate( - { [name]: value }, - { abortEarly: false } - ); - - setErrors((prev) => { - const updatedErrors = { ...prev }; - - if (validation.error) updatedErrors[name] = validation.error.details[0].message; - else delete updatedErrors[name]; - return updatedErrors; - }); + // Handlers + const handlePause = async () => { + const res = await pauseMonitor(); + if (typeof res !== "undefined") { + triggerUpdate(); } }; - const handleSubmit = async (event) => { - event.preventDefault(); - const action = await dispatch(updateUptimeMonitor({ monitor: monitor })); - if (action.meta.requestStatus === "fulfilled") { - createToast({ body: "Monitor updated successfully!" }); - } else { - createToast({ body: "Failed to update monitor." }); - } - }; - - const [isOpen, setIsOpen] = useState(false); const handleRemove = async (event) => { event.preventDefault(); const action = await dispatch(deleteUptimeMonitor({ monitor })); @@ -191,403 +127,393 @@ const Configure = () => { } }; - const frequencies = [ - { _id: 1, name: "1 minute" }, - { _id: 2, name: "2 minutes" }, - { _id: 3, name: "3 minutes" }, - { _id: 4, name: "4 minutes" }, - { _id: 5, name: "5 minutes" }, - ]; + const onChange = (event) => { + let { name, value, checked } = event.target; + + if (name === "ignoreTlsErrors") { + value = checked; + } + + if (name === "interval") { + value = value * MS_PER_MINUTE; + } + setForm({ ...form, [name]: value }); + + const validation = monitorValidation.validate( + { [name]: value }, + { abortEarly: false } + ); + + setErrors((prev) => { + const updatedErrors = { ...prev }; + + if (validation.error) updatedErrors[name] = validation.error.details[0].message; + else delete updatedErrors[name]; + return updatedErrors; + }); + }; + + const onSubmit = async (e) => { + e.preventDefault(); + + const toSubmit = { + _id: form._id, + url: form.url, + name: form.name, + type: form.type, + matchMethod: form.matchMethod, + expectedValue: form.expectedValue, + jsonPath: form.jsonPath, + interval: form.interval, + teamId: form.teamId, + userId: form.userId, + port: form.port, + ignoreTlsErrors: form.ignoreTlsErrors, + }; + + if (!useAdvancedMatching) { + toSubmit.matchMethod = ""; + toSubmit.expectedValue = ""; + toSubmit.jsonPath = ""; + } + + const validation = monitorValidation.validate(toSubmit, { + abortEarly: false, + }); + + if (validation.error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + createToast({ body: "Please check the form for errors." }); + return; + } + + toSubmit.notifications = form.notifications; + const action = await dispatch(updateUptimeMonitor({ monitor: toSubmit })); + if (action.meta.requestStatus === "fulfilled") { + createToast({ body: "Monitor updated successfully!" }); + } else { + createToast({ body: "Failed to update monitor." }); + } + }; + + // Effects + useEffect(() => { + if (monitor?.matchMethod) { + setUseAdvancedMatching(true); + } + + setForm({ + ...monitor, + }); + }, [monitor, notifications]); // Parse the URL const parsedUrl = parseUrl(monitor?.url); const protocol = parsedUrl?.protocol?.replace(":", "") || ""; - // Notification modal state - const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false); - - const handleOpenNotificationModal = () => { - setIsNotificationModalOpen(true); - }; - - const handleClosenNotificationModal = () => { - setIsNotificationModalOpen(false); - }; - const { determineState, statusColor } = useMonitorUtils(); const { t } = useTranslation(); return ( - - {Object.keys(monitor).length === 0 ? ( - - ) : ( - <> - - + + + + + + + {form.name} + - - - {monitor.name} - - - - - - - - - {monitor.url?.replace(/^https?:\/\//, "") || "..."} - - - {t("editing")} - - - - - - - - - - - - {t("settingsGeneralSettings")} - - - {t("distributedUptimeCreateSelectURL")} - - - - - ) - } - id="monitor-url" - label={t("urlMonitor")} - placeholder="google.com" - value={parsedUrl?.host || monitor?.url || ""} - disabled={true} - /> - handleChange(event, "port")} - error={errors["port"] ? true : false} - helperText={errors["port"]} - hidden={monitor.type !== "port"} - /> - - - - - - - {t("distributedUptimeCreateIncidentNotification")} - - - {t("distributedUptimeCreateIncidentDescription")} - - - - {t("whenNewIncident")} - {/* {Leaving components commented for future funtionality implimentation} */} - {/* logger.warn("disabled")} - isDisabled={true} - /> */} - notification.type === "email" - ) || false - } - value={user?.email} - onChange={(event) => handleChange(event)} - /> - - + + - {/* logger.warn("disabled")} - isDisabled={true} - /> */} - {/* {monitor?.notifications?.some( - (notification) => notification.type === "emails" - ) ? ( - - logger.warn("disabled")} - /> - - You can separate multiple emails with a comma - - - ) : ( - "" - )} */} - - - - - - {t("ignoreTLSError")} - - {t("ignoreTLSErrorDescription")} - - - handleChange(event, "ignoreTlsErrors")} - sx={{ mr: theme.spacing(2) }} - /> - } - label={t("tlsErrorIgnored")} - /> - - - - - - {t("distributedUptimeCreateAdvancedSettings")} - - - - handleChange(event, "matchMethod")} - items={matchMethodOptions} - /> - - handleChange(event, "expectedValue")} - error={errors["expectedValue"] ? true : false} - helperText={errors["expectedValue"]} - /> - - {t("uptimeCreate")} - - - - handleChange(event, "jsonPath")} - error={errors["jsonPath"] ? true : false} - helperText={errors["jsonPath"]} - /> - - {t("uptimeCreateJsonPath")}  - - jmespath.org - -  {t("uptimeCreateJsonPathQuery")} - - - - )} - - - - + {form.url?.replace(/^https?:\/\//, "") || "..."} + + + {t("editing")} + + + + + + + + + + + {t("settingsGeneralSettings")} + + {t("distributedUptimeCreateSelectURL")} + + + + } + id="monitor-url" + label={t("urlMonitor")} + placeholder="google.com" + value={parsedUrl?.host || form?.url || ""} + disabled={true} + /> + - - )} - + + + + Notifications + + Select the notifications you want to send out + + + + + + + + {t("ignoreTLSError")} + + {t("ignoreTLSErrorDescription")} + + + + } + label={t("tlsErrorIgnored")} + /> + + + + + + {t("distributedUptimeCreateAdvancedSettings")} + + + + + + + + {t("uptimeCreate")} + + + + + + {t("uptimeCreateJsonPath")}  + + jmespath.org + +  {t("uptimeCreateJsonPathQuery")} + + + + )} + + + + + + { onConfirm={handleRemove} isLoading={isLoading} /> - - ); };