mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-12 20:59:41 -06:00
Merge branch 'bluewave-labs:master' into monitors
This commit is contained in:
@@ -1,19 +1,32 @@
|
||||
import PropTypes from "prop-types";
|
||||
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
|
||||
import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import "./index.css";
|
||||
|
||||
const ToastComponent = () => {
|
||||
/**
|
||||
* ToastComponent displays a notification that is triggered by error messages.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - The component props
|
||||
* @param {string} props.subject - The subject or title of the toast message
|
||||
* @param {string} props.body - The body content of the toast message
|
||||
* @param {string} props.esc - The text for the dismiss action button
|
||||
* @param {string} props.primary - The text for the primary action button
|
||||
* @returns {JSX.Element} JSX element representing the ToastComponent
|
||||
*/
|
||||
|
||||
const ToastComponent = ({ subject, body, esc, primary }) => {
|
||||
const displayMsg = () => {
|
||||
toast(
|
||||
({ closeToast, toastProps }) => (
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<ComplexAlert theme="red" />}
|
||||
subject="There was a problem with that action"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="Learn more"
|
||||
subject={subject}
|
||||
body={body}
|
||||
esc={esc}
|
||||
primary={primary}
|
||||
closeToast={closeToast}
|
||||
/>
|
||||
),
|
||||
@@ -29,4 +42,11 @@ const ToastComponent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
ToastComponent.propTypes = {
|
||||
subject: PropTypes.string.isRequired,
|
||||
body: PropTypes.string.isRequired,
|
||||
esc: PropTypes.string.isRequired,
|
||||
primary: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ToastComponent;
|
||||
|
||||
@@ -161,8 +161,10 @@ const deleteMonitor = async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const monitor = await req.db.deleteMonitor(req, res, next);
|
||||
req.jobQueue.deleteJob(monitor);
|
||||
|
||||
// Delete associated checks and alerts
|
||||
await req.jobQueue.deleteJob(monitor);
|
||||
await req.db.deleteChecks(monitor._id);
|
||||
await req.db.deleteAlertByMonitorId(monitor._id);
|
||||
/**
|
||||
* TODO
|
||||
* We should remove all checks and alerts associated with this monitor
|
||||
@@ -178,6 +180,18 @@ const deleteMonitor = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAllMonitors = async (req, res) => {
|
||||
try {
|
||||
const deleteCount = await req.db.deleteAllMonitors();
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: `Deleted ${deleteCount} monitors` });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Edit a monitor by ID
|
||||
* @async
|
||||
@@ -200,13 +214,11 @@ const editMonitor = async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const editedMonitor = await req.db.editMonitor(req, res);
|
||||
return res
|
||||
.status(200)
|
||||
.json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_EDIT,
|
||||
data: editedMonitor,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_EDIT,
|
||||
data: editedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -219,5 +231,6 @@ module.exports = {
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
};
|
||||
|
||||
@@ -258,6 +258,19 @@ const deleteMonitor = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DELETE ALL MONITORS (TEMP)
|
||||
*/
|
||||
|
||||
const deleteAllMonitors = async (req, res) => {
|
||||
try {
|
||||
const deletedCount = await Monitor.deleteMany({});
|
||||
return deletedCount.deletedCount;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Edit a monitor by ID
|
||||
* @async
|
||||
@@ -336,11 +349,7 @@ const getChecks = async (monitorId) => {
|
||||
const deleteChecks = async (monitorId) => {
|
||||
try {
|
||||
const result = await Check.deleteMany({ monitorId });
|
||||
if (result.deletedCount > 0) {
|
||||
return result.deletedCount;
|
||||
} else {
|
||||
throw new Error(errorMessages.DB_DELETE_CHECKS(monitorId));
|
||||
}
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -457,11 +466,16 @@ const editAlert = async (alertId, alertData) => {
|
||||
const deleteAlert = async (alertId) => {
|
||||
try {
|
||||
const result = await Alert.findByIdAndDelete(alertId);
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(errorMessages.DB_DELETE_ALERT(alertId));
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAlertByMonitorId = async (monitorId) => {
|
||||
try {
|
||||
const result = await Alert.deleteMany({ monitorId });
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
@@ -480,6 +494,7 @@ module.exports = {
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
createCheck,
|
||||
getChecks,
|
||||
@@ -490,4 +505,5 @@ module.exports = {
|
||||
getAlertById,
|
||||
editAlert,
|
||||
deleteAlert,
|
||||
deleteAlertByMonitorId,
|
||||
};
|
||||
|
||||
@@ -15,34 +15,36 @@ const verifyJWT = (req, res, next) => {
|
||||
const token = req.headers["authorization"];
|
||||
// Make sure a token is provided
|
||||
if (!token) {
|
||||
const error = new Error(error.errorMessages.NO_AUTH_TOKEN);
|
||||
const error = new Error(errorMessages.NO_AUTH_TOKEN);
|
||||
error.status = 401;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
// Make sure it is properly formatted
|
||||
if (token.startsWith(TOKEN_PREFIX)) {
|
||||
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
|
||||
// Verify the token's authenticity
|
||||
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
|
||||
if (err) {
|
||||
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
return res
|
||||
.status(401)
|
||||
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
|
||||
}
|
||||
//Add the user to the request object for use in the route
|
||||
req.user = decoded;
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
if (!token.startsWith(TOKEN_PREFIX)) {
|
||||
const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token
|
||||
error.status = 400;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
|
||||
// Verify the token's authenticity
|
||||
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
|
||||
if (err) {
|
||||
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
return res
|
||||
.status(401)
|
||||
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
|
||||
}
|
||||
//Add the user to the request object for use in the route
|
||||
req.user = decoded;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { verifyJWT };
|
||||
|
||||
@@ -20,7 +20,7 @@ const verifyOwnership = (Model, paramName) => {
|
||||
|
||||
// If the userID does not match the document's userID, return a 403 error
|
||||
if (userId.toString() !== doc.userId.toString()) {
|
||||
console.log("boom");
|
||||
console.log(userId.toString(), doc.userId.toString());
|
||||
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
|
||||
error.status = 403;
|
||||
throw error;
|
||||
|
||||
@@ -2,25 +2,42 @@ const router = require("express").Router();
|
||||
const alertController = require("../controllers/alertController");
|
||||
const { verifyOwnership } = require("../middleware/verifyOwnership");
|
||||
const Alert = require("../models/Alert");
|
||||
const Monitor = require("../models/Monitor");
|
||||
|
||||
// Create alert
|
||||
router.post("/:monitorId", alertController.createAlert);
|
||||
router.post(
|
||||
"/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.createAlert
|
||||
);
|
||||
// Get all alerts for a user
|
||||
router.get("/user/:userId", alertController.getAlertsByUserId);
|
||||
router.get(
|
||||
"/user/:userId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.getAlertsByUserId
|
||||
);
|
||||
// Get all alerts for a monitor
|
||||
router.get("/monitor/:monitorId", alertController.getAlertsByMonitorId);
|
||||
router.get(
|
||||
"/monitor/:monitorId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.getAlertsByMonitorId
|
||||
);
|
||||
// Get a single alert
|
||||
router.get("/:alertId", alertController.getAlertById);
|
||||
router.get(
|
||||
"/:alertId",
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.getAlertById
|
||||
);
|
||||
// Edit
|
||||
router.post(
|
||||
"/edit/:alertId",
|
||||
verifyOwnership(Alert, "alertId"),
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.editAlert
|
||||
);
|
||||
//Delete
|
||||
router.post(
|
||||
"/delete/:alertId",
|
||||
verifyOwnership(Alert, "alertId"),
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
alertController.deleteAlert
|
||||
);
|
||||
|
||||
|
||||
@@ -18,4 +18,6 @@ router.post(
|
||||
verifyOwnership(Monitor, "monitorId"),
|
||||
monitorController.editMonitor
|
||||
);
|
||||
|
||||
router.delete("/delete/all", monitorController.deleteAllMonitors);
|
||||
module.exports = router;
|
||||
|
||||
@@ -7,9 +7,8 @@ const connection = {
|
||||
const JOBS_PER_WORKER = 5;
|
||||
const logger = require("../utils/logger");
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const NetworkService = require("./networkService");
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
const axios = require("axios");
|
||||
const ping = require("ping");
|
||||
|
||||
class JobQueue {
|
||||
/**
|
||||
@@ -23,6 +22,7 @@ class JobQueue {
|
||||
});
|
||||
this.workers = [];
|
||||
this.db = null;
|
||||
this.networkService = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,6 +36,7 @@ class JobQueue {
|
||||
const queue = new JobQueue();
|
||||
try {
|
||||
queue.db = db;
|
||||
queue.networkService = new NetworkService(db);
|
||||
const monitors = await db.getAllMonitors();
|
||||
for (const monitor of monitors) {
|
||||
await queue.addJob(monitor.id, monitor);
|
||||
@@ -57,24 +58,14 @@ class JobQueue {
|
||||
const worker = new Worker(
|
||||
QUEUE_NAME,
|
||||
async (job) => {
|
||||
// TODO Extract to service
|
||||
if (job.data.type === "ping") {
|
||||
const response = await ping.promise.probe(job.data.url);
|
||||
// TODO insert checks into DB
|
||||
if (response.alive === true) {
|
||||
console.log(`${job.data.url} is alive`);
|
||||
} else {
|
||||
console.log(`${job.data.url} is dead`);
|
||||
}
|
||||
} else if (job.data.type === "http") {
|
||||
const response = await axios.get(job.data.url);
|
||||
// TODO create a check object and save it to the db
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
// TODO insert checks into DB
|
||||
console.log(`${job.data.url} is alive`);
|
||||
} else {
|
||||
console.log(`${job.data.url} is dead`);
|
||||
}
|
||||
try {
|
||||
const res = await this.networkService.getStatus(job);
|
||||
} catch (error) {
|
||||
logger.error(`Error processing job ${job.id}: ${error.message}`, {
|
||||
service: SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
162
Server/service/networkService.js
Normal file
162
Server/service/networkService.js
Normal file
@@ -0,0 +1,162 @@
|
||||
const axios = require("axios");
|
||||
const ping = require("ping");
|
||||
const logger = require("../utils/logger");
|
||||
const Check = require("../models/Check");
|
||||
|
||||
class NetworkService {
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
this.TYPE_PING = "ping";
|
||||
this.TYPE_HTTP = "http";
|
||||
this.SERVICE_NAME = "NetworkService";
|
||||
this.NETWORK_ERROR = 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the response time of an asynchronous operation.
|
||||
* @param {Function} operation - An asynchronous operation to measure.
|
||||
* @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation.
|
||||
* @throws {Error} The error object from the operation, contains response time.
|
||||
*/
|
||||
async measureResponseTime(operation) {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const response = await operation();
|
||||
const endTime = Date.now();
|
||||
return { responseTime: endTime - startTime, response };
|
||||
} catch (error) {
|
||||
const endTime = Date.now();
|
||||
error.responseTime = endTime - startTime;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the ping operation for a given job, measures its response time, and logs the result.
|
||||
* @param {Object} job - The job object containing data for the ping operation.
|
||||
* @returns {Promise<{boolean}} The result of logging and storing the check
|
||||
*/
|
||||
async handlePing(job) {
|
||||
const operation = async () => {
|
||||
const response = await ping.promise.probe(job.data.url);
|
||||
return response;
|
||||
};
|
||||
|
||||
try {
|
||||
const { responseTime, response } = await this.measureResponseTime(
|
||||
operation
|
||||
);
|
||||
const isAlive = response.alive;
|
||||
|
||||
const check = new Check({
|
||||
monitorId: job.data._id,
|
||||
status: isAlive,
|
||||
responseTime,
|
||||
});
|
||||
return await this.logAndStoreCheck(check);
|
||||
} catch (error) {
|
||||
const check = new Check({
|
||||
monitorId: job.data._id,
|
||||
status: false,
|
||||
responseTime: error.responseTime,
|
||||
});
|
||||
return await this.logAndStoreCheck(check);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the http operation for a given job, measures its response time, and logs the result.
|
||||
* @param {Object} job - The job object containing data for the ping operation.
|
||||
* @returns {Promise<{boolean}} The result of logging and storing the check
|
||||
*/
|
||||
async handleHttp(job) {
|
||||
// Define operation for timing
|
||||
const operation = async () => {
|
||||
const response = await axios.get(job.data.url);
|
||||
return response;
|
||||
};
|
||||
|
||||
// attempt connection
|
||||
try {
|
||||
const { responseTime, response } = await this.measureResponseTime(
|
||||
operation
|
||||
);
|
||||
|
||||
// check if response is in the 200 range, if so, service is up
|
||||
const isAlive = response.status >= 200 && response.status < 300;
|
||||
|
||||
//Create a check with relevant data
|
||||
const check = new Check({
|
||||
monitorId: job.data._id,
|
||||
status: isAlive,
|
||||
responseTime,
|
||||
statusCode: response.status,
|
||||
});
|
||||
return await this.logAndStoreCheck(check);
|
||||
} catch (error) {
|
||||
const check = new Check({
|
||||
monitorId: job.data._id,
|
||||
status: false,
|
||||
responseTime: error.responseTime,
|
||||
});
|
||||
// The server returned a response
|
||||
if (error.response) {
|
||||
check.statusCode = error.response.status;
|
||||
} else {
|
||||
check.statusCode = this.NETWORK_ERROR;
|
||||
}
|
||||
return await this.logAndStoreCheck(check);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the status of a given job based on its type.
|
||||
* For unsupported job types, it logs an error and returns false.
|
||||
*
|
||||
* @param {Object} job - The job object containing data necessary for processing.
|
||||
* @returns {Promise<boolean>} The status of the job if it is supported and processed successfully, otherwise false.
|
||||
*/
|
||||
async getStatus(job) {
|
||||
switch (job.data.type) {
|
||||
case this.TYPE_PING:
|
||||
return await this.handlePing(job);
|
||||
case this.TYPE_HTTP:
|
||||
return await this.handleHttp(job);
|
||||
default:
|
||||
logger.error(`Unsupported type: ${job.data.type}`, {
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs and stores the result of a check for a specific job.
|
||||
* This function creates a new Check object with the job's details and the result of the check,
|
||||
* then attempts to save this object to the database. If the save operation is successful,
|
||||
* it returns the status of the inserted check. If an error occurs during the save operation,
|
||||
* it logs the error and returns false.
|
||||
*
|
||||
* @param {Object} job - The job object containing data necessary for the check.
|
||||
* @param {boolean} isAlive - The result of the check, indicating if the target is alive.
|
||||
* @param {number} responseTime - The response time measured during the check.
|
||||
* @param {Error} [error=null] - Optional error object if an error occurred during the check.
|
||||
* @returns {Promise<boolean>} The status of the inserted check if successful, otherwise false.
|
||||
*/
|
||||
async logAndStoreCheck(check) {
|
||||
try {
|
||||
const insertedCheck = await check.save();
|
||||
return insertedCheck.status;
|
||||
} catch (error) {
|
||||
logger.error(`Error wrtiting check for ${job.id}`, {
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
error: error,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NetworkService;
|
||||
Reference in New Issue
Block a user