Refactored monitor details
This commit is contained in:
1 parent
da39dae0c4
commit
d7251bfd81
7 files changed
+269
-70
No files matched your search
@@ -119,7 +119,7 @@ const DetailsPage = () => {
|
||||
const fetchMonitor = useCallback(async () => {
|
||||
try {
|
||||
const res = await axiosInstance.get(
|
||||
`/monitors/${monitorId}?sortOrder=asc&filter=${dateRange}&numToDisplay=50&normalize=true`,
|
||||
`/monitors/stats/${monitorId}?filter=${dateRange}&numToDisplay=50&normalize=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
@@ -139,67 +139,6 @@ const DetailsPage = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
/**
|
||||
* Function to calculate uptime duration based on the most recent check.
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @returns {number} Uptime duration in ms.
|
||||
*/
|
||||
|
||||
// TODO: This can be done more efficiently by iteratting backwards
|
||||
// and breaking when the first down check is found, calculate current time - downtime
|
||||
const calculateUptimeDuration = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const latestCheck = new Date(checks[checks.length - 1].createdAt);
|
||||
let latestDownCheck = 0;
|
||||
|
||||
// Checks are ordered oldest -> newest
|
||||
// So we iterate backwards and find the first down check
|
||||
for (let i = checks.length - 1; i >= 0; i--) {
|
||||
if (checks[i].status === false) {
|
||||
latestDownCheck = new Date(checks[i].createdAt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no down check is found, uptime is from the first check to now
|
||||
if (latestDownCheck === 0) {
|
||||
return Date.now() - new Date(checks[0].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 oldest -> newest, so last check is the most recent
|
||||
return new Date() - new Date(checks[checks.length - 1].createdAt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to count incidents (checks with status === false).
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @returns {number} Number of incidents.
|
||||
*/
|
||||
const countIncidents = (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);
|
||||
};
|
||||
|
||||
let loading = Object.keys(monitor).length === 0;
|
||||
|
||||
return (
|
||||
@@ -243,7 +182,7 @@ const DetailsPage = () => {
|
||||
</Typography>{" "}
|
||||
Checking every {formatDurationRounded(monitor?.interval)}.
|
||||
Last time checked{" "}
|
||||
{formatDurationRounded(getLastChecked(monitor?.checks))} ago.
|
||||
{formatDurationRounded(monitor?.lastChecked)} ago.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
@@ -277,16 +216,13 @@ const DetailsPage = () => {
|
||||
>
|
||||
<StatBox
|
||||
title="Currently up for"
|
||||
value={formatDuration(calculateUptimeDuration(monitor.checks))}
|
||||
value={formatDuration(monitor?.uptimeDuration)}
|
||||
/>
|
||||
<StatBox
|
||||
title="Last checked"
|
||||
value={`${formatDuration(getLastChecked(monitor.checks))} ago`}
|
||||
/>
|
||||
<StatBox
|
||||
title="Incidents"
|
||||
value={countIncidents(monitor.checks)}
|
||||
value={`${formatDuration(monitor?.lastChecked)} ago`}
|
||||
/>
|
||||
<StatBox title="Incidents" value={monitor?.incidents} />
|
||||
</Stack>
|
||||
<Box>
|
||||
<Stack
|
||||
|
||||
@@ -33,6 +33,38 @@ const getAllMonitors = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns monitor stats for monitor with matching ID
|
||||
* @async
|
||||
* @param {Express.Request} req
|
||||
* @param {Express.Response} res
|
||||
* @returns {Promise<Express.Response>}
|
||||
* @throws {Error}
|
||||
*/
|
||||
const getMonitorStatsById = async (req, res, next) => {
|
||||
try {
|
||||
//Validation
|
||||
} catch (error) {
|
||||
error.status = 422;
|
||||
error.message =
|
||||
error.details?.[0]?.message || error.message || "Validation Error";
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const monitorStats = await req.db.getMonitorStatsById(req);
|
||||
return res.json({
|
||||
success: true,
|
||||
msg: successMessages.MONTIOR_STATS_BY_ID,
|
||||
data: monitorStats,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns monitor with matching ID
|
||||
* @async
|
||||
@@ -269,6 +301,7 @@ const editMonitor = async (req, res, next) => {
|
||||
|
||||
module.exports = {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
|
||||
@@ -64,6 +64,7 @@ const {
|
||||
|
||||
const {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
@@ -142,6 +143,7 @@ module.exports = {
|
||||
resetPassword,
|
||||
checkAdmin,
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
|
||||
@@ -33,7 +33,6 @@ const getChecksCount = async (req) => {
|
||||
const monitorId = req.params.monitorId;
|
||||
const dateRange = req.query.dateRange;
|
||||
const filter = req.query.filter;
|
||||
console.log(filter);
|
||||
// Build query
|
||||
const checksQuery = { monitorId: monitorId };
|
||||
// Filter checks by "day", "week", or "month"
|
||||
|
||||
@@ -22,6 +22,232 @@ const getAllMonitors = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to calculate uptime duration based on the most recent check.
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @returns {number} Uptime duration in ms.
|
||||
*/
|
||||
const caluclteUptimeDuration = (checks) => {
|
||||
if (!checks || checks.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const latestCheck = new Date(checks[0].createdAt);
|
||||
let latestDownCheck = 0;
|
||||
|
||||
for (let i = 0; i < checks.length; i++) {
|
||||
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 24h response time
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @returns {number} Timestamp of the most recent check.
|
||||
*/
|
||||
const getAverageResponseTime24Hours = (checks) => {
|
||||
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);
|
||||
};
|
||||
/**
|
||||
* Helper function to get all incidents
|
||||
* @param {Array} checks Array of check objects.
|
||||
* @returns {Array<Boolean>} Array of booleans representing up/down.
|
||||
*/
|
||||
const getStatusBarValues = (checks) => {
|
||||
return checks.map((check) => {
|
||||
return check.status;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Get stats by monitor ID
|
||||
* @async
|
||||
* @param {Express.Request} req
|
||||
* @param {Express.Response} res
|
||||
* @returns {Promise<Monitor>}
|
||||
* @throws {Error}
|
||||
*/
|
||||
const getMonitorStatsById = async (req) => {
|
||||
try {
|
||||
const { monitorId } = req.params;
|
||||
let { status, limit, sortOrder, filter, numToDisplay, normalize } =
|
||||
req.query;
|
||||
|
||||
// This effectively removes limit, returning all checks
|
||||
if (limit === undefined) limit = 0;
|
||||
|
||||
// Default sort order is newest -> oldest
|
||||
sortOrder = sortOrder === "asc" ? 1 : -1;
|
||||
|
||||
// Get monitor
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
|
||||
// Get all checks to calculate stats
|
||||
const checksQuery = { monitorId: monitor._id };
|
||||
let model =
|
||||
monitor.type === "http" || monitor.type === "ping"
|
||||
? Check
|
||||
: PageSpeedCheck;
|
||||
|
||||
const filterLookup = {
|
||||
hour: new Date(new Date().getTime() - 60 * 60 * 1000),
|
||||
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)),
|
||||
};
|
||||
|
||||
// Get all checks
|
||||
const checksAll = await model.find(checksQuery).sort({
|
||||
createdAt: sortOrder,
|
||||
});
|
||||
// Get checks in 24 hours
|
||||
checksQuery.createdAt = { $gte: filterLookup.day };
|
||||
const checks24Hours = await model.find(checksQuery).sort({
|
||||
createdAt: sortOrder,
|
||||
});
|
||||
// Get checks in 30 days
|
||||
checksQuery.createdAt = { $gte: filterLookup.month };
|
||||
const checks30Days = await model.find(checksQuery).sort({
|
||||
createdAt: sortOrder,
|
||||
});
|
||||
// Get checks in 60 mins
|
||||
checksQuery.createdAt = { $gte: filterLookup.hour };
|
||||
const checks60Mins = await model
|
||||
.find(checksQuery)
|
||||
.sort({ createdAt: sortOrder });
|
||||
|
||||
//Get checks for dateRange
|
||||
|
||||
if (status !== undefined) {
|
||||
checksQuery.status = status;
|
||||
}
|
||||
|
||||
// Filter checks by "day", "week", or "month"
|
||||
if (filter !== undefined) {
|
||||
checksQuery.createdAt = { $gte: filterLookup[filter] };
|
||||
}
|
||||
|
||||
let dateRangeChecks = await model
|
||||
.find(checksQuery)
|
||||
.sort({
|
||||
createdAt: sortOrder,
|
||||
})
|
||||
.limit(limit);
|
||||
const incidents = getIncidents(dateRangeChecks);
|
||||
|
||||
// If more than numToDisplay checks, pick every nth check
|
||||
if (
|
||||
numToDisplay !== undefined &&
|
||||
dateRangeChecks &&
|
||||
dateRangeChecks.length > numToDisplay
|
||||
) {
|
||||
const n = Math.ceil(dateRangeChecks.length / numToDisplay);
|
||||
dateRangeChecks = dateRangeChecks.filter((_, index) => index % n === 0);
|
||||
}
|
||||
|
||||
// Normalize checks if requested
|
||||
if (normalize !== undefined) {
|
||||
dateRangeChecks = NormalizeData(dateRangeChecks, 1, 100);
|
||||
}
|
||||
|
||||
const uptimeDuration = caluclteUptimeDuration(checksAll);
|
||||
const lastChecked = getLastChecked(checksAll);
|
||||
const latestResponseTime = getLatestResponseTime(checksAll);
|
||||
const avgResponseTime24hours = getAverageResponseTime24Hours(checks24Hours);
|
||||
const uptime24Hours = getUptimePercentage(checks24Hours);
|
||||
const uptime30Days = getUptimePercentage(checks30Days);
|
||||
const statusBar = getStatusBarValues(checks60Mins);
|
||||
const monitorStats = {
|
||||
...monitor.toObject(),
|
||||
uptimeDuration,
|
||||
lastChecked,
|
||||
latestResponseTime,
|
||||
avgResponseTime24hours,
|
||||
uptime24Hours,
|
||||
uptime30Days,
|
||||
statusBar,
|
||||
incidents,
|
||||
dateRangeChecks,
|
||||
};
|
||||
|
||||
return monitorStats;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a monitor by ID
|
||||
* @async
|
||||
@@ -263,6 +489,7 @@ const editMonitor = async (req, res) => {
|
||||
|
||||
module.exports = {
|
||||
getAllMonitors,
|
||||
getMonitorStatsById,
|
||||
getMonitorById,
|
||||
getMonitorsByUserId,
|
||||
createMonitor,
|
||||
|
||||
@@ -4,6 +4,7 @@ const { verifyOwnership } = require("../middleware/verifyOwnership");
|
||||
const Monitor = require("../models/Monitor");
|
||||
|
||||
router.get("/", monitorController.getAllMonitors);
|
||||
router.get("/stats/:monitorId", monitorController.getMonitorStatsById);
|
||||
router.get("/:monitorId", monitorController.getMonitorById);
|
||||
router.get("/user/:userId", monitorController.getMonitorsByUserId);
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ const successMessages = {
|
||||
|
||||
//Monitor Controller
|
||||
MONITOR_GET_ALL: "Got all monitors successfully",
|
||||
MONTIOR_STATS_BY_ID: "Got monitor stats by Id successfully",
|
||||
MONTIOR_GET_BY_ID: "Got monitor by Id successfully",
|
||||
MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`,
|
||||
MONITOR_CREATE: "Monitor created successfully",
|
||||
|
||||
Reference in new issue
Block a user