Merge pull request #830 from bluewave-labs/feat/load-monitors
Feat/load monitors, resolves #793
This commit is contained in:
14 files changed
+1560
-1316
No files matched your search
@@ -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";
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
Reference in new issue
Block a user