mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-20 00:18:47 -05:00
Merge pull request #2664 from Jesulayomy/fe-uptime_monitor_config
[Frontend]: Refactor Uptime monitor Create & Configure components
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
.configure-monitor button.MuiButtonBase-root {
|
||||
height: var(--env-var-height-2);
|
||||
}
|
||||
|
||||
.configure-monitor .MuiStack-root:has(span.MuiTypography-root.input-error) {
|
||||
position: relative;
|
||||
}
|
||||
.configure-monitor span.MuiTypography-root.input-error {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
// Components
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Button from "@mui/material/Button";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
import PulseDot from "../../../Components/Animated/PulseDot";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
|
||||
// Utils
|
||||
import { useParams } from "react-router";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PauseOutlinedIcon from "@mui/icons-material/PauseOutlined";
|
||||
import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined";
|
||||
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
|
||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
||||
import {
|
||||
useDeleteMonitor,
|
||||
useUpdateMonitor,
|
||||
usePauseMonitor,
|
||||
useFetchMonitorById,
|
||||
} from "../../../Hooks/monitorHooks";
|
||||
import NotificationsConfig from "../../../Components/NotificationConfig";
|
||||
|
||||
/**
|
||||
* Parses a URL string and returns a URL object.
|
||||
*
|
||||
* @param {string} url - The URL string to parse.
|
||||
* @returns {URL} - The parsed URL object if valid, otherwise an empty string.
|
||||
*/
|
||||
const parseUrl = (url) => {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Configure page displays monitor configurations and allows for editing actions.
|
||||
* @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 [notifications, notificationsAreLoading, notificationsError] =
|
||||
useGetNotificationsByTeamId();
|
||||
const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({});
|
||||
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
||||
const [updateMonitor, isUpdating] = useUpdateMonitor();
|
||||
const [isLoading] = useFetchMonitorById({
|
||||
monitorId,
|
||||
setMonitor: setForm,
|
||||
updateTrigger,
|
||||
});
|
||||
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const theme = useTheme();
|
||||
|
||||
const matchMethodOptions = [
|
||||
{ _id: "equal", name: "Equal" },
|
||||
{ _id: "include", name: "Include" },
|
||||
{ _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",
|
||||
};
|
||||
|
||||
// Handlers
|
||||
const handlePause = async () => {
|
||||
const res = await pauseMonitor({ monitorId: form?._id, triggerUpdate });
|
||||
if (typeof res !== "undefined") {
|
||||
triggerUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
await deleteMonitor({ monitor: form, redirect: "/uptime" });
|
||||
};
|
||||
|
||||
const onChange = (event) => {
|
||||
let { name, value, checked } = event.target;
|
||||
|
||||
if (name === "ignoreTlsErrors") {
|
||||
value = checked;
|
||||
}
|
||||
|
||||
if (name === "useAdvancedMatching") {
|
||||
setForm((prevForm) => {
|
||||
return {
|
||||
...prevForm,
|
||||
matchMethod: "equal",
|
||||
};
|
||||
});
|
||||
setUseAdvancedMatching(!useAdvancedMatching);
|
||||
return;
|
||||
}
|
||||
|
||||
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 = {};
|
||||
validation.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;
|
||||
await updateMonitor({ monitor: toSubmit, redirect: "/uptime" });
|
||||
};
|
||||
|
||||
// Parse the URL
|
||||
const parsedUrl = parseUrl(form?.url);
|
||||
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
||||
|
||||
const { determineState, statusColor } = useMonitorUtils();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs
|
||||
list={[
|
||||
{ name: "uptime", path: "/uptime" },
|
||||
{ name: "details", path: `/uptime/${monitorId}` },
|
||||
{ name: "configure", path: `/uptime/configure/${monitorId}` },
|
||||
]}
|
||||
/>
|
||||
<Stack
|
||||
component="form"
|
||||
onSubmit={onSubmit}
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
gap={theme.spacing(12)}
|
||||
flex={1}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(12)}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="monitorName"
|
||||
>
|
||||
{form?.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={t(`statusMsg.${[determineState(form)]}`)}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(form)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="monitorUrl"
|
||||
>
|
||||
{form?.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
ml={theme.spacing(6)}
|
||||
mt={theme.spacing(1)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
||||
opacity: 0.8,
|
||||
left: -10,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("editing")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
justifyContent="space-between"
|
||||
sx={{
|
||||
alignSelf: "flex-end",
|
||||
ml: "auto",
|
||||
display: "flex",
|
||||
gap: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isPausing}
|
||||
startIcon={
|
||||
form?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
}
|
||||
onClick={handlePause}
|
||||
>
|
||||
{form?.isActive ? t("pause") : t("resume")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
variant="contained"
|
||||
color="error"
|
||||
sx={{ px: theme.spacing(8) }}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("settingsGeneralSettings")}
|
||||
</Typography>
|
||||
<Typography component="p">{t("distributedUptimeCreateSelectURL")}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
type={form?.type === "http" ? "url" : "text"}
|
||||
https={protocol === "https"}
|
||||
startAdornment={
|
||||
form?.type === "http" && <HttpAdornment https={protocol === "https"} />
|
||||
}
|
||||
id="monitor-url"
|
||||
label={t("urlMonitor")}
|
||||
placeholder="google.com"
|
||||
value={parsedUrl?.host || form?.url || ""}
|
||||
disabled={true}
|
||||
/>
|
||||
<TextInput
|
||||
name="port"
|
||||
type="number"
|
||||
label={t("portToMonitor")}
|
||||
placeholder="5173"
|
||||
value={form?.port || ""}
|
||||
onChange={onChange}
|
||||
error={errors["port"] ? true : false}
|
||||
helperText={errors["port"]}
|
||||
hidden={form?.type !== "port"}
|
||||
/>
|
||||
<TextInput
|
||||
name="name"
|
||||
type="text"
|
||||
label={t("displayName")}
|
||||
isOptional={true}
|
||||
placeholder="Google"
|
||||
value={form?.name || ""}
|
||||
onChange={onChange}
|
||||
error={errors["name"] ? true : false}
|
||||
helperText={errors["name"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h2">{t("notificationConfig.title")}</Typography>
|
||||
<Typography component="p">{t("notificationConfig.description")}</Typography>
|
||||
</Box>
|
||||
<NotificationsConfig
|
||||
notifications={notifications}
|
||||
setMonitor={setForm}
|
||||
setNotifications={form?.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("ignoreTLSError")}
|
||||
</Typography>
|
||||
<Typography component="p">{t("ignoreTLSErrorDescription")}</Typography>
|
||||
</Box>
|
||||
<Stack>
|
||||
<FormControlLabel
|
||||
sx={{ marginLeft: 0 }}
|
||||
control={
|
||||
<Switch
|
||||
name="ignoreTlsErrors"
|
||||
checked={form?.ignoreTlsErrors ?? false}
|
||||
onChange={onChange}
|
||||
sx={{ mr: theme.spacing(2) }}
|
||||
/>
|
||||
}
|
||||
label={t("tlsErrorIgnored")}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("distributedUptimeCreateAdvancedSettings")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
name="interval"
|
||||
label={t("checkFrequency")}
|
||||
value={form?.interval / MS_PER_MINUTE || 1}
|
||||
onChange={onChange}
|
||||
items={frequencies}
|
||||
/>
|
||||
<Checkbox
|
||||
name="useAdvancedMatching"
|
||||
label={t("advancedMatching")}
|
||||
isChecked={useAdvancedMatching}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{form?.type === "http" && useAdvancedMatching && (
|
||||
<>
|
||||
<Select
|
||||
name="matchMethod"
|
||||
label={t("matchMethod")}
|
||||
value={form?.matchMethod || "equal"}
|
||||
onChange={onChange}
|
||||
items={matchMethodOptions}
|
||||
/>
|
||||
<Stack>
|
||||
<TextInput
|
||||
type="text"
|
||||
name="expectedValue"
|
||||
label={t("expectedValue")}
|
||||
isOptional={true}
|
||||
placeholder={expectedValuePlaceholders[form?.matchMethod || "equal"]}
|
||||
value={form?.expectedValue}
|
||||
onChange={onChange}
|
||||
error={errors["expectedValue"] ? true : false}
|
||||
helperText={errors["expectedValue"]}
|
||||
/>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
opacity={0.8}
|
||||
>
|
||||
{t("uptimeCreate")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="jsonPath"
|
||||
type="text"
|
||||
label="JSON Path"
|
||||
isOptional={true}
|
||||
placeholder="data.status"
|
||||
value={form?.jsonPath}
|
||||
onChange={onChange}
|
||||
error={errors["jsonPath"] ? true : false}
|
||||
helperText={errors["jsonPath"]}
|
||||
/>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
opacity={0.8}
|
||||
>
|
||||
{t("uptimeCreateJsonPath")}
|
||||
<Typography
|
||||
component="a"
|
||||
href="https://jmespath.org/"
|
||||
target="_blank"
|
||||
color="info"
|
||||
>
|
||||
jmespath.org
|
||||
</Typography>
|
||||
{t("uptimeCreateJsonPathQuery")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
mt="auto"
|
||||
>
|
||||
<Button
|
||||
disabled={isDeleting || isUpdating}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="accent"
|
||||
loading={isLoading}
|
||||
sx={{ px: theme.spacing(12) }}
|
||||
>
|
||||
{t("settingsSave")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
theme={theme}
|
||||
title="Do you really want to delete this monitor?"
|
||||
description="Once deleted, this monitor cannot be retrieved."
|
||||
onCancel={() => setIsOpen(false)}
|
||||
confirmationButtonLabel="Delete"
|
||||
onConfirm={handleRemove}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Configure;
|
||||
@@ -1,4 +1,14 @@
|
||||
//Components
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
FormControlLabel,
|
||||
Stack,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import TextInput from "../../../Components/Inputs/TextInput";
|
||||
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
|
||||
@@ -6,132 +16,186 @@ import Radio from "../../../Components/Inputs/Radio";
|
||||
import Select from "../../../Components/Inputs/Select";
|
||||
import ConfigBox from "../../../Components/ConfigBox";
|
||||
import NotificationsConfig from "../../../Components/NotificationConfig";
|
||||
import Button from "@mui/material/Button";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import Box from "@mui/material/Box";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Checkbox from "../../../Components/Inputs/Checkbox";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
import PulseDot from "../../../Components/Animated/PulseDot";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
|
||||
// Utils
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import {
|
||||
PauseOutlined as PauseOutlinedIcon,
|
||||
PlayArrowOutlined as PlayArrowOutlinedIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
|
||||
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
|
||||
import { useCreateMonitor, useFetchMonitorById } from "../../../Hooks/monitorHooks";
|
||||
import { useParams } from "react-router-dom";
|
||||
import {
|
||||
useCreateMonitor,
|
||||
useDeleteMonitor,
|
||||
useUpdateMonitor,
|
||||
usePauseMonitor,
|
||||
useFetchMonitorById,
|
||||
} from "../../../Hooks/monitorHooks";
|
||||
|
||||
const CreateMonitor = () => {
|
||||
// Local state
|
||||
const [errors, setErrors] = useState({});
|
||||
const [https, setHttps] = useState(true);
|
||||
const [useAdvancedMatching, setUseAdvancedMatching] = useState(false);
|
||||
/**
|
||||
* Parses a URL string and returns a URL object.
|
||||
*
|
||||
* @param {string} url - The URL string to parse.
|
||||
* @returns {URL} - The parsed URL object if valid, otherwise an empty string.
|
||||
*/
|
||||
const parseUrl = (url) => {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create page renders monitor creation or configuration views.
|
||||
* @component
|
||||
*/
|
||||
const UptimeCreate = ({ isClone = false }) => {
|
||||
const { monitorId } = useParams();
|
||||
const isCreate = typeof monitorId === "undefined" || isClone;
|
||||
|
||||
// States
|
||||
const [monitor, setMonitor] = useState({
|
||||
url: "",
|
||||
name: "",
|
||||
type: "http",
|
||||
matchMethod: "equal",
|
||||
expectedValue: "",
|
||||
jsonPath: "",
|
||||
notifications: [],
|
||||
interval: 1,
|
||||
interval: 60000,
|
||||
ignoreTlsErrors: false,
|
||||
...(isCreate ? { url: "", name: "" } : { port: undefined }),
|
||||
});
|
||||
|
||||
// Setup
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
|
||||
const [createMonitor, isCreating] = useCreateMonitor();
|
||||
const { monitorId } = useParams();
|
||||
|
||||
const formatAndSet = (monitor) => {
|
||||
monitor.interval = monitor.interval / MS_PER_MINUTE;
|
||||
setMonitor(monitor);
|
||||
const [errors, setErrors] = useState({});
|
||||
const [https, setHttps] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [useAdvancedMatching, setUseAdvancedMatching] = useState(false);
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
const triggerUpdate = () => {
|
||||
setUpdateTrigger(!updateTrigger);
|
||||
};
|
||||
|
||||
// Hooks
|
||||
const [notifications, notificationsAreLoading, notificationsError] =
|
||||
useGetNotificationsByTeamId();
|
||||
const { determineState, statusColor } = useMonitorUtils();
|
||||
// Network
|
||||
const [isLoading] = useFetchMonitorById({
|
||||
monitorId,
|
||||
setMonitor: formatAndSet,
|
||||
setMonitor,
|
||||
updateTrigger: true,
|
||||
});
|
||||
const [createMonitor, isCreating] = useCreateMonitor();
|
||||
const [pauseMonitor, isPausing] = usePauseMonitor({});
|
||||
const [deleteMonitor, isDeleting] = useDeleteMonitor();
|
||||
const [updateMonitor, isUpdating] = useUpdateMonitor();
|
||||
|
||||
const SELECT_VALUES = [
|
||||
{ _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" },
|
||||
// Setup
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Constants
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const FREQUENCIES = [
|
||||
{ _id: 1, name: t("time.oneMinute") },
|
||||
{ _id: 2, name: t("time.twoMinutes") },
|
||||
{ _id: 3, name: t("time.threeMinutes") },
|
||||
{ _id: 4, name: t("time.fourMinutes") },
|
||||
{ _id: 5, name: t("time.fiveMinutes") },
|
||||
];
|
||||
const CRUMBS = [
|
||||
{ name: "uptime", path: "/uptime" },
|
||||
...(isCreate
|
||||
? [{ name: "create", path: `/uptime/create` }]
|
||||
: [
|
||||
{ name: "details", path: `/uptime/${monitorId}` },
|
||||
{ name: "configure", path: `/uptime/configure/${monitorId}` },
|
||||
]),
|
||||
];
|
||||
|
||||
const matchMethodOptions = [
|
||||
{ _id: "equal", name: "Equal" },
|
||||
{ _id: "include", name: "Include" },
|
||||
{ _id: "regex", name: "Regex" },
|
||||
{ _id: "equal", name: t("matchMethodOptions.equal") },
|
||||
{ _id: "include", name: t("matchMethodOptions.include") },
|
||||
{ _id: "regex", name: t("matchMethodOptions.regex") },
|
||||
];
|
||||
|
||||
const expectedValuePlaceholders = {
|
||||
regex: "^(success|ok)$",
|
||||
equal: "success",
|
||||
include: "ok",
|
||||
regex: t("matchMethodOptions.regexPlaceholder"),
|
||||
equal: t("matchMethodOptions.equalPlaceholder"),
|
||||
include: t("matchMethodOptions.includePlaceholder"),
|
||||
};
|
||||
|
||||
const monitorTypeMaps = {
|
||||
http: {
|
||||
label: "URL to monitor",
|
||||
placeholder: "google.com",
|
||||
namePlaceholder: "Google",
|
||||
label: t("monitorType.http.label"),
|
||||
placeholder: t("monitorType.http.placeholder"),
|
||||
namePlaceholder: t("monitorType.http.namePlaceholder"),
|
||||
},
|
||||
ping: {
|
||||
label: "IP address to monitor",
|
||||
placeholder: "1.1.1.1",
|
||||
namePlaceholder: "Google",
|
||||
label: t("monitorType.ping.label"),
|
||||
placeholder: t("monitorType.ping.placeholder"),
|
||||
namePlaceholder: t("monitorType.ping.namePlaceholder"),
|
||||
},
|
||||
docker: {
|
||||
label: "Container ID",
|
||||
placeholder: "abc123",
|
||||
namePlaceholder: "My Container",
|
||||
label: t("monitorType.docker.label"),
|
||||
placeholder: t("monitorType.docker.placeholder"),
|
||||
namePlaceholder: t("monitorType.docker.namePlaceholder"),
|
||||
},
|
||||
port: {
|
||||
label: "URL to monitor",
|
||||
placeholder: "localhost",
|
||||
namePlaceholder: "Localhost:5173",
|
||||
label: t("monitorType.port.label"),
|
||||
placeholder: t("monitorType.port.placeholder"),
|
||||
namePlaceholder: t("monitorType.port.namePlaceholder"),
|
||||
},
|
||||
};
|
||||
|
||||
const BREADCRUMBS = [
|
||||
{ name: "uptime", path: "/uptime" },
|
||||
{ name: "create", path: `/uptime/create` },
|
||||
];
|
||||
|
||||
// Handlers
|
||||
|
||||
const onSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const { notifications, ...rest } = monitor;
|
||||
|
||||
let form = {
|
||||
...rest,
|
||||
url:
|
||||
//prepending protocol for url
|
||||
monitor.type === "http"
|
||||
? `http${https ? "s" : ""}://` + monitor.url
|
||||
: monitor.url,
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
type: monitor.type,
|
||||
interval: monitor.interval * MS_PER_MINUTE,
|
||||
};
|
||||
|
||||
// If not using advanced matching, remove advanced settings
|
||||
let form = {};
|
||||
if (isCreate) {
|
||||
form = {
|
||||
url:
|
||||
monitor.type === "http" && !isClone
|
||||
? `http${https ? "s" : ""}://` + monitor.url
|
||||
: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
type: monitor.type,
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
interval: monitor.interval,
|
||||
matchMethod: monitor.matchMethod,
|
||||
expectedValue: monitor.expectedValue,
|
||||
jsonPath: monitor.jsonPath,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
};
|
||||
} else {
|
||||
form = {
|
||||
_id: monitor._id,
|
||||
url: monitor.url,
|
||||
name: monitor.name || monitor.url.substring(0, 50),
|
||||
type: monitor.type,
|
||||
matchMethod: monitor.matchMethod,
|
||||
expectedValue: monitor.expectedValue,
|
||||
jsonPath: monitor.jsonPath,
|
||||
interval: monitor.interval,
|
||||
teamId: monitor.teamId,
|
||||
userId: monitor.userId,
|
||||
port: monitor.type === "port" ? monitor.port : undefined,
|
||||
ignoreTlsErrors: monitor.ignoreTlsErrors,
|
||||
};
|
||||
}
|
||||
if (!useAdvancedMatching) {
|
||||
form.matchMethod = undefined;
|
||||
form.expectedValue = undefined;
|
||||
form.jsonPath = undefined;
|
||||
form.matchMethod = isCreate ? undefined : "";
|
||||
form.expectedValue = isCreate ? undefined : "";
|
||||
form.jsonPath = isCreate ? undefined : "";
|
||||
}
|
||||
|
||||
const { error } = monitorValidation.validate(form, {
|
||||
@@ -144,7 +208,7 @@ const CreateMonitor = () => {
|
||||
newErrors[err.path[0]] = err.message;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
createToast({ body: "Please check the form for errors." });
|
||||
createToast({ body: t("checkFormError") });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,15 +218,18 @@ const CreateMonitor = () => {
|
||||
notifications: monitor.notifications,
|
||||
};
|
||||
|
||||
await createMonitor({ monitor: form, redirect: "/uptime" });
|
||||
if (isCreate) {
|
||||
await createMonitor({ monitor: form, redirect: "/uptime" });
|
||||
} else {
|
||||
await updateMonitor({ monitor: form, redirect: "/uptime" });
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (event) => {
|
||||
const { name, value, checked } = event.target;
|
||||
let { name, value, checked } = event.target;
|
||||
|
||||
let newValue = value;
|
||||
if (name === "ignoreTlsErrors") {
|
||||
newValue = checked;
|
||||
value = checked;
|
||||
}
|
||||
|
||||
if (name === "useAdvancedMatching") {
|
||||
@@ -170,15 +237,14 @@ const CreateMonitor = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedMonitor = {
|
||||
...monitor,
|
||||
[name]: newValue,
|
||||
};
|
||||
if (name === "interval") {
|
||||
value = value * MS_PER_MINUTE;
|
||||
}
|
||||
|
||||
setMonitor(updatedMonitor);
|
||||
setMonitor((prev) => ({ ...prev, [name]: value }));
|
||||
|
||||
const { error } = monitorValidation.validate(
|
||||
{ type: monitor.type, [name]: newValue },
|
||||
{ type: monitor.type, [name]: value },
|
||||
{ abortEarly: false }
|
||||
);
|
||||
|
||||
@@ -188,122 +254,254 @@ const CreateMonitor = () => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePause = async () => {
|
||||
await pauseMonitor({ monitorId, triggerUpdate });
|
||||
};
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
await deleteMonitor({ monitor, redirect: "/uptime" });
|
||||
};
|
||||
|
||||
const isBusy = isLoading || isCreating || isDeleting || isUpdating || isPausing;
|
||||
const displayInterval = monitor?.interval / MS_PER_MINUTE || 1;
|
||||
const parsedUrl = parseUrl(monitor?.url);
|
||||
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCreate || isClone) {
|
||||
if (monitor.matchMethod) {
|
||||
setUseAdvancedMatching(true);
|
||||
} else {
|
||||
setUseAdvancedMatching(false);
|
||||
}
|
||||
}
|
||||
}, [monitor, isCreate]);
|
||||
|
||||
if (Object.keys(monitor).length === 0) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
<Breadcrumbs list={CRUMBS} />
|
||||
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
>
|
||||
{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}
|
||||
noValidate
|
||||
spellCheck="false"
|
||||
gap={theme.spacing(12)}
|
||||
flex={1}
|
||||
>
|
||||
<ConfigBox>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(12)}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
{t("distributedUptimeCreateChecks")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateChecksDescription")}
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
color={
|
||||
!isCreate ? theme.palette.primary.contrastTextSecondary : undefined
|
||||
}
|
||||
>
|
||||
{!isCreate ? monitor.name : t("createYour") + " "}
|
||||
</Typography>
|
||||
{isCreate && (
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize="inherit"
|
||||
fontWeight="inherit"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
{t("monitor")}
|
||||
</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
{!isCreate && (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={t(`statusMsg.${[determineState(monitor)]}`)}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="monitorUrl"
|
||||
>
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
ml={theme.spacing(6)}
|
||||
mt={theme.spacing(1)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: theme.spacing(2),
|
||||
height: theme.spacing(2),
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.primary.contrastTextTertiary,
|
||||
opacity: 0.8,
|
||||
left: theme.spacing(-5),
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("editing")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
{!isCreate && (
|
||||
<Box
|
||||
justifyContent="space-between"
|
||||
sx={{
|
||||
alignSelf: "flex-end",
|
||||
ml: "auto",
|
||||
display: "flex",
|
||||
gap: theme.spacing(2),
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
loading={isBusy}
|
||||
startIcon={
|
||||
monitor?.isActive ? <PauseOutlinedIcon /> : <PlayArrowOutlinedIcon />
|
||||
}
|
||||
onClick={handlePause}
|
||||
>
|
||||
{monitor?.isActive ? t("pause") : t("resume")}
|
||||
</Button>
|
||||
<Button
|
||||
loading={isBusy}
|
||||
variant="contained"
|
||||
color="error"
|
||||
sx={{ px: theme.spacing(8) }}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
{isCreate && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("distributedUptimeCreateChecks")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("distributedUptimeCreateChecksDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("websiteMonitoring")}
|
||||
desc={t("websiteMonitoringDescription")}
|
||||
size="small"
|
||||
value="http"
|
||||
checked={monitor.type === "http"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{monitor.type === "http" ? (
|
||||
<ButtonGroup sx={{ ml: theme.spacing(16) }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={https.toString()}
|
||||
onClick={() => setHttps(true)}
|
||||
>
|
||||
{t("https")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(!https).toString()}
|
||||
onClick={() => setHttps(false)}
|
||||
>
|
||||
{t("http")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("websiteMonitoring")}
|
||||
desc={t("websiteMonitoringDescription")}
|
||||
title={t("pingMonitoring")}
|
||||
desc={t("pingMonitoringDescription")}
|
||||
size="small"
|
||||
value="http"
|
||||
checked={monitor.type === "http"}
|
||||
value="ping"
|
||||
checked={monitor.type === "ping"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{monitor.type === "http" ? (
|
||||
<ButtonGroup sx={{ ml: theme.spacing(16) }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={https.toString()}
|
||||
onClick={() => setHttps(true)}
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("dockerContainerMonitoring")}
|
||||
desc={t("dockerContainerMonitoringDescription")}
|
||||
size="small"
|
||||
value="docker"
|
||||
checked={monitor.type === "docker"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("portMonitoring")}
|
||||
desc={t("portMonitoringDescription")}
|
||||
size="small"
|
||||
value="port"
|
||||
checked={monitor.type === "port"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{errors["type"] ? (
|
||||
<Box className="error-container">
|
||||
<Typography
|
||||
component="p"
|
||||
className="input-error"
|
||||
color={theme.palette.error.contrastText}
|
||||
>
|
||||
{t("https")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(!https).toString()}
|
||||
onClick={() => setHttps(false)}
|
||||
>
|
||||
{t("http")}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
{errors["type"]}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("pingMonitoring")}
|
||||
desc={t("pingMonitoringDescription")}
|
||||
size="small"
|
||||
value="ping"
|
||||
checked={monitor.type === "ping"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("dockerContainerMonitoring")}
|
||||
desc={t("dockerContainerMonitoringDescription")}
|
||||
size="small"
|
||||
value="docker"
|
||||
checked={monitor.type === "docker"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Radio
|
||||
name="type"
|
||||
title={t("portMonitoring")}
|
||||
desc={t("portMonitoringDescription")}
|
||||
size="small"
|
||||
value="port"
|
||||
checked={monitor.type === "port"}
|
||||
onChange={onChange}
|
||||
/>
|
||||
{errors["type"] ? (
|
||||
<Box className="error-container">
|
||||
<Typography
|
||||
component="p"
|
||||
className="input-error"
|
||||
color={theme.palette.error.contrastText}
|
||||
>
|
||||
{errors["type"]}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
</ConfigBox>
|
||||
)}
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
@@ -313,30 +511,39 @@ const CreateMonitor = () => {
|
||||
{t("settingsGeneralSettings")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t(`uptimeGeneralInstructions.${monitor.type}`)}
|
||||
{isCreate
|
||||
? t(`uptimeGeneralInstructions.${monitor.type}`)
|
||||
: t("distributedUptimeCreateSelectURL")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
id="monitor-url"
|
||||
name="url"
|
||||
type={monitor.type === "http" ? "url" : "text"}
|
||||
startAdornment={
|
||||
monitor.type === "http" ? <HttpAdornment https={https} /> : null
|
||||
type={monitor?.type === "http" ? "url" : "text"}
|
||||
label={
|
||||
(monitor.type === "http" || monitor.type === "port") && !isCreate
|
||||
? t("url")
|
||||
: monitorTypeMaps[monitor.type].label || t("urlMonitor")
|
||||
}
|
||||
label={monitorTypeMaps[monitor.type].label || "URL to monitor"}
|
||||
https={https}
|
||||
placeholder={monitorTypeMaps[monitor.type].placeholder || ""}
|
||||
value={monitor.url}
|
||||
onChange={onChange}
|
||||
error={errors["url"] ? true : false}
|
||||
value={parsedUrl?.host + parsedUrl?.pathname || monitor?.url || ""}
|
||||
https={isCreate ? https : protocol === "https"}
|
||||
startAdornment={
|
||||
monitor?.type === "http" && (
|
||||
<HttpAdornment https={isCreate ? https : protocol === "https"} />
|
||||
)
|
||||
}
|
||||
helperText={errors["url"]}
|
||||
onChange={onChange}
|
||||
disabled={!isCreate}
|
||||
/>
|
||||
<TextInput
|
||||
name="port"
|
||||
type="number"
|
||||
label={t("portToMonitor")}
|
||||
placeholder="5173"
|
||||
value={monitor.port}
|
||||
value={monitor.port || ""}
|
||||
onChange={onChange}
|
||||
error={errors["port"] ? true : false}
|
||||
helperText={errors["port"]}
|
||||
@@ -347,8 +554,8 @@ const CreateMonitor = () => {
|
||||
type="text"
|
||||
label={t("displayName")}
|
||||
isOptional={true}
|
||||
placeholder={monitorTypeMaps[monitor.type].namePlaceholder || ""}
|
||||
value={monitor.name}
|
||||
placeholder={monitorTypeMaps[monitor.type].namePlaceholder}
|
||||
value={monitor.name || ""}
|
||||
onChange={onChange}
|
||||
error={errors["name"] ? true : false}
|
||||
helperText={errors["name"]}
|
||||
@@ -368,6 +575,7 @@ const CreateMonitor = () => {
|
||||
<NotificationsConfig
|
||||
notifications={notifications}
|
||||
setMonitor={setMonitor}
|
||||
setNotifications={isCreate ? null : monitor.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
@@ -382,7 +590,7 @@ const CreateMonitor = () => {
|
||||
</Box>
|
||||
<Stack>
|
||||
<FormControlLabel
|
||||
sx={{ marginLeft: 0 }}
|
||||
sx={{ marginLeft: theme.spacing(0) }}
|
||||
control={
|
||||
<Switch
|
||||
name="ignoreTlsErrors"
|
||||
@@ -404,34 +612,36 @@ const CreateMonitor = () => {
|
||||
{t("distributedUptimeCreateAdvancedSettings")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
name="interval"
|
||||
label="Check frequency"
|
||||
value={monitor.interval || 1}
|
||||
onChange={onChange}
|
||||
items={SELECT_VALUES}
|
||||
/>
|
||||
<Checkbox
|
||||
name="useAdvancedMatching"
|
||||
label={t("advancedMatching")}
|
||||
isChecked={useAdvancedMatching}
|
||||
label={t("checkFrequency")}
|
||||
value={displayInterval}
|
||||
onChange={onChange}
|
||||
items={FREQUENCIES}
|
||||
/>
|
||||
{monitor.type === "http" && (
|
||||
<Checkbox
|
||||
name="useAdvancedMatching"
|
||||
label={t("advancedMatching")}
|
||||
isChecked={useAdvancedMatching}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)}
|
||||
{monitor.type === "http" && useAdvancedMatching && (
|
||||
<>
|
||||
<Select
|
||||
name="matchMethod"
|
||||
label="Match Method"
|
||||
label={t("matchMethod")}
|
||||
value={monitor.matchMethod || "equal"}
|
||||
onChange={onChange}
|
||||
items={matchMethodOptions}
|
||||
/>
|
||||
<Stack>
|
||||
<TextInput
|
||||
name="expectedValue"
|
||||
type="text"
|
||||
label="Expected value"
|
||||
name="expectedValue"
|
||||
label={t("expectedValue")}
|
||||
isOptional={true}
|
||||
placeholder={
|
||||
expectedValuePlaceholders[monitor.matchMethod || "equal"]
|
||||
@@ -453,7 +663,7 @@ const CreateMonitor = () => {
|
||||
<TextInput
|
||||
name="jsonPath"
|
||||
type="text"
|
||||
label="JSON Path"
|
||||
label={t("uptimeAdvancedMatching.jsonPath")}
|
||||
isOptional={true}
|
||||
placeholder="data.status"
|
||||
value={monitor.jsonPath}
|
||||
@@ -466,7 +676,7 @@ const CreateMonitor = () => {
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
opacity={0.8}
|
||||
>
|
||||
{t("uptimeCreateJsonPath")}
|
||||
{t("uptimeCreateJsonPath") + " "}
|
||||
<Typography
|
||||
component="a"
|
||||
href="https://jmespath.org/"
|
||||
@@ -475,7 +685,7 @@ const CreateMonitor = () => {
|
||||
>
|
||||
jmespath.org
|
||||
</Typography>
|
||||
{t("uptimeCreateJsonPathQuery")}
|
||||
{" " + t("uptimeCreateJsonPathQuery")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</>
|
||||
@@ -491,14 +701,32 @@ const CreateMonitor = () => {
|
||||
variant="contained"
|
||||
color="accent"
|
||||
disabled={!Object.values(errors).every((value) => value === undefined)}
|
||||
loading={isCreating}
|
||||
loading={isBusy}
|
||||
sx={{ px: theme.spacing(12) }}
|
||||
>
|
||||
{t("createMonitor")}
|
||||
{t("settingsSave")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{!isCreate && (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
theme={theme}
|
||||
title={t("deleteDialogTitle")}
|
||||
description={t("deleteDialogDescription")}
|
||||
onCancel={() => setIsOpen(false)}
|
||||
confirmationButtonLabel={t("delete")}
|
||||
onConfirm={handleRemove}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateMonitor;
|
||||
UptimeCreate.propTypes = {
|
||||
isClone: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default UptimeCreate;
|
||||
|
||||
@@ -14,7 +14,6 @@ import AuthNewPasswordConfirmed from "../Pages/Auth/NewPasswordConfirmed";
|
||||
import Uptime from "../Pages/Uptime/Monitors";
|
||||
import UptimeDetails from "../Pages/Uptime/Details";
|
||||
import UptimeCreate from "../Pages/Uptime/Create";
|
||||
import UptimeConfigure from "../Pages/Uptime/Configure";
|
||||
|
||||
// PageSpeed
|
||||
import PageSpeed from "../Pages/PageSpeed/Monitors";
|
||||
@@ -81,16 +80,20 @@ const Routes = () => {
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="/uptime/create/:monitorId?"
|
||||
path="/uptime/create"
|
||||
element={<UptimeCreate />}
|
||||
/>
|
||||
<Route
|
||||
path="/uptime/create/:monitorId"
|
||||
element={<UptimeCreate isClone={true} />}
|
||||
/>
|
||||
<Route
|
||||
path="/uptime/:monitorId/"
|
||||
element={<UptimeDetails />}
|
||||
/>
|
||||
<Route
|
||||
path="/uptime/configure/:monitorId/"
|
||||
element={<UptimeConfigure />}
|
||||
element={<UptimeCreate />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
||||
@@ -378,6 +378,7 @@
|
||||
"dockerContainerMonitoringDescription": "Check whether your Docker container is running or not.",
|
||||
"duration": "Duration",
|
||||
"edit": "Edit",
|
||||
"editing": "Editing...",
|
||||
"editMaintenance": "Edit maintenance",
|
||||
"editUserPage": {
|
||||
"form": {
|
||||
@@ -397,7 +398,6 @@
|
||||
"validationErrors": "Validation errors"
|
||||
}
|
||||
},
|
||||
"editing": "Editing...",
|
||||
"emailSent": "Email sent successfully",
|
||||
"errorInvalidFieldId": "Invalid field ID provided",
|
||||
"errorInvalidTypeId": "Invalid notification type provided",
|
||||
@@ -543,6 +543,15 @@
|
||||
"maintenanceWindowName": "Maintenance Window Name",
|
||||
"maskedPageSpeedKeyPlaceholder": "*************************************",
|
||||
"matchMethod": "Match Method",
|
||||
"matchMethodOptions": {
|
||||
"equal": "Equal",
|
||||
"equalPlaceholder": "success",
|
||||
"include": "Include",
|
||||
"includePlaceholder": "ok",
|
||||
"regex": "Regex",
|
||||
"regexPlaceholder": "^(success|ok)$",
|
||||
"text": "Match Method"
|
||||
},
|
||||
"mb": "MB",
|
||||
"mem": "Mem",
|
||||
"memory": "Memory",
|
||||
@@ -582,6 +591,7 @@
|
||||
"failureAddDemoMonitors": "Failed to add demo monitors",
|
||||
"successAddDemoMonitors": "Successfully added demo monitors"
|
||||
},
|
||||
"monitors": "monitors",
|
||||
"monitorState": {
|
||||
"active": "Active",
|
||||
"paused": "Paused",
|
||||
@@ -596,8 +606,29 @@
|
||||
},
|
||||
"monitorStatusDown": "Monitor {name} ({url}) is DOWN and not responding",
|
||||
"monitorStatusUp": "Monitor {name} ({url}) is now UP and responding",
|
||||
"monitors": "monitors",
|
||||
"monitorsToApply": "Monitors to apply maintenance window to",
|
||||
"monitorType": {
|
||||
"docker": {
|
||||
"label": "Container ID",
|
||||
"namePlaceholder": "My Container",
|
||||
"placeholder": "abcd1234"
|
||||
},
|
||||
"http": {
|
||||
"label": "URL to monitor",
|
||||
"namePlaceholder": "Google",
|
||||
"placeholder": "google.com"
|
||||
},
|
||||
"ping": {
|
||||
"label": "IP address to monitor",
|
||||
"namePlaceholder": "Google",
|
||||
"placeholder": "1.1.1.1"
|
||||
},
|
||||
"port": {
|
||||
"label": "URL to monitor",
|
||||
"namePlaceholder": "Localhost:5173",
|
||||
"placeholder": "localhost"
|
||||
}
|
||||
},
|
||||
"ms": "ms",
|
||||
"navControls": "Controls",
|
||||
"nextWindow": "Next window",
|
||||
@@ -723,8 +754,8 @@
|
||||
"queuePage": {
|
||||
"failedJobTable": {
|
||||
"failCountHeader": "Fail count",
|
||||
"failReasonHeader": "Fail reason",
|
||||
"failedAtHeader": "Last failed at",
|
||||
"failReasonHeader": "Fail reason",
|
||||
"monitorIdHeader": "Monitor ID",
|
||||
"monitorUrlHeader": "Monitor URL",
|
||||
"title": "Failed jobs"
|
||||
@@ -865,8 +896,8 @@
|
||||
"settingsTestEmailUnknownError": "Unknown error",
|
||||
"showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page",
|
||||
"showCharts": "Show charts",
|
||||
"showUptimePercentage": "Show uptime percentage",
|
||||
"shown": "Shown",
|
||||
"showUptimePercentage": "Show uptime percentage",
|
||||
"starPromptDescription": "See the latest releases and help grow the community on GitHub",
|
||||
"starPromptTitle": "Star Checkmate",
|
||||
"startTime": "Start time",
|
||||
@@ -945,21 +976,27 @@
|
||||
"testNotificationsDisabled": "There are no notifications setup for this monitor. You need to add one by clicking 'Configure' button",
|
||||
"time": {
|
||||
"fiveMinutes": "5 minutes",
|
||||
"fourMinutes": "4 minutes",
|
||||
"oneDay": "1 day",
|
||||
"oneHour": "1 hour",
|
||||
"oneMinute": "1 minute",
|
||||
"oneWeek": "1 week",
|
||||
"tenMinutes": "10 minutes",
|
||||
"threeMinutes": "3 minutes",
|
||||
"twentyMinutes": "20 minutes"
|
||||
"twentyMinutes": "20 minutes",
|
||||
"twoMinutes": "2 minutes"
|
||||
},
|
||||
"timeZoneInfo": "All dates and times are in GMT+0 time zone.",
|
||||
"timezone": "Timezone",
|
||||
"timeZoneInfo": "All dates and times are in GMT+0 time zone.",
|
||||
"title": "Title",
|
||||
"tlsErrorIgnored": "TLS/SSL errors ignored",
|
||||
"total": "Total",
|
||||
"type": "Type",
|
||||
"update": "Update",
|
||||
"uptime": "Uptime",
|
||||
"uptimeAdvancedMatching": {
|
||||
"jsonPath": "JSON Path"
|
||||
},
|
||||
"uptimeCreate": "The expected value is used to match against response result, and the match determines the status.",
|
||||
"uptimeCreateJsonPath": "This expression will be evaluated against the reponse JSON data and the result will be used to match against the expected value. See",
|
||||
"uptimeCreateJsonPathQuery": "for query language documentation.",
|
||||
|
||||
Reference in New Issue
Block a user