mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-16 14:49:48 -06:00
Merge branch 'develop' into 915-be-add-middeware-for-verification-of-refresh-token
This commit is contained in:
@@ -120,7 +120,7 @@
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.monitors .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
|
||||
.table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
|
||||
text-transform: uppercase;
|
||||
opacity: 0.8;
|
||||
font-size: var(--env-var-font-size-small-plus);
|
||||
|
||||
80
Client/src/Components/Dialog/index.jsx
Normal file
80
Client/src/Components/Dialog/index.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import { Button, Modal, Stack, Typography } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
const Dialog = ({ modelTitle, modelDescription,
|
||||
open, onClose, title, confirmationBtnLbl, confirmationBtnOnClick, cancelBtnLbl, cancelBtnOnClick, theme, isLoading, description }) => {
|
||||
return <Modal
|
||||
aria-labelledby={modelTitle}
|
||||
aria-describedby={modelDescription}
|
||||
open={open}
|
||||
onClose={onClose} >
|
||||
<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={modelTitle} component="h2"
|
||||
fontSize={16}
|
||||
color={theme.palette.text.primary}
|
||||
fontWeight={600}>
|
||||
{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"
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={cancelBtnOnClick}
|
||||
>
|
||||
{cancelBtnLbl}
|
||||
</Button>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading= {isLoading}
|
||||
onClick={confirmationBtnOnClick}
|
||||
>
|
||||
{confirmationBtnLbl}
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
Dialog.propTypes = {
|
||||
modelTitle: PropTypes.string.isRequired,
|
||||
modelDescription: PropTypes.string.isRequired,
|
||||
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,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
description: PropTypes.string
|
||||
}
|
||||
|
||||
export default Dialog
|
||||
@@ -223,6 +223,7 @@ const TeamPanel = () => {
|
||||
|
||||
return (
|
||||
<TabPanel
|
||||
className="team-panel table-container"
|
||||
value="team"
|
||||
sx={{
|
||||
"& h1": {
|
||||
|
||||
@@ -61,7 +61,7 @@ const Incidents = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack className="incidents" pt={theme.spacing(6)} gap={theme.spacing(12)}>
|
||||
<Stack className="incidents table-container" pt={theme.spacing(6)} gap={theme.spacing(12)}>
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
|
||||
@@ -46,7 +46,7 @@ const Maintenance = ({ isAdmin }) => {
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="maintenance"
|
||||
className="maintenance table-container"
|
||||
sx={{
|
||||
':has(> [class*="fallback__"])': {
|
||||
position: "relative",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useNavigate, useParams } from "react-router";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Button, Modal, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { monitorValidation } from "../../../Validation/validation";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
@@ -24,6 +24,7 @@ import PulseDot from "../../../Components/Animated/PulseDot";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import "./index.css";
|
||||
import Dialog from "../../../Components/Dialog";
|
||||
|
||||
/**
|
||||
* Parses a URL string and returns a URL object.
|
||||
@@ -447,71 +448,21 @@ const Configure = () => {
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
<Modal
|
||||
aria-labelledby="modal-delete-monitor"
|
||||
aria-describedby="delete-monitor-confirmation"
|
||||
<Dialog
|
||||
modelTitle="modal-delete-monitor"
|
||||
modelDescription="delete-monitor-confirmation"
|
||||
open={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
disablePortal
|
||||
title="Do you really want to delete this monitor?"
|
||||
confirmationBtnLbl="Delete"
|
||||
confirmationBtnOnClick={handleRemove}
|
||||
cancelBtnLbl="Cancel"
|
||||
cancelBtnOnClick={() => setIsOpen(false)}
|
||||
theme={theme}
|
||||
isLoading={isLoading}
|
||||
description="Once deleted, this monitor cannot be retrieved."
|
||||
>
|
||||
<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-monitor"
|
||||
component="h2"
|
||||
fontSize={16}
|
||||
color={theme.palette.text.primary}
|
||||
fontWeight={600}
|
||||
>
|
||||
Do you really want to delete this monitor?
|
||||
</Typography>
|
||||
<Typography
|
||||
id="delete-monitor-confirmation"
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
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>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading={isLoading}
|
||||
onClick={handleRemove}
|
||||
>
|
||||
Delete
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Dialog>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
Box,
|
||||
Button,
|
||||
Popover,
|
||||
Stack,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { networkService } from "../../../main";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import {
|
||||
formatDurationRounded,
|
||||
formatDurationSplit,
|
||||
} from "../../../Utils/timeUtils";
|
||||
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
|
||||
import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart";
|
||||
import ButtonGroup from "@mui/material/ButtonGroup";
|
||||
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
|
||||
@@ -41,447 +38,455 @@ import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
* @component
|
||||
*/
|
||||
const DetailsPage = ({ isAdmin }) => {
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
|
||||
const [monitor, setMonitor] = useState({});
|
||||
const { monitorId } = useParams();
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const openCertificate = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const closeCertificate = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const openCertificate = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const closeCertificate = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await networkService.getStatsByMonitorId({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
sortOrder: null,
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
numToDisplay: 50,
|
||||
normalize: true,
|
||||
});
|
||||
setMonitor(res?.data?.data ?? {});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
navigate("/not-found", { replace: true });
|
||||
}
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await networkService.getStatsByMonitorId({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
sortOrder: null,
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
numToDisplay: 50,
|
||||
normalize: true,
|
||||
});
|
||||
setMonitor(res?.data?.data ?? {});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
navigate("/not-found", { replace: true });
|
||||
}
|
||||
}, [authToken, monitorId, navigate, dateRange]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
}, [fetchMonitor]);
|
||||
useEffect(() => {
|
||||
fetchMonitor();
|
||||
}, [fetchMonitor]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCertificate = async () => {
|
||||
if (monitor?.type !== "http") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await networkService.getCertificateExpiry({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
});
|
||||
useEffect(() => {
|
||||
const fetchCertificate = async () => {
|
||||
if (monitor?.type !== "http") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await networkService.getCertificateExpiry({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
});
|
||||
if (res?.data?.data?.certificateDate) {
|
||||
const date = res.data.data.certificateDate;
|
||||
setCertificateExpiry(formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
fetchCertificate();
|
||||
}, [authToken, monitorId, monitor]);
|
||||
|
||||
if (res?.data?.data?.certificateDate) {
|
||||
let [month, day, year] = res.data.data.certificateDate.split("/");
|
||||
const date = new Date(year, month - 1, day);
|
||||
const splitDuration = (duration) => {
|
||||
const { time, format } = formatDurationSplit(duration);
|
||||
return (
|
||||
<>
|
||||
{time}
|
||||
<Typography component="span">{format}</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
setCertificateExpiry(
|
||||
formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
fetchCertificate();
|
||||
}, [authToken, monitorId, monitor]);
|
||||
let loading = Object.keys(monitor).length === 0;
|
||||
|
||||
const splitDuration = (duration) => {
|
||||
const { time, format } = formatDurationSplit(duration);
|
||||
return (
|
||||
<>
|
||||
{time}
|
||||
<Typography component="span">{format}</Typography>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
let loading = Object.keys(monitor).length === 0;
|
||||
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
|
||||
return (
|
||||
<Box className="monitor-details">
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Breadcrumbs
|
||||
list={[
|
||||
{ name: "monitors", path: "/monitors" },
|
||||
{ name: "details", path: `/monitors/${monitorId}` },
|
||||
]}
|
||||
/>
|
||||
<Stack gap={theme.spacing(10)} mt={theme.spacing(10)}>
|
||||
<Stack direction="row" gap={theme.spacing(2)}>
|
||||
<Box>
|
||||
<Typography component="h1" variant="h1">
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography component="h2" variant="h2">
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
mt={theme.spacing(1)}
|
||||
ml={theme.spacing(6)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -9,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
height={34}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
>
|
||||
<IconBox
|
||||
mr={theme.spacing(4)}
|
||||
onClick={openCertificate}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"& svg": {
|
||||
width: 23,
|
||||
height: 23,
|
||||
top: "52%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CertificateIcon />
|
||||
</IconBox>
|
||||
<Popover
|
||||
id="certificate-dropdown"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closeCertificate}
|
||||
disableScrollLock
|
||||
marginThreshold={null}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
mt: theme.spacing(4),
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
width: 140,
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Certificate Expiry</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
</Popover>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" gap={theme.spacing(8)}>
|
||||
<StatBox sx={statusStyles[determineState(monitor)]}>
|
||||
<Typography component="h2">active for</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.uptimeDuration)}
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last check</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.lastChecked)}
|
||||
<Typography component="span">ago</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last response time</Typography>
|
||||
<Typography>
|
||||
{monitor?.latestResponseTime}
|
||||
<Typography component="span">ms</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(4)}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
{dateRange === "day"
|
||||
? "24 hours"
|
||||
: dateRange === "week"
|
||||
? "7 days"
|
||||
: "30 days"}
|
||||
.
|
||||
</Typography>
|
||||
<ButtonGroup sx={{ height: 32 }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "month").toString()}
|
||||
onClick={() => setDateRange("month")}
|
||||
>
|
||||
Month
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack direction="row" flexWrap="wrap" gap={theme.spacing(8)}>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<UptimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Uptime</Typography>
|
||||
</Stack>
|
||||
<Stack justifyContent="space-between">
|
||||
<Box position="relative">
|
||||
<Typography>Total Checks</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? hoveredUptimeData.totalChecks
|
||||
: monitor?.periodTotalChecks}
|
||||
</Typography>
|
||||
{hoveredUptimeData !== null &&
|
||||
hoveredUptimeData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredUptimeData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Uptime Percentage</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(
|
||||
hoveredUptimeData.uptimePercentage * 10
|
||||
) / 10
|
||||
: Math.floor(monitor?.periodUptime * 10) / 10}
|
||||
<Typography component="span">%</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<UpBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredUptimeData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<IncidentsIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Incidents</Typography>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
<Typography>Total Incidents</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredIncidentsData !== null
|
||||
? hoveredIncidentsData.totalIncidents
|
||||
: monitor?.periodIncidents}
|
||||
</Typography>
|
||||
{hoveredIncidentsData !== null &&
|
||||
hoveredIncidentsData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredIncidentsData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<DownBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredIncidentsData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox justifyContent="space-between">
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<AverageResponseIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">
|
||||
Average Response Time
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ResponseGaugeChart
|
||||
data={[{ response: monitor?.periodAvgResponseTime }]}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox sx={{ padding: 0 }}>
|
||||
<Stack pt={theme.spacing(8)} pl={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<ResponseTimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Response Times</Typography>
|
||||
</Stack>
|
||||
<MonitorDetailsAreaChart
|
||||
checks={[...monitor.checks].reverse()}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
gap={theme.spacing(8)}
|
||||
sx={{
|
||||
flex: "100%",
|
||||
height: "fit-content",
|
||||
"& nav": { mt: theme.spacing(12) },
|
||||
}}
|
||||
>
|
||||
<Stack mb={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<HistoryIcon />
|
||||
</IconBox>
|
||||
<Typography
|
||||
component="h2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
History
|
||||
</Typography>
|
||||
</Stack>
|
||||
<PaginationTable
|
||||
monitorId={monitorId}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box className="monitor-details">
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
<>
|
||||
<Breadcrumbs
|
||||
list={[
|
||||
{ name: "monitors", path: "/monitors" },
|
||||
{ name: "details", path: `/monitors/${monitorId}` },
|
||||
]}
|
||||
/>
|
||||
<Stack
|
||||
gap={theme.spacing(10)}
|
||||
mt={theme.spacing(10)}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h1"
|
||||
>
|
||||
{monitor.name}
|
||||
</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
height="fit-content"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Tooltip
|
||||
title={statusMsg[determineState(monitor)]}
|
||||
disableInteractive
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -8],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PulseDot color={statusColor[determineState(monitor)]} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
|
||||
</Typography>
|
||||
<Typography
|
||||
position="relative"
|
||||
variant="body2"
|
||||
mt={theme.spacing(1)}
|
||||
ml={theme.spacing(6)}
|
||||
sx={{
|
||||
"&:before": {
|
||||
position: "absolute",
|
||||
content: `""`,
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: theme.palette.text.tertiary,
|
||||
opacity: 0.8,
|
||||
left: -9,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
height={34}
|
||||
sx={{
|
||||
ml: "auto",
|
||||
alignSelf: "flex-end",
|
||||
}}
|
||||
>
|
||||
<IconBox
|
||||
mr={theme.spacing(4)}
|
||||
onClick={openCertificate}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
"& svg": {
|
||||
width: 23,
|
||||
height: 23,
|
||||
top: "52%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CertificateIcon />
|
||||
</IconBox>
|
||||
<Popover
|
||||
id="certificate-dropdown"
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={closeCertificate}
|
||||
disableScrollLock
|
||||
marginThreshold={null}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
mt: theme.spacing(4),
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
width: 140,
|
||||
backgroundColor: theme.palette.background.accent,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2">Certificate Expiry</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
fontSize={13}
|
||||
color={theme.palette.text.primary}
|
||||
>
|
||||
{certificateExpiry}
|
||||
</Typography>
|
||||
</Popover>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
|
||||
sx={{
|
||||
px: theme.spacing(5),
|
||||
"& svg": {
|
||||
mr: theme.spacing(3),
|
||||
"& path": {
|
||||
stroke: theme.palette.text.tertiary,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SettingsIcon /> Configure
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<StatBox sx={statusStyles[determineState(monitor)]}>
|
||||
<Typography component="h2">active for</Typography>
|
||||
<Typography>{splitDuration(monitor?.uptimeDuration)}</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last check</Typography>
|
||||
<Typography>
|
||||
{splitDuration(monitor?.lastChecked)}
|
||||
<Typography component="span">ago</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
<StatBox>
|
||||
<Typography component="h2">last response time</Typography>
|
||||
<Typography>
|
||||
{monitor?.latestResponseTime}
|
||||
<Typography component="span">ms</Typography>
|
||||
</Typography>
|
||||
</StatBox>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="flex-end"
|
||||
gap={theme.spacing(4)}
|
||||
mb={theme.spacing(8)}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
{dateRange === "day"
|
||||
? "24 hours"
|
||||
: dateRange === "week"
|
||||
? "7 days"
|
||||
: "30 days"}
|
||||
.
|
||||
</Typography>
|
||||
<ButtonGroup sx={{ height: 32 }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
onClick={() => setDateRange("day")}
|
||||
>
|
||||
Day
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "week").toString()}
|
||||
onClick={() => setDateRange("week")}
|
||||
>
|
||||
Week
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "month").toString()}
|
||||
onClick={() => setDateRange("month")}
|
||||
>
|
||||
Month
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
flexWrap="wrap"
|
||||
gap={theme.spacing(8)}
|
||||
>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<UptimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Uptime</Typography>
|
||||
</Stack>
|
||||
<Stack justifyContent="space-between">
|
||||
<Box position="relative">
|
||||
<Typography>Total Checks</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? hoveredUptimeData.totalChecks
|
||||
: monitor?.periodTotalChecks}
|
||||
</Typography>
|
||||
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredUptimeData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Uptime Percentage</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredUptimeData !== null
|
||||
? Math.floor(hoveredUptimeData.uptimePercentage * 10) / 10
|
||||
: Math.floor(monitor?.periodUptime * 10) / 10}
|
||||
<Typography component="span">%</Typography>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
<UpBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredUptimeData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox>
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<IncidentsIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Incidents</Typography>
|
||||
</Stack>
|
||||
<Box position="relative">
|
||||
<Typography>Total Incidents</Typography>
|
||||
<Typography component="span">
|
||||
{hoveredIncidentsData !== null
|
||||
? hoveredIncidentsData.totalIncidents
|
||||
: monitor?.periodIncidents}
|
||||
</Typography>
|
||||
{hoveredIncidentsData !== null &&
|
||||
hoveredIncidentsData.time !== null && (
|
||||
<Typography
|
||||
component="h5"
|
||||
position="absolute"
|
||||
top="100%"
|
||||
fontSize={11}
|
||||
color={theme.palette.text.tertiary}
|
||||
>
|
||||
{formatDateWithTz(
|
||||
hoveredIncidentsData.time,
|
||||
dateFormat,
|
||||
uiTimezone
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<DownBarChart
|
||||
data={monitor?.aggregateData}
|
||||
type={dateRange}
|
||||
onBarHover={setHoveredIncidentsData}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox justifyContent="space-between">
|
||||
<Stack>
|
||||
<IconBox>
|
||||
<AverageResponseIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Average Response Time</Typography>
|
||||
</Stack>
|
||||
<ResponseGaugeChart
|
||||
data={[{ response: monitor?.periodAvgResponseTime }]}
|
||||
/>
|
||||
</ChartBox>
|
||||
<ChartBox sx={{ padding: 0 }}>
|
||||
<Stack
|
||||
pt={theme.spacing(8)}
|
||||
pl={theme.spacing(8)}
|
||||
>
|
||||
<IconBox>
|
||||
<ResponseTimeIcon />
|
||||
</IconBox>
|
||||
<Typography component="h2">Response Times</Typography>
|
||||
</Stack>
|
||||
<MonitorDetailsAreaChart checks={[...monitor.checks].reverse()} />
|
||||
</ChartBox>
|
||||
<ChartBox
|
||||
gap={theme.spacing(8)}
|
||||
sx={{
|
||||
flex: "100%",
|
||||
height: "fit-content",
|
||||
"& nav": { mt: theme.spacing(12) },
|
||||
}}
|
||||
>
|
||||
<Stack mb={theme.spacing(8)}>
|
||||
<IconBox>
|
||||
<HistoryIcon />
|
||||
</IconBox>
|
||||
<Typography
|
||||
component="h2"
|
||||
color={theme.palette.text.secondary}
|
||||
>
|
||||
History
|
||||
</Typography>
|
||||
</Stack>
|
||||
<PaginationTable
|
||||
monitorId={monitorId}
|
||||
dateRange={dateRange}
|
||||
/>
|
||||
</ChartBox>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
DetailsPage.propTypes = {
|
||||
isAdmin: PropTypes.bool,
|
||||
isAdmin: PropTypes.bool,
|
||||
};
|
||||
export default DetailsPage;
|
||||
|
||||
@@ -5,13 +5,9 @@ import { useNavigate } from "react-router-dom";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Stack,
|
||||
Typography,
|
||||
MenuItem
|
||||
} from "@mui/material";
|
||||
import {
|
||||
deleteUptimeMonitor,
|
||||
@@ -20,6 +16,7 @@ import {
|
||||
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
|
||||
import Settings from "../../../assets/icons/settings-bold.svg?react";
|
||||
import PropTypes from "prop-types";
|
||||
import Dialog from "../../../Components/Dialog"
|
||||
|
||||
const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
@@ -29,7 +26,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
const theme = useTheme();
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const authToken = authState.authToken;
|
||||
|
||||
const { isLoading } = useSelector((state) => state.uptimeMonitors);
|
||||
|
||||
const handleRemove = async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -199,69 +196,21 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
|
||||
</MenuItem>
|
||||
)}
|
||||
</Menu>
|
||||
<Modal
|
||||
aria-labelledby="modal-delete-monitor"
|
||||
aria-describedby="delete-monitor-confirmation"
|
||||
<Dialog
|
||||
modelTitle="modal-delete-monitor"
|
||||
modelDescription="delete-monitor-confirmation"
|
||||
open={isOpen}
|
||||
onClose={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title="Do you really want to delete this monitor?"
|
||||
confirmationBtnLbl="Delete"
|
||||
confirmationBtnOnClick={e => { e.stopPropagation(); handleRemove(e) }}
|
||||
cancelBtnLbl="Cancel"
|
||||
cancelBtnOnClick={e => { e.stopPropagation(); setIsOpen(false) }}
|
||||
theme={theme}
|
||||
isLoading={isLoading}
|
||||
description="Once deleted, this monitor cannot be retrieved."
|
||||
>
|
||||
<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-monitor" component="h2" variant="h2">
|
||||
Do you really want to delete this monitor?
|
||||
</Typography>
|
||||
<Typography id="delete-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={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(e);
|
||||
handleRemove(e);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ const Monitors = ({ isAdmin }) => {
|
||||
monitorState?.monitorsSummary?.monitors?.length === 0;
|
||||
|
||||
return (
|
||||
<Stack className="monitors" gap={theme.spacing(8)}>
|
||||
<Stack className="monitors table-container" gap={theme.spacing(8)}>
|
||||
{loading ? (
|
||||
<SkeletonLayout />
|
||||
) : (
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ConfigBox } from "./styled";
|
||||
import { networkService } from "../../main";
|
||||
import { settingsValidation } from "../../Validation/validation";
|
||||
import { useNavigate } from "react-router";
|
||||
import Dialog from "../../Components/Dialog"
|
||||
|
||||
const SECONDS_PER_DAY = 86400;
|
||||
|
||||
@@ -39,6 +40,8 @@ const Settings = ({ isAdmin }) => {
|
||||
});
|
||||
const [version,setVersion]=useState("unknown");
|
||||
const [errors, setErrors] = useState({});
|
||||
const deleteStatsMonitorsInitState = { deleteMonitors: false, deleteStats: false };
|
||||
const [isOpen, setIsOpen] = useState(deleteStatsMonitorsInitState);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -139,6 +142,8 @@ const Settings = ({ isAdmin }) => {
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ body: "Failed to clear stats" });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -167,9 +172,11 @@ const Settings = ({ isAdmin }) => {
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ Body: "Failed to delete all monitors" });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="settings"
|
||||
@@ -243,17 +250,30 @@ const Settings = ({ isAdmin }) => {
|
||||
/>
|
||||
<Box>
|
||||
<Typography>Clear all stats. This is irreversible.</Typography>
|
||||
<LoadingButton
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={handleClearStats}
|
||||
color="error"
|
||||
onClick={()=>setIsOpen({...deleteStatsMonitorsInitState, deleteStats: true})}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Clear all stats
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</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}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
>
|
||||
</Dialog>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
@@ -283,13 +303,27 @@ const Settings = ({ isAdmin }) => {
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={handleDeleteAllMonitors}
|
||||
onClick={()=>setIsOpen({...deleteStatsMonitorsInitState, deleteMonitors: true})}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Remove all monitors
|
||||
</LoadingButton>
|
||||
</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}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
>
|
||||
</Dialog>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
|
||||
8
Server/.mocharc.cjs
Normal file
8
Server/.mocharc.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
require: ["esm", "chai/register-expect.js"], // Include Chai's "expect" interface globally
|
||||
spec: "tests/**/*.test.js", // Specify test files
|
||||
timeout: 5000, // Set test-case timeout in milliseconds
|
||||
recursive: true, // Include subdirectories
|
||||
reporter: "spec", // Use the "spec" reporter
|
||||
exit: true, // Force Mocha to quit after tests complete
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
module.exports = {
|
||||
require: ["chai/register-expect.js"], // Include Chai's "expect" interface globally
|
||||
spec: "tests/**/*.test.js", // Specify test files
|
||||
timeout: 5000, // Set test-case timeout in milliseconds
|
||||
recursive: true, // Include subdirectories
|
||||
reporter: "spec", // Use the "spec" reporter
|
||||
exit: true, // Force Mocha to quit after tests complete
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"all": true,
|
||||
"include": ["controllers/*.js"],
|
||||
"exclude": ["**/*.test.js"],
|
||||
"reporter": ["html", "text", "lcov"],
|
||||
"sourceMap": false,
|
||||
"instrument": true
|
||||
"all": true,
|
||||
"include": ["controllers/*.js", "utils/*.js"],
|
||||
"exclude": ["**/*.test.js"],
|
||||
"reporter": ["html", "text", "lcov"],
|
||||
"sourceMap": false,
|
||||
"instrument": true
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ const connectDbAndRunServer = async (app, db) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { connectDbAndRunServer };
|
||||
export { connectDbAndRunServer };
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
const {
|
||||
registrationBodyValidation,
|
||||
loginValidation,
|
||||
editUserParamValidation,
|
||||
editUserBodyValidation,
|
||||
recoveryValidation,
|
||||
recoveryTokenValidation,
|
||||
newPasswordValidation,
|
||||
} = require("../validation/joi");
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config();
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const SERVICE_NAME = "AuthController";
|
||||
const { getTokenFromHeaders, tokenType } = require("../utils/utils");
|
||||
const crypto = require("crypto");
|
||||
const { handleValidationError, handleError } = require("./controllerUtils");
|
||||
import {
|
||||
registrationBodyValidation,
|
||||
loginValidation,
|
||||
editUserParamValidation,
|
||||
editUserBodyValidation,
|
||||
recoveryValidation,
|
||||
recoveryTokenValidation,
|
||||
newPasswordValidation,
|
||||
} from "../validation/joi.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import dotenv from "dotenv";
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { getTokenFromHeaders, tokenType } from "../utils/utils.js";
|
||||
import crypto from "crypto";
|
||||
import { handleValidationError, handleError } from "./controllerUtils.js";
|
||||
const SERVICE_NAME = "authController";
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* Creates and returns JWT token with an arbitrary payload
|
||||
@@ -26,15 +27,21 @@ const { handleValidationError, handleError } = require("./controllerUtils");
|
||||
* @throws {Error}
|
||||
*/
|
||||
const issueToken = (payload, typeOfToken, appSettings) => {
|
||||
try {
|
||||
const tokenTTL = (typeOfToken === tokenType.REFRESH_TOKEN) ? (appSettings?.refreshTokenTTL?? "7d") : (appSettings?.jwtTTL?? "2h");
|
||||
const tokenSecret = (typeOfToken === tokenType.REFRESH_TOKEN) ? appSettings?.refreshTokenSecret : appSettings?.jwtSecret;
|
||||
const payloadData = (typeOfToken === tokenType.REFRESH_TOKEN) ? {} : payload;
|
||||
try {
|
||||
const tokenTTL =
|
||||
typeOfToken === tokenType.REFRESH_TOKEN
|
||||
? (appSettings?.refreshTokenTTL ?? "7d")
|
||||
: (appSettings?.jwtTTL ?? "2h");
|
||||
const tokenSecret =
|
||||
typeOfToken === tokenType.REFRESH_TOKEN
|
||||
? appSettings?.refreshTokenSecret
|
||||
: appSettings?.jwtSecret;
|
||||
const payloadData = typeOfToken === tokenType.REFRESH_TOKEN ? {} : payload;
|
||||
|
||||
return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
|
||||
} catch (error) {
|
||||
throw handleError(error, SERVICE_NAME, "issueToken");
|
||||
}
|
||||
return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
|
||||
} catch (error) {
|
||||
throw handleError(error, SERVICE_NAME, "issueToken");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,66 +57,65 @@ const issueToken = (payload, typeOfToken, appSettings) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const registerUser = async (req, res, next) => {
|
||||
// joi validation
|
||||
try {
|
||||
await registrationBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
// Create a new user
|
||||
try {
|
||||
const { inviteToken } = req.body;
|
||||
// If superAdmin exists, a token should be attached to all further register requests
|
||||
const superAdminExists = await req.db.checkSuperadmin(req, res);
|
||||
if (superAdminExists) {
|
||||
await req.db.getInviteTokenAndDelete(inviteToken);
|
||||
} else {
|
||||
// This is the first account, create JWT secret to use if one is not supplied by env
|
||||
const jwtSecret = crypto.randomBytes(64).toString("hex");
|
||||
await req.db.updateAppSettings({ jwtSecret });
|
||||
}
|
||||
// joi validation
|
||||
try {
|
||||
await registrationBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
// Create a new user
|
||||
try {
|
||||
const { inviteToken } = req.body;
|
||||
// If superAdmin exists, a token should be attached to all further register requests
|
||||
const superAdminExists = await req.db.checkSuperadmin(req, res);
|
||||
if (superAdminExists) {
|
||||
await req.db.getInviteTokenAndDelete(inviteToken);
|
||||
} else {
|
||||
// This is the first account, create JWT secret to use if one is not supplied by env
|
||||
const jwtSecret = crypto.randomBytes(64).toString("hex");
|
||||
await req.db.updateAppSettings({ jwtSecret });
|
||||
}
|
||||
|
||||
const newUser = await req.db.insertUser({ ...req.body }, req.file);
|
||||
const newUser = await req.db.insertUser({ ...req.body }, req.file);
|
||||
|
||||
logger.info(successMessages.AUTH_CREATE_USER, {
|
||||
service: SERVICE_NAME,
|
||||
userId: newUser._id,
|
||||
});
|
||||
logger.info(successMessages.AUTH_CREATE_USER, {
|
||||
service: SERVICE_NAME,
|
||||
userId: newUser._id,
|
||||
});
|
||||
|
||||
const userForToken = { ...newUser._doc };
|
||||
delete userForToken.profileImage;
|
||||
delete userForToken.avatarImage;
|
||||
const userForToken = { ...newUser._doc };
|
||||
delete userForToken.profileImage;
|
||||
delete userForToken.avatarImage;
|
||||
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
|
||||
const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings);
|
||||
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
|
||||
const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings);
|
||||
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
|
||||
|
||||
req.emailService
|
||||
.buildAndSendEmail(
|
||||
"welcomeEmailTemplate",
|
||||
{ name: newUser.firstName },
|
||||
newUser.email,
|
||||
"Welcome to Uptime Monitor"
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.error("Error sending welcome email", {
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
req.emailService
|
||||
.buildAndSendEmail(
|
||||
"welcomeEmailTemplate",
|
||||
{ name: newUser.firstName },
|
||||
newUser.email,
|
||||
"Welcome to Uptime Monitor"
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.error("Error sending welcome email", {
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_USER,
|
||||
data: { user: newUser, token: token, refreshToken: refreshToken },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("ERROR", error);
|
||||
next(handleError(error, SERVICE_NAME, "registerController"));
|
||||
}
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_USER,
|
||||
data: { user: newUser, token: token, refreshToken: refreshToken },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "registerController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -125,45 +131,46 @@ const registerUser = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect.
|
||||
*/
|
||||
const loginUser = async (req, res, next) => {
|
||||
try {
|
||||
await loginValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
// Check if user exists
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
try {
|
||||
await loginValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Compare password
|
||||
const match = await user.comparePassword(password);
|
||||
if (match !== true) {
|
||||
next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD));
|
||||
return;
|
||||
}
|
||||
// Check if user exists
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
|
||||
// Remove password from user object. Should this be abstracted to DB layer?
|
||||
const userWithoutPassword = { ...user._doc };
|
||||
delete userWithoutPassword.password;
|
||||
delete userWithoutPassword.avatarImage;
|
||||
// Compare password
|
||||
const match = await user.comparePassword(password);
|
||||
if (match !== true) {
|
||||
next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD));
|
||||
return;
|
||||
}
|
||||
|
||||
// Happy path, return token
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings);
|
||||
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
|
||||
// reset avatar image
|
||||
userWithoutPassword.avatarImage = user.avatarImage;
|
||||
// Remove password from user object. Should this be abstracted to DB layer?
|
||||
const userWithoutPassword = { ...user._doc };
|
||||
delete userWithoutPassword.password;
|
||||
delete userWithoutPassword.avatarImage;
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_LOGIN_USER,
|
||||
data: { user: userWithoutPassword, token: token, refreshToken: refreshToken },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "loginController"));
|
||||
}
|
||||
// Happy path, return token
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings);
|
||||
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
|
||||
// reset avatar image
|
||||
userWithoutPassword.avatarImage = user.avatarImage;
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_LOGIN_USER,
|
||||
data: { user: userWithoutPassword, token: token, refreshToken: refreshToken },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "loginUser"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -181,58 +188,58 @@ const loginUser = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403).
|
||||
*/
|
||||
const editUser = async (req, res, next) => {
|
||||
try {
|
||||
await editUserParamValidation.validateAsync(req.params);
|
||||
await editUserBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await editUserParamValidation.validateAsync(req.params);
|
||||
await editUserBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO is this neccessary any longer? Verify ownership middleware should handle this
|
||||
if (req.params.userId !== req.user._id.toString()) {
|
||||
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
|
||||
error.status = 401;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
// TODO is this neccessary any longer? Verify ownership middleware should handle this
|
||||
if (req.params.userId !== req.user._id.toString()) {
|
||||
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
|
||||
error.status = 401;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Change Password check
|
||||
if (req.body.password && req.body.newPassword) {
|
||||
// Get token from headers
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
// Get email from token
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { email } = jwt.verify(token, jwtSecret);
|
||||
// Add user email to body for DB operation
|
||||
req.body.email = email;
|
||||
// Get user
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
// Compare passwords
|
||||
const match = await user.comparePassword(req.body.password);
|
||||
// If not a match, throw a 403
|
||||
if (!match) {
|
||||
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
|
||||
error.status = 403;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
// If a match, update the password
|
||||
req.body.password = req.body.newPassword;
|
||||
}
|
||||
try {
|
||||
// Change Password check
|
||||
if (req.body.password && req.body.newPassword) {
|
||||
// Get token from headers
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
// Get email from token
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { email } = jwt.verify(token, jwtSecret);
|
||||
// Add user email to body for DB operation
|
||||
req.body.email = email;
|
||||
// Get user
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
// Compare passwords
|
||||
const match = await user.comparePassword(req.body.password);
|
||||
// If not a match, throw a 403
|
||||
if (!match) {
|
||||
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
|
||||
error.status = 403;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
// If a match, update the password
|
||||
req.body.password = req.body.newPassword;
|
||||
}
|
||||
|
||||
const updatedUser = await req.db.updateUser(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_UPDATE_USER,
|
||||
data: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "userEditController"));
|
||||
}
|
||||
const updatedUser = await req.db.updateUser(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_UPDATE_USER,
|
||||
data: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "userEditController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -244,17 +251,17 @@ const editUser = async (req, res, next) => {
|
||||
* @returns {Object} The response object with a success status, a message indicating the existence of a superadmin, and a boolean indicating the existence of a superadmin.
|
||||
* @throws {Error} If there is an error during the process.
|
||||
*/
|
||||
const checkSuperAdminExists = async (req, res, next) => {
|
||||
try {
|
||||
const superAdminExists = await req.db.checkSuperadmin(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_ADMIN_EXISTS,
|
||||
data: superAdminExists,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "checkSuperadminController"));
|
||||
}
|
||||
const checkSuperadminExists = async (req, res, next) => {
|
||||
try {
|
||||
const superAdminExists = await req.db.checkSuperadmin(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_ADMIN_EXISTS,
|
||||
data: superAdminExists,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "checkSuperadminController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -269,46 +276,46 @@ const checkSuperAdminExists = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const requestRecovery = async (req, res, next) => {
|
||||
try {
|
||||
await recoveryValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await recoveryValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { email } = req.body;
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (user) {
|
||||
const recoveryToken = await req.db.requestRecoveryToken(req, res);
|
||||
const name = user.firstName;
|
||||
const email = req.body.email;
|
||||
const { clientHost } = req.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
try {
|
||||
const { email } = req.body;
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (user) {
|
||||
const recoveryToken = await req.db.requestRecoveryToken(req, res);
|
||||
const name = user.firstName;
|
||||
const email = req.body.email;
|
||||
const { clientHost } = req.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
|
||||
const msgId = await req.emailService.buildAndSendEmail(
|
||||
"passwordResetTemplate",
|
||||
{
|
||||
name,
|
||||
email,
|
||||
url,
|
||||
},
|
||||
email,
|
||||
"Bluewave Uptime Password Reset"
|
||||
);
|
||||
const msgId = await req.emailService.buildAndSendEmail(
|
||||
"passwordResetTemplate",
|
||||
{
|
||||
name,
|
||||
email,
|
||||
url,
|
||||
},
|
||||
email,
|
||||
"Bluewave Uptime Password Reset"
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
|
||||
data: msgId,
|
||||
});
|
||||
} else {
|
||||
throw new Error(errorMessages.FRIENDLY_ERROR);
|
||||
}
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
|
||||
}
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
|
||||
data: msgId,
|
||||
});
|
||||
} else {
|
||||
throw new Error(errorMessages.FRIENDLY_ERROR);
|
||||
}
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -323,23 +330,23 @@ const requestRecovery = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const validateRecovery = async (req, res, next) => {
|
||||
try {
|
||||
await recoveryTokenValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await recoveryTokenValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await req.db.validateRecoveryToken(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
|
||||
}
|
||||
try {
|
||||
await req.db.validateRecoveryToken(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -355,26 +362,26 @@ const validateRecovery = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const resetPassword = async (req, res, next) => {
|
||||
try {
|
||||
await newPasswordValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const user = await req.db.resetPassword(req, res);
|
||||
try {
|
||||
await newPasswordValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
const validationError = handleValidationError(error, SERVICE_NAME);
|
||||
next(validationError);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const user = await req.db.resetPassword(req, res);
|
||||
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_RESET_PASSWORD,
|
||||
data: { user, token },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "resetPasswordController"));
|
||||
}
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_RESET_PASSWORD,
|
||||
data: { user, token },
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "resetPasswordController"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -387,74 +394,72 @@ const resetPassword = async (req, res, next) => {
|
||||
* @throws {Error} If user validation fails or user is not found in the database.
|
||||
*/
|
||||
const deleteUser = async (req, res, next) => {
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const decodedToken = jwt.decode(token);
|
||||
const { email } = decodedToken;
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const decodedToken = jwt.decode(token);
|
||||
const { email } = decodedToken;
|
||||
|
||||
// Check if the user exists
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (!user) {
|
||||
next(new Error(errorMessages.DB_USER_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
// Check if the user exists
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (!user) {
|
||||
next(new Error(errorMessages.DB_USER_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
|
||||
const result = await req.db.getMonitorsByTeamId({
|
||||
params: { teamId: user.teamId },
|
||||
});
|
||||
const result = await req.db.getMonitorsByTeamId({
|
||||
params: { teamId: user.teamId },
|
||||
});
|
||||
|
||||
if (user.role.includes("superadmin")) {
|
||||
//2. Remove all jobs, delete checks and alerts
|
||||
result?.monitors.length > 0 &&
|
||||
(await Promise.all(
|
||||
result.monitors.map(async (monitor) => {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
})
|
||||
));
|
||||
if (user.role.includes("superadmin")) {
|
||||
//2. Remove all jobs, delete checks and alerts
|
||||
result?.monitors.length > 0 &&
|
||||
(await Promise.all(
|
||||
result.monitors.map(async (monitor) => {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
})
|
||||
));
|
||||
|
||||
// 3. Delete team
|
||||
await req.db.deleteTeam(user.teamId);
|
||||
// 4. Delete all other team members
|
||||
await req.db.deleteAllOtherUsers();
|
||||
// 5. Delete each monitor
|
||||
await req.db.deleteMonitorsByUserId(user._id);
|
||||
}
|
||||
// 6. Delete the user by id
|
||||
await req.db.deleteUser(user._id);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_DELETE_USER,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteUserController"));
|
||||
}
|
||||
// 3. Delete team
|
||||
await req.db.deleteTeam(user.teamId);
|
||||
// 4. Delete all other team members
|
||||
await req.db.deleteAllOtherUsers();
|
||||
// 5. Delete each monitor
|
||||
await req.db.deleteMonitorsByUserId(user._id);
|
||||
}
|
||||
// 6. Delete the user by id
|
||||
await req.db.deleteUser(user._id);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_DELETE_USER,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteUserController"));
|
||||
}
|
||||
};
|
||||
|
||||
const getAllUsers = async (req, res) => {
|
||||
try {
|
||||
const allUsers = await req.db.getAllUsers(req, res);
|
||||
res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Got all users", data: allUsers });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getAllUsersController"));
|
||||
}
|
||||
const getAllUsers = async (req, res, next) => {
|
||||
try {
|
||||
const allUsers = await req.db.getAllUsers(req, res);
|
||||
res.status(200).json({ success: true, msg: "Got all users", data: allUsers });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getAllUsersController"));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
issueToken,
|
||||
registerUser,
|
||||
loginUser,
|
||||
editUser,
|
||||
checkSuperadminExists: checkSuperAdminExists,
|
||||
requestRecovery,
|
||||
validateRecovery,
|
||||
resetPassword,
|
||||
deleteUser,
|
||||
getAllUsers,
|
||||
export {
|
||||
issueToken,
|
||||
registerUser,
|
||||
loginUser,
|
||||
editUser,
|
||||
checkSuperadminExists,
|
||||
requestRecovery,
|
||||
validateRecovery,
|
||||
resetPassword,
|
||||
deleteUser,
|
||||
getAllUsers,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {
|
||||
import {
|
||||
createCheckParamValidation,
|
||||
createCheckBodyValidation,
|
||||
getChecksParamValidation,
|
||||
@@ -8,12 +8,13 @@ const {
|
||||
deleteChecksParamValidation,
|
||||
deleteChecksByTeamIdParamValidation,
|
||||
updateChecksTTLBodyValidation,
|
||||
} = require("../validation/joi");
|
||||
const { successMessages } = require("../utils/messages");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { getTokenFromHeaders } = require("../utils/utils");
|
||||
} from "../validation/joi.js";
|
||||
import { successMessages } from "../utils/messages.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { getTokenFromHeaders } from "../utils/utils.js";
|
||||
import { handleValidationError, handleError } from "./controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "checkController";
|
||||
const { handleValidationError, handleError } = require("./controllerUtils");
|
||||
|
||||
const createCheck = async (req, res, next) => {
|
||||
try {
|
||||
@@ -143,7 +144,7 @@ const updateChecksTTL = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createCheck,
|
||||
getChecks,
|
||||
getTeamChecks,
|
||||
|
||||
@@ -1,19 +1,43 @@
|
||||
const handleValidationError = (error, serviceName) => {
|
||||
error.status = 422;
|
||||
error.service = serviceName;
|
||||
error.message =
|
||||
error.details?.[0]?.message || error.message || "Validation Error";
|
||||
return error;
|
||||
error.status = 422;
|
||||
error.service = serviceName;
|
||||
error.message = error.details?.[0]?.message || error.message || "Validation Error";
|
||||
return error;
|
||||
};
|
||||
|
||||
const handleError = (error, serviceName, method, status = 500) => {
|
||||
error.status === undefined ? (error.status = status) : null;
|
||||
error.service === undefined ? (error.service = serviceName) : null;
|
||||
error.method === undefined ? (error.method = method) : null;
|
||||
return error;
|
||||
error.status === undefined ? (error.status = status) : null;
|
||||
error.service === undefined ? (error.service = serviceName) : null;
|
||||
error.method === undefined ? (error.method = method) : null;
|
||||
return error;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
handleValidationError,
|
||||
handleError,
|
||||
const fetchMonitorCertificate = async (tls, monitor) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const monitorUrl = new URL(monitor.url);
|
||||
const hostname = monitorUrl.hostname;
|
||||
try {
|
||||
let socket = tls.connect(
|
||||
{
|
||||
port: 443,
|
||||
host: hostname,
|
||||
servername: hostname, // this is required in case the server enabled SNI
|
||||
},
|
||||
() => {
|
||||
try {
|
||||
let x509Certificate = socket.getPeerX509Certificate();
|
||||
resolve(x509Certificate);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
} finally {
|
||||
socket.end();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export { handleValidationError, handleError, fetchMonitorCertificate };
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
const {
|
||||
import {
|
||||
inviteRoleValidation,
|
||||
inviteBodyValidation,
|
||||
inviteVerificationBodyValidation,
|
||||
} = require("../validation/joi");
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config();
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { handleError, handleValidationError } = require("./controllerUtils");
|
||||
} from "../validation/joi.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import dotenv from "dotenv";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { handleError, handleValidationError } from "./controllerUtils.js";
|
||||
import { getTokenFromHeaders } from "../utils/utils.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const SERVICE_NAME = "inviteController";
|
||||
const { getTokenFromHeaders } = require("../utils/utils");
|
||||
|
||||
/**
|
||||
* Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email.
|
||||
@@ -82,7 +85,4 @@ const inviteVerifyController = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
issueInvitation,
|
||||
inviteVerifyController,
|
||||
};
|
||||
export { issueInvitation, inviteVerifyController };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {
|
||||
import {
|
||||
createMaintenanceWindowBodyValidation,
|
||||
editMaintenanceWindowByIdParamValidation,
|
||||
editMaintenanceByIdWindowBodyValidation,
|
||||
@@ -6,11 +6,12 @@ const {
|
||||
getMaintenanceWindowsByMonitorIdParamValidation,
|
||||
getMaintenanceWindowsByTeamIdQueryValidation,
|
||||
deleteMaintenanceWindowByIdParamValidation,
|
||||
} = require("../validation/joi");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { getTokenFromHeaders } = require("../utils/utils");
|
||||
const { successMessages } = require("../utils/messages");
|
||||
const { handleValidationError, handleError } = require("./controllerUtils");
|
||||
} from "../validation/joi.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { getTokenFromHeaders } from "../utils/utils.js";
|
||||
import { successMessages } from "../utils/messages.js";
|
||||
import { handleValidationError, handleError } from "./controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "maintenanceWindowController";
|
||||
|
||||
const createMaintenanceWindows = async (req, res, next) => {
|
||||
@@ -160,7 +161,7 @@ const editMaintenanceWindow = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createMaintenanceWindows,
|
||||
getMaintenanceWindowById,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
const {
|
||||
getMonitorByIdParamValidation,
|
||||
getMonitorByIdQueryValidation,
|
||||
getMonitorsByTeamIdValidation,
|
||||
createMonitorBodyValidation,
|
||||
editMonitorBodyValidation,
|
||||
getMonitorsAndSummaryByTeamIdParamValidation,
|
||||
getMonitorsAndSummaryByTeamIdQueryValidation,
|
||||
getMonitorsByTeamIdQueryValidation,
|
||||
pauseMonitorParamValidation,
|
||||
getMonitorStatsByIdParamValidation,
|
||||
getMonitorStatsByIdQueryValidation,
|
||||
getCertificateParamValidation,
|
||||
} = require("../validation/joi");
|
||||
import {
|
||||
getMonitorByIdParamValidation,
|
||||
getMonitorByIdQueryValidation,
|
||||
getMonitorsByTeamIdValidation,
|
||||
createMonitorBodyValidation,
|
||||
editMonitorBodyValidation,
|
||||
getMonitorsAndSummaryByTeamIdParamValidation,
|
||||
getMonitorsAndSummaryByTeamIdQueryValidation,
|
||||
getMonitorsByTeamIdQueryValidation,
|
||||
pauseMonitorParamValidation,
|
||||
getMonitorStatsByIdParamValidation,
|
||||
getMonitorStatsByIdQueryValidation,
|
||||
getCertificateParamValidation,
|
||||
} from "../validation/joi.js";
|
||||
import * as tls from "tls";
|
||||
|
||||
const sslChecker = require("ssl-checker");
|
||||
const SERVICE_NAME = "monitorController";
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { getTokenFromHeaders } = require("../utils/utils");
|
||||
const logger = require("../utils/logger");
|
||||
const { handleError, handleValidationError } = require("./controllerUtils");
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { getTokenFromHeaders } from "../utils/utils.js";
|
||||
import logger from "../utils/logger.js";
|
||||
import {
|
||||
handleError,
|
||||
handleValidationError,
|
||||
fetchMonitorCertificate,
|
||||
} from "./controllerUtils.js";
|
||||
|
||||
/**
|
||||
* Returns all monitors
|
||||
@@ -31,16 +35,16 @@ const { handleError, handleValidationError } = require("./controllerUtils");
|
||||
* @throws {Error}
|
||||
*/
|
||||
const getAllMonitors = async (req, res, next) => {
|
||||
try {
|
||||
const monitors = await req.db.getAllMonitors();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_ALL,
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getAllMonitors"));
|
||||
}
|
||||
try {
|
||||
const monitors = await req.db.getAllMonitors();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_ALL,
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getAllMonitors"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -53,56 +57,55 @@ const getAllMonitors = async (req, res, next) => {
|
||||
* @throws {Error}
|
||||
*/
|
||||
const getMonitorStatsById = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const monitorStats = await req.db.getMonitorStatsById(req);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_STATS_BY_ID,
|
||||
data: monitorStats,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorStatsById"));
|
||||
}
|
||||
try {
|
||||
const monitorStats = await req.db.getMonitorStatsById(req);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_STATS_BY_ID,
|
||||
data: monitorStats,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorStatsById"));
|
||||
}
|
||||
};
|
||||
|
||||
const getMonitorCertificate = async (req, res, next) => {
|
||||
try {
|
||||
await getCertificateParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
}
|
||||
const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => {
|
||||
try {
|
||||
await getCertificateParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
}
|
||||
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
const monitor = await req.db.getMonitorById(monitorId);
|
||||
const monitorUrl = new URL(monitor.url);
|
||||
const certificate = await sslChecker(monitorUrl.hostname);
|
||||
if (certificate && certificate.validTo) {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CERTIFICATE,
|
||||
data: {
|
||||
certificateDate: new Date(certificate.validTo).toLocaleDateString(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CERTIFICATE,
|
||||
data: { certificateDate: "N/A" },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorCertificate"));
|
||||
}
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
const monitor = await req.db.getMonitorById(monitorId);
|
||||
const certificate = await fetchMonitorCertificate(tls, monitor);
|
||||
if (certificate && certificate.validTo) {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CERTIFICATE,
|
||||
data: {
|
||||
certificateDate: new Date(certificate.validTo),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CERTIFICATE,
|
||||
data: { certificateDate: "N/A" },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorCertificate"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -117,30 +120,30 @@ const getMonitorCertificate = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422).
|
||||
*/
|
||||
const getMonitorById = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorByIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorByIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const monitor = await req.db.getMonitorById(req.params.monitorId);
|
||||
if (!monitor) {
|
||||
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
const monitor = await req.db.getMonitorById(req.params.monitorId);
|
||||
if (!monitor) {
|
||||
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_ID,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorById"));
|
||||
}
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_ID,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorById"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -157,31 +160,26 @@ const getMonitorById = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const getMonitorsAndSummaryByTeamId = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(
|
||||
req.params
|
||||
);
|
||||
await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params);
|
||||
await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { teamId } = req.params;
|
||||
const { type } = req.query;
|
||||
const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId(
|
||||
teamId,
|
||||
type
|
||||
);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
|
||||
data: monitorsSummary,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId"));
|
||||
}
|
||||
try {
|
||||
const { teamId } = req.params;
|
||||
const { type } = req.query;
|
||||
const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId(teamId, type);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
|
||||
data: monitorsSummary,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -197,26 +195,26 @@ const getMonitorsAndSummaryByTeamId = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const getMonitorsByTeamId = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorsByTeamIdValidation.validateAsync(req.params);
|
||||
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorsByTeamIdValidation.validateAsync(req.params);
|
||||
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const teamId = req.params.teamId;
|
||||
const monitors = await req.db.getMonitorsByTeamId(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
|
||||
next(error);
|
||||
}
|
||||
try {
|
||||
const teamId = req.params.teamId;
|
||||
const monitors = await req.db.getMonitorsByTeamId(req, res);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -231,36 +229,36 @@ const getMonitorsByTeamId = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const createMonitor = async (req, res, next) => {
|
||||
try {
|
||||
await createMonitorBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await createMonitorBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const notifications = req.body.notifications;
|
||||
const monitor = await req.db.createMonitor(req, res);
|
||||
try {
|
||||
const notifications = req.body.notifications;
|
||||
const monitor = await req.db.createMonitor(req, res);
|
||||
|
||||
if (notifications && notifications.length !== 0) {
|
||||
monitor.notifications = await Promise.all(
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = monitor._id;
|
||||
await req.db.createNotification(notification);
|
||||
})
|
||||
);
|
||||
await monitor.save();
|
||||
}
|
||||
// Add monitor to job queue
|
||||
req.jobQueue.addJob(monitor._id, monitor);
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CREATE,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "createMonitor"));
|
||||
}
|
||||
if (notifications && notifications.length !== 0) {
|
||||
monitor.notifications = await Promise.all(
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = monitor._id;
|
||||
await req.db.createNotification(notification);
|
||||
})
|
||||
);
|
||||
await monitor.save();
|
||||
}
|
||||
// Add monitor to job queue
|
||||
req.jobQueue.addJob(monitor._id, monitor);
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CREATE,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "createMonitor"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -275,37 +273,35 @@ const createMonitor = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records.
|
||||
*/
|
||||
const deleteMonitor = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const monitor = await req.db.deleteMonitor(req, res, next);
|
||||
// Delete associated checks,alerts,and notifications
|
||||
try {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
}
|
||||
);
|
||||
}
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: successMessages.MONITOR_DELETE });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteMonitor"));
|
||||
}
|
||||
try {
|
||||
const monitor = await req.db.deleteMonitor(req, res, next);
|
||||
// Delete associated checks,alerts,and notifications
|
||||
try {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
}
|
||||
);
|
||||
}
|
||||
return res.status(200).json({ success: true, msg: successMessages.MONITOR_DELETE });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteMonitor"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -320,34 +316,34 @@ const deleteMonitor = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the deletion process.
|
||||
*/
|
||||
const deleteAllMonitors = async (req, res, next) => {
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { teamId } = jwt.verify(token, jwtSecret);
|
||||
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
|
||||
await Promise.all(
|
||||
monitors.map(async (monitor) => {
|
||||
try {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteAllMonitors",
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteAllMonitors"));
|
||||
}
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { teamId } = jwt.verify(token, jwtSecret);
|
||||
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
|
||||
await Promise.all(
|
||||
monitors.map(async (monitor) => {
|
||||
try {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteAllMonitors",
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "deleteAllMonitors"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -364,46 +360,46 @@ const deleteAllMonitors = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const editMonitor = async (req, res, next) => {
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await editMonitorBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await getMonitorByIdParamValidation.validateAsync(req.params);
|
||||
await editMonitorBodyValidation.validateAsync(req.body);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
const monitorBeforeEdit = await req.db.getMonitorById(monitorId);
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
const monitorBeforeEdit = await req.db.getMonitorById(monitorId);
|
||||
|
||||
// Get notifications from the request body
|
||||
const notifications = req.body.notifications;
|
||||
// Get notifications from the request body
|
||||
const notifications = req.body.notifications;
|
||||
|
||||
const editedMonitor = await req.db.editMonitor(monitorId, req.body);
|
||||
const editedMonitor = await req.db.editMonitor(monitorId, req.body);
|
||||
|
||||
await req.db.deleteNotificationsByMonitorId(editedMonitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(editedMonitor._id);
|
||||
|
||||
if (notifications && notifications.length !== 0) {
|
||||
await Promise.all(
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = editedMonitor._id;
|
||||
await req.db.createNotification(notification);
|
||||
})
|
||||
);
|
||||
}
|
||||
if (notifications && notifications.length !== 0) {
|
||||
await Promise.all(
|
||||
notifications.map(async (notification) => {
|
||||
notification.monitorId = editedMonitor._id;
|
||||
await req.db.createNotification(notification);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Delete the old job(editedMonitor has the same ID as the old monitor)
|
||||
await req.jobQueue.deleteJob(monitorBeforeEdit);
|
||||
// Add the new job back to the queue
|
||||
await req.jobQueue.addJob(editedMonitor._id, editedMonitor);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_EDIT,
|
||||
data: editedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "editMonitor"));
|
||||
}
|
||||
// Delete the old job(editedMonitor has the same ID as the old monitor)
|
||||
await req.jobQueue.deleteJob(monitorBeforeEdit);
|
||||
// Add the new job back to the queue
|
||||
await req.jobQueue.addJob(editedMonitor._id, editedMonitor);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_EDIT,
|
||||
data: editedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "editMonitor"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -418,32 +414,32 @@ const editMonitor = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process.
|
||||
*/
|
||||
const pauseMonitor = async (req, res, next) => {
|
||||
try {
|
||||
await pauseMonitorParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
}
|
||||
try {
|
||||
await pauseMonitorParamValidation.validateAsync(req.params);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
}
|
||||
|
||||
try {
|
||||
const monitor = await req.db.getMonitorById(req.params.monitorId);
|
||||
if (monitor.isActive) {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
} else {
|
||||
await req.jobQueue.addJob(monitor._id, monitor);
|
||||
}
|
||||
monitor.isActive = !monitor.isActive;
|
||||
monitor.status = undefined;
|
||||
monitor.save();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: monitor.isActive
|
||||
? successMessages.MONITOR_RESUME
|
||||
: successMessages.MONITOR_PAUSE,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "pauseMonitor"));
|
||||
}
|
||||
try {
|
||||
const monitor = await req.db.getMonitorById(req.params.monitorId);
|
||||
if (monitor.isActive) {
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
} else {
|
||||
await req.jobQueue.addJob(monitor._id, monitor);
|
||||
}
|
||||
monitor.isActive = !monitor.isActive;
|
||||
monitor.status = undefined;
|
||||
monitor.save();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: monitor.isActive
|
||||
? successMessages.MONITOR_RESUME
|
||||
: successMessages.MONITOR_PAUSE,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "pauseMonitor"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -458,36 +454,36 @@ const pauseMonitor = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process.
|
||||
*/
|
||||
const addDemoMonitors = async (req, res, next) => {
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { _id, teamId } = jwt.verify(token, jwtSecret);
|
||||
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
|
||||
await Promise.all(
|
||||
demoMonitors.map((monitor) => req.jobQueue.addJob(monitor._id, monitor))
|
||||
);
|
||||
try {
|
||||
const token = getTokenFromHeaders(req.headers);
|
||||
const { jwtSecret } = req.settingsService.getSettings();
|
||||
const { _id, teamId } = jwt.verify(token, jwtSecret);
|
||||
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
|
||||
await Promise.all(
|
||||
demoMonitors.map((monitor) => req.jobQueue.addJob(monitor._id, monitor))
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_DEMO_ADDED,
|
||||
data: demoMonitors.length,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "addDemoMonitors"));
|
||||
}
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_DEMO_ADDED,
|
||||
data: demoMonitors.length,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "addDemoMonitors"));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorCertificate,
|
||||
getMonitorById,
|
||||
getMonitorsAndSummaryByTeamId,
|
||||
getMonitorsByTeamId,
|
||||
createMonitor,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
pauseMonitor,
|
||||
addDemoMonitors,
|
||||
export {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorCertificate,
|
||||
getMonitorById,
|
||||
getMonitorsAndSummaryByTeamId,
|
||||
getMonitorsByTeamId,
|
||||
createMonitor,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
pauseMonitor,
|
||||
addDemoMonitors,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const { handleError } = require("./controllerUtils");
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
import { handleError } from "./controllerUtils.js";
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
|
||||
const SERVICE_NAME = "JobQueueController";
|
||||
|
||||
const getMetrics = async (req, res, next) => {
|
||||
@@ -55,9 +56,4 @@ const obliterateQueue = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getMetrics,
|
||||
getJobs,
|
||||
addJob,
|
||||
obliterateQueue,
|
||||
};
|
||||
export { getMetrics, getJobs, addJob, obliterateQueue };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { successMessages } = require("../utils/messages");
|
||||
import { successMessages } from "../utils/messages.js";
|
||||
import { updateAppSettingsBodyValidation } from "../validation/joi.js";
|
||||
import { handleValidationError, handleError } from "./controllerUtils.js";
|
||||
const SERVICE_NAME = "SettingsController";
|
||||
const { updateAppSettingsBodyValidation } = require("../validation/joi");
|
||||
const { handleValidationError, handleError } = require("./controllerUtils");
|
||||
|
||||
const getAppSettings = async (req, res, next) => {
|
||||
try {
|
||||
@@ -39,7 +39,4 @@ const updateAppSettings = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAppSettings,
|
||||
updateAppSettings,
|
||||
};
|
||||
export { getAppSettings, updateAppSettings };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const AppSettingsSchema = mongoose.Schema(
|
||||
{
|
||||
@@ -88,4 +88,4 @@ const AppSettingsSchema = mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("AppSettings", AppSettingsSchema);
|
||||
export default mongoose.model("AppSettings", AppSettingsSchema);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const mongoose = require("mongoose");
|
||||
const EmailService = require("../../service/emailService");
|
||||
const Notification = require("./Notification");
|
||||
import mongoose from "mongoose";
|
||||
import EmailService from "../../service/emailService.js";
|
||||
import Notification from "./Notification.js";
|
||||
|
||||
/**
|
||||
* Check Schema for MongoDB collection.
|
||||
@@ -73,5 +73,4 @@ const CheckSchema = mongoose.Schema(
|
||||
);
|
||||
|
||||
CheckSchema.index({ createdAt: 1 });
|
||||
|
||||
module.exports = mongoose.model("Check", CheckSchema);
|
||||
export default mongoose.model("Check", CheckSchema);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
import mongoose from "mongoose";
|
||||
const InviteTokenSchema = mongoose.Schema(
|
||||
{
|
||||
email: {
|
||||
@@ -32,4 +31,4 @@ const InviteTokenSchema = mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("InviteToken", InviteTokenSchema);
|
||||
export default mongoose.model("InviteToken", InviteTokenSchema);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
import mongoose from "mongoose";
|
||||
/**
|
||||
* MaintenanceWindow Schema
|
||||
* @module MaintenanceWindow
|
||||
@@ -66,4 +65,4 @@ const MaintenanceWindow = mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("MaintenanceWindow", MaintenanceWindow);
|
||||
export default mongoose.model("MaintenanceWindow", MaintenanceWindow);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Notification = require("./Notification");
|
||||
import mongoose from "mongoose";
|
||||
import Notification from "./Notification.js";
|
||||
|
||||
const MonitorSchema = mongoose.Schema(
|
||||
{
|
||||
@@ -60,4 +60,4 @@ const MonitorSchema = mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Monitor", MonitorSchema);
|
||||
export default mongoose.model("Monitor", MonitorSchema);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
import mongoose from "mongoose";
|
||||
const NotificationSchema = mongoose.Schema(
|
||||
{
|
||||
monitorId: {
|
||||
@@ -22,5 +21,4 @@ const NotificationSchema = mongoose.Schema(
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Notification", NotificationSchema);
|
||||
export default mongoose.model("Notification", NotificationSchema);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
import mongoose from "mongoose";
|
||||
const AuditSchema = mongoose.Schema({
|
||||
id: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
@@ -109,4 +108,4 @@ PageSpeedCheck.pre("save", async function (next) {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("PageSpeedCheck", PageSpeedCheck);
|
||||
export default mongoose.model("PageSpeedCheck", PageSpeedCheck);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const RecoveryTokenSchema = mongoose.Schema(
|
||||
{
|
||||
@@ -22,4 +22,4 @@ const RecoveryTokenSchema = mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("RecoveryToken", RecoveryTokenSchema);
|
||||
export default mongoose.model("RecoveryToken", RecoveryTokenSchema);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
import mongoose from "mongoose";
|
||||
const TeamSchema = mongoose.Schema(
|
||||
{
|
||||
email: {
|
||||
@@ -12,5 +11,4 @@ const TeamSchema = mongoose.Schema(
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Team", TeamSchema);
|
||||
export default mongoose.model("Team", TeamSchema);
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
const mongoose = require("mongoose");
|
||||
const bcrypt = require("bcrypt");
|
||||
import mongoose from "mongoose";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const UserSchema = mongoose.Schema(
|
||||
{
|
||||
firstName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lastName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
avatarImage: {
|
||||
type: String,
|
||||
},
|
||||
profileImage: {
|
||||
data: Buffer,
|
||||
contentType: String,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
role: {
|
||||
type: [String],
|
||||
default: "user",
|
||||
enum: ["user", "admin", "superadmin", "demo"],
|
||||
},
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
},
|
||||
checkTTL: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
{
|
||||
firstName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lastName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
avatarImage: {
|
||||
type: String,
|
||||
},
|
||||
profileImage: {
|
||||
data: Buffer,
|
||||
contentType: String,
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
isVerified: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
role: {
|
||||
type: [String],
|
||||
default: "user",
|
||||
enum: ["user", "admin", "superadmin", "demo"],
|
||||
},
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
},
|
||||
checkTTL: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
UserSchema.pre("save", async function (next) {
|
||||
if (!this.isModified("password")) {
|
||||
next();
|
||||
}
|
||||
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
|
||||
this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync
|
||||
next();
|
||||
if (!this.isModified("password")) {
|
||||
next();
|
||||
}
|
||||
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
|
||||
this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync
|
||||
next();
|
||||
});
|
||||
|
||||
UserSchema.pre("findOneAndUpdate", async function (next) {
|
||||
const update = this.getUpdate();
|
||||
if ("password" in update) {
|
||||
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
|
||||
update.password = await bcrypt.hash(update.password, salt); // hash is also async, need to eitehr await or use hashSync
|
||||
}
|
||||
const update = this.getUpdate();
|
||||
if ("password" in update) {
|
||||
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
|
||||
update.password = await bcrypt.hash(update.password, salt); // hash is also async, need to eitehr await or use hashSync
|
||||
}
|
||||
|
||||
next();
|
||||
next();
|
||||
});
|
||||
|
||||
UserSchema.methods.comparePassword = async function (submittedPassword) {
|
||||
res = await bcrypt.compare(submittedPassword, this.password);
|
||||
return res;
|
||||
const res = await bcrypt.compare(submittedPassword, this.password);
|
||||
return res;
|
||||
};
|
||||
|
||||
const User = mongoose.model("User", UserSchema);
|
||||
|
||||
User.init().then(() => {
|
||||
console.log("User model initialized");
|
||||
console.log("User model initialized");
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("User", UserSchema);
|
||||
export default mongoose.model("User", UserSchema);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const mongoose = require("mongoose");
|
||||
const UserModel = require("../models/User");
|
||||
const AppSettings = require("../models/AppSettings");
|
||||
import mongoose from "mongoose";
|
||||
import UserModel from "../models/User.js";
|
||||
import AppSettings from "../models/AppSettings.js";
|
||||
|
||||
//****************************************
|
||||
// DB Connection
|
||||
@@ -41,7 +41,7 @@ const checkSuperadmin = async (req, res) => {
|
||||
// User Operations
|
||||
//****************************************
|
||||
|
||||
const {
|
||||
import {
|
||||
insertUser,
|
||||
getUserByEmail,
|
||||
updateUser,
|
||||
@@ -50,32 +50,32 @@ const {
|
||||
deleteAllOtherUsers,
|
||||
getAllUsers,
|
||||
logoutUser,
|
||||
} = require("./modules/userModule");
|
||||
} from "./modules/userModule.js";
|
||||
|
||||
//****************************************
|
||||
// Invite Token Operations
|
||||
//****************************************
|
||||
|
||||
const {
|
||||
import {
|
||||
requestInviteToken,
|
||||
getInviteToken,
|
||||
getInviteTokenAndDelete,
|
||||
} = require("./modules/inviteModule");
|
||||
} from "./modules/inviteModule.js";
|
||||
|
||||
//****************************************
|
||||
// Recovery Operations
|
||||
//****************************************
|
||||
const {
|
||||
import {
|
||||
requestRecoveryToken,
|
||||
validateRecoveryToken,
|
||||
resetPassword,
|
||||
} = require("./modules/recoveryModule");
|
||||
} from "./modules/recoveryModule.js";
|
||||
|
||||
//****************************************
|
||||
// Monitors
|
||||
//****************************************
|
||||
|
||||
const {
|
||||
import {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
@@ -87,23 +87,23 @@ const {
|
||||
deleteMonitorsByUserId,
|
||||
editMonitor,
|
||||
addDemoMonitors,
|
||||
} = require("./modules/monitorModule");
|
||||
} from "./modules/monitorModule.js";
|
||||
|
||||
//****************************************
|
||||
// Page Speed Checks
|
||||
//****************************************
|
||||
|
||||
const {
|
||||
import {
|
||||
createPageSpeedCheck,
|
||||
getPageSpeedChecks,
|
||||
deletePageSpeedChecksByMonitorId,
|
||||
} = require("./modules/pageSpeedCheckModule");
|
||||
} from "./modules/pageSpeedCheckModule.js";
|
||||
|
||||
//****************************************
|
||||
// Checks
|
||||
//****************************************
|
||||
|
||||
const {
|
||||
import {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
@@ -111,12 +111,12 @@ const {
|
||||
deleteChecks,
|
||||
deleteChecksByTeamId,
|
||||
updateChecksTTL,
|
||||
} = require("./modules/checkModule");
|
||||
} from "./modules/checkModule.js";
|
||||
|
||||
//****************************************
|
||||
// Maintenance Window
|
||||
//****************************************
|
||||
const {
|
||||
import {
|
||||
createMaintenanceWindow,
|
||||
getMaintenanceWindowById,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
@@ -125,26 +125,23 @@ const {
|
||||
deleteMaintenanceWindowByMonitorId,
|
||||
deleteMaintenanceWindowByUserId,
|
||||
editMaintenanceWindowById,
|
||||
} = require("./modules/maintenanceWindowModule");
|
||||
} from "./modules/maintenanceWindowModule.js";
|
||||
|
||||
//****************************************
|
||||
// Notifications
|
||||
//****************************************
|
||||
const {
|
||||
import {
|
||||
createNotification,
|
||||
getNotificationsByMonitorId,
|
||||
deleteNotificationsByMonitorId,
|
||||
} = require("./modules/notificationModule");
|
||||
} from "./modules/notificationModule.js";
|
||||
|
||||
//****************************************
|
||||
// AppSettings
|
||||
//****************************************
|
||||
const {
|
||||
getAppSettings,
|
||||
updateAppSettings,
|
||||
} = require("./modules/settingsModule");
|
||||
import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js";
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
connect,
|
||||
insertUser,
|
||||
getUserByEmail,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Check = require("../../models/Check");
|
||||
const Monitor = require("../../models/Monitor");
|
||||
const User = require("../../models/User");
|
||||
const logger = require("../../../utils/logger");
|
||||
import Check from "../../models/Check.js";
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import User from "../../models/User.js";
|
||||
import logger from "../../../utils/logger.js";
|
||||
const SERVICE_NAME = "checkModule";
|
||||
const dateRangeLookup = {
|
||||
day: new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||
@@ -274,7 +274,7 @@ const updateChecksTTL = async (teamId, ttl) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createCheck,
|
||||
getChecksCount,
|
||||
getChecks,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const InviteToken = require("../../models/InviteToken");
|
||||
const crypto = require("crypto");
|
||||
const { errorMessages } = require("../../../utils/messages");
|
||||
const SERVICE_NAME = "inviteModule";
|
||||
import InviteToken from "../../models/InviteToken.js";
|
||||
import crypto from "crypto";
|
||||
import { errorMessages } from "../../../utils/messages.js";
|
||||
|
||||
const SERVICE_NAME = "inviteModule";
|
||||
/**
|
||||
* Request an invite token for a user.
|
||||
*
|
||||
@@ -83,8 +83,4 @@ const getInviteTokenAndDelete = async (token) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
requestInviteToken,
|
||||
getInviteToken,
|
||||
getInviteTokenAndDelete,
|
||||
};
|
||||
export { requestInviteToken, getInviteToken, getInviteTokenAndDelete };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const MaintenanceWindow = require("../../models/MaintenanceWindow");
|
||||
import MaintenanceWindow from "../../models/MaintenanceWindow.js";
|
||||
const SERVICE_NAME = "maintenanceWindowModule";
|
||||
|
||||
/**
|
||||
@@ -220,7 +220,7 @@ const editMaintenanceWindowById = async (
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createMaintenanceWindow,
|
||||
getMaintenanceWindowById,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
const Monitor = require("../../models/Monitor");
|
||||
const Check = require("../../models/Check");
|
||||
const PageSpeedCheck = require("../../models/PageSpeedCheck");
|
||||
const { errorMessages } = require("../../../utils/messages");
|
||||
const Notification = require("../../models/Notification");
|
||||
const { NormalizeData } = require("../../../utils/dataUtils");
|
||||
const demoMonitors = require("../../../utils/demoMonitors.json");
|
||||
import Monitor from "../../models/Monitor.js";
|
||||
import Check from "../../models/Check.js";
|
||||
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
||||
import { errorMessages } from "../../../utils/messages.js";
|
||||
import Notification from "../../models/Notification.js";
|
||||
import { NormalizeData } from "../../../utils/dataUtils.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const demoMonitorsPath = path.resolve(
|
||||
__dirname,
|
||||
"../../../utils/demoMonitors.json"
|
||||
);
|
||||
const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8"));
|
||||
|
||||
const SERVICE_NAME = "monitorModule";
|
||||
|
||||
/**
|
||||
@@ -572,7 +584,7 @@ const addDemoMonitors = async (userId, teamId) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Notification = require("../../models/Notification");
|
||||
import Notification from "../../models/Notification.js";
|
||||
const SERVICE_NAME = "notificationModule";
|
||||
/**
|
||||
* Creates a new notification.
|
||||
@@ -49,7 +49,7 @@ const deleteNotificationsByMonitorId = async (monitorId) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createNotification,
|
||||
getNotificationsByMonitorId,
|
||||
deleteNotificationsByMonitorId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const PageSpeedCheck = require("../../models/PageSpeedCheck");
|
||||
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
||||
const SERVICE_NAME = "pageSpeedCheckModule";
|
||||
/**
|
||||
* Create a PageSpeed check for a monitor
|
||||
@@ -63,7 +63,7 @@ const deletePageSpeedChecksByMonitorId = async (monitorId) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
createPageSpeedCheck,
|
||||
getPageSpeedChecks,
|
||||
deletePageSpeedChecksByMonitorId,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
const UserModel = require("../../models/User");
|
||||
const RecoveryToken = require("../../models/RecoveryToken");
|
||||
const crypto = require("crypto");
|
||||
const { errorMessages } = require("../../../utils/messages");
|
||||
import UserModel from "../../models/User.js";
|
||||
import RecoveryToken from "../../models/RecoveryToken.js";
|
||||
import crypto from "crypto";
|
||||
import { errorMessages } from "../../../utils/messages.js";
|
||||
|
||||
const SERVICE_NAME = "recoveryModule";
|
||||
|
||||
/**
|
||||
@@ -81,8 +82,4 @@ const resetPassword = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
requestRecoveryToken,
|
||||
validateRecoveryToken,
|
||||
resetPassword,
|
||||
};
|
||||
export { requestRecoveryToken, validateRecoveryToken, resetPassword };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const AppSettings = require("../../models/AppSettings");
|
||||
import AppSettings from "../../models/AppSettings.js";
|
||||
const SERVICE_NAME = "SettingsModule";
|
||||
|
||||
const getAppSettings = async () => {
|
||||
@@ -27,7 +27,4 @@ const updateAppSettings = async (newSettings) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getAppSettings,
|
||||
updateAppSettings,
|
||||
};
|
||||
export { getAppSettings, updateAppSettings };
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const UserModel = require("../../models/User");
|
||||
const TeamModel = require("../../models/Team");
|
||||
const { errorMessages } = require("../../../utils/messages");
|
||||
const { GenerateAvatarImage } = require("../../../utils/imageProcessing");
|
||||
import UserModel from "../../models/User.js";
|
||||
import TeamModel from "../../models/Team.js";
|
||||
import { errorMessages } from "../../../utils/messages.js";
|
||||
import { GenerateAvatarImage } from "../../../utils/imageProcessing.js";
|
||||
|
||||
const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key
|
||||
const { ParseBoolean } = require("../../../utils/utils");
|
||||
SERVICE_NAME = "userModule";
|
||||
import { ParseBoolean } from "../../../utils/utils.js";
|
||||
const SERVICE_NAME = "userModule";
|
||||
|
||||
/**
|
||||
* Insert a User
|
||||
@@ -208,7 +208,7 @@ const logoutUser = async (userId) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
insertUser,
|
||||
getUserByEmail,
|
||||
updateUser,
|
||||
|
||||
278
Server/index.js
278
Server/index.js
@@ -1,163 +1,165 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const swaggerUi = require("swagger-ui-express");
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import swaggerUi from "swagger-ui-express";
|
||||
|
||||
const express = require("express");
|
||||
const helmet = require("helmet");
|
||||
const cors = require("cors");
|
||||
const logger = require("./utils/logger");
|
||||
const { verifyJWT } = require("./middleware/verifyJWT");
|
||||
const { handleErrors } = require("./middleware/handleErrors");
|
||||
const { errorMessages } = require("./utils/messages");
|
||||
const authRouter = require("./routes/authRoute");
|
||||
const inviteRouter = require("./routes/inviteRoute");
|
||||
const monitorRouter = require("./routes/monitorRoute");
|
||||
const checkRouter = require("./routes/checkRoute");
|
||||
const maintenanceWindowRouter = require("./routes/maintenanceWindowRoute");
|
||||
const settingsRouter = require("./routes/settingsRoute");
|
||||
import express from "express";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import logger from "./utils/logger.js";
|
||||
import { verifyJWT } from "./middleware/verifyJWT.js";
|
||||
import { handleErrors } from "./middleware/handleErrors.js";
|
||||
import { errorMessages } from "./utils/messages.js";
|
||||
import authRouter from "./routes/authRoute.js";
|
||||
import inviteRouter from "./routes/inviteRoute.js";
|
||||
import monitorRouter from "./routes/monitorRoute.js";
|
||||
import checkRouter from "./routes/checkRoute.js";
|
||||
import maintenanceWindowRouter from "./routes/maintenanceWindowRoute.js";
|
||||
import settingsRouter from "./routes/settingsRoute.js";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const { connectDbAndRunServer } = require("./configs/db");
|
||||
const queueRouter = require("./routes/queueRoute");
|
||||
const JobQueue = require("./service/jobQueue");
|
||||
const NetworkService = require("./service/networkService");
|
||||
const EmailService = require("./service/emailService");
|
||||
const SettingsService = require("./service/settingsService");
|
||||
import { connectDbAndRunServer } from "./configs/db.js";
|
||||
import queueRouter from "./routes/queueRoute.js";
|
||||
import JobQueue from "./service/jobQueue.js";
|
||||
import NetworkService from "./service/networkService.js";
|
||||
import EmailService from "./service/emailService.js";
|
||||
import SettingsService from "./service/settingsService.js";
|
||||
import db from "./db/mongo/MongoDB.js";
|
||||
import { fetchMonitorCertificate } from "./controllers/controllerUtils.js";
|
||||
const SERVICE_NAME = "Server";
|
||||
|
||||
let cleaningUp = false;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const openApiSpec = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
|
||||
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
|
||||
);
|
||||
// Need to wrap server setup in a function to handle async nature of JobQueue
|
||||
const startApp = async () => {
|
||||
// **************************
|
||||
// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
|
||||
// Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
|
||||
// We can easily swap between any type of data source as long as the methods are implemented
|
||||
//
|
||||
// FakeDB
|
||||
// const db = require("./db/FakeDb");
|
||||
//
|
||||
// MongoDB
|
||||
// const db = require("./db/MongoDB");
|
||||
//
|
||||
// **************************
|
||||
const DB_TYPE = {
|
||||
MongoDB: () => require("./db/mongo/MongoDB"),
|
||||
FakedDB: () => require("./db/FakeDb"),
|
||||
};
|
||||
// **************************
|
||||
// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
|
||||
// Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
|
||||
// We can easily swap between any type of data source as long as the methods are implemented
|
||||
//
|
||||
// FakeDB
|
||||
// const db = require("./db/FakeDb");
|
||||
//
|
||||
// MongoDB
|
||||
// const db = require("./db/MongoDB");
|
||||
//
|
||||
// **************************
|
||||
// const DB_TYPE = {
|
||||
// MongoDB: async () => (await import("./db/mongo/MongoDB.js")).default,
|
||||
// FakedDB: async () => (await import("./db/FakeDb.js")).default,
|
||||
// };
|
||||
|
||||
// const db = DB_TYPE[process.env.DB_TYPE]
|
||||
// ? DB_TYPE[process.env.DB_TYPE]()
|
||||
// : require("./db/FakeDb");
|
||||
// const db = DB_TYPE[process.env.DB_TYPE]
|
||||
// ? DB_TYPE[process.env.DB_TYPE]()
|
||||
// : require("./db/FakeDb");
|
||||
|
||||
const db = DB_TYPE.MongoDB();
|
||||
// const db = DB_TYPE.MongoDB();
|
||||
|
||||
const app = express();
|
||||
const app = express();
|
||||
|
||||
// middlewares
|
||||
app.use(
|
||||
cors()
|
||||
//We will add configuration later
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
// **************************
|
||||
// Make DB accessible anywhere we have a Request object
|
||||
// By adding the DB to the request object, we can access it in any route
|
||||
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
|
||||
// Same applies for JobQueue and emailService
|
||||
// **************************
|
||||
app.use((req, res, next) => {
|
||||
req.db = db;
|
||||
req.jobQueue = jobQueue;
|
||||
req.emailService = emailService;
|
||||
req.settingsService = settingsService;
|
||||
next();
|
||||
});
|
||||
// middlewares
|
||||
app.use(
|
||||
cors()
|
||||
//We will add configuration later
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
// **************************
|
||||
// Make DB accessible anywhere we have a Request object
|
||||
// By adding the DB to the request object, we can access it in any route
|
||||
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
|
||||
// Same applies for JobQueue and emailService
|
||||
// **************************
|
||||
app.use((req, res, next) => {
|
||||
req.db = db;
|
||||
req.jobQueue = jobQueue;
|
||||
req.emailService = emailService;
|
||||
req.settingsService = settingsService;
|
||||
next();
|
||||
});
|
||||
|
||||
// Swagger UI
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
||||
// Swagger UI
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
|
||||
|
||||
//routes
|
||||
app.use("/api/v1/auth", authRouter);
|
||||
app.use("/api/v1/settings", verifyJWT, settingsRouter);
|
||||
app.use("/api/v1/invite", inviteRouter);
|
||||
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
|
||||
app.use("/api/v1/checks", verifyJWT, checkRouter);
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRouter);
|
||||
app.use("/api/v1/queue", verifyJWT, queueRouter);
|
||||
//routes
|
||||
app.use("/api/v1/auth", authRouter);
|
||||
app.use("/api/v1/settings", verifyJWT, settingsRouter);
|
||||
app.use("/api/v1/invite", inviteRouter);
|
||||
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
|
||||
app.use("/api/v1/checks", verifyJWT, checkRouter);
|
||||
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRouter);
|
||||
app.use("/api/v1/queue", verifyJWT, queueRouter);
|
||||
|
||||
//health check
|
||||
app.use("/api/v1/healthy", (req, res) => {
|
||||
try {
|
||||
logger.info("Checking Health of the server.");
|
||||
return res.status(200).json({ message: "Healthy" });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
//health check
|
||||
app.use("/api/v1/healthy", (req, res) => {
|
||||
try {
|
||||
logger.info("Checking Health of the server.");
|
||||
return res.status(200).json({ message: "Healthy" });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/api/v1/mail", async (req, res) => {
|
||||
try {
|
||||
const id = await req.emailService.buildAndSendEmail(
|
||||
"welcomeEmailTemplate",
|
||||
{
|
||||
name: "Alex",
|
||||
},
|
||||
"ajhollid@gmail.com",
|
||||
"Welcome"
|
||||
);
|
||||
res.status(200).json({ success: true, msg: "Email sent", data: id });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
app.use("/api/v1/mail", async (req, res) => {
|
||||
try {
|
||||
const id = await req.emailService.buildAndSendEmail(
|
||||
"welcomeEmailTemplate",
|
||||
{
|
||||
name: "Alex",
|
||||
},
|
||||
"ajhollid@gmail.com",
|
||||
"Welcome"
|
||||
);
|
||||
res.status(200).json({ success: true, msg: "Email sent", data: id });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Error handler middleware
|
||||
* Should be called last
|
||||
*/
|
||||
app.use(handleErrors);
|
||||
/**
|
||||
* Error handler middleware
|
||||
* Should be called last
|
||||
*/
|
||||
app.use(handleErrors);
|
||||
|
||||
// Create services
|
||||
await connectDbAndRunServer(app, db);
|
||||
const settingsService = new SettingsService();
|
||||
// Create services
|
||||
await connectDbAndRunServer(app, db);
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
await settingsService.loadSettings();
|
||||
const emailService = new EmailService(settingsService);
|
||||
const networkService = new NetworkService(db, emailService);
|
||||
const jobQueue = await JobQueue.createJobQueue(
|
||||
db,
|
||||
networkService,
|
||||
settingsService
|
||||
);
|
||||
await settingsService.loadSettings();
|
||||
const emailService = new EmailService(settingsService);
|
||||
const networkService = new NetworkService(db, emailService);
|
||||
const jobQueue = await JobQueue.createJobQueue(db, networkService, settingsService);
|
||||
|
||||
const cleanup = async () => {
|
||||
if (cleaningUp) {
|
||||
console.log("Already cleaning up");
|
||||
return;
|
||||
}
|
||||
cleaningUp = true;
|
||||
try {
|
||||
console.log("Shutting down gracefully");
|
||||
await jobQueue.obliterate();
|
||||
console.log("Finished cleanup");
|
||||
} catch (error) {
|
||||
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
service: SERVICE_NAME,
|
||||
errorMsg: error.message,
|
||||
});
|
||||
}
|
||||
process.exit(0);
|
||||
};
|
||||
process.on("SIGUSR2", cleanup);
|
||||
process.on("SIGINT", cleanup);
|
||||
process.on("SIGTERM", cleanup);
|
||||
const cleanup = async () => {
|
||||
if (cleaningUp) {
|
||||
console.log("Already cleaning up");
|
||||
return;
|
||||
}
|
||||
cleaningUp = true;
|
||||
try {
|
||||
console.log("Shutting down gracefully");
|
||||
await jobQueue.obliterate();
|
||||
console.log("Finished cleanup");
|
||||
} catch (error) {
|
||||
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
service: SERVICE_NAME,
|
||||
errorMsg: error.message,
|
||||
});
|
||||
}
|
||||
process.exit(0);
|
||||
};
|
||||
process.on("SIGUSR2", cleanup);
|
||||
process.on("SIGINT", cleanup);
|
||||
process.on("SIGTERM", cleanup);
|
||||
};
|
||||
|
||||
startApp().catch((error) => {
|
||||
console.log(error);
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const logger = require("../utils/logger");
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
import logger from "../utils/logger.js";
|
||||
import { errorMessages } from "../utils/messages.js";
|
||||
|
||||
const handleErrors = (error, req, res, next) => {
|
||||
const status = error.status || 500;
|
||||
@@ -12,4 +12,4 @@ const handleErrors = (error, req, res, next) => {
|
||||
res.status(status).json({ success: false, msg: message });
|
||||
};
|
||||
|
||||
module.exports = { handleErrors };
|
||||
export { handleErrors };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const jwt = require("jsonwebtoken");
|
||||
import jwt from "jsonwebtoken";
|
||||
const TOKEN_PREFIX = "Bearer ";
|
||||
const SERVICE_NAME = "allowedRoles";
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
import { errorMessages } from "../utils/messages.js";
|
||||
|
||||
const isAllowed = (allowedRoles) => {
|
||||
return (req, res, next) => {
|
||||
@@ -52,4 +52,4 @@ const isAllowed = (allowedRoles) => {
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = { isAllowed };
|
||||
export { isAllowed };
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
const jwt = require("jsonwebtoken");
|
||||
import jwt from "jsonwebtoken";
|
||||
import { errorMessages } from "../utils/messages.js";
|
||||
import { handleError } from "../controllers/controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "verifyJWT";
|
||||
const TOKEN_PREFIX = "Bearer ";
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
const { handleError } = require("../controllers/controllerUtils");
|
||||
|
||||
/**
|
||||
* Verifies the JWT token
|
||||
@@ -91,4 +92,5 @@ function handleExpiredJwtToken(req, res, next) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { verifyJWT };
|
||||
export { verifyJWT };
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const logger = require("../utils/logger");
|
||||
import logger from "../utils/logger.js";
|
||||
import { errorMessages } from "../utils/messages.js";
|
||||
const SERVICE_NAME = "verifyOwnership";
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
|
||||
const verifyOwnership = (Model, paramName) => {
|
||||
return async (req, res, next) => {
|
||||
@@ -46,4 +46,4 @@ const verifyOwnership = (Model, paramName) => {
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = { verifyOwnership };
|
||||
export { verifyOwnership };
|
||||
|
||||
14830
Server/package-lock.json
generated
14830
Server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,9 @@
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "nyc mocha",
|
||||
"test": "c8 mocha",
|
||||
"dev": "nodemon index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -15,7 +16,6 @@
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "5.7.15",
|
||||
"chai": "5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
@@ -25,21 +25,21 @@
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mailersend": "^2.2.0",
|
||||
"mjml": "^5.0.0-alpha.4",
|
||||
"mocha": "10.7.3",
|
||||
"mongoose": "^8.3.3",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nodemailer": "^6.9.14",
|
||||
"ping": "0.4.4",
|
||||
"sharp": "0.33.4",
|
||||
"ssl-checker": "2.0.10",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "3.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"nyc": "17.1.0",
|
||||
"proxyquire": "2.1.3",
|
||||
"chai": "5.1.1",
|
||||
"mocha": "10.7.3",
|
||||
"c8": "10.1.2",
|
||||
"esm": "3.2.25",
|
||||
"nodemon": "3.1.0",
|
||||
"sinon": "19.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
const router = require("express").Router();
|
||||
const { verifyJWT } = require("../middleware/verifyJWT");
|
||||
const { verifyOwnership } = require("../middleware/verifyOwnership");
|
||||
const { isAllowed } = require("../middleware/isAllowed");
|
||||
const multer = require("multer");
|
||||
const upload = multer();
|
||||
const User = require("../db/models/User");
|
||||
import { Router } from "express";
|
||||
import { verifyJWT } from "../middleware/verifyJWT.js";
|
||||
import { verifyOwnership } from "../middleware/verifyOwnership.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
import multer from "multer";
|
||||
import User from "../db/models/User.js";
|
||||
|
||||
const {
|
||||
const router = Router();
|
||||
const upload = multer();
|
||||
|
||||
import {
|
||||
registerUser,
|
||||
loginUser,
|
||||
editUser,
|
||||
@@ -16,7 +18,7 @@ const {
|
||||
checkSuperadminExists,
|
||||
getAllUsers,
|
||||
deleteUser,
|
||||
} = require("../controllers/authController");
|
||||
} from "../controllers/authController.js";
|
||||
|
||||
//Auth routes
|
||||
router.post("/register", upload.single("profileImage"), registerUser);
|
||||
@@ -31,4 +33,4 @@ router.post("/recovery/request", requestRecovery);
|
||||
router.post("/recovery/validate", validateRecovery);
|
||||
router.post("/recovery/reset/", resetPassword);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
const router = require("express").Router();
|
||||
const checkController = require("../controllers/checkController");
|
||||
const { verifyOwnership } = require("../middleware/verifyOwnership");
|
||||
const { isAllowed } = require("../middleware/isAllowed");
|
||||
const Monitor = require("../db/models/Monitor");
|
||||
import { Router } from "express";
|
||||
import {
|
||||
createCheck,
|
||||
getChecks,
|
||||
deleteChecks,
|
||||
getTeamChecks,
|
||||
deleteChecksByTeamId,
|
||||
updateChecksTTL,
|
||||
} from "../controllers/checkController.js";
|
||||
import { verifyOwnership } from "../middleware/verifyOwnership.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
import Monitor from "../db/models/Monitor.js";
|
||||
|
||||
router.get("/:monitorId", checkController.getChecks);
|
||||
router.post(
|
||||
"/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
checkController.createCheck
|
||||
);
|
||||
const router = Router();
|
||||
|
||||
router.get("/:monitorId", getChecks);
|
||||
router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), createCheck);
|
||||
router.delete(
|
||||
"/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
checkController.deleteChecks
|
||||
deleteChecks
|
||||
);
|
||||
|
||||
router.get("/team/:teamId", checkController.getTeamChecks);
|
||||
router.get("/team/:teamId", getTeamChecks);
|
||||
|
||||
router.delete(
|
||||
"/team/:teamId",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
checkController.deleteChecksByTeamId
|
||||
deleteChecksByTeamId
|
||||
);
|
||||
|
||||
router.put(
|
||||
"/team/ttl",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
checkController.updateChecksTTL
|
||||
);
|
||||
router.put("/team/ttl", isAllowed(["admin", "superadmin"]), updateChecksTTL);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const router = require("express").Router();
|
||||
const { verifyJWT } = require("../middleware/verifyJWT");
|
||||
const { isAllowed } = require("../middleware/isAllowed");
|
||||
|
||||
const {
|
||||
import { Router } from "express";
|
||||
import { verifyJWT } from "../middleware/verifyJWT.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
import {
|
||||
issueInvitation,
|
||||
inviteVerifyController,
|
||||
} = require("../controllers/inviteController");
|
||||
} from "../controllers/inviteController.js";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
@@ -15,4 +16,4 @@ router.post(
|
||||
);
|
||||
router.post("/verify", issueInvitation);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
const router = require("express").Router();
|
||||
const maintenanceWindowController = require("../controllers/maintenanceWindowController");
|
||||
const { verifyOwnership } = require("../middleware/verifyOwnership");
|
||||
const Monitor = require("../db/models/Monitor");
|
||||
import { Router } from "express";
|
||||
import {
|
||||
createMaintenanceWindows,
|
||||
getMaintenanceWindowById,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
getMaintenanceWindowsByMonitorId,
|
||||
deleteMaintenanceWindow,
|
||||
editMaintenanceWindow,
|
||||
} from "../controllers/maintenanceWindowController.js";
|
||||
import { verifyOwnership } from "../middleware/verifyOwnership.js";
|
||||
import Monitor from "../db/models/Monitor.js";
|
||||
|
||||
router.post("/", maintenanceWindowController.createMaintenanceWindows);
|
||||
const router = Router();
|
||||
|
||||
router.post("/", createMaintenanceWindows);
|
||||
|
||||
router.get(
|
||||
"/monitor/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
maintenanceWindowController.getMaintenanceWindowsByMonitorId
|
||||
getMaintenanceWindowsByMonitorId
|
||||
);
|
||||
|
||||
router.get("/team/", maintenanceWindowController.getMaintenanceWindowsByTeamId);
|
||||
router.get("/team/", getMaintenanceWindowsByTeamId);
|
||||
|
||||
router.get("/:id", maintenanceWindowController.getMaintenanceWindowById);
|
||||
router.get("/:id", getMaintenanceWindowById);
|
||||
|
||||
router.put("/:id", maintenanceWindowController.editMaintenanceWindow);
|
||||
router.put("/:id", editMaintenanceWindow);
|
||||
|
||||
router.delete("/:id", maintenanceWindowController.deleteMaintenanceWindow);
|
||||
router.delete("/:id", deleteMaintenanceWindow);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
@@ -1,51 +1,42 @@
|
||||
const router = require("express").Router();
|
||||
const monitorController = require("../controllers/monitorController");
|
||||
const { isAllowed } = require("../middleware/isAllowed");
|
||||
import { Router } from "express";
|
||||
import {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorCertificate,
|
||||
getMonitorById,
|
||||
getMonitorsAndSummaryByTeamId,
|
||||
getMonitorsByTeamId,
|
||||
createMonitor,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
pauseMonitor,
|
||||
addDemoMonitors,
|
||||
} from "../controllers/monitorController.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
import { fetchMonitorCertificate } from "../controllers/controllerUtils.js";
|
||||
|
||||
router.get("/", monitorController.getAllMonitors);
|
||||
router.get("/stats/:monitorId", monitorController.getMonitorStatsById);
|
||||
router.get("/certificate/:monitorId", monitorController.getMonitorCertificate);
|
||||
router.get("/:monitorId", monitorController.getMonitorById);
|
||||
router.get(
|
||||
"/team/summary/:teamId",
|
||||
monitorController.getMonitorsAndSummaryByTeamId
|
||||
);
|
||||
router.get("/team/:teamId", monitorController.getMonitorsByTeamId);
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
monitorController.createMonitor
|
||||
);
|
||||
router.get("/", getAllMonitors);
|
||||
router.get("/stats/:monitorId", getMonitorStatsById);
|
||||
router.get("/certificate/:monitorId", (req, res, next) => {
|
||||
getMonitorCertificate(req, res, next, fetchMonitorCertificate);
|
||||
});
|
||||
router.get("/:monitorId", getMonitorById);
|
||||
router.get("/team/summary/:teamId", getMonitorsAndSummaryByTeamId);
|
||||
router.get("/team/:teamId", getMonitorsByTeamId);
|
||||
|
||||
router.delete(
|
||||
"/:monitorId",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
monitorController.deleteMonitor
|
||||
);
|
||||
router.post("/", isAllowed(["admin", "superadmin"]), createMonitor);
|
||||
|
||||
router.put(
|
||||
"/:monitorId",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
monitorController.editMonitor
|
||||
);
|
||||
router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor);
|
||||
|
||||
router.delete(
|
||||
"/",
|
||||
isAllowed(["superadmin"]),
|
||||
monitorController.deleteAllMonitors
|
||||
);
|
||||
router.put("/:monitorId", isAllowed(["admin", "superadmin"]), editMonitor);
|
||||
|
||||
router.post(
|
||||
"/pause/:monitorId",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
monitorController.pauseMonitor
|
||||
);
|
||||
router.delete("/", isAllowed(["superadmin"]), deleteAllMonitors);
|
||||
|
||||
router.post(
|
||||
"/demo",
|
||||
isAllowed(["admin", "superadmin"]),
|
||||
monitorController.addDemoMonitors
|
||||
);
|
||||
router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), pauseMonitor);
|
||||
|
||||
module.exports = router;
|
||||
router.post("/demo", isAllowed(["admin", "superadmin"]), addDemoMonitors);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
const router = require("express").Router();
|
||||
const queueController = require("../controllers/queueController");
|
||||
import { Router } from "express";
|
||||
import {
|
||||
getMetrics,
|
||||
getJobs,
|
||||
addJob,
|
||||
obliterateQueue,
|
||||
} from "../controllers/queueController.js";
|
||||
|
||||
router.get("/metrics", queueController.getMetrics);
|
||||
const router = Router();
|
||||
router.get("/metrics", getMetrics);
|
||||
|
||||
// Get Jobs
|
||||
router.get("/jobs", queueController.getJobs);
|
||||
router.get("/jobs", getJobs);
|
||||
|
||||
// Add Job
|
||||
router.post("/jobs", queueController.addJob);
|
||||
router.post("/jobs", addJob);
|
||||
|
||||
// Obliterate Queue
|
||||
router.post("/obliterate", queueController.obliterateQueue);
|
||||
router.post("/obliterate", obliterateQueue);
|
||||
|
||||
module.exports = router;
|
||||
export default router;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const router = require("express").Router();
|
||||
const settingsController = require("../controllers/settingsController");
|
||||
const { isAllowed } = require("../middleware/isAllowed");
|
||||
const Monitor = require("../db/models/Monitor");
|
||||
import { Router } from "express";
|
||||
import {
|
||||
getAppSettings,
|
||||
updateAppSettings,
|
||||
} from "../controllers/settingsController.js";
|
||||
import { isAllowed } from "../middleware/isAllowed.js";
|
||||
|
||||
router.get("/", settingsController.getAppSettings);
|
||||
router.put(
|
||||
"/",
|
||||
isAllowed(["superadmin"]),
|
||||
settingsController.updateAppSettings
|
||||
);
|
||||
const router = Router();
|
||||
|
||||
module.exports = router;
|
||||
router.get("/", getAppSettings);
|
||||
router.put("/", isAllowed(["superadmin"]), updateAppSettings);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const nodemailer = require("nodemailer");
|
||||
const { compile } = require("handlebars");
|
||||
const mjml2html = require("mjml");
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import nodemailer from "nodemailer";
|
||||
import pkg from "handlebars";
|
||||
const { compile } = pkg;
|
||||
import mjml2html from "mjml";
|
||||
import logger from "../utils/logger.js";
|
||||
import { fileURLToPath } from "url";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const SERVICE_NAME = "EmailService";
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
/**
|
||||
* Represents an email service that can load templates, build, and send emails.
|
||||
@@ -134,5 +139,4 @@ class EmailService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = EmailService;
|
||||
export default EmailService;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const { Queue, Worker, Job } = require("bullmq");
|
||||
import { Queue, Worker, Job } from "bullmq";
|
||||
const QUEUE_NAME = "monitors";
|
||||
|
||||
const JOBS_PER_WORKER = 5;
|
||||
const logger = require("../utils/logger");
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
import logger from "../utils/logger.js";
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
/**
|
||||
* JobQueue
|
||||
@@ -360,4 +360,4 @@ class JobQueue {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JobQueue;
|
||||
export default JobQueue;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const axios = require("axios");
|
||||
const ping = require("ping");
|
||||
const logger = require("../utils/logger");
|
||||
const http = require("http");
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import logger from "../utils/logger.js";
|
||||
import http from "http";
|
||||
import { errorMessages, successMessages } from "../utils/messages.js";
|
||||
|
||||
/**
|
||||
* NetworkService
|
||||
@@ -329,4 +329,4 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NetworkService;
|
||||
export default NetworkService;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { env } = require("process");
|
||||
const AppSettings = require("../db/models/AppSettings");
|
||||
import AppSettings from "../db/models/AppSettings.js";
|
||||
const SERVICE_NAME = "SettingsService";
|
||||
const envConfig = {
|
||||
logLevel: undefined,
|
||||
@@ -83,4 +82,4 @@ class SettingsService {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SettingsService;
|
||||
export default SettingsService;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,16 @@
|
||||
const {
|
||||
import {
|
||||
createCheck,
|
||||
getChecks,
|
||||
getTeamChecks,
|
||||
deleteChecks,
|
||||
deleteChecksByTeamId,
|
||||
updateChecksTTL,
|
||||
} = require("../../controllers/checkController");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { errorMessages, successMessages } = require("../../utils/messages");
|
||||
const sinon = require("sinon");
|
||||
|
||||
} from "../../controllers/checkController.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { errorMessages, successMessages } from "../../utils/messages.js";
|
||||
import sinon from "sinon";
|
||||
describe("Check Controller - createCheck", () => {
|
||||
let req, res, next, handleError;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
@@ -88,6 +88,7 @@ describe("Check Controller - createCheck", () => {
|
||||
});
|
||||
|
||||
describe("Check Controller - getChecks", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
@@ -143,6 +144,7 @@ describe("Check Controller - getChecks", () => {
|
||||
});
|
||||
|
||||
describe("Check Controller - getTeamChecks", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
@@ -197,6 +199,7 @@ describe("Check Controller - getTeamChecks", () => {
|
||||
});
|
||||
|
||||
describe("Check Controller - deleteChecks", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
@@ -248,6 +251,7 @@ describe("Check Controller - deleteChecks", () => {
|
||||
});
|
||||
|
||||
describe("Check Controller - deleteChecksByTeamId", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
params: {},
|
||||
@@ -301,6 +305,7 @@ describe("Check Controller - deleteChecksByTeamId", () => {
|
||||
});
|
||||
|
||||
describe("Check Controller - updateCheckTTL", () => {
|
||||
let stub, req, res, next;
|
||||
beforeEach(() => {
|
||||
stub = sinon.stub(jwt, "verify").callsFake(() => {
|
||||
return { teamId: "123" };
|
||||
|
||||
@@ -1,105 +1,210 @@
|
||||
const {
|
||||
handleValidationError,
|
||||
handleError,
|
||||
} = require("../../controllers/controllerUtils");
|
||||
import sinon from "sinon";
|
||||
|
||||
import {
|
||||
handleValidationError,
|
||||
handleError,
|
||||
fetchMonitorCertificate,
|
||||
} from "../../controllers/controllerUtils.js";
|
||||
import { expect } from "chai";
|
||||
|
||||
describe("controllerUtils - handleValidationError", () => {
|
||||
it("should set status to 422", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.status).to.equal(422);
|
||||
});
|
||||
it("should set status to 422", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.status).to.equal(422);
|
||||
});
|
||||
|
||||
it("should set service to the provided serviceName", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.service).to.equal(serviceName);
|
||||
});
|
||||
it("should set service to the provided serviceName", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.service).to.equal(serviceName);
|
||||
});
|
||||
|
||||
it("should set message to error.details[0].message if present", () => {
|
||||
const error = {
|
||||
details: [{ message: "Detail message" }],
|
||||
};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Detail message");
|
||||
});
|
||||
it("should set message to error.details[0].message if present", () => {
|
||||
const error = {
|
||||
details: [{ message: "Detail message" }],
|
||||
};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Detail message");
|
||||
});
|
||||
|
||||
it("should set message to error.message if error.details is not present", () => {
|
||||
const error = {
|
||||
message: "Error message",
|
||||
};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Error message");
|
||||
});
|
||||
it("should set message to error.message if error.details is not present", () => {
|
||||
const error = {
|
||||
message: "Error message",
|
||||
};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Error message");
|
||||
});
|
||||
|
||||
it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Validation Error");
|
||||
});
|
||||
it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const result = handleValidationError(error, serviceName);
|
||||
expect(result.message).to.equal("Validation Error");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleError", () => {
|
||||
it("should set stats to the provided status if error.code is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const status = 400;
|
||||
const result = handleError(error, serviceName, method, status);
|
||||
expect(result.status).to.equal(status);
|
||||
});
|
||||
describe("controllerUtils - handleError", () => {
|
||||
it("should set stats to the provided status if error.code is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const status = 400;
|
||||
const result = handleError(error, serviceName, method, status);
|
||||
expect(result.status).to.equal(status);
|
||||
});
|
||||
|
||||
it("should not overwrite error.code if it is already defined", () => {
|
||||
const error = { status: 404 };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const status = 400;
|
||||
const result = handleError(error, serviceName, method, status);
|
||||
expect(result.status).to.equal(404);
|
||||
});
|
||||
it("should not overwrite error.code if it is already defined", () => {
|
||||
const error = { status: 404 };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const status = 400;
|
||||
const result = handleError(error, serviceName, method, status);
|
||||
expect(result.status).to.equal(404);
|
||||
});
|
||||
|
||||
it("should set service to the provided serviceName if error.service is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.service).to.equal(serviceName);
|
||||
});
|
||||
it("should set service to the provided serviceName if error.service is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.service).to.equal(serviceName);
|
||||
});
|
||||
|
||||
it("should not overwrite error.service if it is already defined", () => {
|
||||
const error = { service: "ExistingService" };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.service).to.equal("ExistingService");
|
||||
});
|
||||
it("should not overwrite error.service if it is already defined", () => {
|
||||
const error = { service: "ExistingService" };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.service).to.equal("ExistingService");
|
||||
});
|
||||
|
||||
it("should set method to the provided method if error.method is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.method).to.equal(method);
|
||||
});
|
||||
it("should set method to the provided method if error.method is undefined", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.method).to.equal(method);
|
||||
});
|
||||
|
||||
it("should not overwrite error.method if it is already defined", () => {
|
||||
const error = { method: "existingMethod" };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.method).to.equal("existingMethod");
|
||||
});
|
||||
it("should not overwrite error.method if it is already defined", () => {
|
||||
const error = { method: "existingMethod" };
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.method).to.equal("existingMethod");
|
||||
});
|
||||
|
||||
it("should set code to 500 if error.code is undefined and no code is provided", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.status).to.equal(500);
|
||||
});
|
||||
it("should set code to 500 if error.code is undefined and no code is provided", () => {
|
||||
const error = {};
|
||||
const serviceName = "TestService";
|
||||
const method = "testMethod";
|
||||
const result = handleError(error, serviceName, method);
|
||||
expect(result.status).to.equal(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe("controllerUtils - fetchMonitorCertificate", () => {
|
||||
const originalTls = {
|
||||
connect: sinon.stub().callsFake((options, callback) => {
|
||||
// Create socket stub with sinon stubs for all methods
|
||||
socket = {
|
||||
getPeerX509Certificate: sinon.stub().returns({
|
||||
subject: "CN=fake-cert",
|
||||
validTo: "Dec 31 23:59:59 2023 GMT",
|
||||
}),
|
||||
end: sinon.stub(),
|
||||
on: sinon.stub(),
|
||||
};
|
||||
|
||||
// Use process.nextTick to ensure async behavior
|
||||
process.nextTick(() => {
|
||||
callback.call(socket); // Ensure correct 'this' binding
|
||||
});
|
||||
|
||||
return socket;
|
||||
}),
|
||||
};
|
||||
|
||||
let tls, monitor, socket;
|
||||
beforeEach(() => {
|
||||
monitor = { url: "https://www.google.com" };
|
||||
tls = {
|
||||
connect: sinon.stub().callsFake((options, callback) => {
|
||||
// Create socket stub with sinon stubs for all methods
|
||||
socket = {
|
||||
getPeerX509Certificate: sinon.stub().returns({
|
||||
subject: "CN=fake-cert",
|
||||
validTo: "Dec 31 23:59:59 2023 GMT",
|
||||
}),
|
||||
end: sinon.stub(),
|
||||
on: sinon.stub(),
|
||||
};
|
||||
|
||||
// Use process.nextTick to ensure async behavior
|
||||
process.nextTick(() => {
|
||||
callback.call(socket); // Ensure correct 'this' binding
|
||||
});
|
||||
|
||||
return socket;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tls = { ...originalTls };
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should resolve with the certificate when the connection is successful", async () => {
|
||||
const certificate = await fetchMonitorCertificate(tls, monitor);
|
||||
expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT");
|
||||
expect(socket.end.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it("should reject with an error when the connection fails", async () => {
|
||||
tls.connect = sinon.stub().throws(new Error("Connection error"));
|
||||
try {
|
||||
await fetchMonitorCertificate(tls, monitor);
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Connection error");
|
||||
}
|
||||
});
|
||||
|
||||
it("should reject with an error if monitorURL is invalid", async () => {
|
||||
monitor.url = "invalid-url";
|
||||
try {
|
||||
await fetchMonitorCertificate(tls, monitor);
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Invalid URL");
|
||||
}
|
||||
});
|
||||
it("should do a thing", async () => {
|
||||
tls = {
|
||||
connect: sinon.stub().callsFake((options, callback) => {
|
||||
// Create socket stub with sinon stubs for all methods
|
||||
socket = {
|
||||
getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")),
|
||||
end: sinon.stub(),
|
||||
on: sinon.stub(),
|
||||
};
|
||||
|
||||
// Use process.nextTick to ensure async behavior
|
||||
process.nextTick(() => {
|
||||
callback.call(socket); // Ensure correct 'this' binding
|
||||
});
|
||||
|
||||
return socket;
|
||||
}),
|
||||
};
|
||||
try {
|
||||
await fetchMonitorCertificate(tls, monitor);
|
||||
} catch (error) {
|
||||
expect(error.message).to.equal("Certificate error");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const {
|
||||
import {
|
||||
issueInvitation,
|
||||
inviteVerifyController,
|
||||
} = require("../../controllers/inviteController");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { errorMessages, successMessages } = require("../../utils/messages");
|
||||
const sinon = require("sinon");
|
||||
const joi = require("joi");
|
||||
} from "../../controllers/inviteController.js";
|
||||
import jwt from "jsonwebtoken";
|
||||
import sinon from "sinon";
|
||||
import joi from "joi";
|
||||
describe("inviteController - issueInvitation", () => {
|
||||
let req, res, next, stub;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: { authorization: "Bearer token" },
|
||||
@@ -154,6 +154,7 @@ describe("inviteController - issueInvitation", () => {
|
||||
});
|
||||
|
||||
describe("inviteController - inviteVerifyController", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: { token: "token" },
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
const {
|
||||
import {
|
||||
createMaintenanceWindows,
|
||||
getMaintenanceWindowById,
|
||||
getMaintenanceWindowsByTeamId,
|
||||
getMaintenanceWindowsByMonitorId,
|
||||
deleteMaintenanceWindow,
|
||||
editMaintenanceWindow,
|
||||
} = require("../../controllers/maintenanceWindowController");
|
||||
} from "../../controllers/maintenanceWindowController.js";
|
||||
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { errorMessages, successMessages } = require("../../utils/messages");
|
||||
const sinon = require("sinon");
|
||||
import jwt from "jsonwebtoken";
|
||||
import { successMessages } from "../../utils/messages.js";
|
||||
import sinon from "sinon";
|
||||
|
||||
describe("maintenanceWindowController - createMaintenanceWindows", () => {
|
||||
let req, res, next, stub;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {
|
||||
@@ -103,6 +104,7 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
|
||||
});
|
||||
|
||||
describe("maintenanceWindowController - getMaintenanceWindowById", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {},
|
||||
@@ -154,6 +156,7 @@ describe("maintenanceWindowController - getMaintenanceWindowById", () => {
|
||||
});
|
||||
|
||||
describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
|
||||
let req, res, next, stub;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {},
|
||||
@@ -220,6 +223,7 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
|
||||
});
|
||||
|
||||
describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {},
|
||||
@@ -283,6 +287,7 @@ describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () =>
|
||||
});
|
||||
|
||||
describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {},
|
||||
@@ -338,6 +343,7 @@ describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
|
||||
});
|
||||
|
||||
describe("maintenanceWindowController - editMaintenanceWindow", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
body: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,15 @@
|
||||
const { afterEach } = require("node:test");
|
||||
const {
|
||||
import { afterEach } from "node:test";
|
||||
import {
|
||||
getMetrics,
|
||||
getJobs,
|
||||
addJob,
|
||||
obliterateQueue,
|
||||
} = require("../../controllers/queueController");
|
||||
const SERVICE_NAME = "JobQueueController";
|
||||
|
||||
const { errorMessages, successMessages } = require("../../utils/messages");
|
||||
const sinon = require("sinon");
|
||||
} from "../../controllers/queueController.js";
|
||||
import { successMessages } from "../../utils/messages.js";
|
||||
import sinon from "sinon";
|
||||
|
||||
describe("Queue Controller - getMetrics", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -26,7 +25,6 @@ describe("Queue Controller - getMetrics", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
@@ -52,6 +50,7 @@ describe("Queue Controller - getMetrics", () => {
|
||||
});
|
||||
|
||||
describe("Queue Controller - getJobs", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -67,7 +66,6 @@ describe("Queue Controller - getJobs", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
@@ -92,6 +90,7 @@ describe("Queue Controller - getJobs", () => {
|
||||
});
|
||||
|
||||
describe("Queue Controller - addJob", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -107,7 +106,6 @@ describe("Queue Controller - addJob", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
@@ -130,6 +128,7 @@ describe("Queue Controller - addJob", () => {
|
||||
});
|
||||
|
||||
describe("Queue Controller - obliterateQueue", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -145,7 +144,6 @@ describe("Queue Controller - obliterateQueue", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const { afterEach } = require("node:test");
|
||||
const {
|
||||
import { afterEach } from "node:test";
|
||||
import {
|
||||
getAppSettings,
|
||||
updateAppSettings,
|
||||
} = require("../../controllers/settingsController");
|
||||
} from "../../controllers/settingsController.js";
|
||||
|
||||
const { errorMessages, successMessages } = require("../../utils/messages");
|
||||
const sinon = require("sinon");
|
||||
import { successMessages } from "../../utils/messages.js";
|
||||
import sinon from "sinon";
|
||||
|
||||
describe("Settings Controller - getAppSettings", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -23,7 +24,6 @@ describe("Settings Controller - getAppSettings", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
@@ -49,6 +49,7 @@ describe("Settings Controller - getAppSettings", () => {
|
||||
});
|
||||
|
||||
describe("Settings Controller - updateAppSettings", () => {
|
||||
let req, res, next;
|
||||
beforeEach(() => {
|
||||
req = {
|
||||
headers: {},
|
||||
@@ -66,7 +67,6 @@ describe("Settings Controller - updateAppSettings", () => {
|
||||
json: sinon.stub(),
|
||||
};
|
||||
next = sinon.stub();
|
||||
handleError = sinon.stub();
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
|
||||
68
Server/tests/utils/dataUtils.test.js
Normal file
68
Server/tests/utils/dataUtils.test.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js";
|
||||
import sinon from "sinon";
|
||||
|
||||
describe("NormalizeData", () => {
|
||||
it("should normalize response times when checks length is greater than 1", () => {
|
||||
const checks = [
|
||||
{ responseTime: 20, _doc: { id: 1 } },
|
||||
{ responseTime: 40, _doc: { id: 2 } },
|
||||
{ responseTime: 60, _doc: { id: 3 } },
|
||||
];
|
||||
const rangeMin = 1;
|
||||
const rangeMax = 100;
|
||||
|
||||
const result = NormalizeData(checks, rangeMin, rangeMax);
|
||||
|
||||
expect(result).to.be.an("array");
|
||||
expect(result).to.have.lengthOf(3);
|
||||
result.forEach((check) => {
|
||||
expect(check).to.have.property("responseTime").that.is.a("number");
|
||||
expect(check).to.have.property("originalResponseTime").that.is.a("number");
|
||||
});
|
||||
});
|
||||
|
||||
it("should return checks with original response times when checks length is 1", () => {
|
||||
const checks = [{ responseTime: 20, _doc: { id: 1 } }];
|
||||
const rangeMin = 1;
|
||||
const rangeMax = 100;
|
||||
|
||||
const result = NormalizeData(checks, rangeMin, rangeMax);
|
||||
expect(result).to.be.an("array");
|
||||
expect(result).to.have.lengthOf(1);
|
||||
expect(result[0]).to.have.property("originalResponseTime", 20);
|
||||
});
|
||||
|
||||
it("should handle edge cases with extreme response times", () => {
|
||||
const checks = [
|
||||
{ responseTime: 5, _doc: { id: 1 } },
|
||||
{ responseTime: 95, _doc: { id: 2 } },
|
||||
];
|
||||
const rangeMin = 1;
|
||||
const rangeMax = 100;
|
||||
|
||||
const result = NormalizeData(checks, rangeMin, rangeMax);
|
||||
|
||||
expect(result).to.be.an("array");
|
||||
expect(result).to.have.lengthOf(2);
|
||||
expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin);
|
||||
expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculatePercentile", () => {
|
||||
it("should return the lower value when upper is greater than or equal to the length of the sorted array", () => {
|
||||
const checks = [
|
||||
{ responseTime: 10 },
|
||||
{ responseTime: 20 },
|
||||
{ responseTime: 30 },
|
||||
{ responseTime: 40 },
|
||||
{ responseTime: 50 },
|
||||
];
|
||||
|
||||
const percentile = 100;
|
||||
const result = calculatePercentile(checks, percentile);
|
||||
const expected = 50;
|
||||
console.log(result);
|
||||
expect(result).to.equal(expected);
|
||||
});
|
||||
});
|
||||
57
Server/tests/utils/imageProcessing.test.js
Normal file
57
Server/tests/utils/imageProcessing.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { expect } from "chai";
|
||||
import sinon from "sinon";
|
||||
import sharp from "sharp";
|
||||
import { GenerateAvatarImage } from "../../utils/imageProcessing.js";
|
||||
|
||||
describe("imageProcessing - GenerateAvatarImage", () => {
|
||||
it("should resize the image to 64x64 and return a base64 string", async () => {
|
||||
const file = {
|
||||
buffer: Buffer.from("test image buffer"),
|
||||
};
|
||||
|
||||
// Stub the sharp function
|
||||
const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer"));
|
||||
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
|
||||
const sharpStub = sinon
|
||||
.stub(sharp.prototype, "resize")
|
||||
.returns({ toBuffer: toBufferStub });
|
||||
|
||||
const result = await GenerateAvatarImage(file);
|
||||
|
||||
// Verify the result
|
||||
const expected = Buffer.from("resized image buffer").toString("base64");
|
||||
expect(result).to.equal(expected);
|
||||
|
||||
// Verify that the sharp function was called with the correct arguments
|
||||
expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true;
|
||||
expect(toBufferStub.calledOnce).to.be.true;
|
||||
|
||||
// Restore the stubbed functions
|
||||
sharpStub.restore();
|
||||
});
|
||||
|
||||
it("should throw an error if resizing fails", async () => {
|
||||
const file = {
|
||||
buffer: Buffer.from("test image buffer"),
|
||||
};
|
||||
|
||||
// Stub the sharp function to throw an error
|
||||
const toBufferStub = sinon.stub().rejects(new Error("Resizing failed"));
|
||||
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
|
||||
const sharpStub = sinon
|
||||
.stub(sharp.prototype, "resize")
|
||||
.returns({ toBuffer: toBufferStub });
|
||||
|
||||
try {
|
||||
await GenerateAvatarImage(file);
|
||||
// If no error is thrown, fail the test
|
||||
expect.fail("Expected error to be thrown");
|
||||
} catch (error) {
|
||||
// Verify that the error message is correct
|
||||
expect(error.message).to.equal("Resizing failed");
|
||||
}
|
||||
|
||||
// Restore the stubbed functions
|
||||
sharpStub.restore();
|
||||
});
|
||||
});
|
||||
29
Server/tests/utils/messages.test.js
Normal file
29
Server/tests/utils/messages.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { errorMessages, successMessages } from "../../utils/messages.js";
|
||||
describe("Messages", () => {
|
||||
describe("messages - errorMessages", () => {
|
||||
it("should have a DB_FIND_MONITOR_BY_ID function", () => {
|
||||
const monitorId = "12345";
|
||||
expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(
|
||||
`Monitor with id ${monitorId} not found`
|
||||
);
|
||||
});
|
||||
|
||||
it("should have a DB_DELETE_CHECKS function", () => {
|
||||
const monitorId = "12345";
|
||||
expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(
|
||||
`No checks found for monitor with id ${monitorId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("messages - successMessages", () => {
|
||||
it("should have a MONITOR_GET_BY_USER_ID function", () => {
|
||||
const userId = "12345";
|
||||
expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(
|
||||
`Got monitor for ${userId} successfully"`
|
||||
);
|
||||
});
|
||||
|
||||
// Add more tests for other success messages as needed
|
||||
});
|
||||
});
|
||||
50
Server/tests/utils/utils.test.js
Normal file
50
Server/tests/utils/utils.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js";
|
||||
|
||||
describe("utils - ParseBoolean", () => {
|
||||
it("should return true", () => {
|
||||
const result = ParseBoolean("true");
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it("should return false", () => {
|
||||
const result = ParseBoolean("false");
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
|
||||
it("should return false", () => {
|
||||
const result = ParseBoolean(null);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
|
||||
it("should return false", () => {
|
||||
const result = ParseBoolean(undefined);
|
||||
expect(result).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("utils - getTokenFromHeaders", () => {
|
||||
it("should throw an error if authorization header is missing", () => {
|
||||
const headers = {};
|
||||
expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers");
|
||||
});
|
||||
|
||||
it("should throw an error if authorization header does not start with Bearer", () => {
|
||||
const headers = { authorization: "Basic abcdef" };
|
||||
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
|
||||
});
|
||||
|
||||
it("should return the token if authorization header is correctly formatted", () => {
|
||||
const headers = { authorization: "Bearer abcdef" };
|
||||
expect(getTokenFromHeaders(headers)).to.equal("abcdef");
|
||||
});
|
||||
|
||||
it("should throw an error if authorization header has more than two parts", () => {
|
||||
const headers = { authorization: "Bearer abc def" };
|
||||
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
|
||||
});
|
||||
|
||||
it("should throw an error if authorization header has less than two parts", () => {
|
||||
const headers = { authorization: "Bearer" };
|
||||
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
|
||||
});
|
||||
});
|
||||
@@ -1,50 +1,44 @@
|
||||
const calculatePercentile = (arr, percentile) => {
|
||||
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
const upper = lower + 1;
|
||||
const weight = index % 1;
|
||||
if (upper >= sorted.length) return sorted[lower].responseTime;
|
||||
return (
|
||||
sorted[lower].responseTime * (1 - weight) +
|
||||
sorted[upper].responseTime * weight
|
||||
);
|
||||
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
|
||||
const index = (percentile / 100) * (sorted.length - 1);
|
||||
const lower = Math.floor(index);
|
||||
const upper = lower + 1;
|
||||
const weight = index % 1;
|
||||
if (upper >= sorted.length) return sorted[lower].responseTime;
|
||||
return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight;
|
||||
};
|
||||
|
||||
const NormalizeData = (checks, rangeMin, rangeMax) => {
|
||||
if (checks.length > 1) {
|
||||
// Get the 5th and 95th percentile
|
||||
const min = calculatePercentile(checks, 0);
|
||||
const max = calculatePercentile(checks, 95);
|
||||
if (checks.length > 1) {
|
||||
// Get the 5th and 95th percentile
|
||||
const min = calculatePercentile(checks, 0);
|
||||
const max = calculatePercentile(checks, 95);
|
||||
|
||||
const normalizedChecks = checks.map((check) => {
|
||||
const originalResponseTime = check.responseTime;
|
||||
// Normalize the response time between 1 and 100
|
||||
let normalizedResponseTime =
|
||||
rangeMin +
|
||||
((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
||||
const normalizedChecks = checks.map((check) => {
|
||||
const originalResponseTime = check.responseTime;
|
||||
// Normalize the response time between 1 and 100
|
||||
let normalizedResponseTime =
|
||||
rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
|
||||
|
||||
// Put a floor on the response times so we don't have extreme outliers
|
||||
// Better visuals
|
||||
normalizedResponseTime = Math.max(
|
||||
rangeMin,
|
||||
Math.min(rangeMax, normalizedResponseTime)
|
||||
);
|
||||
return {
|
||||
...check._doc,
|
||||
responseTime: normalizedResponseTime,
|
||||
originalResponseTime: originalResponseTime,
|
||||
};
|
||||
});
|
||||
// Put a floor on the response times so we don't have extreme outliers
|
||||
// Better visuals
|
||||
normalizedResponseTime = Math.max(
|
||||
rangeMin,
|
||||
Math.min(rangeMax, normalizedResponseTime)
|
||||
);
|
||||
return {
|
||||
...check._doc,
|
||||
responseTime: normalizedResponseTime,
|
||||
originalResponseTime: originalResponseTime,
|
||||
};
|
||||
});
|
||||
|
||||
return normalizedChecks;
|
||||
} else {
|
||||
return checks.map((check) => {
|
||||
return { ...check._doc, originalResponseTime: check.responseTime };
|
||||
});
|
||||
}
|
||||
return normalizedChecks;
|
||||
} else {
|
||||
return checks.map((check) => {
|
||||
return { ...check._doc, originalResponseTime: check.responseTime };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
NormalizeData,
|
||||
};
|
||||
export { calculatePercentile, NormalizeData };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const sharp = require("sharp");
|
||||
import sharp from "sharp";
|
||||
/**
|
||||
* Generates a 64 * 64 pixel image from a given image
|
||||
* @param {} file
|
||||
@@ -22,6 +22,4 @@ const GenerateAvatarImage = async (file) => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
GenerateAvatarImage,
|
||||
};
|
||||
export { GenerateAvatarImage };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const winston = require('winston');
|
||||
import winston from "winston";
|
||||
/**
|
||||
* @module
|
||||
* @example
|
||||
* logger.info("Registered a new user!")
|
||||
* logger.info("Registered a new user!")
|
||||
* logger.warn("User not found!")
|
||||
* logger.error("Cannot save")
|
||||
* @example
|
||||
@@ -12,15 +12,15 @@ const winston = require('winston');
|
||||
* logger.error("User not found!",{"service":"Auth"})
|
||||
*/
|
||||
const logger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
level: "info",
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
new winston.transports.File({ filename: 'app.log' }),
|
||||
new winston.transports.File({ filename: "app.log" }),
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
export default logger;
|
||||
|
||||
@@ -121,7 +121,4 @@ const successMessages = {
|
||||
UPDATE_APP_SETTINGS: "Updated app settings successfully",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorMessages,
|
||||
successMessages,
|
||||
};
|
||||
export { errorMessages, successMessages };
|
||||
|
||||
@@ -4,36 +4,32 @@
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const ParseBoolean = (value) => {
|
||||
if (value === true || value === "true") {
|
||||
return true;
|
||||
} else if (
|
||||
value === false ||
|
||||
value === "false" ||
|
||||
value === null ||
|
||||
value === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (value === true || value === "true") {
|
||||
return true;
|
||||
} else if (
|
||||
value === false ||
|
||||
value === "false" ||
|
||||
value === null ||
|
||||
value === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getTokenFromHeaders = (headers) => {
|
||||
const authorizationHeader = headers.authorization;
|
||||
if (!authorizationHeader) throw new Error("No auth headers");
|
||||
const authorizationHeader = headers.authorization;
|
||||
if (!authorizationHeader) throw new Error("No auth headers");
|
||||
|
||||
const parts = authorizationHeader.split(" ");
|
||||
if (parts.length !== 2 || parts[0] !== "Bearer")
|
||||
throw new Error("Invalid auth headers");
|
||||
const parts = authorizationHeader.split(" ");
|
||||
if (parts.length !== 2 || parts[0] !== "Bearer")
|
||||
throw new Error("Invalid auth headers");
|
||||
|
||||
return parts[1];
|
||||
return parts[1];
|
||||
};
|
||||
|
||||
const tokenType = Object.freeze({
|
||||
ACCESS_TOKEN: "Access token",
|
||||
REFRESH_TOKEN: "Refresh token"
|
||||
ACCESS_TOKEN: "Access token",
|
||||
REFRESH_TOKEN: "Refresh token",
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
ParseBoolean,
|
||||
getTokenFromHeaders,
|
||||
tokenType,
|
||||
};
|
||||
export { ParseBoolean, getTokenFromHeaders, tokenType };
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
const exp = require("constants");
|
||||
const joi = require("joi");
|
||||
const { normalize } = require("path");
|
||||
const { start } = require("repl");
|
||||
import joi from "joi";
|
||||
|
||||
//****************************************
|
||||
// Custom Validators
|
||||
@@ -143,7 +140,7 @@ const inviteBodyValidation = joi.object({
|
||||
teamId: joi.string().required(),
|
||||
});
|
||||
|
||||
const inviteVerifciationBodyValidation = joi.object({
|
||||
const inviteVerificationBodyValidation = joi.object({
|
||||
token: joi.string().required(),
|
||||
});
|
||||
|
||||
@@ -427,7 +424,7 @@ const updateAppSettingsBodyValidation = joi.object({
|
||||
systemEmailPassword: joi.string().allow(""),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
export {
|
||||
roleValidatior,
|
||||
loginValidation,
|
||||
registrationBodyValidation,
|
||||
@@ -436,7 +433,7 @@ module.exports = {
|
||||
newPasswordValidation,
|
||||
inviteRoleValidation,
|
||||
inviteBodyValidation,
|
||||
inviteVerificationBodyValidation: inviteVerifciationBodyValidation,
|
||||
inviteVerificationBodyValidation,
|
||||
createMonitorBodyValidation,
|
||||
getMonitorByIdParamValidation,
|
||||
getMonitorByIdQueryValidation,
|
||||
|
||||
Reference in New Issue
Block a user