mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-18 07:28:31 -05:00
add notificaitons channel to Create Uptime
This commit is contained in:
@@ -1,15 +1,12 @@
|
||||
// React, Redux, Router
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Utility and Network
|
||||
import { checkEndpointResolution } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { getUptimeMonitorById } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
// MUI
|
||||
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
|
||||
@@ -22,11 +19,37 @@ import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import Radio from "../../../Components/Inputs/Radio";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import NotificationIntegrationModal from "../../../Components/NotificationIntegrationModal/Components/NotificationIntegrationModal";
|
||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
||||
import NotificationsConfig from "../../../Components/NotificationConfig";
|
||||
|
||||
const CreateMonitor = () => {
|
||||
// Redux state
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const { isLoading } = useSelector((state) => state.uptimeMonitors);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Local state
|
||||
const [errors, setErrors] = useState({});
|
||||
const [https, setHttps] = useState(true);
|
||||
const [useAdvancedMatching, setUseAdvancedMatching] = useState(false);
|
||||
const [monitor, setMonitor] = useState({
|
||||
url: "",
|
||||
name: "",
|
||||
type: "http",
|
||||
matchMethod: "equal",
|
||||
notifications: [],
|
||||
interval: 1,
|
||||
ignoreTlsErrors: false,
|
||||
});
|
||||
|
||||
// Setup
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
|
||||
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const SELECT_VALUES = [
|
||||
{ _id: 1, name: "1 minute" },
|
||||
@@ -71,37 +94,19 @@ const CreateMonitor = () => {
|
||||
},
|
||||
};
|
||||
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const { isLoading } = useSelector((state) => state.uptimeMonitors);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { monitorId } = useParams();
|
||||
const crumbs = [
|
||||
const BREADCRUMBS = [
|
||||
{ name: "uptime", path: "/uptime" },
|
||||
{ name: "create", path: `/uptime/create` },
|
||||
];
|
||||
|
||||
// State
|
||||
const [isNotificationModalOpen, setIsNotificationModalOpen] = useState(false);
|
||||
// Handlers
|
||||
|
||||
const handleOpenNotificationModal = () => {
|
||||
setIsNotificationModalOpen(true);
|
||||
};
|
||||
const [errors, setErrors] = useState({});
|
||||
const [https, setHttps] = useState(true);
|
||||
const [monitor, setMonitor] = useState({
|
||||
url: "",
|
||||
name: "",
|
||||
type: "http",
|
||||
ignoreTlsErrors: false,
|
||||
notifications: [],
|
||||
interval: 1,
|
||||
});
|
||||
|
||||
const handleCreateMonitor = async (event) => {
|
||||
const onSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const { notifications, ...rest } = monitor;
|
||||
|
||||
let form = {
|
||||
...rest,
|
||||
url:
|
||||
//prepending protocol for url
|
||||
monitor.type === "http"
|
||||
@@ -110,14 +115,14 @@ const CreateMonitor = () => {
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
type: monitor.type,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
interval: monitor.interval * MS_PER_MINUTE,
|
||||
};
|
||||
|
||||
if (monitor.type === "http") {
|
||||
form.expectedValue = monitor.expectedValue;
|
||||
form.jsonPath = monitor.jsonPath;
|
||||
form.matchMethod = monitor.matchMethod;
|
||||
// If not using advanced matching, remove advanced settings
|
||||
if (!useAdvancedMatching) {
|
||||
form.matchMethod = undefined;
|
||||
form.expectedValue = undefined;
|
||||
form.jsonPath = undefined;
|
||||
}
|
||||
|
||||
const { error } = monitorValidation.validate(form, {
|
||||
@@ -141,6 +146,7 @@ const CreateMonitor = () => {
|
||||
userId: user._id,
|
||||
notifications: monitor.notifications,
|
||||
};
|
||||
|
||||
const action = await dispatch(createUptimeMonitor({ monitor: form }));
|
||||
if (action.meta.requestStatus === "fulfilled") {
|
||||
createToast({ body: "Monitor created successfully!" });
|
||||
@@ -150,115 +156,60 @@ const CreateMonitor = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (event, formName) => {
|
||||
const { type, checked, value } = event.target;
|
||||
|
||||
const newVal = type === "checkbox" ? checked : value;
|
||||
|
||||
const newMonitor = {
|
||||
...monitor,
|
||||
[formName]: newVal,
|
||||
};
|
||||
if (formName === "type") {
|
||||
newMonitor.url = "";
|
||||
const onChange = (event) => {
|
||||
const { name, value, checked } = event.target;
|
||||
let newValue = value;
|
||||
if (name === "ignoreTlsErrors") {
|
||||
newValue = checked;
|
||||
}
|
||||
setMonitor(newMonitor);
|
||||
const updatedMonitor = {
|
||||
...monitor,
|
||||
[name]: newValue,
|
||||
};
|
||||
|
||||
setMonitor(updatedMonitor);
|
||||
|
||||
const { error } = monitorValidation.validate(
|
||||
{ type: monitor.type, [formName]: newVal },
|
||||
{ type: monitor.type, [name]: newValue },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
url: undefined,
|
||||
...(error ? { [formName]: error.details[0].message } : { [formName]: undefined }),
|
||||
...(error ? { [name]: error.details[0].message } : { [name]: undefined }),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleNotifications = (event, type) => {
|
||||
const { value } = event.target;
|
||||
let notifications = [...monitor.notifications];
|
||||
const notificationExists = notifications.some((notification) => {
|
||||
if (notification.type === type && notification.address === value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (notificationExists) {
|
||||
notifications = notifications.filter((notification) => {
|
||||
if (notification.type === type && notification.address === value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
notifications.push({ type, address: value });
|
||||
}
|
||||
|
||||
setMonitor((prev) => ({
|
||||
...prev,
|
||||
notifications,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMonitor = async () => {
|
||||
if (monitorId) {
|
||||
const action = await dispatch(getUptimeMonitorById({ monitorId }));
|
||||
|
||||
if (action.payload.success) {
|
||||
const data = action.payload.data;
|
||||
const { name, ...rest } = data; //data.name is read-only
|
||||
if (rest.type === "http") {
|
||||
const url = new URL(rest.url);
|
||||
rest.url = url.host;
|
||||
}
|
||||
rest.name = `${name} (Clone)`;
|
||||
rest.interval /= MS_PER_MINUTE;
|
||||
setMonitor({
|
||||
...rest,
|
||||
});
|
||||
} else {
|
||||
navigate("/not-found", { replace: true });
|
||||
createToast({
|
||||
body: "There was an error cloning the monitor.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchMonitor();
|
||||
}, [monitorId, dispatch, navigate]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box className="create-monitor">
|
||||
<Breadcrumbs list={crumbs} />
|
||||
<Stack
|
||||
component="form"
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
onSubmit={handleCreateMonitor}
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
>
|
||||
{t("createYour")}{" "}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h2"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
>
|
||||
{t("monitor")}
|
||||
</Typography>
|
||||
{t("createYour")}{" "}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h2"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
>
|
||||
{t("monitor")}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Stack
|
||||
component="form"
|
||||
noValidate
|
||||
gap={theme.spacing(12)}
|
||||
mt={theme.spacing(6)}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
@@ -274,13 +225,13 @@ const CreateMonitor = () => {
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Radio
|
||||
id="monitor-checks-http"
|
||||
name="type"
|
||||
title={t("websiteMonitoring")}
|
||||
desc={t("websiteMonitoringDescription")}
|
||||
size="small"
|
||||
value="http"
|
||||
checked={monitor.type === "http"}
|
||||
onChange={(event) => handleChange(event, "type")}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{monitor.type === "http" ? (
|
||||
<ButtonGroup sx={{ ml: theme.spacing(16) }}>
|
||||
@@ -304,31 +255,31 @@ const CreateMonitor = () => {
|
||||
)}
|
||||
</Stack>
|
||||
<Radio
|
||||
id="monitor-checks-ping"
|
||||
name="type"
|
||||
title={t("pingMonitoring")}
|
||||
desc={t("pingMonitoringDescription")}
|
||||
size="small"
|
||||
value="ping"
|
||||
checked={monitor.type === "ping"}
|
||||
onChange={(event) => handleChange(event, "type")}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
id="monitor-checks-docker"
|
||||
name="type"
|
||||
title={t("dockerContainerMonitoring")}
|
||||
desc={t("dockerContainerMonitoringDescription")}
|
||||
size="small"
|
||||
value="docker"
|
||||
checked={monitor.type === "docker"}
|
||||
onChange={(event) => handleChange(event, "type")}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
id="monitor-checks-port"
|
||||
name="type"
|
||||
title={t("portMonitoring")}
|
||||
desc={t("portMonitoringDescription")}
|
||||
size="small"
|
||||
value="port"
|
||||
checked={monitor.type === "port"}
|
||||
onChange={(event) => handleChange(event, "type")}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{errors["type"] ? (
|
||||
<Box className="error-container">
|
||||
@@ -359,8 +310,8 @@ const CreateMonitor = () => {
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
<TextInput
|
||||
name="url"
|
||||
type={monitor.type === "http" ? "url" : "text"}
|
||||
id="monitor-url"
|
||||
startAdornment={
|
||||
monitor.type === "http" ? <HttpAdornment https={https} /> : null
|
||||
}
|
||||
@@ -368,29 +319,29 @@ const CreateMonitor = () => {
|
||||
https={https}
|
||||
placeholder={monitorTypeMaps[monitor.type].placeholder || ""}
|
||||
value={monitor.url}
|
||||
onChange={(event) => handleChange(event, "url")}
|
||||
onChange={onChange}
|
||||
error={errors["url"] ? true : false}
|
||||
helperText={errors["url"]}
|
||||
/>
|
||||
<TextInput
|
||||
name="port"
|
||||
type="number"
|
||||
id="monitor-port"
|
||||
label={t("portToMonitor")}
|
||||
placeholder="5173"
|
||||
value={monitor.port}
|
||||
onChange={(event) => handleChange(event, "port")}
|
||||
onChange={onChange}
|
||||
error={errors["port"] ? true : false}
|
||||
helperText={errors["port"]}
|
||||
hidden={monitor.type !== "port"}
|
||||
/>
|
||||
<TextInput
|
||||
name="name"
|
||||
type="text"
|
||||
id="monitor-name"
|
||||
label={t("displayName")}
|
||||
isOptional={true}
|
||||
placeholder={monitorTypeMaps[monitor.type].namePlaceholder || ""}
|
||||
value={monitor.name}
|
||||
onChange={(event) => handleChange(event, "name")}
|
||||
onChange={onChange}
|
||||
error={errors["name"] ? true : false}
|
||||
helperText={errors["name"]}
|
||||
/>
|
||||
@@ -398,37 +349,15 @@ const CreateMonitor = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("distributedUptimeCreateIncidentNotification")}
|
||||
</Typography>
|
||||
<Typography component="h2">Notifications</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateIncidentDescription")}
|
||||
Select the notifications you want to send out
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Checkbox
|
||||
id="notify-email-default"
|
||||
label={`Notify via email (to ${user.email})`}
|
||||
isChecked={monitor.notifications.some(
|
||||
(notification) => notification.type === "email"
|
||||
)}
|
||||
value={user?.email}
|
||||
onChange={(event) => handleNotifications(event, "email")}
|
||||
/>
|
||||
|
||||
<Box mt={theme.spacing(2)}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleOpenNotificationModal}
|
||||
>
|
||||
{t("notifications.integrationButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<NotificationsConfig
|
||||
notifications={notifications}
|
||||
setMonitor={setMonitor}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
@@ -445,9 +374,9 @@ const CreateMonitor = () => {
|
||||
sx={{ marginLeft: 0 }}
|
||||
control={
|
||||
<Switch
|
||||
name="ignore-error"
|
||||
name="ignoreTlsErrors"
|
||||
checked={monitor.ignoreTlsErrors}
|
||||
onChange={(event) => handleChange(event, "ignoreTlsErrors")}
|
||||
onChange={onChange}
|
||||
sx={{ mr: theme.spacing(2) }}
|
||||
/>
|
||||
}
|
||||
@@ -466,32 +395,32 @@ const CreateMonitor = () => {
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Select
|
||||
id="monitor-interval"
|
||||
name="interval"
|
||||
label="Check frequency"
|
||||
value={monitor.interval || 1}
|
||||
onChange={(event) => handleChange(event, "interval")}
|
||||
onChange={onChange}
|
||||
items={SELECT_VALUES}
|
||||
/>
|
||||
{monitor.type === "http" && (
|
||||
<>
|
||||
<Select
|
||||
id="match-method"
|
||||
name="matchMethod"
|
||||
label="Match Method"
|
||||
value={monitor.matchMethod || "equal"}
|
||||
onChange={(event) => handleChange(event, "matchMethod")}
|
||||
onChange={onChange}
|
||||
items={matchMethodOptions}
|
||||
/>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="expectedValue"
|
||||
type="text"
|
||||
id="expected-value"
|
||||
label="Expected value"
|
||||
isOptional={true}
|
||||
placeholder={
|
||||
expectedValuePlaceholders[monitor.matchMethod || "equal"]
|
||||
}
|
||||
value={monitor.expectedValue}
|
||||
onChange={(event) => handleChange(event, "expectedValue")}
|
||||
onChange={onChange}
|
||||
error={errors["expectedValue"] ? true : false}
|
||||
helperText={errors["expectedValue"]}
|
||||
/>
|
||||
@@ -505,13 +434,13 @@ const CreateMonitor = () => {
|
||||
</Stack>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="jsonPath"
|
||||
type="text"
|
||||
id="json-path"
|
||||
label="JSON Path"
|
||||
isOptional={true}
|
||||
placeholder="data.status"
|
||||
value={monitor.jsonPath}
|
||||
onChange={(event) => handleChange(event, "jsonPath")}
|
||||
onChange={onChange}
|
||||
error={errors["jsonPath"] ? true : false}
|
||||
helperText={errors["jsonPath"]}
|
||||
/>
|
||||
@@ -541,9 +470,9 @@ const CreateMonitor = () => {
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={handleCreateMonitor}
|
||||
disabled={!Object.values(errors).every((value) => value === undefined)}
|
||||
loading={isLoading}
|
||||
>
|
||||
@@ -551,14 +480,7 @@ const CreateMonitor = () => {
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<NotificationIntegrationModal
|
||||
open={isNotificationModalOpen}
|
||||
onClose={() => setIsNotificationModalOpen(false)}
|
||||
monitor={monitor}
|
||||
setMonitor={setMonitor}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user