Merge branch 'develop' into feat/search-infra-moniters

This commit is contained in:
Amol Vyas authored and GitHub committed 2025-06-15 03:39:10 +05:30
commit 87ccc93ff9
95 files changed
+1313 -2750

No files matched your search

+1 -1
View File
@@ -5,7 +5,7 @@ import Button from "@mui/material/Button";
import TextInput from "../../../Components/Inputs/TextInput";
import { PasswordEndAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import { loginCredentials } from "../../../Validation/validation";
import TextLink from "../components/TextLink";
import TextLink from "../../../Components/TextLink";
import Typography from "@mui/material/Typography";
// Utils
@@ -3,7 +3,6 @@ import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router";
import { useDispatch } from "react-redux";
import { clearAuthState } from "../../Features/Auth/authSlice";
import { clearUptimeMonitorState } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Background from "../../assets/Images/background-grid.svg?react";
import ConfirmIcon from "../../assets/icons/check-outlined.svg?react";
import Logo from "../../assets/icons/checkmate-icon.svg?react";
@@ -19,7 +18,6 @@ const NewPasswordConfirmed = () => {
const handleNavigate = () => {
dispatch(clearAuthState());
dispatch(clearUptimeMonitorState());
navigate("/login");
};
@@ -1,34 +0,0 @@
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import Link from "@mui/material/Link";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Link as RouterLink } from "react-router-dom";
const TextLink = ({ text, linkText, href }) => {
const theme = useTheme();
return (
<Stack
direction="row"
gap={theme.spacing(4)}
>
<Typography>{text}</Typography>
<Link
color="accent"
to={href}
component={RouterLink}
>
{linkText}
</Link>
</Stack>
);
};
TextLink.propTypes = {
text: PropTypes.string,
linkText: PropTypes.string,
href: PropTypes.string,
};
export default TextLink;
@@ -10,10 +10,10 @@ import NetworkError from "../../../../Components/GenericFallback/NetworkError";
//Utils
import { formatDateWithTz } from "../../../../Utils/timeUtils";
import { useSelector } from "react-redux";
import { useState } from "react";
import useChecksFetch from "../../Hooks/useChecksFetch";
import { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useFetchChecks } from "../../../../Hooks/checkHooks";
const IncidentTable = ({
shouldRender,
@@ -26,17 +26,25 @@ const IncidentTable = ({
const uiTimezone = useSelector((state) => state.ui.timezone);
//Local state
const [teamId, setTeamId] = useState(undefined);
const [monitorId, setMonitorId] = useState(undefined);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const selectedMonitorDetails = monitors?.[selectedMonitor];
const selectedMonitorType = selectedMonitorDetails?.type;
const { isLoading, networkError, checks, checksCount } = useChecksFetch({
selectedMonitor,
selectedMonitorType,
filter,
const [checks, checksCount, isLoading, networkError] = useFetchChecks({
status: false,
monitorId,
teamId,
type: selectedMonitorType,
sortOrder: "desc",
limit: null,
dateRange,
page,
rowsPerPage,
filter: filter,
page: page,
rowsPerPage: rowsPerPage,
});
const { t } = useTranslation();
@@ -50,6 +58,16 @@ const IncidentTable = ({
setRowsPerPage(event.target.value);
};
useEffect(() => {
if (selectedMonitor === "0") {
setTeamId("placeholder"); // TODO this isn't needed any longer, fix hook
setMonitorId(undefined);
} else {
setMonitorId(selectedMonitor);
setTeamId(undefined);
}
}, [selectedMonitor]);
const headers = [
{
id: "monitorName",
@@ -1,66 +0,0 @@
import { useState, useEffect } from "react";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import { useSelector } from "react-redux";
const useChecksFetch = ({
selectedMonitor,
selectedMonitorType,
filter,
dateRange,
page,
rowsPerPage,
}) => {
//Redux
const { user } = useSelector((state) => state.auth);
//Local
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
useEffect(() => {
const fetchChecks = async () => {
try {
setIsLoading(true);
let res;
if (selectedMonitor === "0") {
res = await networkService.getChecksByTeam({
status: false,
teamId: user.teamId,
sortOrder: "desc",
limit: null,
dateRange,
filter: filter,
page: page,
rowsPerPage: rowsPerPage,
});
} else {
res = await networkService.getChecksByMonitor({
status: false,
monitorId: selectedMonitor,
type: selectedMonitorType,
sortOrder: "desc",
limit: null,
dateRange,
filter: filter,
page,
rowsPerPage,
});
}
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchChecks();
}, [user, dateRange, page, rowsPerPage, filter, selectedMonitor, selectedMonitorType]);
return { isLoading, networkError, checks, checksCount };
};
export default useChecksFetch;
@@ -1,54 +0,0 @@
import { useState, useEffect } from "react";
import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
const useMonitorsFetch = ({ teamId }) => {
//Local state
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitors, setMonitors] = useState(undefined);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
teamId,
limit: null,
types: null,
status: null,
checkOrder: null,
normalize: null,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (res?.data?.data?.filteredMonitors?.length > 0) {
const monitorLookup = res.data.data.filteredMonitors.reduce((acc, monitor) => {
acc[monitor._id] = {
_id: monitor._id,
name: monitor.name,
type: monitor.type,
};
return acc;
}, {});
setMonitors(monitorLookup);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [teamId]);
return { isLoading, monitors, networkError };
};
export { useMonitorsFetch };
+22 -13
View File
@@ -1,22 +1,20 @@
// Components
import { Stack } from "@mui/material";
import Breadcrumbs from "../../Components/Breadcrumbs";
import GenericFallback from "../../Components/GenericFallback";
import IncidentTable from "./Components/IncidentTable";
import OptionsHeader from "./Components/OptionsHeader";
//Utils
import { useTheme } from "@emotion/react";
import { useMonitorsFetch } from "./Hooks/useMonitorsFetch";
import { useSelector } from "react-redux";
import OptionsHeader from "./Components/OptionsHeader";
import { useState } from "react";
import IncidentTable from "./Components/IncidentTable";
import GenericFallback from "../../Components/GenericFallback";
import { useFetchMonitorsByTeamId } from "../../Hooks/monitorHooks";
import { useState, useEffect } from "react";
import NetworkError from "../../Components/GenericFallback/NetworkError";
import { useTranslation } from "react-i18next";
//Constants
//Constants
const Incidents = () => {
// Redux state
const { user } = useSelector((state) => state.auth);
const { t } = useTranslation();
const BREADCRUMBS = [
@@ -27,12 +25,23 @@ const Incidents = () => {
const [selectedMonitor, setSelectedMonitor] = useState("0");
const [filter, setFilter] = useState(undefined);
const [dateRange, setDateRange] = useState(undefined);
const [monitorLookup, setMonitorLookup] = useState(undefined);
//Utils
const theme = useTheme();
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
const { monitors, isLoading, networkError } = useMonitorsFetch({
teamId: user.teamId,
});
useEffect(() => {
const monitorLookup = monitors?.reduce((acc, monitor) => {
acc[monitor._id] = {
_id: monitor._id,
name: monitor.name,
type: monitor.type,
};
return acc;
}, {});
setMonitorLookup(monitorLookup);
}, [monitors]);
if (networkError) {
return (
@@ -47,7 +56,7 @@ const Incidents = () => {
<Breadcrumbs list={BREADCRUMBS} />
<OptionsHeader
shouldRender={!isLoading}
monitors={monitors}
monitors={monitorLookup}
selectedMonitor={selectedMonitor}
setSelectedMonitor={setSelectedMonitor}
filter={filter}
@@ -57,7 +66,7 @@ const Incidents = () => {
/>
<IncidentTable
shouldRender={!isLoading}
monitors={monitors}
monitors={monitorLookup ? monitorLookup : {}}
selectedMonitor={selectedMonitor}
filter={filter}
dateRange={dateRange}
@@ -1,20 +1,17 @@
// React, Redux, Router
import { useTheme } from "@emotion/react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { useSelector } from "react-redux";
// Utility and Network
import { infrastructureMonitorValidation } from "../../../Validation/validation";
import {
createInfrastructureMonitor,
updateInfrastructureMonitor,
} from "../../../Features/InfrastructureMonitors/infrastructureMonitorsSlice";
import { useHardwareMonitorsFetch } from "../Details/Hooks/useHardwareMonitorsFetch";
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
import { capitalizeFirstLetter } from "../../../Utils/stringUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import NotificationsConfig from "../../../Components/NotificationConfig";
import { useUpdateMonitor, useCreateMonitor } from "../../../Hooks/monitorHooks";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
@@ -55,9 +52,6 @@ const getAlertError = (errors) => {
const CreateInfrastructureMonitor = () => {
const theme = useTheme();
const { user } = useSelector((state) => state.auth);
const monitorState = useSelector((state) => state.infrastructureMonitor);
const dispatch = useDispatch();
const navigate = useNavigate();
const { monitorId } = useParams();
const { t } = useTranslation();
@@ -65,9 +59,11 @@ const CreateInfrastructureMonitor = () => {
const isCreate = typeof monitorId === "undefined";
// Fetch monitor details if editing
const { monitor, isLoading, networkError } = useHardwareMonitorsFetch({ monitorId });
const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({ monitorId });
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [updateMonitor, isUpdating] = useUpdateMonitor();
const [createMonitor, isCreating] = useCreateMonitor();
// State
const [errors, setErrors] = useState({});
@@ -187,29 +183,18 @@ const CreateInfrastructureMonitor = () => {
};
form = {
...(isCreate ? {} : { _id: monitorId }),
...rest,
description: form.name,
teamId: user.teamId,
userId: user._id,
type: "hardware",
notifications: infrastructureMonitor.notifications,
thresholds,
};
// Handle create or update
const action = isCreate
? await dispatch(createInfrastructureMonitor({ monitor: form }))
: await dispatch(updateInfrastructureMonitor({ monitorId, monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({
body: isCreate
? t("infrastructureMonitorCreated")
: t("infrastructureMonitorUpdated"),
});
navigate("/infrastructure");
} else {
createToast({ body: "Failed to save monitor." });
}
isCreate
? await createMonitor({ monitor: form, redirect: "/infrastructure" })
: await updateMonitor({ monitor: form, redirect: "/infrastructure" });
};
const onChange = (event) => {
@@ -448,7 +433,7 @@ const CreateInfrastructureMonitor = () => {
type="submit"
variant="contained"
color="accent"
loading={monitorState?.isLoading}
loading={isLoading || isUpdating || isCreating || notificationsAreLoading}
>
{t(isCreate ? "infrastructureCreateMonitor" : "infrastructureEditMonitor")}
</Button>
@@ -4,14 +4,14 @@ import StatusBoxes from "../../../../../Components/StatusBoxes";
import StatBox from "../../../../../Components/StatBox";
//Utils
import useUtils from "../../../../../Pages/Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useHardwareUtils } from "../../Hooks/useHardwareUtils";
import { useTranslation } from "react-i18next";
const InfraStatBoxes = ({ shouldRender, monitor }) => {
// Utils
const { formatBytes } = useHardwareUtils();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const { t } = useTranslation();
const { stats } = monitor ?? {};
@@ -1,38 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => {
// Abort early if creating monitor
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitor, setMonitor] = useState(undefined);
useEffect(() => {
const fetchData = async () => {
try {
if (!monitorId) {
return { monitor: undefined, isLoading: false, networkError: undefined };
}
const response = await networkService.getHardwareDetailsByMonitorId({
monitorId: monitorId,
dateRange: dateRange,
});
setMonitor(response.data.data);
} catch (error) {
setNetworkError(true);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [monitorId, dateRange]);
return {
isLoading,
networkError,
monitor,
};
};
export { useHardwareMonitorsFetch };
@@ -11,7 +11,7 @@ import GenericFallback from "../../../Components/GenericFallback";
// Utils
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useHardwareMonitorsFetch } from "./Hooks/useHardwareMonitorsFetch";
import { useFetchHardwareMonitorById } from "../../../Hooks/monitorHooks";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
@@ -33,7 +33,7 @@ const InfrastructureDetails = () => {
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const { isLoading, networkError, monitor } = useHardwareMonitorsFetch({
const [monitor, isLoading, networkError] = useFetchHardwareMonitorById({
monitorId,
dateRange,
});
@@ -12,7 +12,7 @@ import CustomGauge from "../../../../../Components/Charts/CustomGauge";
// Utils
import { useTheme } from "@emotion/react";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -27,7 +27,7 @@ const MonitorsTable = ({
// Utils
const theme = useTheme();
const { t } = useTranslation();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const navigate = useNavigate();
// Handlers
@@ -94,6 +94,7 @@ const MonitorsTable = ({
monitor={row}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
isLoading={isLoading}
/>
),
},
@@ -151,7 +152,7 @@ const MonitorsTable = ({
};
MonitorsTable.propTypes = {
shouldRender: PropTypes.bool,
isLoading: PropTypes.bool,
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
handleActionMenuDelete: PropTypes.func,
@@ -1,7 +1,6 @@
/* TODO I basically copied and pasted this component from the actionsMenu. Check how we can make it reusable */
import { useRef, useState } from "react";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../../../Utils/toastUtils";
@@ -30,7 +29,6 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
const [isOpen, setIsOpen] = useState(false);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const theme = useTheme();
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const openMenu = (e) => {
e.stopPropagation();
@@ -117,7 +115,6 @@ const InfrastructureMenu = ({ monitor, isAdmin, updateCallback }) => {
onCancel={cancelRemove}
confirmationButtonLabel="Delete"
onConfirm={handleRemove}
isLoading={isLoading}
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
/>
@@ -1,46 +0,0 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
const useMonitorFetch = ({ page, field, filter, rowsPerPage, updateTrigger }) => {
// Redux state
const { user } = useSelector((state) => state.auth);
// Local state
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [summary, setSummary] = useState(undefined);
useEffect(() => {
const fetchMonitors = async () => {
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: 1,
field: field,
filter: filter,
types: ["hardware"],
page: page,
rowsPerPage: rowsPerPage,
});
setMonitors(response?.data?.data?.filteredMonitors ?? []);
setSummary(response?.data?.data?.summary ?? {});
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [page, field, filter, rowsPerPage, user.teamId, updateTrigger]);
return { monitors, summary, isLoading, networkError };
};
export { useMonitorFetch };
@@ -11,15 +11,18 @@ import Filter from "./Components/Filters";
import SearchComponent from "../../Uptime/Monitors/Components/SearchComponent";
// Utils
import { useTheme } from "@emotion/react";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
import { useState } from "react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useTranslation } from "react-i18next";
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
// Constants
const TYPES = ["hardware"];
const BREADCRUMBS = [{ name: `infrastructure`, path: "/infrastructure" }];
const InfrastructureMonitors = () => {
// Redux state
// Local state
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const [updateTrigger, setUpdateTrigger] = useState(false);
@@ -53,7 +56,9 @@ const InfrastructureMonitors = () => {
const field = toFilterStatus !== undefined ? "status" : undefined;
const { monitors, summary, isLoading, networkError } = useMonitorFetch({
const [monitors, summary, isLoading, networkError] = useFetchMonitorsByTeamId({
limit: 1,
types: TYPES,
page,
field: field,
filter: toFilterStatus ?? search,
@@ -119,7 +124,7 @@ const InfrastructureMonitors = () => {
</Stack>
<MonitorsTable
shouldRender={!isLoading}
isLoading={isLoading}
monitors={monitors}
isAdmin={isAdmin}
handleActionMenuDelete={handleActionMenuDelete}
@@ -135,7 +135,6 @@ const CreateMaintenance = () => {
setIsLoading(true);
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: null,
types: ["http", "ping", "pagespeed", "port"],
});
@@ -48,8 +48,6 @@ const CreateNotifications = () => {
// local state
const [notification, setNotification] = useState({
userId: user._id,
teamId: user.teamId,
notificationName: "",
address: "",
type: NOTIFICATION_TYPES[0]._id,
-1
View File
@@ -17,7 +17,6 @@ import {
useDeleteNotification,
} from "../../Hooks/useNotifications";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
const Notifications = () => {
const navigate = useNavigate();
+25 -66
View File
@@ -12,45 +12,43 @@ import NotificationsConfig from "../../../Components/NotificationConfig";
import Dialog from "../../../Components/Dialog";
// Utils
import { useEffect, useState } from "react";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router";
import {
deletePageSpeed,
getPagespeedMonitorById,
getPageSpeedByTeamId,
updatePageSpeed,
pausePageSpeed,
} from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import { useParams } from "react-router";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
import useUtils from "../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../Hooks/useMonitorUtils";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import {
useFetchMonitorById,
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
} from "../../../Hooks/monitorHooks";
const PageSpeedConfigure = () => {
// Redux state
const { isLoading } = useSelector((state) => state.pageSpeedMonitors);
// Local state
const [monitor, setMonitor] = useState({});
const [errors, setErrors] = useState({});
const [buttonLoading, setButtonLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [updateTrigger, setUpdateTrigger] = useState(false);
// Utils
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const MS_PER_MINUTE = 60000;
const { monitorId } = useParams();
const { statusColor, pagespeedStatusMsg, determineState } = useUtils();
const { statusColor, pagespeedStatusMsg, determineState } = useMonitorUtils();
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [isLoading] = useFetchMonitorById({ monitorId, setMonitor, updateTrigger });
const [deleteMonitor, isDeleting] = useDeleteMonitor();
const [updateMonitor, isUpdating] = useUpdateMonitor();
const [pauseMonitor, isPausing] = usePauseMonitor();
const frequencies = [
{ _id: 3, name: "3 minutes" },
{ _id: 5, name: "5 minutes" },
@@ -61,24 +59,10 @@ const PageSpeedConfigure = () => {
{ _id: 10080, name: "1 week" },
];
useEffect(() => {
const fetchMonitor = async () => {
try {
const action = await dispatch(getPagespeedMonitorById({ monitorId }));
if (getPagespeedMonitorById.fulfilled.match(action)) {
const monitor = action.payload.data;
setMonitor(monitor);
} else if (getPagespeedMonitorById.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();
}, [dispatch, monitorId, navigate]);
// Handlers
const triggerUpdate = () => {
setUpdateTrigger(!updateTrigger);
};
const onChange = (event) => {
let { value, name } = event.target;
@@ -106,42 +90,17 @@ const PageSpeedConfigure = () => {
};
const handlePause = async () => {
try {
const action = await dispatch(pausePageSpeed({ monitorId }));
if (pausePageSpeed.fulfilled.match(action)) {
const monitor = action.payload.data;
setMonitor(monitor);
const state = action?.payload?.data.isActive === false ? "paused" : "resumed";
createToast({ body: `Monitor ${state} successfully.` });
} else if (pausePageSpeed.rejected.match(action)) {
throw new Error(action.error.message);
}
} catch (error) {
createToast({ body: "Failed to pause monitor" });
}
await pauseMonitor({ monitorId, triggerUpdate });
};
const onSubmit = async (event) => {
event.preventDefault();
const action = await dispatch(updatePageSpeed({ monitor: monitor }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor updated successfully!" });
dispatch(getPageSpeedByTeamId());
} else {
createToast({ body: "Failed to update monitor." });
}
await updateMonitor({ monitor, redirect: "/pagespeed" });
};
const handleRemove = async (event) => {
event.preventDefault();
setButtonLoading(true);
const action = await dispatch(deletePageSpeed({ monitor }));
if (action.meta.requestStatus === "fulfilled") {
navigate("/pagespeed");
} else {
createToast({ body: "Failed to delete monitor." });
}
setButtonLoading(false);
await deleteMonitor({ monitor, redirect: "/pagespeed" });
};
return (
@@ -363,7 +322,7 @@ const PageSpeedConfigure = () => {
mt="auto"
>
<Button
loading={isLoading}
loading={isLoading || isDeleting || isUpdating || isPausing}
type="submit"
variant="contained"
color="accent"
@@ -383,7 +342,7 @@ const PageSpeedConfigure = () => {
onCancel={() => setIsOpen(false)}
confirmationButtonLabel={t("delete")}
onConfirm={handleRemove}
isLoading={buttonLoading}
isLoading={isLoading || isDeleting || isUpdating || isPausing}
/>
</Stack>
);
+19 -44
View File
@@ -1,31 +1,28 @@
// React, Redux, Router
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
// Utility and Network
import { monitorValidation } from "../../../Validation/validation";
import {
createPageSpeed,
checkEndpointResolution,
} from "../../../Features/PageSpeedMonitor/pageSpeedMonitorSlice";
import { parseDomainName } from "../../../Utils/monitorUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
// MUI
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
//Components
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button";
import ButtonGroup from "@mui/material/ButtonGroup";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import TextInput from "../../../Components/Inputs/TextInput";
import { HttpAdornment } from "../../../Components/Inputs/TextInput/Adornments";
import ConfigBox from "../../../Components/ConfigBox";
import { createToast } from "../../../Utils/toastUtils";
import Radio from "../../../Components/Inputs/Radio";
import Select from "../../../Components/Inputs/Select";
import NotificationsConfig from "../../../Components/NotificationConfig";
// Utils
import { useState } from "react";
import { useSelector } from "react-redux";
import { monitorValidation } from "../../../Validation/validation";
import { parseDomainName } from "../../../Utils/monitorUtils";
import { useTranslation } from "react-i18next";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import { useTheme } from "@emotion/react";
import { createToast } from "../../../Utils/toastUtils";
import { useCreateMonitor } from "../../../Hooks/monitorHooks";
const MS_PER_MINUTE = 60000;
const CRUMBS = [
@@ -56,13 +53,11 @@ const CreatePageSpeed = () => {
const [https, setHttps] = useState(true);
const [errors, setErrors] = useState({});
const { user } = useSelector((state) => state.auth);
const { isLoading } = useSelector((state) => state.pageSpeedMonitors);
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
// Setup
const dispatch = useDispatch();
const navigate = useNavigate();
const theme = useTheme();
const [createMonitor, isCreating] = useCreateMonitor();
// Handlers
const onSubmit = async (event) => {
@@ -88,33 +83,13 @@ const CreatePageSpeed = () => {
return;
}
const checkEndpointAction = await dispatch(
checkEndpointResolution({ monitorURL: form.url })
);
if (checkEndpointAction.meta.requestStatus === "rejected") {
createToast({
body: "The endpoint you entered doesn't resolve. Check the URL again.",
});
setErrors({ url: "The entered URL is not reachable." });
return;
}
form = {
...form,
description: form.name,
teamId: user.teamId,
userId: user._id,
notifications: monitor.notifications,
};
const action = await dispatch(createPageSpeed({ monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/pagespeed");
} else {
createToast({ body: "Failed to create monitor." });
}
await createMonitor({ monitor: form, redirect: "/pagespeed" });
};
const handleChange = (event) => {
@@ -325,7 +300,7 @@ const CreatePageSpeed = () => {
variant="contained"
color="accent"
disabled={!Object.values(errors).every((value) => value === undefined)}
loading={isLoading}
loading={isCreating}
>
{t("createMonitor")}
</Button>
@@ -1,39 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useNavigate } from "react-router-dom";
const useMonitorFetch = ({ monitorId }) => {
const navigate = useNavigate();
const [monitor, setMonitor] = useState(undefined);
const [audits, setAudits] = useState(undefined);
const [isLoading, setIsLoading] = useState(true);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitor = async () => {
try {
const res = await networkService.getStatsByMonitorId({
monitorId: monitorId,
sortOrder: "desc",
limit: 50,
dateRange: "day",
numToDisplay: null,
normalize: null,
});
setMonitor(res?.data?.data ?? undefined);
setAudits(res?.data?.data?.checks?.[0]?.audits ?? undefined);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitor();
}, [monitorId, navigate]);
return { monitor, audits, isLoading };
};
export { useMonitorFetch };
+7 -3
View File
@@ -11,8 +11,7 @@ import GenericFallback from "../../../Components/GenericFallback";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useMonitorFetch } from "./Hooks/useMonitorFetch";
import { useFetchStatsByMonitorId } from "../../../Hooks/monitorHooks";
import { useState } from "react";
import { useTranslation } from "react-i18next";
// Constants
@@ -28,8 +27,13 @@ const PageSpeedDetails = () => {
const isAdmin = useIsAdmin();
const { monitorId } = useParams();
const { monitor, audits, isLoading, networkError } = useMonitorFetch({
const [monitor, audits, isLoading, networkError] = useFetchStatsByMonitorId({
monitorId,
sortOrder: "desc",
limit: 50,
dateRange: "day",
numToDisplay: null,
normalize: null,
});
const [metrics, setMetrics] = useState({
@@ -8,7 +8,7 @@ import { useTheme } from "@emotion/react";
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip } from "recharts";
import { useSelector } from "react-redux";
import { formatDateWithTz, formatDurationSplit } from "../../../../../Utils/timeUtils";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import IconBox from "../../../../../Components/IconBox";
@@ -118,7 +118,7 @@ const processData = (data) => {
const PagespeedAreaChart = ({ data, status }) => {
const theme = useTheme();
const [isHovered, setIsHovered] = useState(false);
const { statusToTheme } = useUtils();
const { statusToTheme } = useMonitorUtils();
const themeColor = statusToTheme[status];
@@ -206,7 +206,7 @@ PagespeedAreaChart.propTypes = {
* @returns {JSX.Element} - The rendered card.
*/
const Card = ({ monitor }) => {
const { determineState, pagespeedStatusMsg } = useUtils();
const { determineState, pagespeedStatusMsg } = useMonitorUtils();
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
@@ -275,7 +275,7 @@ const Card = ({ monitor }) => {
sx={{ gridColumnStart: 1, gridColumnEnd: 4 }}
>
<PagespeedAreaChart
data={monitor.checks.slice().reverse()}
data={monitor?.checks?.slice().reverse()}
status={monitorState}
/>
</Box>
@@ -1,44 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
const useMonitorsFetch = ({ teamId }) => {
const [isLoading, setIsLoading] = useState(true);
const [monitors, setMonitors] = useState([]);
const [summary, setSummary] = useState({});
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
const fetchMonitors = async () => {
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
teamId: teamId,
limit: 10,
types: ["pagespeed"],
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (res?.data?.data?.filteredMonitors) {
setMonitors(res.data.data.filteredMonitors);
setSummary(res.data.data.summary);
}
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [teamId]);
return { isLoading, monitors, summary, networkError };
};
export default useMonitorsFetch;
+13 -10
View File
@@ -5,26 +5,29 @@ import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
import MonitorCountHeader from "../../../Components/MonitorCountHeader";
import MonitorGrid from "./Components/MonitorGrid";
import Fallback from "../../../Components/Fallback";
import GenericFallback from "../../../Components/GenericFallback";
// Utils
import { useTheme } from "@emotion/react";
import { useSelector } from "react-redux";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import useMonitorsFetch from "./Hooks/useMonitorsFetch";
import GenericFallback from "../../../Components/GenericFallback";
import { useTranslation } from "react-i18next";
import { useFetchMonitorsByTeamId } from "../../../Hooks/monitorHooks";
// Constants
const BREADCRUMBS = [{ name: `pagespeed`, path: "/pagespeed" }];
const TYPES = ["pagespeed"];
const PageSpeed = () => {
const theme = useTheme();
const { t } = useTranslation();
const isAdmin = useIsAdmin();
const { user } = useSelector((state) => state.auth);
const { pagespeedApiKey } = useSelector((state) => state.settings);
const { isLoading, monitors, summary, networkError } = useMonitorsFetch({
teamId: user.teamId,
const [monitors, monitorsSummary, isLoading, networkError] = useFetchMonitorsByTeamId({
limit: 10,
types: TYPES,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
if (networkError === true) {
@@ -53,7 +56,7 @@ const PageSpeed = () => {
]}
link="/pagespeed/create"
isAdmin={isAdmin}
showPageSpeedWarning={isAdmin && !pagespeedApiKey}
// showPageSpeedWarning={isAdmin && !pagespeedApiKey}
/>
);
}
@@ -68,7 +71,7 @@ const PageSpeed = () => {
/>
<MonitorCountHeader
shouldRender={!isLoading}
monitorCount={summary?.totalMonitors}
monitorCount={monitorsSummary?.totalMonitors}
sx={{ mb: theme.spacing(8) }}
/>
<MonitorGrid
+20 -34
View File
@@ -11,22 +11,20 @@ import SettingsEmail from "./SettingsEmail";
import Button from "@mui/material/Button";
// Utils
import { settingsValidation } from "../../Validation/validation";
import { createToast } from "../../Utils/toastUtils";
import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { useSelector, useDispatch } from "react-redux";
import { setTimezone, setMode, setLanguage, setShowURL } from "../../Features/UI/uiSlice";
import SettingsStats from "./SettingsStats";
import {
deleteMonitorChecksByTeamId,
addDemoMonitors,
deleteAllMonitors,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useFetchSettings, useSaveSettings } from "../../Hooks/useFetchSettings";
import { UseDeleteMonitorStats } from "../../Hooks/useDeleteMonitorStats";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import { useFetchSettings, useSaveSettings } from "../../Hooks/settingsHooks";
import { useIsAdmin } from "../../Hooks/useIsAdmin";
import {
useAddDemoMonitors,
useDeleteAllMonitors,
useDeleteMonitorStats,
} from "../../Hooks/monitorHooks";
// Constants
const BREADCRUMBS = [{ name: `Settings`, path: "/settings" }];
@@ -43,15 +41,17 @@ const Settings = () => {
const [isSettingsLoading, settingsError] = useFetchSettings({
setSettingsData,
});
const [addDemoMonitors, isAddingDemoMonitors] = useAddDemoMonitors();
const [isSaving, saveError, saveSettings] = useSaveSettings();
const [deleteMonitorStats, isDeletingMonitorStats] = UseDeleteMonitorStats();
const [deleteAllMonitors, isDeletingMonitors] = useDeleteAllMonitors();
const [deleteMonitorStats, isDeletingMonitorStats] = useDeleteMonitorStats();
// Setup
const isAdmin = useIsAdmin();
const theme = useTheme();
const HEADING_SX = { mt: theme.spacing(2), mb: theme.spacing(2) };
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const dispatch = useDispatch();
// Handlers
@@ -96,35 +96,17 @@ const Settings = () => {
}
if (name === "deleteStats") {
await deleteMonitorStats({ teamId: user.teamId });
await deleteMonitorStats();
return;
}
if (name === "demo") {
try {
const action = await dispatch(addDemoMonitors());
if (addDemoMonitors.fulfilled.match(action)) {
createToast({ body: t("settingsDemoMonitorsAdded") });
} else {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
} catch (error) {
createToast({ body: t("settingsFailedToAddDemoMonitors") });
}
await addDemoMonitors();
return;
}
if (name === "deleteMonitors") {
try {
const action = await dispatch(deleteAllMonitors());
if (deleteAllMonitors.fulfilled.match(action)) {
createToast({ body: t("settingsMonitorsDeleted") });
} else {
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
} catch (error) {
createToast({ body: t("settingsFailedToDeleteMonitors") });
}
await deleteAllMonitors();
return;
}
@@ -185,7 +167,9 @@ const Settings = () => {
isAdmin={isAdmin}
HEADER_SX={HEADING_SX}
handleChange={handleChange}
isLoading={isSettingsLoading || isSaving || isDeletingMonitorStats}
isLoading={
isSettingsLoading || isSaving || isDeletingMonitorStats || isAddingDemoMonitors
}
/>
<SettingsEmail
isAdmin={isAdmin}
@@ -219,7 +203,9 @@ const Settings = () => {
}}
>
<Button
loading={isSaving || isDeletingMonitorStats || isSettingsLoading}
loading={
isSaving || isDeletingMonitorStats || isSettingsLoading || isDeletingMonitors
}
disabled={Object.keys(errors).length > 0}
variant="contained"
color="accent"
@@ -1,17 +1,14 @@
import { useState } from "react";
import { networkService } from "../../../../main";
import { useSelector } from "react-redux";
import { createToast } from "../../../../Utils/toastUtils";
const useCreateStatusPage = (isCreate, url) => {
const { user } = useSelector((state) => state.auth);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
const createStatusPage = async ({ form }) => {
setIsLoading(true);
try {
await networkService.createStatusPage({ user, form, isCreate, url });
await networkService.createStatusPage({ form, isCreate, url });
return true;
} catch (error) {
setNetworkError(true);
@@ -13,7 +13,6 @@ const useMonitorsFetch = () => {
const fetchMonitors = async () => {
try {
const response = await networkService.getMonitorsByTeamId({
teamId: user.teamId,
limit: null, // donot return any checks for the monitors
types: ["http", "ping", "port"], // status page is available for uptime, ping, and port monitors
});
@@ -6,14 +6,14 @@ import { StatusLabel } from "../../../../../Components/Label";
//Utils
import { useTheme } from "@mui/material/styles";
import useUtils from "../../../../Uptime/Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
const MonitorsList = ({ isLoading = false, shouldRender = true, monitors = [] }) => {
const theme = useTheme();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const { showURL } = useSelector((state) => state.ui);
@@ -8,6 +8,7 @@ import StatusBar from "./Components/StatusBar";
import MonitorsList from "./Components/MonitorsList";
import Dialog from "../../../Components/Dialog";
import Breadcrumbs from "../../../Components/Breadcrumbs/index.jsx";
import TextLink from "../../../Components/TextLink";
// Utils
import { useStatusPageFetch } from "./Hooks/useStatusPageFetch";
@@ -30,6 +31,7 @@ const PublicStatus = () => {
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const [statusPage, monitors, isLoading, networkError, fetchStatusPage] =
useStatusPageFetch(false, url);
@@ -60,6 +62,26 @@ const PublicStatus = () => {
return <SkeletonLayout />;
}
if (monitors.length === 0) {
return (
<GenericFallback>
<Typography
variant="h1"
marginY={theme.spacing(4)}
color={theme.palette.primary.contrastTextTertiary}
>
{"Theres nothing here yet"}
</Typography>
{isAdmin && (
<TextLink
linkText={"Add a monitor to get started"}
href={`/status/uptime/configure/${url}`}
/>
)}
</GenericFallback>
);
}
// Error fetching data
if (networkError === true) {
return (
@@ -13,9 +13,7 @@ const useStatusPagesFetch = () => {
useEffect(() => {
const fetchStatusPages = async () => {
try {
const res = await networkService.getStatusPagesByTeamId({
teamId: user.teamId,
});
const res = await networkService.getStatusPagesByTeamId();
setStatusPages(res?.data?.data);
} catch (error) {
setNetworkError(true);
+2 -2
View File
@@ -12,7 +12,7 @@ import UploadFile from "./Upload";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router";
import { Trans, useTranslation } from "react-i18next";
import { useBulkMonitors } from "../../../Hooks/useBulkMonitors";
import { useCreateBulkMonitors } from "../../../Hooks/monitorHooks";
const BulkImport = () => {
const theme = useTheme();
@@ -26,7 +26,7 @@ const BulkImport = () => {
{ name: t("bulkImport.title"), path: `/uptime/bulk-import` },
];
const [createBulkMonitors, hookLoading] = useBulkMonitors();
const [createBulkMonitors, hookLoading] = useCreateBulkMonitors();
const handleSubmit = async () => {
if (!selectedFile) {
+57 -59
View File
@@ -1,37 +1,37 @@
import { useNavigate, useParams } from "react-router";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import {
Box,
Stack,
Tooltip,
Typography,
Button,
FormControlLabel,
Switch,
} from "@mui/material";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useTranslation } from "react-i18next";
// 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 {
updateUptimeMonitor,
deleteUptimeMonitor,
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
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 Breadcrumbs from "../../../Components/Breadcrumbs";
import PulseDot from "../../../Components/Animated/PulseDot";
import "./index.css";
import Dialog from "../../../Components/Dialog";
import { usePauseMonitor } from "../../../Hooks/useMonitorControls";
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 { useFetchUptimeMonitorById } from "../../../Hooks/useFetchUptimeMonitorById";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import {
useDeleteMonitor,
useUpdateMonitor,
usePauseMonitor,
useFetchMonitorById,
} from "../../../Hooks/monitorHooks";
import NotificationsConfig from "../../../Components/NotificationConfig";
/**
@@ -76,18 +76,19 @@ const Configure = () => {
};
// Network
const [monitor, isLoading, error] = useFetchUptimeMonitorById(monitorId, updateTrigger);
const [notifications, notificationsAreLoading, notificationsError] =
useGetNotificationsByTeamId();
const [pauseMonitor, isPausing, pauseError] = usePauseMonitor({
monitorId: monitor?._id,
triggerUpdate,
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 navigate = useNavigate();
const theme = useTheme();
const dispatch = useDispatch();
const matchMethodOptions = [
{ _id: "equal", name: "Equal" },
@@ -111,7 +112,7 @@ const Configure = () => {
// Handlers
const handlePause = async () => {
const res = await pauseMonitor();
const res = await pauseMonitor({ monitorId: form?._id, triggerUpdate });
if (typeof res !== "undefined") {
triggerUpdate();
}
@@ -119,12 +120,7 @@ const Configure = () => {
const handleRemove = async (event) => {
event.preventDefault();
const action = await dispatch(deleteUptimeMonitor({ monitor }));
if (action.meta.requestStatus === "fulfilled") {
navigate("/uptime");
} else {
createToast({ body: "Failed to delete monitor." });
}
await deleteMonitor({ monitor: form, redirect: "/uptime" });
};
const onChange = (event) => {
@@ -134,6 +130,17 @@ const Configure = () => {
value = checked;
}
if (name === "useAdvancedMatching") {
setForm((prevForm) => {
return {
...prevForm,
matchMethod: "equal",
};
});
setUseAdvancedMatching(!useAdvancedMatching);
return;
}
if (name === "interval") {
value = value * MS_PER_MINUTE;
}
@@ -146,7 +153,6 @@ const Configure = () => {
setErrors((prev) => {
const updatedErrors = { ...prev };
if (validation.error) updatedErrors[name] = validation.error.details[0].message;
else delete updatedErrors[name];
return updatedErrors;
@@ -183,7 +189,7 @@ const Configure = () => {
if (validation.error) {
const newErrors = {};
error.details.forEach((err) => {
validation.error.details.forEach((err) => {
newErrors[err.path[0]] = err.message;
});
setErrors(newErrors);
@@ -192,27 +198,12 @@ const Configure = () => {
}
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." });
}
console.log(JSON.stringify(toSubmit, null, 2));
// await updateMonitor({ monitor: toSubmit, redirect: "/uptime" });
};
// Effects
useEffect(() => {
if (monitor?.matchMethod) {
setUseAdvancedMatching(true);
}
setForm({
...monitor,
});
}, [monitor, notifications]);
// Parse the URL
const parsedUrl = parseUrl(monitor?.url);
const parsedUrl = parseUrl(form?.url);
const protocol = parsedUrl?.protocol?.replace(":", "") || "";
const { determineState, statusColor } = useMonitorUtils();
@@ -434,7 +425,13 @@ const Configure = () => {
onChange={onChange}
items={frequencies}
/>
{form.type === "http" && (
<Checkbox
name="useAdvancedMatching"
label={t("advancedMatching")}
isChecked={useAdvancedMatching}
onChange={onChange}
/>
{form.type === "http" && useAdvancedMatching && (
<>
<Select
name="matchMethod"
@@ -502,6 +499,7 @@ const Configure = () => {
mt="auto"
>
<Button
disabled={isDeleting || isUpdating}
type="submit"
variant="contained"
color="accent"
+35 -34
View File
@@ -1,35 +1,30 @@
// React, Redux, Router
import { useTheme } from "@emotion/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 { monitorValidation } from "../../../Validation/validation";
import { createUptimeMonitor } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
// MUI
import { Box, Stack, Typography, Button, ButtonGroup } from "@mui/material";
import Switch from "@mui/material/Switch";
import FormControlLabel from "@mui/material/FormControlLabel";
//Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
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 Select from "../../../Components/Inputs/Select";
import ConfigBox from "../../../Components/ConfigBox";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
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";
// Utils
import { useTheme } from "@emotion/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { useGetNotificationsByTeamId } from "../../../Hooks/useNotifications";
import { useCreateMonitor } from "../../../Hooks/monitorHooks";
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);
@@ -39,6 +34,8 @@ const CreateMonitor = () => {
name: "",
type: "http",
matchMethod: "equal",
expectedValue: "",
jsonPath: "",
notifications: [],
interval: 1,
ignoreTlsErrors: false,
@@ -47,8 +44,8 @@ const CreateMonitor = () => {
// Setup
const theme = useTheme();
const { t } = useTranslation();
const navigate = useNavigate();
const [notifications, notificationsAreLoading, error] = useGetNotificationsByTeamId();
const [createMonitor, isCreating] = useCreateMonitor();
const MS_PER_MINUTE = 60000;
const SELECT_VALUES = [
@@ -142,18 +139,10 @@ const CreateMonitor = () => {
form = {
...form,
description: monitor.name || monitor.url,
teamId: user.teamId,
userId: user._id,
notifications: monitor.notifications,
};
const action = await dispatch(createUptimeMonitor({ monitor: form }));
if (action.meta.requestStatus === "fulfilled") {
createToast({ body: "Monitor created successfully!" });
navigate("/uptime");
} else {
createToast({ body: "Failed to create monitor." });
}
await createMonitor({ monitor: form, redirect: "/uptime" });
};
const onChange = (event) => {
@@ -162,6 +151,12 @@ const CreateMonitor = () => {
if (name === "ignoreTlsErrors") {
newValue = checked;
}
if (name === "useAdvancedMatching") {
setUseAdvancedMatching(checked);
return;
}
const updatedMonitor = {
...monitor,
[name]: newValue,
@@ -399,7 +394,13 @@ const CreateMonitor = () => {
onChange={onChange}
items={SELECT_VALUES}
/>
{monitor.type === "http" && (
<Checkbox
name="useAdvancedMatching"
label={t("advancedMatching")}
isChecked={useAdvancedMatching}
onChange={onChange}
/>
{monitor.type === "http" && useAdvancedMatching && (
<>
<Select
name="matchMethod"
@@ -472,7 +473,7 @@ const CreateMonitor = () => {
variant="contained"
color="accent"
disabled={!Object.values(errors).every((value) => value === undefined)}
loading={isLoading}
loading={isCreating}
>
{t("createMonitor")}
</Button>
@@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import { getHumanReadableDuration } from "../../../../../Utils/timeUtils";
import { useTheme } from "@mui/material/styles";
import { Typography } from "@mui/material";
import useUtils from "../../../Monitors/Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
const UptimeStatusBoxes = ({
isLoading = false,
@@ -14,7 +14,7 @@ const UptimeStatusBoxes = ({
certificateExpiry,
}) => {
const theme = useTheme();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
// Determine time since last failure
const timeOfLastFailure = monitorStats?.timeOfLastFailure;
@@ -1,50 +0,0 @@
import { useState } from "react";
import { useEffect } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
export const useChecksFetch = ({
monitorId,
monitorType,
dateRange,
page,
rowsPerPage,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
useEffect(() => {
if (!monitorType) {
return;
}
const fetchChecks = async () => {
try {
setIsLoading(true);
const res = await networkService.getChecksByMonitor({
monitorId: monitorId,
type: monitorType,
sortOrder: "desc",
limit: null,
dateRange: dateRange,
filter: null,
page: page,
rowsPerPage: rowsPerPage,
});
setChecks(res.data.data.checks);
setChecksCount(res.data.data.checksCount);
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchChecks();
}, [monitorId, monitorType, dateRange, page, rowsPerPage]);
return [checks, checksCount, isLoading, networkError];
};
export default useChecksFetch;
@@ -1,33 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { useNavigate } from "react-router-dom";
import { createToast } from "../../../../Utils/toastUtils";
export const useMonitorFetch = ({ monitorId, dateRange }) => {
const [networkError, setNetworkError] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitor, setMonitor] = useState(undefined);
const navigate = useNavigate();
useEffect(() => {
const fetchMonitors = async () => {
try {
const res = await networkService.getUptimeDetailsById({
monitorId: monitorId,
dateRange: dateRange,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
setNetworkError(true);
createToast({ body: error.message });
} finally {
setIsLoading(false);
}
};
fetchMonitors();
}, [dateRange, monitorId, navigate]);
return [monitor, isLoading, networkError];
};
export default useMonitorFetch;
+11 -8
View File
@@ -1,15 +1,14 @@
// Components
import Breadcrumbs from "../../../Components/Breadcrumbs";
import MonitorDetailsControlHeader from "../../../Components/MonitorDetailsControlHeader";
import MonitorStatusHeader from "../../../Components/MonitorStatusHeader";
import MonitorTimeFrameHeader from "../../../Components/MonitorTimeFrameHeader";
import ChartBoxes from "./Components/ChartBoxes";
import ResponseTimeChart from "./Components/Charts/ResponseTimeChart";
import ResponseTable from "./Components/ResponseTable";
import UptimeStatusBoxes from "./Components/UptimeStatusBoxes";
import GenericFallback from "../../../Components/GenericFallback";
// MUI Components
import { Stack, Typography } from "@mui/material";
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
// Utils
import { useState } from "react";
@@ -17,9 +16,9 @@ import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import { useTheme } from "@emotion/react";
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
import useFetchUptimeMonitorDetails from "../../../Hooks/useFetchUptimeMonitorDetails";
import { useFetchUptimeMonitorById } from "../../../Hooks/monitorHooks";
import useCertificateFetch from "./Hooks/useCertificateFetch";
import useChecksFetch from "./Hooks/useChecksFetch";
import { useFetchChecks } from "../../../Hooks/checkHooks";
import { useTranslation } from "react-i18next";
// Constants
@@ -50,7 +49,7 @@ const UptimeDetails = () => {
const { t } = useTranslation();
const [monitorData, monitorStats, monitorIsLoading, monitorNetworkError] =
useFetchUptimeMonitorDetails({
useFetchUptimeMonitorById({
monitorId,
dateRange,
trigger,
@@ -66,10 +65,14 @@ const UptimeDetails = () => {
});
const monitorType = monitor?.type;
const [checks, checksCount, checksAreLoading, checksNetworkError] = useChecksFetch({
const [checks, checksCount, checksAreLoading, checksNetworkError] = useFetchChecks({
monitorId,
monitorType,
type: monitorType,
sortOrder: "desc",
limit: null,
dateRange,
filter: null,
page,
rowsPerPage,
});
@@ -13,7 +13,7 @@ import TableSkeleton from "../../../../../Components/Table/skeleton";
// Utils
import { useTheme } from "@emotion/react";
import useUtils from "../../Hooks/useUtils";
import { useMonitorUtils } from "../../../../../Hooks/useMonitorUtils";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
@@ -58,7 +58,7 @@ const UptimeDataTable = ({
}) => {
// Utils
const navigate = useNavigate();
const { determineState } = useUtils();
const { determineState } = useMonitorUtils();
const theme = useTheme();
const { t } = useTranslation();
@@ -1,77 +0,0 @@
import { useEffect, useState } from "react";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
import { useTheme } from "@emotion/react";
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
export const useMonitorFetch = ({
teamId,
limit,
page,
rowsPerPage,
filter,
field,
order,
triggerUpdate,
}) => {
const [monitorsAreLoading, setMonitorsAreLoading] = useState(false);
const [monitors, setMonitors] = useState(undefined);
const [filteredMonitors, setFilteredMonitors] = useState(undefined);
const [monitorsSummary, setMonitorsSummary] = useState(undefined);
const [networkError, setNetworkError] = useState(false);
const theme = useTheme();
const { getMonitorWithPercentage } = useMonitorUtils();
useEffect(() => {
const fetchMonitors = async () => {
try {
setMonitorsAreLoading(true);
const res = await networkService.getMonitorsByTeamId({
teamId,
limit,
types: ["http", "ping", "docker", "port"],
page,
rowsPerPage,
filter,
field,
order,
});
const { monitors, filteredMonitors, summary } = res.data.data;
const mappedMonitors = filteredMonitors.map((monitor) =>
getMonitorWithPercentage(monitor, theme)
);
setMonitors(monitors);
setFilteredMonitors(mappedMonitors);
setMonitorsSummary(summary);
} catch (error) {
setNetworkError(true);
createToast({
body: error.message,
});
} finally {
setMonitorsAreLoading(false);
}
};
fetchMonitors();
}, [
teamId,
limit,
field,
filter,
order,
page,
rowsPerPage,
theme,
triggerUpdate,
getMonitorWithPercentage,
]);
return {
monitors,
filteredMonitors,
monitorsSummary,
monitorsAreLoading,
networkError,
};
};
export default useMonitorFetch;
@@ -1,110 +0,0 @@
import { useTheme } from "@mui/material";
const useUtils = () => {
const determineState = (monitor) => {
if (typeof monitor === "undefined") return "pending";
if (monitor.isActive === false) return "paused";
if (monitor?.status === undefined) return "pending";
return monitor?.status == true ? "up" : "down";
};
/* TODO Refactor: from here on shouldn't live in a custom hook, but on theme, or constants */
const theme = useTheme();
const statusColor = {
up: theme.palette.success.lowContrast,
down: theme.palette.error.lowContrast,
paused: theme.palette.warning.lowContrast,
pending: theme.palette.warning.lowContrast,
};
const statusMsg = {
up: "Your site is up.",
down: "Your site is down.",
paused: "Pending...",
};
const pagespeedStatusMsg = {
up: "Live (collecting data)",
down: "Inactive",
paused: "Paused",
};
/*
TODO
This is used on
1) Details > Gradient card */
/* These are rediections. We should do something that maps up to success, down to error, and get the theme by that
See Client\src\Components\Label\index.jsx
*/
const statusToTheme = {
up: "success",
down: "error",
paused: "warning",
pending: "secondary",
"cannot resolve": "tertiary",
};
const getStatusStyles = (status) => {
const themeColor = statusToTheme[status];
return {
backgroundColor: theme.palette[themeColor].lowContrast,
background: `linear-gradient(340deg, ${theme.palette[themeColor].main} -60%, ${theme.palette[themeColor].lowContrast} 35%)`,
borderColor: theme.palette[themeColor].lowContrast,
"& h2": {
color: theme.palette[themeColor].contrastText,
textTransform: "uppercase",
},
"& p": {
color: theme.palette[themeColor].contrastText,
},
};
};
const statusStyles = {
up: {
backgroundColor: theme.palette.success.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.success.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.success.contrastText,
// "& h2": { color: theme.palette.success.contrastText }, // CAIO_REVIEW
},
down: {
backgroundColor: theme.palette.error.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.error.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.error.contrastText,
"& h2": { color: theme.palette.error.contrastText }, // CAIO_REVIEW
"& .MuiTypography-root": { color: theme.palette.error.contrastText }, // CAIO_REVIEW
},
paused: {
backgroundColor: theme.palette.warning.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.warning.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.warning.contrastText,
"& h2": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
"& .MuiTypography-root": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
},
pending: {
backgroundColor: theme.palette.warning.lowContrast,
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} -60%, ${theme.palette.warning.lowContrast} 35%)`, // CAIO_REVIEW
borderColor: theme.palette.warning.contrastText,
"& h2": { color: theme.palette.warning.contrastText }, // CAIO_REVIEW
},
};
/* These are rediections. We should do something that maps up to success, down to error, and get the theme by that
*/
return {
determineState,
statusColor,
statusMsg,
pagespeedStatusMsg,
statusStyles,
statusToTheme,
getStatusStyles,
};
};
export default useUtils;
+5 -8
View File
@@ -28,9 +28,12 @@ import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import { setRowsPerPage } from "../../../Features/UI/uiSlice";
import PropTypes from "prop-types";
import useFetchMonitorsWithSummary from "../../../Hooks/useFetchMonitorsWithSummary";
import useFetchMonitorsWithChecks from "../../../Hooks/useFetchMonitorsWithChecks";
import {
useFetchMonitorsWithSummary,
useFetchMonitorsWithChecks,
} from "../../../Hooks/monitorHooks";
import { useTranslation } from "react-i18next";
const TYPES = ["http", "ping", "docker", "port"];
const CreateMonitorButton = ({ shouldRender }) => {
// Utils
@@ -61,7 +64,6 @@ CreateMonitorButton.propTypes = {
const UptimeMonitors = () => {
// Redux state
const { user } = useSelector((state) => state.auth);
const rowsPerPage = useSelector((state) => state.ui.monitors.rowsPerPage);
// Local state
@@ -78,7 +80,6 @@ const UptimeMonitors = () => {
// Utils
const theme = useTheme();
const navigate = useNavigate();
const isAdmin = useIsAdmin();
const dispatch = useDispatch();
const { t } = useTranslation();
@@ -104,11 +105,8 @@ const UptimeMonitors = () => {
setMonitorUpdateTrigger((prev) => !prev);
}, []);
const teamId = user.teamId;
const [monitors, monitorsSummary, monitorsWithSummaryIsLoading, networkError] =
useFetchMonitorsWithSummary({
teamId,
types: TYPES,
monitorUpdateTrigger,
});
@@ -138,7 +136,6 @@ const UptimeMonitors = () => {
monitorsWithChecksIsLoading,
monitorsWithChecksNetworkError,
] = useFetchMonitorsWithChecks({
teamId,
types: effectiveTypes,
limit: 25,
page: page,