diff --git a/client/src/Components/Common/AppBar.jsx b/client/src/Components/Common/AppBar.jsx
index df0660cf5..de5f0992e 100644
--- a/client/src/Components/Common/AppBar.jsx
+++ b/client/src/Components/Common/AppBar.jsx
@@ -47,9 +47,6 @@ const AppAppBar = () => {
const location = useLocation();
const navigate = useNavigate();
- // Debugging: Log the current theme mode
- console.log("Current theme mode:", mode);
-
const logoSrc =
mode === "light" ? "/images/prism-black.png" : "/images/prism-white.png";
diff --git a/client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx b/client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
index c966abf33..7ad6ae9af 100644
--- a/client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
+++ b/client/src/Components/MonitorStatusHeader/ConfigButton/index.jsx
@@ -34,8 +34,8 @@ const ConfigButton = ({ shouldRender = true, monitorId, path }) => {
ConfigButton.propTypes = {
shouldRender: PropTypes.bool,
- monitorId: PropTypes.string.isRequired,
- path: PropTypes.string.isRequired,
+ monitorId: PropTypes.string,
+ path: PropTypes.string,
};
export default ConfigButton;
diff --git a/client/src/Components/NotificationConfig/index.jsx b/client/src/Components/NotificationConfig/index.jsx
new file mode 100644
index 000000000..d3707eae4
--- /dev/null
+++ b/client/src/Components/NotificationConfig/index.jsx
@@ -0,0 +1,106 @@
+// Components
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import Divider from "@mui/material/Divider";
+import DeleteOutlineRoundedIcon from "@mui/icons-material/DeleteOutlineRounded";
+import Search from "../Inputs/Search";
+
+// Utils
+import { useState, useEffect } from "react";
+import { useTheme } from "@mui/material/styles";
+import PropTypes from "prop-types";
+
+const NotificationConfig = ({ notifications, setMonitor, setNotifications }) => {
+ // Local state
+ const [notificationsSearch, setNotificationsSearch] = useState("");
+ const [selectedNotifications, setSelectedNotifications] = useState([]);
+
+ const handleSearch = (value) => {
+ setSelectedNotifications(value);
+ setMonitor((prev) => {
+ return {
+ ...prev,
+ notifications: value.map((notification) => notification._id),
+ };
+ });
+ };
+
+ // Handlers
+ const handleDelete = (id) => {
+ const updatedNotifications = selectedNotifications.filter(
+ (notification) => notification._id !== id
+ );
+
+ setSelectedNotifications(updatedNotifications);
+ setMonitor((prev) => {
+ return {
+ ...prev,
+ notifications: updatedNotifications.map((notification) => notification._id),
+ };
+ });
+ };
+
+ // Setup
+ const theme = useTheme();
+
+ useEffect(() => {
+ if (setNotifications) {
+ const toSet = setNotifications.map((notification) => {
+ return notifications.find((n) => n._id === notification);
+ });
+ setSelectedNotifications(toSet);
+ }
+ }, [setNotifications, notifications]);
+
+ return (
+
+ {
+ handleSearch(value);
+ }}
+ />
+
+ {selectedNotifications.map((notification, index) => (
+
+
+ {notification.notificationName}
+
+ {
+ handleDelete(notification._id);
+ }}
+ sx={{ cursor: "pointer" }}
+ />
+ {index < selectedNotifications.length - 1 && }
+
+ ))}
+
+
+ );
+};
+
+NotificationConfig.propTypes = {
+ notifications: PropTypes.array,
+ setMonitor: PropTypes.func,
+ setNotifications: PropTypes.array,
+};
+
+export default NotificationConfig;
diff --git a/client/src/Components/Sidebar/index.jsx b/client/src/Components/Sidebar/index.jsx
index f1c512771..831782b53 100644
--- a/client/src/Components/Sidebar/index.jsx
+++ b/client/src/Components/Sidebar/index.jsx
@@ -38,6 +38,8 @@ import ChangeLog from "../../assets/icons/changeLog.svg?react";
import Docs from "../../assets/icons/docs.svg?react";
import StatusPages from "../../assets/icons/status-pages.svg?react";
import Discussions from "../../assets/icons/discussions.svg?react";
+import NotificationAddOutlinedIcon from "@mui/icons-material/NotificationAddOutlined";
+
import "./index.css";
// Utils
@@ -52,13 +54,18 @@ import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMon
const getMenu = (t) => [
{ name: t("menu.uptime"), path: "uptime", icon: },
{ name: t("menu.pagespeed"), path: "pagespeed", icon: },
- { name: t("menu.infrastructure"), path: "infrastructure", icon: },
+ { name: t("menu.infrastructure"), path: "infrastructure", icon: },
+ {
+ name: t("menu.notifications"),
+ path: "notifications",
+ icon: ,
+ },
{ name: t("menu.incidents"), path: "incidents", icon: },
{ name: t("menu.statusPages"), path: "status", icon: },
{ name: t("menu.maintenance"), path: "maintenance", icon: },
- // { name: t("menu.integrations"), path: "integrations", icon: },
+
{
name: t("menu.settings"),
icon: ,
diff --git a/client/src/Hooks/useFetchUptimeMonitorById.js b/client/src/Hooks/useFetchUptimeMonitorById.js
new file mode 100644
index 000000000..cb344569c
--- /dev/null
+++ b/client/src/Hooks/useFetchUptimeMonitorById.js
@@ -0,0 +1,35 @@
+import { useState, useEffect } from "react";
+import { useDispatch } from "react-redux";
+import { getUptimeMonitorById } from "../Features/UptimeMonitors/uptimeMonitorsSlice";
+import { useNavigate } from "react-router";
+
+const useFetchUptimeMonitorById = (monitorId, updateTrigger) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [monitor, setMonitor] = useState(null);
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ useEffect(() => {
+ const fetchMonitor = async () => {
+ try {
+ setIsLoading(true);
+ 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) {
+ navigate("/not-found", { replace: true });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchMonitor();
+ }, [monitorId, dispatch, navigate, updateTrigger]);
+ return [monitor, isLoading, error];
+};
+
+export { useFetchUptimeMonitorById };
diff --git a/client/src/Hooks/useNotifications.js b/client/src/Hooks/useNotifications.js
new file mode 100644
index 000000000..4ece55acd
--- /dev/null
+++ b/client/src/Hooks/useNotifications.js
@@ -0,0 +1,91 @@
+import { useState, useEffect, useCallback } from "react";
+import { createToast } from "../Utils/toastUtils";
+import { networkService } from "../main";
+import { useNavigate } from "react-router-dom";
+import { useSelector } from "react-redux";
+import { useTranslation } from "react-i18next";
+
+const useCreateNotification = () => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const createNotification = async (notification) => {
+ try {
+ setIsLoading(true);
+ await networkService.createNotification({ notification });
+ createToast({
+ body: t("notifications.create.success"),
+ });
+ navigate("/notifications");
+ } catch (error) {
+ setError(error);
+ createToast({
+ body: t("notifications.create.failed"),
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [createNotification, isLoading, error];
+};
+
+const useGetNotificationsByTeamId = (updateTrigger) => {
+ const [notifications, setNotifications] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { user } = useSelector((state) => state.auth);
+ const { t } = useTranslation();
+
+ const getNotifications = useCallback(async () => {
+ try {
+ setIsLoading(true);
+ const response = await networkService.getNotificationsByTeamId({
+ teamId: user.teamId,
+ });
+ setNotifications(response?.data?.data ?? []);
+ } catch (error) {
+ setError(error);
+ createToast({
+ body: t("notifications.fetch.failed"),
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }, [user.teamId]);
+
+ useEffect(() => {
+ getNotifications();
+ }, [getNotifications, updateTrigger]);
+
+ return [notifications, isLoading, error];
+};
+
+const useDeleteNotification = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const { t } = useTranslation();
+
+ const deleteNotification = async (id, triggerUpdate) => {
+ try {
+ setIsLoading(true);
+ await networkService.deleteNotificationById({ id });
+ createToast({
+ body: t("notifications.delete.success"),
+ });
+ triggerUpdate();
+ } catch (error) {
+ setError(error);
+ createToast({
+ body: t("notifications.delete.failed"),
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return [deleteNotification, isLoading, error];
+};
+export { useCreateNotification, useGetNotificationsByTeamId, useDeleteNotification };
diff --git a/client/src/Pages/Infrastructure/Create/index.jsx b/client/src/Pages/Infrastructure/Create/index.jsx
index 3e439a6e0..9425b7429 100644
--- a/client/src/Pages/Infrastructure/Create/index.jsx
+++ b/client/src/Pages/Infrastructure/Create/index.jsx
@@ -13,6 +13,8 @@ import {
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
import { useTranslation } from "react-i18next";
+import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
+import NotificationsConfig from "../../../Components/NotificationConfig";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
@@ -24,7 +26,6 @@ import ConfigBox from "../../../Components/ConfigBox";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import { createToast } from "../../../Utils/toastUtils";
-import Checkbox from "../../../Components/Inputs/Checkbox";
import Select from "../../../Components/Inputs/Select";
import { CustomThreshold } from "./Components/CustomThreshold";
@@ -65,6 +66,8 @@ const CreateInfrastructureMonitor = () => {
// Fetch monitor details if editing
const { monitor, isLoading, networkError } = useHardwareMonitorsFetch({ monitorId });
+ const [notifications, notificationsAreLoading, notificationsError] =
+ useGetNotificationsByTeamId();
// State
const [errors, setErrors] = useState({});
@@ -93,8 +96,7 @@ const CreateInfrastructureMonitor = () => {
setInfrastructureMonitor({
url: monitor.url.replace(/^https?:\/\//, ""),
name: monitor.name || "",
- notifications: monitor.notifications?.filter((n) => typeof n === "object") || [],
- notify_email: (monitor.notifications?.length ?? 0) > 0,
+ notifications: monitor.notifications,
interval: monitor.interval / MS_PER_MINUTE,
cpu: monitor.thresholds?.usage_cpu !== undefined,
usage_cpu: monitor.thresholds?.usage_cpu ? monitor.thresholds.usage_cpu * 100 : "",
@@ -119,17 +121,9 @@ const CreateInfrastructureMonitor = () => {
}, [isCreate, monitor]);
// Handlers
- const handleCreateInfrastructureMonitor = async (event) => {
+ const onSubmit = async (event) => {
event.preventDefault();
- const formattedNotifications = infrastructureMonitor.notifications.map((n) =>
- typeof n === "string" ? { type: "email", address: n } : n
- );
-
- if (infrastructureMonitor.notify_email) {
- formattedNotifications.push({ type: "email", address: user.email });
- }
-
// Build the form
let form = {
url: `http${https ? "s" : ""}://` + infrastructureMonitor.url,
@@ -155,7 +149,6 @@ const CreateInfrastructureMonitor = () => {
? { usage_temperature: infrastructureMonitor.usage_temperature }
: {}),
secret: infrastructureMonitor.secret,
- notifications: formattedNotifications,
};
const { error } = infrastructureMonitorValidation.validate(form, {
@@ -167,6 +160,7 @@ const CreateInfrastructureMonitor = () => {
error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
+ console.log(newErrors);
setErrors(newErrors);
createToast({ body: "Please check the form for errors." });
return;
@@ -218,7 +212,7 @@ const CreateInfrastructureMonitor = () => {
}
};
- const handleChange = (event) => {
+ const onChange = (event) => {
const { value, name } = event.target;
setInfrastructureMonitor({
...infrastructureMonitor,
@@ -244,27 +238,6 @@ const CreateInfrastructureMonitor = () => {
});
};
- const handleNotifications = (event, type) => {
- const { value, checked } = event.target;
- let notifications = [...infrastructureMonitor.notifications];
-
- if (checked) {
- if (!notifications.some((n) => n.type === type && n.address === value)) {
- notifications.push({ type, address: value });
- }
- } else {
- notifications = notifications.filter(
- (n) => !(n.type === type && n.address === value)
- );
- }
-
- setInfrastructureMonitor((prev) => ({
- ...prev,
- notifications,
- ...(type === "email" ? { notify_email: checked } : {}),
- }));
- };
-
return (
{
/>
{
label={t("infrastructureServerUrlLabel")}
https={https}
value={infrastructureMonitor.url}
- onChange={handleChange}
+ onChange={onChange}
error={errors["url"] ? true : false}
helperText={errors["url"]}
disabled={!isCreate}
@@ -370,7 +342,7 @@ const CreateInfrastructureMonitor = () => {
placeholder="Google"
isOptional={true}
value={infrastructureMonitor.name}
- onChange={handleChange}
+ onChange={onChange}
error={errors["name"]}
/>
{
name="secret"
label={t("infrastructureAuthorizationSecretLabel")}
value={infrastructureMonitor.secret}
- onChange={handleChange}
+ onChange={onChange}
error={errors["secret"] ? true : false}
helperText={errors["secret"]}
/>
@@ -387,25 +359,14 @@ const CreateInfrastructureMonitor = () => {
-
- {t("distributedUptimeCreateIncidentNotification")}
-
-
- {t("distributedUptimeCreateIncidentDescription")}
-
+ {t("notificationConfig.title")}
+ {t("notificationConfig.description")}
-
- handleNotifications(event, "email")}
- />
-
+
@@ -438,7 +399,7 @@ const CreateInfrastructureMonitor = () => {
fieldId={METRIC_PREFIX + metric}
fieldName={METRIC_PREFIX + metric}
fieldValue={String(infrastructureMonitor[METRIC_PREFIX + metric])}
- onFieldChange={handleChange}
+ onFieldChange={onChange}
alertUnit={metric == "temperature" ? "°C" : "%"}
/>
);
@@ -474,7 +435,7 @@ const CreateInfrastructureMonitor = () => {
name="interval"
label="Check frequency"
value={infrastructureMonitor.interval || 15}
- onChange={handleChange}
+ onChange={onChange}
items={SELECT_VALUES}
/>
@@ -484,9 +445,9 @@ const CreateInfrastructureMonitor = () => {
justifyContent="flex-end"
>
-
- {t("whenNewIncident")}
- notification.type === "email"
- )}
- value={user?.email}
- onChange={(event) => handleNotifications(event, "email")}
- />
-
+
@@ -356,9 +321,9 @@ const CreatePageSpeed = () => {
justifyContent="flex-end"
>
value === undefined)}
loading={isLoading}
>
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index dd0135edb..b0e3c476b 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -132,7 +132,6 @@ const Settings = () => {
};
const handleSave = () => {
- console.log(settingsData.settings);
const { error } = settingsValidation.validate(settingsData.settings, {
abortEarly: false,
});
diff --git a/client/src/Pages/Uptime/Configure/index.jsx b/client/src/Pages/Uptime/Configure/index.jsx
index f75e53e84..8718a2209 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,391 @@ 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")}
-
-
-
-
- :
- }
- onClick={() => {
- pauseMonitor();
- }}
- >
- {monitor?.isActive ? t("pause") : t("resume")}
-
- setIsOpen(true)}
- >
- {t("remove")}
-
-
-
-
-
-
- {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)}
- />
-
-
- {t("notifications.integrationButton")}
-
+
+
- {/* 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")}
-
-
-
-
-
-
-
+
- {t("settingsSave")}
-
+ {form.url?.replace(/^https?:\/\//, "") || "..."}
+
+
+ {t("editing")}
+
+
+
+ :
+ }
+ onClick={handlePause}
+ >
+ {form?.isActive ? t("pause") : t("resume")}
+
+ setIsOpen(true)}
+ >
+ {t("remove")}
+
+
+
+
+
+
+ {t("settingsGeneralSettings")}
+
+ {t("distributedUptimeCreateSelectURL")}
+
+
+
+ }
+ id="monitor-url"
+ label={t("urlMonitor")}
+ placeholder="google.com"
+ value={parsedUrl?.host || form?.url || ""}
+ disabled={true}
+ />
+
+
- >
- )}
-
+
+
+
+ {t("notificationConfig.title")}
+ {t("notificationConfig.description")}
+
+
+
+
+
+
+ {t("ignoreTLSError")}
+
+ {t("ignoreTLSErrorDescription")}
+
+
+
+ }
+ label={t("tlsErrorIgnored")}
+ />
+
+
+
+
+
+ {t("distributedUptimeCreateAdvancedSettings")}
+
+
+
+
+ {form.type === "http" && (
+ <>
+
+
+
+
+ {t("uptimeCreate")}
+
+
+
+
+
+ {t("uptimeCreateJsonPath")}
+
+ jmespath.org
+
+ {t("uptimeCreateJsonPathQuery")}
+
+
+ >
+ )}
+
+
+
+
+ {t("settingsSave")}
+
+
+