Resolve conflicts

This commit is contained in:
Daniel Cojocea committed 2024-09-13 11:37:31 -04:00
commit ee82a20b4d
22 files changed
+1610 -1349

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ function App() {
element={<ProtectedRoute Component={Configure} />}
/>
<Route
path="incidents"
path="incidents/:monitorId?"
element={<ProtectedRoute Component={Incidents} />}
/>
@@ -164,6 +164,44 @@ export const deleteMonitorChecksByTeamId = createAsyncThunk(
}
}
);
export const addDemoMonitors = createAsyncThunk(
"monitors/addDemoMonitors",
async (data, thunkApi) => {
try {
const { authToken } = data;
const res = await networkService.addDemoMonitors(authToken);
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
export const deleteAllMonitors = createAsyncThunk(
"monitors/deleteAllMonitors",
async (data, thunkApi) => {
try {
const { authToken } = data;
const res = await networkService.deleteAllMonitors(authToken);
return res.data;
} catch (error) {
if (error.response && error.response.data) {
return thunkApi.rejectWithValue(error.response.data);
}
const payload = {
status: false,
msg: error.message ? error.message : "Unknown error",
};
return thunkApi.rejectWithValue(payload);
}
}
);
const uptimeMonitorsSlice = createSlice({
name: "uptimeMonitors",
@@ -306,6 +344,42 @@ const uptimeMonitorsSlice = createSlice({
state.msg = action.payload
? action.payload.msg
: "Failed to pause uptime monitor";
})
// *****************************************************
// Add Demo Monitors
// *****************************************************
.addCase(addDemoMonitors.pending, (state) => {
state.isLoading = true;
})
.addCase(addDemoMonitors.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(addDemoMonitors.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to add demo uptime monitors";
})
// *****************************************************
// Delete all Monitors
// *****************************************************
.addCase(deleteAllMonitors.pending, (state) => {
state.isLoading = true;
})
.addCase(deleteAllMonitors.fulfilled, (state, action) => {
state.isLoading = false;
state.success = action.payload.success;
state.msg = action.payload.msg;
})
.addCase(deleteAllMonitors.rejected, (state, action) => {
state.isLoading = false;
state.success = false;
state.msg = action.payload
? action.payload.msg
: "Failed to delete all monitors";
});
},
});
+4
View File
@@ -1,6 +1,8 @@
import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { ButtonGroup, Stack, Typography, Button } from "@mui/material";
import { useParams } from "react-router-dom";
import { networkService } from "../../main";
import { useTheme } from "@emotion/react";
import Select from "../../Components/Inputs/Select";
@@ -11,6 +13,7 @@ import SkeletonLayout from "./skeleton";
const Incidents = () => {
const theme = useTheme();
const authState = useSelector((state) => state.auth);
const { monitorId } = useParams();
const [monitors, setMonitors] = useState({});
const [selectedMonitor, setSelectedMonitor] = useState("0");
@@ -40,6 +43,7 @@ const Incidents = () => {
return acc;
}, {});
setMonitors(monitorLookup);
monitorId !== undefined && setSelectedMonitor(monitorId);
}
setLoading(false);
};
+18 -9
View File
@@ -125,7 +125,14 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
Details
</MenuItem>
{/* TODO - pass monitor id to Incidents page */}
<MenuItem disabled>Incidents</MenuItem>
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/incidents/${actions.id}`);
}}
>
Incidents
</MenuItem>
{isAdmin && (
<MenuItem
onClick={(e) => {
@@ -137,14 +144,16 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
Configure
</MenuItem>
)}
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/monitors/create/${actions.id}`);
}}
>
Clone
</MenuItem>
{isAdmin && (
<MenuItem
onClick={(e) => {
e.stopPropagation();
navigate(`/monitors/create/${actions.id}`);
}}
>
Clone
</MenuItem>
)}
{isAdmin && (
<MenuItem
onClick={(e) => {
+7
View File
@@ -20,6 +20,12 @@ const useUtils = () => {
down: "Your site is down.",
paused: "Pending...",
};
const pagespeedStatusMsg = {
up: "Live (collecting data)",
down: "Inactive",
paused: "Paused",
};
const statusStyles = {
up: {
backgroundColor: theme.palette.success.bg,
@@ -51,6 +57,7 @@ const useUtils = () => {
determineState,
statusColor,
statusMsg,
pagespeedStatusMsg,
statusStyles,
};
};
+5 -11
View File
@@ -23,6 +23,7 @@ import LoadingButton from "@mui/lab/LoadingButton";
import PlayCircleOutlineRoundedIcon from "@mui/icons-material/PlayCircleOutlineRounded";
import SkeletonLayout from "./skeleton";
import "./index.css";
import useUtils from "../../Monitors/utils";
const PageSpeedConfigure = () => {
const theme = useTheme();
@@ -34,6 +35,7 @@ const PageSpeedConfigure = () => {
const { monitorId } = useParams();
const [monitor, setMonitor] = useState({});
const [errors, setErrors] = useState({});
const { determineState, statusColor } = useUtils();
const frequencies = [
{ _id: 3, name: "3 minutes" },
@@ -147,13 +149,7 @@ const PageSpeedConfigure = () => {
gap={theme.spacing(12)}
>
<Stack direction="row" gap={theme.spacing(2)}>
<PulseDot
color={
monitor?.status
? theme.palette.success.main
: theme.palette.error.main
}
/>
<PulseDot color={statusColor[determineState(monitor)]} />
<Box>
<Typography
component="h1"
@@ -166,12 +162,10 @@ const PageSpeedConfigure = () => {
<Typography
component="span"
sx={{
color: monitor?.status
? theme.palette.success.main
: theme.palette.error.text,
color: statusColor[determineState(monitor)],
}}
>
Your pagespeed monitor is {monitor?.status ? "live" : "down"}.
Your pagespeed monitor is {determineState(monitor)}.
</Typography>
</Box>
<Box alignSelf="flex-end" ml="auto">
+1 -1
View File
@@ -7,7 +7,6 @@ import {
Tooltip,
Typography,
} from "@mui/material";
import { useDrawingArea } from "@mui/x-charts";
import { useEffect, useState } from "react";
import { useTheme } from "@emotion/react";
import { useNavigate, useParams } from "react-router-dom";
@@ -39,6 +38,7 @@ const PageSpeedDetails = () => {
const [audits, setAudits] = useState({});
const { monitorId } = useParams();
const { authToken } = useSelector((state) => state.auth);
const { determineState, statusColor, pagespeedStatusMsg } = useUtils();
useEffect(() => {
const fetchMonitor = async () => {
+13 -10
View File
@@ -5,12 +5,14 @@ import { StatusLabel } from "../../Components/Label";
import { Box, Grid, Stack, Typography } from "@mui/material";
import { useNavigate } from "react-router";
import { useTheme } from "@emotion/react";
import useUtils from "../Monitors/utils";
import PropTypes from "prop-types";
const Card = ({ data }) => {
const Card = ({ monitor }) => {
const { determineState, pagespeedStatusMsg } = useUtils();
const theme = useTheme();
const navigate = useNavigate();
const monitorState = determineState(monitor);
return (
<Grid
item
@@ -26,7 +28,7 @@ const Card = ({ data }) => {
direction="row"
gap={theme.spacing(6)}
p={theme.spacing(8)}
onClick={() => navigate(`/pagespeed/${data._id}`)}
onClick={() => navigate(`/pagespeed/${monitor._id}`)}
border={1}
borderColor={theme.palette.border.light}
borderRadius={theme.shape.borderRadius}
@@ -46,23 +48,24 @@ const Card = ({ data }) => {
mb={theme.spacing(2)}
color={theme.palette.primary.main}
>
{data.name}
{monitor.name}
</Typography>
<StatusLabel
status={data.status ? "up" : "cannot resolve"}
text={data.status ? "Live (collecting data)" : "Inactive"}
status={monitorState}
text={pagespeedStatusMsg[monitorState] || "Pending..."}
customStyles={{ textTransform: "capitalize" }}
/>
</Stack>
<Typography fontSize={13}>
{data.url.replace(/^https?:\/\//, "")}
{monitor.url.replace(/^https?:\/\//, "")}
</Typography>
<Typography mt={theme.spacing(12)}>
<Typography component="span" fontWeight={600}>
Last checked:{" "}
</Typography>
{formatDate(getLastChecked(data.checks, false))}{" "}
{formatDate(getLastChecked(monitor.checks, false))}{" "}
<Typography component="span" fontStyle="italic">
({formatDurationRounded(getLastChecked(data.checks))} ago)
({formatDurationRounded(getLastChecked(monitor.checks))} ago)
</Typography>
</Typography>
</Box>
@@ -72,7 +75,7 @@ const Card = ({ data }) => {
};
Card.propTypes = {
data: PropTypes.object.isRequired,
monitor: PropTypes.object.isRequired,
};
export default Card;
+1 -1
View File
@@ -74,7 +74,7 @@ const PageSpeed = ({ isAdmin }) => {
</Box>
<Grid container spacing={theme.spacing(12)}>
{monitorsSummary?.monitors?.map((monitor) => (
<Card data={monitor} key={monitor._id} />
<Card monitor={monitor} key={monitor._id} />
))}
</Grid>
</Box>
+70 -2
View File
@@ -1,5 +1,5 @@
import { useTheme } from "@emotion/react";
import { Box, Button, Stack, styled, Typography } from "@mui/material";
import { Box, Stack, styled, Typography } from "@mui/material";
import Field from "../../Components/Inputs/Field";
import Link from "../../Components/Link";
import Select from "../../Components/Inputs/Select";
@@ -7,7 +7,11 @@ import { logger } from "../../Utils/Logger";
import "./index.css";
import { useDispatch, useSelector } from "react-redux";
import { createToast } from "../../Utils/toastUtils";
import { deleteMonitorChecksByTeamId } from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import {
deleteMonitorChecksByTeamId,
addDemoMonitors,
deleteAllMonitors,
} from "../../Features/UptimeMonitors/uptimeMonitorsSlice";
import PropTypes from "prop-types";
import LoadingButton from "@mui/lab/LoadingButton";
const Settings = ({ isAdmin }) => {
@@ -36,6 +40,34 @@ const Settings = ({ isAdmin }) => {
}
};
const handleInsertDemoMonitors = async () => {
try {
const action = await dispatch(addDemoMonitors({ authToken }));
if (addDemoMonitors.fulfilled.match(action)) {
createToast({ body: "Successfully added demo monitors" });
} else {
createToast({ body: "Failed to add demo monitors" });
}
} catch (error) {
logger.error(error);
createToast({ Body: "Failed to add demo monitors" });
}
};
const handleDeleteAllMonitors = async () => {
try {
const action = await dispatch(deleteAllMonitors({ authToken }));
if (deleteAllMonitors.fulfilled.match(action)) {
createToast({ body: "Successfully deleted all monitors" });
} else {
createToast({ body: "Failed to add demo monitors" });
}
} catch (error) {
logger.error(error);
createToast({ Body: "Failed to delete all monitors" });
}
};
const ConfigBox = styled("div")({
display: "flex",
flexDirection: "row",
@@ -140,6 +172,42 @@ const Settings = ({ isAdmin }) => {
</Stack>
</ConfigBox>
)}
{isAdmin && (
<ConfigBox>
<Box>
<Typography component="h1">Demo Monitors</Typography>
<Typography sx={{ mt: theme.spacing(2) }}>
Here you can add and remove demo monitors
</Typography>
</Box>
<Stack gap={theme.spacing(20)}>
<Box>
<Typography>Add demo monitors</Typography>
<LoadingButton
variant="contained"
color="primary"
loading={isLoading}
onClick={handleInsertDemoMonitors}
sx={{ mt: theme.spacing(4) }}
>
Add demo monitors
</LoadingButton>
</Box>
<Box>
<Typography>Remove all monitors</Typography>
<LoadingButton
variant="contained"
color="error"
loading={isLoading}
onClick={handleDeleteAllMonitors}
sx={{ mt: theme.spacing(4) }}
>
Remove all monitors
</LoadingButton>
</Box>
</Stack>
</ConfigBox>
)}
<ConfigBox>
<Box>
<Typography component="h1">About</Typography>
+40
View File
@@ -274,6 +274,46 @@ class NetworkService {
);
}
/**
* ************************************
* Adds demo monitors
* ************************************
*
* @async
* @param {string} authToken - The authorization token to be used in the request header.
* @returns {Promise<AxiosResponse>} The response from the axios POST request.
*/
async addDemoMonitors(authToken) {
return this.axiosInstance.post(
`/monitors/demo`,
{},
{
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
}
);
}
/**
* ************************************
* Deletes all monitors for a team by team ID
* ************************************
*
* @async
* @param {string} authToken - The authorization token to be used in the request header.
* @returns {Promise<AxiosResponse>} The response from the axios DELETE request.
*/
async deleteAllMonitors(authToken) {
return this.axiosInstance.delete(`/monitors/`, {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
},
});
}
/**
* ************************************
* Gets the certificate expiry for a monitor
File diff suppressed because it is too large. Load diff
+1 -11
View File
@@ -17,6 +17,7 @@ require("dotenv").config();
const { errorMessages, successMessages } = require("../utils/messages");
var jwt = require("jsonwebtoken");
const SERVICE_NAME = "auth";
const { getTokenFromHeaders } = require("../utils/utils");
/**
* Creates and returns JWT token with an arbitrary payload
@@ -30,17 +31,6 @@ const issueToken = (payload) => {
return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: tokenTTL });
};
const getTokenFromHeaders = (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");
return parts[1];
};
/**
* @function
* @param {express.Request} req
+38 -2
View File
@@ -18,6 +18,9 @@ const {
const sslChecker = require("ssl-checker");
const SERVICE_NAME = "monitorController";
const { errorMessages, successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
/**
* Returns all monitors
* @async
@@ -365,12 +368,24 @@ const deleteMonitor = async (req, res, next) => {
const deleteAllMonitors = async (req, res) => {
try {
const deleteCount = await req.db.deleteAllMonitors();
console.log("WTF");
const token = getTokenFromHeaders(req.headers);
const { teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
await monitors.forEach(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deleteAlertByMonitorId(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
});
return res
.status(200)
.json({ success: true, msg: `Deleted ${deleteCount} monitors` });
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
} catch (error) {
error.service = SERVICE_NAME;
error.method === undefined && error.method === "deleteAllMonitors";
next(error);
}
};
@@ -466,6 +481,26 @@ const pauseMonitor = async (req, res, next) => {
}
};
const addDemoMonitors = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { _id, teamId } = jwt.verify(token, process.env.JWT_SECRET);
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
await demoMonitors.forEach(async (monitor) => {
await req.jobQueue.addJob(monitor._id, monitor);
});
return res.status(200).json({
success: true,
message: successMessages.MONITOR_DEMO_ADDED,
data: demoMonitors.length,
});
} catch (error) {
error.service = SERVICE_NAME;
error.method === undefined && error.method === "addDemoMonitors";
next(error);
}
};
module.exports = {
getAllMonitors,
getMonitorAggregateStats,
@@ -479,4 +514,5 @@ module.exports = {
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
};
+2
View File
@@ -73,6 +73,7 @@ const {
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
} = require("./modules/monitorModule");
//****************************************
@@ -153,6 +154,7 @@ module.exports = {
deleteMonitor,
deleteAllMonitors,
editMonitor,
addDemoMonitors,
createCheck,
getChecksCount,
getChecks,
+10 -5
View File
@@ -1,6 +1,7 @@
const Check = require("../../../models/Check");
const Monitor = require("../../../models/Monitor");
const mongoose = require("mongoose");
const logger = require("../../../utils/logger");
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
@@ -24,21 +25,25 @@ const createCheck = async (checkData) => {
try {
const { monitorId, status } = checkData;
const n = await Check.countDocuments({ monitorId }) + 1;
const n = (await Check.countDocuments({ monitorId })) + 1;
const check = await new Check({ ...checkData }).save();
const monitor = await Monitor.findById(monitorId);
if (!monitor) {
throw new Error("Monitor not found");
logger.error("Monitor not found", {
service: SERVICE_NAME,
monitorId,
});
return;
}
if (monitor.uptimePercentage === undefined) {
monitor.uptimePercentage = (status === true) ? 1 : 0;
monitor.uptimePercentage = status === true ? 1 : 0;
} else {
monitor.uptimePercentage =
(monitor.uptimePercentage * (n - 1) + (status === true ? 1: 0)) / n;
(monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n;
}
await monitor.save();
+29 -3
View File
@@ -4,6 +4,7 @@ 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");
/**
* Get all monitors
@@ -450,10 +451,12 @@ const deleteMonitor = async (req, res) => {
* DELETE ALL MONITORS (TEMP)
*/
const deleteAllMonitors = async (req, res) => {
const deleteAllMonitors = async (teamId) => {
try {
const deletedCount = await Monitor.deleteMany({});
return deletedCount.deletedCount;
const monitors = await Monitor.find({ teamId });
const { deletedCount } = await Monitor.deleteMany({ teamId });
return { monitors, deletedCount };
} catch (error) {
throw error;
}
@@ -493,6 +496,28 @@ const editMonitor = async (candidateId, candidateMonitor) => {
);
return editedMonitor;
} catch (error) {
error.method = "editMonitor";
throw error;
}
};
const addDemoMonitors = async (userId, teamId) => {
try {
const demoMonitorsToInsert = demoMonitors.map((monitor) => {
return {
userId,
teamId,
name: monitor.name,
description: monitor.name,
type: "http",
url: monitor.url,
interval: 60000,
};
});
const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert);
return insertedMonitors;
} catch (error) {
error.method = "addDemoMonitors";
throw error;
}
};
@@ -508,4 +533,5 @@ module.exports = {
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
};
+7 -1
View File
@@ -32,7 +32,7 @@ router.put(
);
router.delete(
"/all",
"/",
isAllowed(["superadmin"]),
monitorController.deleteAllMonitors
);
@@ -43,4 +43,10 @@ router.post(
monitorController.pauseMonitor
);
router.post(
"/demo",
isAllowed(["admin", "superadmin"]),
monitorController.addDemoMonitors
);
module.exports = router;
+5 -2
View File
@@ -278,6 +278,7 @@ class NetworkService {
default:
logger.error(`Unsupported type: ${job.data.type}`, {
service: this.SERVICE_NAME,
method: "getStatus",
jobId: job.id,
});
return false;
@@ -296,11 +297,13 @@ class NetworkService {
async logAndStoreCheck(data, writeToDB) {
try {
const insertedCheck = await writeToDB(data);
return insertedCheck.status;
if (insertedCheck !== null && insertedCheck !== undefined) {
return insertedCheck.status;
}
} catch (error) {
console.log(error);
logger.error(`Error wrtiting check for ${data.monitorId}`, {
service: this.SERVICE_NAME,
method: "logAndStoreCheck",
monitorId: data.monitorId,
error: error,
});
File diff suppressed because it is too large. Load diff
+1
View File
@@ -83,6 +83,7 @@ const successMessages = {
MONITOR_DELETE: "Monitor deleted successfully",
MONITOR_EDIT: "Monitor edited successfully",
MONITOR_CERTIFICATE: "Got monitor certificate successfully",
MONITOR_DEMO_ADDED: "Successfully added demo monitors",
//Job Queue
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
+12
View File
@@ -16,6 +16,18 @@ const ParseBoolean = (value) => {
}
};
const getTokenFromHeaders = (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");
return parts[1];
};
module.exports = {
ParseBoolean,
getTokenFromHeaders,
};