Merge branch 'develop' into rog404/http-status-label

This commit is contained in:
Rogerio Bordignon authored and GitHub committed 2024-10-25 11:28:55 -03:00
commit fd88df5a59
64 files changed
+3704 -2083

No files matched your search

+16 -1
View File
@@ -36,7 +36,7 @@ import { useSelector } from "react-redux";
import { CssBaseline } from "@mui/material";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { getAppSettings } from "./Features/Settings/settingsSlice";
import { getAppSettings, updateAppSettings } from "./Features/Settings/settingsSlice";
import { logger } from "./Utils/Logger"; // Import the logger
import { networkService } from "./main";
function App() {
@@ -66,6 +66,21 @@ function App() {
};
}, []);
useEffect(() => {
const thing = async () => {
const action = await dispatch(
updateAppSettings({ authToken, settings: { apiBaseUrl: "test" } })
);
if (action.payload.success) {
console.log(action.payload.data);
} else {
console.log(action);
}
};
thing();
}, [dispatch, authToken]);
return (
<ThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
<CssBaseline />
@@ -0,0 +1,68 @@
import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
return (
<Modal
aria-labelledby={titleId}
aria-describedby={ariaDescribedBy}
open={open}
onClose={onClose}
onClick={(e)=>e.stopPropagation()}
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id={titleId}
component="h2"
fontSize={16}
color={theme.palette.text.primary}
fontWeight={600}
>
{title}
</Typography>
{description && (
<Typography
id={descriptionId}
color={theme.palette.text.tertiary}
>
{description}
</Typography>
)}
{children}
</Stack>
</Modal>
);
};
GenericDialog.propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
children: PropTypes.element.isRequired,
};
export { GenericDialog };
+38 -79
View File
@@ -1,103 +1,62 @@
import LoadingButton from "@mui/lab/LoadingButton";
import { Button, Modal, Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
import LoadingButton from "@mui/lab/LoadingButton";
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "./genericDialog";
const Dialog = ({
modelTitle,
modelDescription,
open,
onClose,
title,
confirmationBtnLbl,
confirmationBtnOnClick,
cancelBtnLbl,
cancelBtnOnClick,
theme,
isLoading,
description,
open,
theme,
onCancel,
confirmationButtonLabel,
onConfirm,
isLoading,
}) => {
return (
<Modal
aria-labelledby={modelTitle}
aria-describedby={modelDescription}
<GenericDialog
title={title}
description={description}
open={open}
onClose={onClose}
onClose={onCancel}
theme={theme}
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Typography
id={modelTitle}
component="h2"
fontSize={16}
color={theme.palette.text.primary}
fontWeight={600}
<Button
variant="text"
color="info"
onClick={onCancel}
>
{title}
</Typography>
{description && (
<Typography
id={modelDescription}
color={theme.palette.text.tertiary}
>
{description}
</Typography>
)}
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
Cancel
</Button>
<LoadingButton
variant="contained"
color="error"
loading={isLoading}
onClick={onConfirm}
>
<Button
variant="text"
color="info"
onClick={cancelBtnOnClick}
>
{cancelBtnLbl}
</Button>
<LoadingButton
variant="contained"
color="error"
loading={isLoading}
onClick={confirmationBtnOnClick}
>
{confirmationBtnLbl}
</LoadingButton>
</Stack>
{confirmationButtonLabel}
</LoadingButton>
</Stack>
</Modal>
</GenericDialog>
);
};
Dialog.propTypes = {
modelTitle: PropTypes.string.isRequired,
modelDescription: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
confirmationBtnLbl: PropTypes.string.isRequired,
confirmationBtnOnClick: PropTypes.func.isRequired,
cancelBtnLbl: PropTypes.string.isRequired,
cancelBtnOnClick: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
confirmationButtonLabel: PropTypes.string.isRequired,
onConfirm: PropTypes.func.isRequired,
isLoading: PropTypes.bool.isRequired,
description: PropTypes.string,
};
export default Dialog;
@@ -38,6 +38,7 @@ const SearchAdornment = () => {
);
};
//TODO keep search state inside of component
const Search = ({
id,
options,
+2
View File
@@ -181,6 +181,8 @@ function Sidebar() {
direction="row"
alignItems="center"
gap={theme.spacing(4)}
onClick={() => window.open("https://github.com/bluewave-labs/bluewave-uptime", "_blank", "noreferrer")}
sx={{ cursor: "pointer" }}
>
<Stack
justifyContent="center"
@@ -1,7 +1,7 @@
import { useTheme } from "@emotion/react";
import { useRef, useState } from "react";
import TabPanel from "@mui/lab/TabPanel";
import { Box, Button, Divider, Modal, Stack, Typography } from "@mui/material";
import { Box, Button, Divider, Stack, Typography } from "@mui/material";
import Avatar from "../../Avatar";
import Field from "../../Inputs/Field";
import ImageField from "../../Inputs/Image";
@@ -15,6 +15,8 @@ import { clearUptimeMonitorState } from "../../../Features/UptimeMonitors/uptime
import { createToast } from "../../../Utils/toastUtils";
import { logger } from "../../../Utils/Logger";
import LoadingButton from "@mui/lab/LoadingButton";
import { GenericDialog } from "../../Dialog/genericDialog";
import Dialog from "../../Dialog";
/**
* ProfilePanel component displays a form for editing user profile information
@@ -369,158 +371,82 @@ const ProfilePanel = () => {
</Box>
)}
{/* TODO - Update ModalPopup Component with @mui for reusability */}
<Modal
aria-labelledby="modal-delete-account"
aria-describedby="delete-account-confirmation"
<Dialog
open={isModalOpen("delete")}
onClose={() => setIsOpen("")}
disablePortal
>
<Stack
gap={theme.spacing(5)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 500,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id="modal-delete-account"
component="h1"
>
Really delete this account?
</Typography>
<Typography
id="delete-account-confirmation"
component="p"
>
If you delete your account, you will no longer be able to sign in, and all of
your data will be deleted. Deleting your account is permanent and
non-recoverable action.
</Typography>
<Stack
direction="row"
gap={theme.spacing(5)}
mt={theme.spacing(5)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={() => setIsOpen("")}
>
Cancel
</Button>
<LoadingButton
variant="contained"
color="error"
onClick={handleDeleteAccount}
loading={isLoading}
>
Delete account
</LoadingButton>
</Stack>
</Stack>
</Modal>
<Modal
aria-labelledby="modal-update-picture"
aria-describedby="update-profile-picture"
theme={theme}
title={"Really delete this account?"}
description={
"If you delete your account, you will no longer be able to sign in, and all of your data will be deleted. Deleting your account is permanent and non-recoverable action."
}
onCancel={() => setIsOpen("")}
confirmationButtonLabel={"Delete account"}
onConfirm={handleDeleteAccount}
isLoading={isLoading}
/>
<GenericDialog
title={"Upload Image"}
open={isModalOpen("picture")}
onClose={closePictureModal}
theme={theme}
>
<Stack
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 475,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shape.boxShadow,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id="modal-update-picture"
component="h1"
color={theme.palette.text.secondary}
>
Upload Image
</Typography>
<ImageField
id="update-profile-picture"
src={
file?.delete
? ""
: file?.src
? file.src
: localData?.file
? localData.file
: user?.avatarImage
? `data:image/png;base64,${user.avatarImage}`
: ""
}
loading={progress.isLoading && progress.value !== 100}
onChange={handlePicture}
<ImageField
id="update-profile-picture"
src={
file?.delete
? ""
: file?.src
? file.src
: localData?.file
? localData.file
: user?.avatarImage
? `data:image/png;base64,${user.avatarImage}`
: ""
}
loading={progress.isLoading && progress.value !== 100}
onChange={handlePicture}
/>
{progress.isLoading || progress.value !== 0 || errors["picture"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={file?.name}
size={file?.size}
progress={progress.value}
onClick={removePicture}
error={errors["picture"]}
/>
{progress.isLoading || progress.value !== 0 || errors["picture"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={file?.name}
size={file?.size}
progress={progress.value}
onClick={removePicture}
error={errors["picture"]}
/>
) : (
""
)}
<Stack
direction="row"
mt={theme.spacing(10)}
gap={theme.spacing(5)}
justifyContent="flex-end"
) : (
""
)}
<Stack
direction="row"
mt={theme.spacing(10)}
gap={theme.spacing(5)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={removePicture}
>
<Button
variant="text"
color="info"
onClick={removePicture}
>
Remove
</Button>
<Button
variant="contained"
color="primary"
onClick={handleUpdatePicture}
disabled={
(Object.keys(errors).length !== 0 && errors?.picture) ||
progress.value !== 100
? true
: false
}
>
Update
</Button>
</Stack>
Remove
</Button>
<Button
variant="contained"
color="primary"
onClick={handleUpdatePicture}
disabled={
(Object.keys(errors).length !== 0 && errors?.picture) ||
progress.value !== 100
? true
: false
}
>
Update
</Button>
</Stack>
</Modal>
</GenericDialog>
</TabPanel>
);
};
@@ -1,6 +1,6 @@
import { useTheme } from "@emotion/react";
import TabPanel from "@mui/lab/TabPanel";
import { Box, Button, ButtonGroup, Modal, Stack, Typography } from "@mui/material";
import { Button, ButtonGroup, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import Field from "../../Inputs/Field";
import { credentials } from "../../../Validation/validation";
@@ -10,6 +10,7 @@ import { useSelector } from "react-redux";
import BasicTable from "../../BasicTable";
import Select from "../../Inputs/Select";
import LoadingButton from "@mui/lab/LoadingButton";
import { GenericDialog } from "../../Dialog/genericDialog";
/**
* TeamPanel component manages the organization and team members,
@@ -31,10 +32,10 @@ const TeamPanel = () => {
const { authToken, user } = useSelector((state) => state.auth);
//TODO
const [orgStates, setOrgStates] = useState({
name: "Bluewave Labs",
isEdit: false,
});
// const [orgStates, setOrgStates] = useState({
// name: "Bluewave Labs",
// isEdit: false,
// });
const [toInvite, setToInvite] = useState({
email: "",
role: ["0"],
@@ -134,10 +135,10 @@ const TeamPanel = () => {
}, [errors, toInvite.email]);
// RENAME ORGANIZATION
const toggleEdit = () => {
setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit }));
};
const handleRename = () => {};
// const toggleEdit = () => {
// setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit }));
// };
// const handleRename = () => {};
// INVITE MEMBER
const [isOpen, setIsOpen] = useState(false);
@@ -327,100 +328,65 @@ const TeamPanel = () => {
table={"team"}
/>
</Stack>
<Modal
aria-labelledby="modal-invite-member"
aria-describedby="invite-member-to-team"
<GenericDialog
title={"Invite new team member"}
description={
"When you add a new team member, they will get access to all monitors."
}
open={isOpen}
onClose={closeInviteModal}
theme={theme}
>
<Field
type="email"
id="input-team-member"
placeholder="Email"
value={toInvite.email}
onChange={handleChange}
error={errors.email}
/>
<Select
id="team-member-role"
placeholder="Select role"
isHidden={true}
value={toInvite.role[0]}
onChange={(event) =>
setToInvite((prev) => ({
...prev,
role: [event.target.value],
}))
}
items={[
{ _id: "admin", name: "Admin" },
{ _id: "user", name: "User" },
]}
/>
<Stack
gap={theme.spacing(5)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 450,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shape.boxShadow,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(8)}
justifyContent="flex-end"
>
<Box>
<Typography
id="modal-invite-member"
component="h1"
fontWeight={600}
fontColor={theme.palette.text.secondary}
>
Invite new team member
</Typography>
<Typography
id="invite-member-to-team"
component="p"
fontSize={13}
color={theme.palette.text.accent}
sx={{ mt: theme.spacing(1), mb: theme.spacing(4) }}
>
When you add a new team member, they will get access to all monitors.
</Typography>
</Box>
<Field
type="email"
id="input-team-member"
placeholder="Email"
value={toInvite.email}
onChange={handleChange}
error={errors.email}
/>
<Select
id="team-member-role"
placeholder="Select role"
isHidden={true}
value={toInvite.role[0]}
onChange={(event) =>
setToInvite((prev) => ({
...prev,
role: [event.target.value],
}))
}
items={[
{ _id: "admin", name: "Admin" },
{ _id: "user", name: "User" },
]}
/>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(8)}
justifyContent="flex-end"
<LoadingButton
loading={isSendingInvite}
variant="text"
color="info"
onClick={closeInviteModal}
>
<LoadingButton
loading={isSendingInvite}
variant="text"
color="info"
onClick={closeInviteModal}
>
Cancel
</LoadingButton>
<LoadingButton
variant="contained"
color="primary"
onClick={handleInviteMember}
loading={isSendingInvite}
disabled={isDisabled}
>
Send invite
</LoadingButton>
</Stack>
Cancel
</LoadingButton>
<LoadingButton
variant="contained"
color="primary"
onClick={handleInviteMember}
loading={isSendingInvite}
disabled={isDisabled}
>
Send invite
</LoadingButton>
</Stack>
</Modal>
</GenericDialog>
</TabPanel>
);
};
@@ -49,6 +49,7 @@ export const updateAppSettings = createAsyncThunk(
systemEmailAddress: settings.systemEmailAddress,
systemEmailPassword: settings.systemEmailPassword,
};
console.log(parsedSettings);
const res = await networkService.updateAppSettings({
settings: parsedSettings,
authToken,
@@ -0,0 +1,32 @@
import { useTheme } from "@emotion/react";
import PlaceholderLight from "../../../../assets/Images/data_placeholder.svg?react";
import PlaceholderDark from "../../../../assets/Images/data_placeholder_dark.svg?react";
import { Box, Typography } from "@mui/material";
import PropTypes from "prop-types";
const Empty = ({ styles, mode }) => {
const theme = useTheme();
return (
<Box sx={{ ...styles }}>
<Box
textAlign="center"
pb={theme.spacing(20)}
>
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
</Box>
<Typography
textAlign="center"
color={theme.palette.text.secondary}
>
No incidents recorded yet.
</Typography>
</Box>
);
};
Empty.propTypes = {
styles: PropTypes.object,
mode: PropTypes.string,
};
export { Empty };
@@ -0,0 +1,21 @@
import { Skeleton /* , Stack */ } from "@mui/material";
const IncidentSkeleton = () => {
return (
<>
<Skeleton
animation={"wave"}
variant="rounded"
width="100%"
height={300}
/>
<Skeleton
animation={"wave"}
variant="rounded"
width="100%"
height={100}
/>
</>
);
};
export { IncidentSkeleton };
@@ -25,6 +25,8 @@ import { formatDateWithTz } from "../../../Utils/timeUtils";
import PlaceholderLight from "../../../assets/Images/data_placeholder.svg?react";
import PlaceholderDark from "../../../assets/Images/data_placeholder_dark.svg?react";
import { HttpStatusLabel } from "../../../Components/HttpStatusLabel";
import { Empty } from "./Empty/Empty";
import { IncidentSkeleton } from "./Skeleton/Skeleton";
const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
const uiTimezone = useSelector((state) => state.ui.timezone);
@@ -38,6 +40,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
page: 0,
rowsPerPage: 14,
});
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
setPaginationController((prevPaginationController) => ({
@@ -52,6 +55,7 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
return;
}
try {
setIsLoading(true);
let res;
if (selectedMonitor === "0") {
res = await networkService.getChecksByTeam({
@@ -80,6 +84,8 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
setChecksCount(res.data.data.checksCount);
} catch (error) {
logger.error(error);
} finally {
setIsLoading(false);
}
};
fetchPage();
@@ -130,24 +136,20 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => {
p: theme.spacing(30),
};
const hasChecks = checks?.length === 0;
const noIncidentsRecordedYet = hasChecks && selectedMonitor === "0";
const noIncidentsForThatMonitor = hasChecks && selectedMonitor !== "0";
return (
<>
{checks?.length === 0 && selectedMonitor === "0" ? (
<Box sx={{ ...sharedStyles }}>
<Box
textAlign="center"
pb={theme.spacing(20)}
>
{mode === "light" ? <PlaceholderLight /> : <PlaceholderDark />}
</Box>
<Typography
textAlign="center"
color={theme.palette.text.secondary}
>
No incidents recorded yet.
</Typography>
</Box>
) : checks?.length === 0 ? (
{isLoading ? (
<IncidentSkeleton />
) : noIncidentsRecordedYet ? (
<Empty
mode={mode}
styles={sharedStyles}
/>
) : noIncidentsForThatMonitor ? (
<Box sx={{ ...sharedStyles }}>
<Box
textAlign="center"
+35 -29
View File
@@ -17,56 +17,62 @@ const Incidents = () => {
const [monitors, setMonitors] = useState({});
const [selectedMonitor, setSelectedMonitor] = useState("0");
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
// TODO do something with these filters
const [filter, setFilter] = useState("all");
useEffect(() => {
const fetchMonitors = async () => {
setLoading(true);
const res = await networkService.getMonitorsByTeamId({
authToken: authState.authToken,
teamId: authState.user.teamId,
limit: -1,
types: null,
status: null,
checkOrder: null,
normalize: null,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
// Reduce to a lookup object for 0(1) lookup
if (res?.data?.data?.monitors?.length > 0) {
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
acc[monitor._id] = monitor;
return acc;
}, {});
setMonitors(monitorLookup);
monitorId !== undefined && setSelectedMonitor(monitorId);
try {
setIsLoading(true);
const res = await networkService.getMonitorsByTeamId({
authToken: authState.authToken,
teamId: authState.user.teamId,
limit: -1,
types: null,
status: null,
checkOrder: null,
normalize: null,
page: null,
rowsPerPage: null,
filter: null,
field: null,
order: null,
});
// Reduce to a lookup object for 0(1) lookup
if (res?.data?.data?.monitors?.length > 0) {
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
acc[monitor._id] = monitor;
return acc;
}, {});
setMonitors(monitorLookup);
monitorId !== undefined && setSelectedMonitor(monitorId);
}
} catch (error) {
console.info(error);
} finally {
setIsLoading(false);
}
setLoading(false);
};
fetchMonitors();
}, [authState]);
useEffect(() => {}, []);
useEffect(() => {}, [monitors]);
const handleSelect = (event) => {
setSelectedMonitor(event.target.value);
};
const isActuallyLoading = isLoading && Object.keys(monitors)?.length === 0;
return (
<Stack
className="incidents table-container"
className="incidents"
pt={theme.spacing(6)}
gap={theme.spacing(12)}
>
{loading ? (
{isActuallyLoading ? (
<SkeletonLayout />
) : (
<>
@@ -2,23 +2,16 @@ import { useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import {
Button,
IconButton,
Menu,
MenuItem,
Modal,
Stack,
Typography,
} from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
import { IconButton, Menu, MenuItem } from "@mui/material";
import { logger } from "../../../../Utils/Logger";
import Settings from "../../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import { networkService } from "../../../../main";
import { createToast } from "../../../../Utils/toastUtils";
const ActionsMenu = ({ isAdmin, maintenanceWindow, updateCallback }) => {
import Dialog from "../../../../Components/Dialog";
const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
maintenanceWindow;
const { authToken } = useSelector((state) => state.auth);
const [anchorEl, setAnchorEl] = useState(null);
@@ -155,71 +148,21 @@ const ActionsMenu = ({ isAdmin, maintenanceWindow, updateCallback }) => {
Remove
</MenuItem>
</Menu>
<Modal
aria-labelledby="modal-delete-monitor"
aria-describedby="delete-monitor-confirmation"
<Dialog
open={isOpen}
onClose={(e) => {
theme={theme}
title={"Do you really want to remove this maintenance window?"}
onCancel={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id="modal-delete-maintenance"
component="h2"
variant="h2"
>
Do you really want to remove this maintenance window?
</Typography>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
>
Cancel
</Button>
<LoadingButton
loading={isLoading}
variant="contained"
color="error"
onClick={(e) => {
e.stopPropagation(e);
handleRemove(e);
}}
>
Delete
</LoadingButton>
</Stack>
</Stack>
</Modal>
confirmationButtonLabel={"Delete"}
onConfirm={(e) => {
e.stopPropagation(e);
handleRemove(e);
}}
isLoading={isLoading}
/>
</>
);
};
+7 -10
View File
@@ -453,20 +453,17 @@ const Configure = () => {
</Stack>
</>
)}
<Dialog
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
title="Do you really want to delete this monitor?"
confirmationBtnLbl="Delete"
confirmationBtnOnClick={handleRemove}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(false)}
theme={theme}
isLoading={isLoading}
title="Do you really want to delete this monitor?"
description="Once deleted, this monitor cannot be retrieved."
></Dialog>
onCancel={() => setIsOpen(false)}
confirmationButtonLabel="Delete"
onConfirm={handleRemove}
isLoading={isLoading}
/>
</Stack>
);
};
@@ -0,0 +1,79 @@
import { useTheme } from "@emotion/react";
import { Box, Stack, Typography } from "@mui/material";
import Search from "../../../../Components/Inputs/Search";
import MemoizedMonitorTable from "../MonitorTable";
import { useState } from "react";
import useDebounce from "../../../../Utils/debounce";
import PropTypes from "prop-types";
const CurrentMonitoring = ({ totalMonitors, monitors, isAdmin }) => {
const theme = useTheme();
const [search, setSearch] = useState("");
const [isSearching, setIsSearching] = useState(false);
const debouncedFilter = useDebounce(search, 500);
const handleSearch = (value) => {
setIsSearching(true);
setSearch(value);
};
return (
<Box
flex={1}
px={theme.spacing(10)}
py={theme.spacing(8)}
border={1}
borderColor={theme.palette.border.light}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.background.main}
>
<Stack
direction="row"
alignItems="center"
mb={theme.spacing(8)}
>
<Typography
component="h2"
variant="h2"
fontWeight={500}
letterSpacing={-0.2}
>
Actively monitoring
</Typography>
<Box
className="current-monitors-counter"
color={theme.palette.text.primary}
border={1}
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
{totalMonitors}
</Box>
<Box
width="25%"
minWidth={150}
ml="auto"
>
<Search
options={monitors}
filteredBy="name"
inputValue={search}
handleInputChange={handleSearch}
/>
</Box>
</Stack>
<MemoizedMonitorTable
isAdmin={isAdmin}
filter={debouncedFilter}
setIsSearching={setIsSearching}
isSearching={isSearching}
/>
</Box>
);
};
CurrentMonitoring.propTypes = {
totalMonitors: PropTypes.number,
monitors: PropTypes.array,
isAdmin: PropTypes.bool,
};
export { CurrentMonitoring };
@@ -0,0 +1,31 @@
import { Skeleton, TableCell, TableRow } from "@mui/material";
const ROWS_NUMBER = 7;
const ROWS_ARRAY = Array.from({ length: ROWS_NUMBER }, (_, i) => i);
const TableBodySkeleton = () => {
return (
<>
{ROWS_ARRAY.map((row) => (
<TableRow key={row}>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
<TableCell>
<Skeleton />
</TableCell>
</TableRow>
))}
</>
);
};
export { TableBodySkeleton };
@@ -12,6 +12,7 @@ import {
Stack,
Typography,
Button,
CircularProgress,
} from "@mui/material";
import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded";
import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded";
@@ -34,6 +35,7 @@ import RightArrow from "../../../../assets/icons/right-arrow.svg?react";
import SelectorVertical from "../../../../assets/icons/selector-vertical.svg?react";
import ActionsMenu from "../actionsMenu";
import useUtils from "../../utils";
import { TableBodySkeleton } from "./Skeleton";
/**
* Component for pagination actions (first, previous, next, last).
@@ -107,17 +109,17 @@ TablePaginationActions.propTypes = {
onPageChange: PropTypes.func.isRequired,
};
const MonitorTable = ({ isAdmin, filter, setLoading }) => {
const MonitorTable = ({ isAdmin, filter, setIsSearching, isSearching }) => {
const theme = useTheme();
const navigate = useNavigate();
const dispatch = useDispatch();
const { determineState } = useUtils();
const { rowsPerPage } = useSelector((state) => state.ui.monitors);
const authState = useSelector((state) => state.auth);
const [page, setPage] = useState(0);
const [monitors, setMonitors] = useState([]);
const [monitorCount, setMonitorCount] = useState(0);
const authState = useSelector((state) => state.auth);
const [updateTrigger, setUpdateTrigger] = useState(false);
const [sort, setSort] = useState({});
const prevFilter = useRef(filter);
@@ -160,15 +162,25 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
});
setMonitors(res?.data?.data?.monitors ?? []);
setMonitorCount(res?.data?.data?.monitorCount ?? 0);
setLoading(false);
} catch (error) {
logger.error(error);
} finally {
setIsSearching(false);
}
}, [authState, page, rowsPerPage, filter, sort, setLoading]);
}, [authState, page, rowsPerPage, filter, sort, setIsSearching]);
useEffect(() => {
fetchPage();
}, [updateTrigger, authState, page, rowsPerPage, filter, sort, setLoading, fetchPage]);
}, [
updateTrigger,
authState,
page,
rowsPerPage,
filter,
sort,
setIsSearching,
fetchPage,
]);
// Listen for changes in filter, if new value reset the page
useEffect(() => {
@@ -220,7 +232,37 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
};
return (
<>
<Box position="relative">
{isSearching && (
<>
<Box
width="100%"
height="100%"
position="absolute"
sx={{
backgroundColor: theme.palette.background.main,
opacity: 0.8,
zIndex: 100,
}}
/>
<Box
height="100%"
position="absolute"
top="20%"
left="50%"
sx={{
transform: "translateX(-50%)",
zIndex: 101,
}}
>
<CircularProgress
sx={{
color: theme.palette.other.icon,
}}
/>
</Box>
</>
)}
<TableContainer component={Paper}>
<Table>
<TableHead>
@@ -271,77 +313,82 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
</TableRow>
</TableHead>
<TableBody>
{monitors.map((monitor) => {
let uptimePercentage = "";
let percentageColor = theme.palette.percentage.uptimeExcellent;
{/* TODO add empty state. Check if is searching, and empty => skeleton. Is empty, not searching => skeleton */}
{monitors.length > 0 ? (
monitors.map((monitor) => {
let uptimePercentage = "";
let percentageColor = theme.palette.percentage.uptimeExcellent;
// Determine uptime percentage and color based on the monitor's uptimePercentage value
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
// Determine uptime percentage and color based on the monitor's uptimePercentage value
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.percentage.uptimePoor
: monitor.uptimePercentage < 0.5
? theme.palette.percentage.uptimeFair
: monitor.uptimePercentage < 0.75
? theme.palette.percentage.uptimeGood
: theme.palette.percentage.uptimeExcellent;
}
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.percentage.uptimePoor
: monitor.uptimePercentage < 0.5
? theme.palette.percentage.uptimeFair
: monitor.uptimePercentage < 0.75
? theme.palette.percentage.uptimeGood
: theme.palette.percentage.uptimeExcellent;
}
const params = {
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
status: determineState(monitor),
};
const params = {
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor,
status: determineState(monitor),
};
return (
<TableRow
key={monitor._id}
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.background.accent,
},
}}
onClick={() => {
navigate(`/monitors/${monitor._id}`);
}}
>
<TableCell>
<Host
key={monitor._id}
params={params}
/>
</TableCell>
<TableCell>
<StatusLabel
status={params.status}
text={params.status}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>
<BarChart checks={monitor.checks.slice().reverse()} />
</TableCell>
<TableCell>
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
</TableCell>
<TableCell>
<ActionsMenu
monitor={monitor}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
</TableCell>
</TableRow>
);
})}
return (
<TableRow
key={monitor._id}
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: theme.palette.background.accent,
},
}}
onClick={() => {
navigate(`/monitors/${monitor._id}`);
}}
>
<TableCell>
<Host
key={monitor._id}
params={params}
/>
</TableCell>
<TableCell>
<StatusLabel
status={params.status}
text={params.status}
customStyles={{ textTransform: "capitalize" }}
/>
</TableCell>
<TableCell>
<BarChart checks={monitor.checks.slice().reverse()} />
</TableCell>
<TableCell>
<span style={{ textTransform: "uppercase" }}>{monitor.type}</span>
</TableCell>
<TableCell>
<ActionsMenu
monitor={monitor}
isAdmin={isAdmin}
updateCallback={handleActionMenuDelete}
/>
</TableCell>
</TableRow>
);
})
) : (
<TableBodySkeleton />
)}
</TableBody>
</Table>
</TableContainer>
@@ -415,14 +462,15 @@ const MonitorTable = ({ isAdmin, filter, setLoading }) => {
}}
/>
</Stack>
</>
</Box>
);
};
MonitorTable.propTypes = {
isAdmin: PropTypes.bool,
filter: PropTypes.string,
setLoading: PropTypes.func,
setIsSearching: PropTypes.func,
isSearching: PropTypes.bool,
};
const MemoizedMonitorTable = memo(MonitorTable);
+13 -13
View File
@@ -188,25 +188,25 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
)}
</Menu>
<Dialog
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
theme={theme}
title="Do you really want to delete this monitor?"
confirmationBtnLbl="Delete"
confirmationBtnOnClick={(e) => {
e.stopPropagation();
handleRemove(e);
}}
cancelBtnLbl="Cancel"
cancelBtnOnClick={(e) => {
description="Once deleted, this monitor cannot be retrieved."
/* Do we need stop propagation? */
onCancel={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
theme={theme}
confirmationButtonLabel="Delete"
/* Do we need stop propagation? */
onConfirm={(e) => {
e.stopPropagation();
handleRemove(e);
}}
isLoading={isLoading}
description="Once deleted, this monitor cannot be retrieved."
></Dialog>
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
/>
</>
);
};
+44 -126
View File
@@ -1,79 +1,74 @@
import "./index.css";
import { useEffect, useState } from "react";
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getUptimeMonitorsByTeamId } from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
import { Box, Button, CircularProgress, Stack, Typography } from "@mui/material";
import { Box, Button, Stack } from "@mui/material";
import PropTypes from "prop-types";
import SkeletonLayout from "./skeleton";
import Fallback from "./fallback";
import StatusBox from "./StatusBox";
import Breadcrumbs from "../../../Components/Breadcrumbs";
import Greeting from "../../../Utils/greeting";
import MonitorTable from "./MonitorTable";
import Search from "../../../Components/Inputs/Search";
import useDebounce from "../../../Utils/debounce";
import { CurrentMonitoring } from "./CurrentMonitoring";
const Monitors = ({ isAdmin }) => {
const theme = useTheme();
const navigate = useNavigate();
const monitorState = useSelector((state) => state.uptimeMonitors);
const authState = useSelector((state) => state.auth);
const [search, setSearch] = useState("");
const [isSearching, setIsSearching] = useState(false);
const dispatch = useDispatch({});
const debouncedFilter = useDebounce(search, 500);
const handleSearch = (value) => {
setIsSearching(true);
setSearch(value);
};
useEffect(() => {
dispatch(getUptimeMonitorsByTeamId(authState.authToken));
}, [authState.authToken, dispatch]);
let loading =
monitorState?.isLoading && monitorState?.monitorsSummary?.monitors?.length === 0;
//TODO bring fetching to this component, like on pageSpeed
const loading = monitorState?.isLoading;
const totalMonitors = monitorState?.monitorsSummary?.monitorCounts?.total;
const hasMonitors = totalMonitors > 0;
const noMonitors = !hasMonitors;
const canAddMonitor = isAdmin && hasMonitors;
return (
<Stack
className="monitors table-container"
className="monitors"
gap={theme.spacing(8)}
>
<Box>
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.spacing(5)}
gap={theme.spacing(6)}
>
<Greeting type="uptime" />
{canAddMonitor && (
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ fontWeight: 500 }}
>
Create monitor
</Button>
)}
</Stack>
</Box>
{noMonitors && <Fallback isAdmin={isAdmin} />}
{loading ? (
<SkeletonLayout />
) : (
<>
<Box>
<Breadcrumbs list={[{ name: `monitors`, path: "/monitors" }]} />
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
mt={theme.spacing(5)}
>
<Greeting type="uptime" />
{isAdmin && monitorState?.monitorsSummary?.monitors?.length !== 0 && (
<Button
variant="contained"
color="primary"
onClick={() => {
navigate("/monitors/create");
}}
sx={{ fontWeight: 500 }}
>
Create monitor
</Button>
)}
</Stack>
</Box>
{isAdmin && monitorState?.monitorsSummary?.monitors?.length === 0 && (
<Fallback isAdmin={isAdmin} />
)}
{monitorState?.monitorsSummary?.monitors?.length !== 0 && (
{hasMonitors && (
<>
<Stack
gap={theme.spacing(8)}
@@ -93,88 +88,11 @@ const Monitors = ({ isAdmin }) => {
value={monitorState?.monitorsSummary?.monitorCounts?.paused ?? 0}
/>
</Stack>
<Box
flex={1}
px={theme.spacing(10)}
py={theme.spacing(8)}
border={1}
borderColor={theme.palette.border.light}
borderRadius={theme.shape.borderRadius}
backgroundColor={theme.palette.background.main}
>
<Stack
direction="row"
alignItems="center"
mb={theme.spacing(8)}
>
<Typography
component="h2"
variant="h2"
fontWeight={500}
letterSpacing={-0.2}
>
Actively monitoring
</Typography>
<Box
className="current-monitors-counter"
color={theme.palette.text.primary}
border={1}
borderColor={theme.palette.border.light}
backgroundColor={theme.palette.background.accent}
>
{monitorState?.monitorsSummary?.monitorCounts?.total || 0}
</Box>
<Box
width="25%"
minWidth={150}
ml="auto"
>
<Search
options={monitorState?.monitorsSummary?.monitors ?? []}
filteredBy="name"
inputValue={search}
handleInputChange={handleSearch}
/>
</Box>
</Stack>
<Box position="relative">
{isSearching && (
<>
<Box
width="100%"
height="100%"
position="absolute"
sx={{
backgroundColor: theme.palette.background.main,
opacity: 0.8,
zIndex: 100,
}}
/>
<Box
height="100%"
position="absolute"
top="20%"
left="50%"
sx={{
transform: "translateX(-50%)",
zIndex: 101,
}}
>
<CircularProgress
sx={{
color: theme.palette.other.icon,
}}
/>
</Box>
</>
)}
<MonitorTable
isAdmin={isAdmin}
filter={debouncedFilter}
setLoading={setIsSearching}
/>
</Box>
</Box>
<CurrentMonitoring
isAdmin={isAdmin}
monitors={monitorState.monitorsSummary.monitors}
totalMonitors={totalMonitors}
/>
</>
)}
</>
+14 -63
View File
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { Box, Button, Modal, Stack, Tooltip, Typography } from "@mui/material";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router";
import {
@@ -25,6 +25,7 @@ import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineR
import SkeletonLayout from "./skeleton";
import useUtils from "../../Monitors/utils";
import "./index.css";
import Dialog from "../../../Components/Dialog";
const PageSpeedConfigure = () => {
const theme = useTheme();
@@ -37,6 +38,7 @@ const PageSpeedConfigure = () => {
const [monitor, setMonitor] = useState({});
const [errors, setErrors] = useState({});
const { statusColor, pagespeedStatusMsg, determineState } = useUtils();
const [buttonLoading, setButtonLoading] = useState(false);
const idMap = {
"monitor-url": "url",
"monitor-name": "name",
@@ -156,12 +158,14 @@ const PageSpeedConfigure = () => {
const [isOpen, setIsOpen] = useState(false);
const handleRemove = async (event) => {
event.preventDefault();
setButtonLoading(true);
const action = await dispatch(deletePageSpeed({ authToken, monitor }));
if (action.meta.requestStatus === "fulfilled") {
navigate("/pagespeed");
} else {
createToast({ body: "Failed to delete monitor." });
}
setButtonLoading(false);
};
return (
@@ -428,69 +432,16 @@ const PageSpeedConfigure = () => {
</Stack>
</>
)}
<Modal
aria-labelledby="modal-delete-pagespeed-monitor"
aria-describedby="delete-pagespeed-monitor-confirmation"
<Dialog
open={isOpen}
onClose={() => setIsOpen(false)}
disablePortal
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id="modal-delete-pagespeed-monitor"
component="h2"
variant="h2"
fontWeight={500}
>
Do you really want to delete this monitor?
</Typography>
<Typography
id="delete-pagespeed-monitor-confirmation"
variant="body1"
>
Once deleted, this monitor cannot be retrieved.
</Typography>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={() => setIsOpen(false)}
>
Cancel
</Button>
<Button
variant="contained"
color="error"
onClick={handleRemove}
>
Delete
</Button>
</Stack>
</Stack>
</Modal>
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={buttonLoading}
/>
</Stack>
);
};
+1 -1
View File
@@ -19,7 +19,7 @@ const PageSpeed = ({ isAdmin }) => {
const navigate = useNavigate();
const { user, authToken } = useSelector((state) => state.auth);
const [isLoading, setIsLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [monitors, setMonitors] = useState([]);
useEffect(() => {
dispatch(getPageSpeedByTeamId(authToken));
+11 -18
View File
@@ -263,18 +263,15 @@ const Settings = ({ isAdmin }) => {
</Box>
</Stack>
<Dialog
modelTitle="model-clear-stats"
modelDescription="clear-stats-confirmation"
open={isOpen.deleteStats}
onClose={() => setIsOpen(deleteStatsMonitorsInitState)}
title="Do you want to clear all stats?"
confirmationBtnLbl="Yes, clear all stats"
confirmationBtnOnClick={handleClearStats}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)}
theme={theme}
title="Do you want to clear all stats?"
description="Once deleted, this monitor cannot be retrieved."
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
confirmationButtonLabel="Yes, clear all stats"
onConfirm={handleClearStats}
isLoading={isLoading || authIsLoading || checksIsLoading}
></Dialog>
/>
</ConfigBox>
)}
{isAdmin && (
@@ -314,18 +311,14 @@ const Settings = ({ isAdmin }) => {
</Box>
</Stack>
<Dialog
modelTitle="model-delete-all-monitors"
modelDescription="delete-all-monitors-confirmation"
open={isOpen.deleteMonitors}
onClose={() => setIsOpen(deleteStatsMonitorsInitState)}
title="Do you want to remove all monitors?"
confirmationBtnLbl="Yes, clear all monitors"
confirmationBtnOnClick={handleDeleteAllMonitors}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)}
theme={theme}
title="Do you want to remove all monitors?"
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
confirmationButtonLabel="Yes, clear all monitors"
onConfirm={handleDeleteAllMonitors}
isLoading={isLoading || authIsLoading || checksIsLoading}
></Dialog>
/>
</ConfigBox>
)}
{isAdmin && (
+41 -40
View File
@@ -13,6 +13,7 @@ class NetworkService {
this.setBaseUrl(baseURL);
this.unsubscribe = store.subscribe(() => {
const state = store.getState();
console.log(state.settings.apiBaseUrl);
if (BASE_URL !== undefined) {
baseURL = BASE_URL;
} else if (state?.settings?.apiBaseUrl ?? null) {
@@ -87,48 +88,48 @@ class NetworkService {
},
});
}
/**
*
* ************************************
* Check the endpoint resolution
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {Object} config.monitorURL - The monitor url to be sent in the request body.
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async checkEndpointResolution(config) {
const { authToken, monitorURL } = config;
const params = new URLSearchParams();
if (monitorURL) params.append("monitorURL", monitorURL);
return this.axiosInstance.get(`/monitors/resolution/url?${params.toString()}`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
}
})
}
/**
*
* ************************************
* Check the endpoint resolution
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {Object} config.monitorURL - The monitor url to be sent in the request body.
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async checkEndpointResolution(config) {
const { authToken, monitorURL } = config;
const params = new URLSearchParams();
/**
*
* ************************************
* Gets monitors and summary of stats by TeamID
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {string} config.teamId - Team ID
* @param {Array<string>} config.types - Array of monitor types
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async getMonitorsAndSummaryByTeamId(config) {
const params = new URLSearchParams();
if (monitorURL) params.append("monitorURL", monitorURL);
return this.axiosInstance.get(`/monitors/resolution/url?${params.toString()}`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
});
}
/**
*
* ************************************
* Gets monitors and summary of stats by TeamID
* ************************************
*
* @async
* @param {Object} config - The configuration object.
* @param {string} config.authToken - The authorization token to be used in the request header.
* @param {string} config.teamId - Team ID
* @param {Array<string>} config.types - Array of monitor types
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async getMonitorsAndSummaryByTeamId(config) {
const params = new URLSearchParams();
if (config.types) {
config.types.forEach((type) => {