Files
Checkmate/Server/db/mongo/modules/monitorModule.js
T

599 lines
16 KiB
JavaScript
Raw Normal View History

2024-10-15 11:57:37 +08:00
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";
2024-07-26 18:25:20 -07:00
/**
* Get all monitors
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Array<Monitor>>}
* @throws {Error}
*/
const getAllMonitors = async (req, res) => {
try {
const monitors = await Monitor.find();
2024-07-26 18:25:20 -07:00
return monitors;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getAllMonitors";
2024-07-26 18:25:20 -07:00
throw error;
}
};
2024-08-12 12:05:57 -07:00
/**
* Function to calculate uptime duration based on the most recent check.
* @param {Array} checks Array of check objects.
* @returns {number} Uptime duration in ms.
*/
2024-08-12 15:07:03 -07:00
const calculateUptimeDuration = (checks) => {
2024-08-12 12:05:57 -07:00
if (!checks || checks.length === 0) {
return 0;
}
const latestCheck = new Date(checks[0].createdAt);
let latestDownCheck = 0;
2024-09-04 11:09:08 -07:00
for (let i = checks.length; i <= 0; i--) {
2024-08-12 12:05:57 -07:00
if (checks[i].status === false) {
latestDownCheck = new Date(checks[i].createdAt);
break;
}
}
// If no down check is found, uptime is from the last check to now
if (latestDownCheck === 0) {
return Date.now() - new Date(checks[checks.length - 1].createdAt);
}
// Otherwise the uptime is from the last check to the last down check
return latestCheck - latestDownCheck;
};
/**
* Helper function to get duration since last check
* @param {Array} checks Array of check objects.
* @returns {number} Timestamp of the most recent check.
*/
const getLastChecked = (checks) => {
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
// Data is sorted newest->oldest, so last check is the most recent
return new Date() - new Date(checks[0].createdAt);
};
/**
* Helper function to get latestResponseTime
* @param {Array} checks Array of check objects.
* @returns {number} Timestamp of the most recent check.
*/
const getLatestResponseTime = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
return checks[0].responseTime;
};
/**
* Helper function to get average response time
2024-08-12 12:05:57 -07:00
* @param {Array} checks Array of check objects.
* @returns {number} Timestamp of the most recent check.
*/
const getAverageResponseTime = (checks) => {
2024-08-12 12:05:57 -07:00
if (!checks || checks.length === 0) {
return 0;
}
const aggResponseTime = checks.reduce((sum, check) => {
return sum + check.responseTime;
}, 0);
return aggResponseTime / checks.length;
};
/**
* Helper function to get precentage 24h uptime
* @param {Array} checks Array of check objects.
* @returns {number} Timestamp of the most recent check.
*/
const getUptimePercentage = (checks) => {
if (!checks || checks.length === 0) {
return 0;
}
const upCount = checks.reduce((count, check) => {
return check.status === true ? count + 1 : count;
}, 0);
return (upCount / checks.length) * 100;
};
/**
* Helper function to get all incidents
* @param {Array} checks Array of check objects.
* @returns {number} Timestamp of the most recent check.
*/
const getIncidents = (checks) => {
if (!checks || checks.length === 0) {
return 0; // Handle case when no checks are available
}
return checks.reduce((acc, check) => {
return check.status === false ? (acc += 1) : acc;
}, 0);
};
2024-08-12 12:05:57 -07:00
/**
* Get stats by monitor ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const getMonitorStatsById = async (req) => {
2024-09-04 11:02:18 -07:00
const startDates = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
week: new Date(new Date().setDate(new Date().getDate() - 7)),
month: new Date(new Date().setMonth(new Date().getMonth() - 1)),
};
2024-09-04 11:02:18 -07:00
const endDate = new Date();
2024-08-12 12:05:57 -07:00
try {
2024-09-04 11:02:18 -07:00
// Get monitor
2024-08-12 12:05:57 -07:00
const { monitorId } = req.params;
2024-09-04 11:02:18 -07:00
let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query;
const monitor = await Monitor.findById(monitorId);
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
2024-09-04 11:02:18 -07:00
}
2024-08-12 12:05:57 -07:00
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
// Default sort order is newest -> oldest
sortOrder = sortOrder === "asc" ? 1 : -1;
let model =
monitor.type === "http" || monitor.type === "ping"
? Check
: PageSpeedCheck;
const monitorStats = {
...monitor.toObject(),
2024-08-12 12:05:57 -07:00
};
2024-08-13 10:32:29 -07:00
2024-09-04 11:02:18 -07:00
// Build checks query
const checksQuery = { monitorId: monitor._id };
2024-08-13 10:32:29 -07:00
2024-08-12 12:05:57 -07:00
// Get all checks
const checksAll = await model.find(checksQuery).sort({
createdAt: sortOrder,
});
2024-09-04 11:02:18 -07:00
const checksQueryForDateRange = {
...checksQuery,
createdAt: {
$gte: startDates[dateRange],
$lte: endDate,
},
};
2024-08-13 10:32:29 -07:00
2024-09-04 11:02:18 -07:00
const checksForDateRange = await model
.find(checksQueryForDateRange)
.sort({ createdAt: sortOrder });
2024-09-04 11:02:18 -07:00
if (monitor.type === "http" || monitor.type === "ping") {
// HTTP/PING Specific stats
2024-09-04 11:45:54 -07:00
monitorStats.periodAvgResponseTime =
getAverageResponseTime(checksForDateRange);
monitorStats.periodUptime = getUptimePercentage(checksForDateRange);
2024-09-04 11:25:08 -07:00
// Aggregate data
let groupedChecks;
// Group checks by hour if range is day
if (dateRange === "day") {
groupedChecks = checksForDateRange.reduce((acc, check) => {
const time = new Date(check.createdAt);
time.setMinutes(0, 0, 0);
if (!acc[time]) {
acc[time] = { time, checks: [] };
}
acc[time].checks.push(check);
return acc;
}, {});
2024-09-04 11:45:54 -07:00
} else {
groupedChecks = checksForDateRange.reduce((acc, check) => {
2024-09-04 11:54:20 -07:00
const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part
if (!acc[time]) {
acc[time] = { time, checks: [] };
2024-09-04 11:45:54 -07:00
}
2024-09-04 11:54:20 -07:00
acc[time].checks.push(check);
2024-09-04 11:45:54 -07:00
return acc;
}, {});
2024-09-04 11:25:08 -07:00
}
// Map grouped checks to stats
const aggregateData = Object.values(groupedChecks).map((group) => {
const totalChecks = group.checks.length;
2024-09-04 13:21:51 -07:00
const uptimePercentage = getUptimePercentage(group.checks);
2024-09-04 11:25:08 -07:00
const totalIncidents = group.checks.filter(
(check) => check.status === false
).length;
const avgResponseTime =
group.checks.reduce((sum, check) => sum + check.responseTime, 0) /
totalChecks;
return {
time: group.time,
2024-09-04 13:21:51 -07:00
uptimePercentage,
2024-09-04 11:25:08 -07:00
totalChecks,
totalIncidents,
avgResponseTime,
};
});
monitorStats.aggregateData = aggregateData;
2024-08-12 12:05:57 -07:00
}
2024-09-04 11:45:54 -07:00
monitorStats.periodIncidents = getIncidents(checksForDateRange);
2024-09-04 12:15:53 -07:00
monitorStats.periodTotalChecks = checksForDateRange.length;
2024-08-12 12:05:57 -07:00
2024-09-04 11:02:18 -07:00
// If more than numToDisplay checks, pick every nth check
2024-08-12 12:05:57 -07:00
2024-09-04 11:02:18 -07:00
let nthChecks = checksForDateRange;
2024-08-12 12:05:57 -07:00
if (
numToDisplay !== undefined &&
2024-09-04 11:02:18 -07:00
checksForDateRange &&
checksForDateRange.length > numToDisplay
2024-08-12 12:05:57 -07:00
) {
2024-09-04 11:02:18 -07:00
const n = Math.ceil(checksForDateRange.length / numToDisplay);
nthChecks = checksForDateRange.filter((_, index) => index % n === 0);
2024-08-12 12:05:57 -07:00
}
// Normalize checks if requested
if (normalize !== undefined) {
2024-09-04 17:41:28 -07:00
const normailzedChecks = NormalizeData(nthChecks, 1, 100);
2024-09-04 11:02:18 -07:00
monitorStats.checks = normailzedChecks;
} else {
monitorStats.checks = nthChecks;
2024-08-12 12:05:57 -07:00
}
monitorStats.uptimeDuration = calculateUptimeDuration(checksAll);
monitorStats.lastChecked = getLastChecked(checksAll);
monitorStats.latestResponseTime = getLatestResponseTime(checksAll);
2024-09-04 11:02:18 -07:00
2024-08-12 12:05:57 -07:00
return monitorStats;
} catch (error) {
error.service = SERVICE_NAME;
2024-09-14 12:16:13 +08:00
error.method = "getMonitorStatsById";
2024-08-19 13:33:50 -07:00
throw error;
2024-08-12 12:05:57 -07:00
}
};
2024-07-26 18:25:20 -07:00
/**
* Get a monitor by ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const getMonitorById = async (monitorId) => {
2024-07-26 18:25:20 -07:00
try {
2024-07-26 18:56:39 -07:00
const monitor = await Monitor.findById(monitorId);
2024-08-31 16:48:02 -07:00
if (monitor === null || monitor === undefined) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
2024-08-31 16:48:02 -07:00
}
2024-08-30 10:46:51 -07:00
// Get notifications
const notifications = await Notification.find({
monitorId: monitorId,
});
2024-08-30 13:03:59 -07:00
monitor.notifications = notifications;
const monitorWithNotifications = await monitor.save();
return monitorWithNotifications;
2024-07-26 18:25:20 -07:00
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorById";
2024-07-26 18:25:20 -07:00
throw error;
}
};
2024-09-05 11:17:31 -07:00
/**
* Get monitors and Summary by TeamID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Array<Monitor>>}
* @throws {Error}
*/
const getMonitorsAndSummaryByTeamId = async (teamId, type) => {
try {
const monitors = await Monitor.find({ teamId, type });
const monitorCounts = monitors.reduce(
(acc, monitor) => {
if (monitor.status === true) {
acc.up += 1;
}
if (monitor.status === false) {
2024-09-05 12:16:29 -07:00
acc.down += 1;
2024-09-05 11:17:31 -07:00
}
if (monitor.isActive === false) {
acc.paused += 1;
}
2024-09-05 12:16:29 -07:00
return acc;
2024-09-05 11:17:31 -07:00
},
{ up: 0, down: 0, paused: 0 }
);
monitorCounts.total = monitors.length;
return { monitors, monitorCounts };
} catch (error) {
error.service = SERVICE_NAME;
2024-09-05 12:16:29 -07:00
error.method = "getMonitorsAndSummaryByTeamId";
2024-09-05 11:17:31 -07:00
throw error;
}
};
2024-07-26 18:25:20 -07:00
/**
* Get monitors by TeamID
2024-07-26 18:25:20 -07:00
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Array<Monitor>>}
* @throws {Error}
*/
const getMonitorsByTeamId = async (req, res) => {
2024-07-26 18:25:20 -07:00
try {
2024-09-18 10:32:41 +08:00
let {
limit,
2024-09-04 23:09:14 -07:00
type,
2024-09-18 10:32:41 +08:00
status,
checkOrder,
normalize,
page,
rowsPerPage,
filter,
2024-09-18 10:39:47 +08:00
field,
order,
2024-09-18 10:32:41 +08:00
} = req.query || {};
const monitorQuery = { teamId: req.params.teamId };
if (type !== undefined) {
monitorQuery.type = type;
}
2024-09-18 10:32:41 +08:00
// Add filter if provided
2024-09-18 10:39:47 +08:00
// $options: "i" makes the search case-insensitive
2024-09-18 10:32:41 +08:00
if (filter !== undefined) {
monitorQuery.$or = [
{ name: { $regex: filter, $options: "i" } },
{ url: { $regex: filter, $options: "i" } },
];
}
const monitorsCount = await Monitor.countDocuments(monitorQuery);
2024-07-26 18:25:20 -07:00
2024-09-04 17:37:30 -07:00
// Pagination
let skip = 0;
if (page && rowsPerPage) {
skip = page * rowsPerPage;
}
2024-07-26 18:25:20 -07:00
2024-07-26 18:56:39 -07:00
if (type !== undefined) {
const types = Array.isArray(type) ? type : [type];
monitorQuery.type = { $in: types };
}
2024-07-26 18:25:20 -07:00
2024-07-26 18:56:39 -07:00
// Default sort order is newest -> oldest
2024-09-18 10:32:41 +08:00
if (checkOrder === "asc") {
checkOrder = 1;
} else if (checkOrder === "desc") {
checkOrder = -1;
} else checkOrder = -1;
2024-07-26 18:25:20 -07:00
2024-09-18 10:39:47 +08:00
// Sort order for monitors
let sort = {};
if (field !== undefined && order !== undefined) {
sort[field] = order === "asc" ? 1 : -1;
}
2024-09-04 17:37:30 -07:00
const monitors = await Monitor.find(monitorQuery)
.skip(skip)
2024-09-18 10:39:47 +08:00
.limit(rowsPerPage)
.sort(sort);
2024-09-17 08:12:23 +08:00
// Early return if limit is set to -1, indicating we don't want any checks
2024-09-17 08:46:06 +08:00
if (limit === "-1") {
2024-09-17 08:12:23 +08:00
return { monitors, monitorCount: monitorsCount };
}
// This effectively removes limit, returning all checks
if (limit === undefined) limit = 0;
2024-07-26 18:25:20 -07:00
// Map each monitor to include its associated checks
const monitorsWithChecks = await Promise.all(
monitors.map(async (monitor) => {
2024-07-26 18:56:39 -07:00
const checksQuery = { monitorId: monitor._id };
if (status !== undefined) {
checksQuery.status = status;
}
let model =
monitor.type === "http" || monitor.type === "ping"
? Check
: PageSpeedCheck;
// Checks are order newest -> oldest
2024-08-08 11:20:54 -07:00
let checks = await model
2024-07-26 18:56:39 -07:00
.find(checksQuery)
.sort({
2024-09-18 10:32:41 +08:00
createdAt: checkOrder,
2024-07-26 18:56:39 -07:00
})
.limit(limit);
2024-08-08 11:20:54 -07:00
//Normalize checks if requested
if (normalize !== undefined) {
2024-08-08 11:20:54 -07:00
checks = NormalizeData(checks, 10, 100);
}
return { ...monitor.toObject(), checks };
2024-07-26 18:25:20 -07:00
})
);
2024-09-04 18:42:50 -07:00
return { monitors: monitorsWithChecks, monitorCount: monitorsCount };
2024-07-26 18:25:20 -07:00
} catch (error) {
error.service = SERVICE_NAME;
error.method = "getMonitorsByTeamId";
2024-07-26 18:25:20 -07:00
throw error;
}
};
/**
* Create a monitor
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const createMonitor = async (req, res) => {
try {
const monitor = new Monitor({ ...req.body });
// Remove notifications fom monitor as they aren't needed here
2024-07-31 12:27:40 -07:00
monitor.notifications = undefined;
2024-07-26 18:25:20 -07:00
await monitor.save();
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "createMonitor";
2024-07-26 18:25:20 -07:00
throw error;
}
};
/**
* Delete a monitor by ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const deleteMonitor = async (req, res) => {
const monitorId = req.params.monitorId;
try {
const monitor = await Monitor.findByIdAndDelete(monitorId);
if (!monitor) {
throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId));
2024-07-26 18:25:20 -07:00
}
return monitor;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitor";
2024-07-26 18:25:20 -07:00
throw error;
}
};
/**
* DELETE ALL MONITORS (TEMP)
*/
2024-09-11 11:22:33 +08:00
const deleteAllMonitors = async (teamId) => {
2024-07-26 18:25:20 -07:00
try {
2024-09-11 11:22:33 +08:00
const monitors = await Monitor.find({ teamId });
const { deletedCount } = await Monitor.deleteMany({ teamId });
return { monitors, deletedCount };
2024-07-26 18:25:20 -07:00
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteAllMonitors";
2024-07-26 18:25:20 -07:00
throw error;
}
};
/**
* Delete all monitors associated with a user ID
* @async
* @param {string} userId - The ID of the user whose monitors are to be deleted.
* @returns {Promise} A promise that resolves when the operation is complete.
*/
const deleteMonitorsByUserId = async (userId) => {
try {
const result = await Monitor.deleteMany({ userId: userId });
return result;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "deleteMonitorsByUserId";
2024-07-26 18:25:20 -07:00
throw error;
}
};
/**
* Edit a monitor by ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const editMonitor = async (candidateId, candidateMonitor) => {
candidateMonitor.notifications = undefined;
2024-07-26 18:25:20 -07:00
try {
const editedMonitor = await Monitor.findByIdAndUpdate(
candidateId,
candidateMonitor,
{ new: true }
);
return editedMonitor;
} catch (error) {
error.service = SERVICE_NAME;
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.service = SERVICE_NAME;
error.method = "addDemoMonitors";
2024-07-26 18:25:20 -07:00
throw error;
}
};
2024-10-15 11:57:37 +08:00
export {
2024-07-26 18:25:20 -07:00
getAllMonitors,
2024-08-12 12:05:57 -07:00
getMonitorStatsById,
2024-07-26 18:25:20 -07:00
getMonitorById,
2024-09-05 11:17:31 -07:00
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
2024-07-26 18:25:20 -07:00
createMonitor,
deleteMonitor,
deleteAllMonitors,
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
2024-07-26 18:25:20 -07:00
};