Merge remote-tracking branch 'upstream/develop' into feat/monitor-details-revamp

This commit is contained in:
Daniel Cojocea committed 2024-09-03 18:16:51 -04:00
commit cd53f44928
15 files changed
+1145 -64

No files matched your search

+2 -2
View File
@@ -31,7 +31,7 @@ const icons = {
const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
const theme = useTheme();
const { text, light, border } = theme.palette[variant];
const { text, bg, border } = theme.palette[variant];
const icon = icons[variant];
return (
@@ -45,7 +45,7 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
padding: hasIcon
? theme.spacing(8)
: `${theme.spacing(4)} ${theme.spacing(8)}`,
backgroundColor: light,
backgroundColor: bg,
border: `solid 1px ${border}`,
borderRadius: theme.shape.borderRadius,
}}
Whitespace-only changes.
@@ -1,43 +0,0 @@
import PropTypes from "prop-types";
import { Stack } from "@mui/material";
import { BarChart, Bar, ResponsiveContainer, Cell } from "recharts";
import "./index.css";
const ResponseTimeChart = ({ checks = [] }) => {
return (
<Stack
flexDirection="row"
justifyContent="space-around"
alignItems="flex-end"
height="50px"
width="300px"
>
<ResponsiveContainer width="100%" height="100%">
<BarChart
width={150}
height={40}
data={checks}
style={{ cursor: "pointer" }}
>
<Bar maxBarSize={10} dataKey="responseTime">
{checks.map((check, index) => (
<Cell
key={`cell-${index}`}
fill={
check.status === true
? "var(--success-color)"
: "var(--error-color)"
}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</Stack>
);
};
ResponseTimeChart.propTypes = {
checks: PropTypes.array,
};
export default ResponseTimeChart;
@@ -0,0 +1,116 @@
import { Autocomplete, TextField } from "@mui/material";
import React from "react";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
/**
* @example
*
* const options = [
* { _id: "66d6119ef959cbc681e034f0", name: "Googler" },
* { _id: "66d6119ef959cbc681e034f0", name: "CNN" },
* { _id: "66d61a1bf959cbc681e0353f", name: "X Corp." },
* ];
*
* <AutoCompleteField options={options} />
*
*/
/**
* AutoCompleteField component.
*
* @component
* @param {Object} props - The component props.
* @param {string} props.id - The ID of the autocomplete field.
* @param {string} props.type - The type of the input field (text or number).
* @param {Array} props.options - The options for the autocomplete field.
* @param {string} props.placeholder - The placeholder text for the input field.
* @param {boolean} props.disabled - Indicates if the field is disabled.
* @returns {JSX.Element} The AutoCompleteField component.
*/
const AutoCompleteField = ({
id,
type,
options,
placeholder = "Type to search",
disabled,
}) => {
const [value, setValue] = React.useState();
const [inputValue, setInputValue] = React.useState("");
const theme = useTheme();
return (
<Autocomplete
freeSolo
className="auto-complete-field"
id={id}
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
options={options}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<TextField
{...params}
type={type}
placeholder={placeholder}
disabled={disabled}
InputProps={{
...params.InputProps,
sx: {
width: 360,
height: 34,
fontSize: 13,
p: 0,
borderRadius: theme.shape.borderRadius,
"& input": {
p: 0,
},
},
}}
/>
)}
renderOption={(props, option) => {
const { key, ...optionProps } = props;
return (
<li key={option._id} {...optionProps}>
<div>{<span>{option.name}</span>}</div>
</li>
);
}}
slotProps={{
popper: {
sx: {
"& ul": { p: 0 },
"& li": { borderRadius: theme.shape.borderRadius },
},
},
paper: {
sx: {
p: 2,
fontSize: 13,
},
},
}}
/>
);
};
AutoCompleteField.displayName = "AutoCompleteField";
AutoCompleteField.propTypes = {
id: PropTypes.string,
type: PropTypes.oneOf(["text", "number"]),
options: PropTypes.array,
placeholder: PropTypes.string,
disabled: PropTypes.bool,
setWidth: PropTypes.string,
};
export default AutoCompleteField;
+2 -1
View File
@@ -53,7 +53,8 @@ Host.propTypes = {
params: PropTypes.shape({
title: PropTypes.string,
percentageColor: PropTypes.string,
percentage: PropTypes.number,
percentage: PropTypes.string,
url: PropTypes.string,
}).isRequired,
};
+30 -11
View File
@@ -27,30 +27,52 @@ const data = {
rows: [],
};
/**
* Builds table data for a list of monitors.
*
* @param {Array} monitors - An array of monitor objects containing information about each monitor.
* @param {boolean} isAdmin - Flag indicating if the current user is an admin.
* @param {Function} navigate - A function to navigate to the monitor detail page.
* @returns {Object} The data structure containing columns and rows for the table.
*/
export const buildData = (monitors, isAdmin, navigate) => {
const theme = useTheme();
data.rows = monitors.map((monitor, idx) => {
let uptimePercentage = "";
let percentageColor = theme.palette.percentage.uptimeExcellent;
// Determine uptime percentage and color based on the monitor's uptimePercentage value
if (monitor.uptimePercentage !== undefined) {
uptimePercentage =
monitor.uptimePercentage === 0
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
? "0"
: (monitor.uptimePercentage * 100).toFixed(2);
percentageColor =
monitor.uptimePercentage < 0.25
? theme.palette.percentage.uptimePoor
: monitor.uptimePercentage < 0.5
? theme.palette.percentage.uptimeFair
: monitor.uptimePercentage < 0.75
? theme.palette.percentage.uptimeGood
: theme.palette.percentage.uptimeExcellent;
}
const params = {
url: monitor.url,
title: monitor.name,
percentage: uptimePercentage,
percentageColor:
monitor.status === true
? theme.palette.success.main
: theme.palette.error.text,
status: monitor.status === true ? "up" : "down",
percentageColor,
status:
monitor.status === undefined
? "unknown"
: monitor.status === true
? "up"
: "down",
};
// Reverse checks so latest check is on the right
// Reverse checks so the latest check is on the right
const reversedChecks = monitor.checks.slice().reverse();
return {
@@ -86,6 +108,3 @@ export const buildData = (monitors, isAdmin, navigate) => {
});
return data;
};
+1 -1
View File
@@ -116,7 +116,7 @@ class NetworkService {
* @param {string} [sortOrder] - The order in which to sort the retrieved statistics.
* @param {number} [limit] - The maximum number of statistics to retrieve.
* @param {string} [dateRange] - The date range for which to retrieve statistics.
* @param {number} [numToDisplay] - The number of statistics to display.
* @param {number} [numToDisplay] - The number of checks to display.
* @param {boolean} [normalize] - Whether to normalize the retrieved statistics.
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
*/
+6
View File
@@ -57,6 +57,12 @@ const darkTheme = createTheme({
bg: "#624711",
border: "#e88c30",
},
percentage: {
uptimePoor: "#d32f2f",
uptimeFair: "#e88c30",
uptimeGood: "#ffd600",
uptimeExcellent: "#079455",
},
unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" },
divider: border.light,
other: {
+6
View File
@@ -54,6 +54,12 @@ const lightTheme = createTheme({
bg: "#ffecbc",
border: "#fec84b",
},
percentage: {
uptimePoor: "#d32f2f",
uptimeFair: "#ec8013",
uptimeGood: "#ffb800",
uptimeExcellent: "#079455",
},
unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" },
divider: border.light,
other: {
+41 -1
View File
@@ -6,12 +6,13 @@ const {
editMonitorBodyValidation,
getMonitorsByTeamIdQueryValidation,
pauseMonitorParamValidation,
getMonitorAggregateStatsParamValidation,
getMonitorAggregateStatsQueryValidation,
} = require("../validation/joi");
const sslChecker = require("ssl-checker");
const SERVICE_NAME = "monitorController";
const { errorMessages, successMessages } = require("../utils/messages");
const { runInNewContext } = require("vm");
/**
* Returns all monitors
@@ -35,6 +36,44 @@ const getAllMonitors = async (req, res, next) => {
}
};
/**
* Returns agregate stats for a monitor
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Express.Response>}
* @throws {Error}
*/
const getMonitorAggregateStats = async (req, res, next) => {
try {
await getMonitorAggregateStatsParamValidation.validateAsync(req.params);
await getMonitorAggregateStatsQueryValidation.validateAsync(req.query);
} catch (error) {
error.status = 422;
error.message =
error.details?.[0]?.message || error.message || "Validation Error";
next(error);
return;
}
try {
const { monitorId } = req.params;
const dateRange = req.query.dateRange;
const aggregateStats = await req.db.getMonitorAggregateStats(
monitorId,
dateRange
);
return res.json({
success: true,
msg: successMessages.MONTIOR_STATS_BY_ID,
data: aggregateStats,
});
} catch (error) {
next(error);
}
};
/**
* Returns monitor stats for monitor with matching ID
* @async
@@ -376,6 +415,7 @@ const pauseMonitor = async (req, res, next) => {
module.exports = {
getAllMonitors,
getMonitorAggregateStats,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
+2
View File
@@ -64,6 +64,7 @@ const {
const {
getAllMonitors,
getMonitorAggregateStats,
getMonitorStatsById,
getMonitorById,
getMonitorsByTeamId,
@@ -144,6 +145,7 @@ module.exports = {
resetPassword,
checkSuperadmin,
getAllMonitors,
getMonitorAggregateStats,
getMonitorStatsById,
getMonitorById,
getMonitorsByTeamId,
+94 -5
View File
@@ -143,6 +143,87 @@ const getStatusBarValues = (monitor, checks) => {
}
return statusBarValues.reverse();
};
/**
* Get aggregate monitor stats for charts
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<Monitor>}
* @throws {Error}
*/
const getMonitorAggregateStats = async (monitorId, dateRange) => {
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)),
};
try {
const startDate = startDates[dateRange];
if (!startDate) {
throw new Error("Invalid date range specified");
}
const endDate = new Date();
const checks = await Check.find({
monitorId: monitorId,
createdAt: {
$gte: startDate,
$lte: endDate,
},
});
let groupedChecks;
// Group checks by hour if range is day
if (dateRange === "day") {
groupedChecks = checks.reduce((acc, check) => {
const hour = new Date(check.createdAt).getHours();
if (!acc[hour]) {
acc[hour] = { hour, checks: [] };
}
acc[hour].checks.push(check);
return acc;
}, {});
}
// Group checks by day if range is week or month
else {
groupedChecks = checks.reduce((acc, check) => {
const day = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part
if (!acc[day]) {
acc[day] = { day, checks: [] };
}
acc[day].checks.push(check);
return acc;
}, {});
}
// Map grouped checks to stats
stats = Object.values(groupedChecks).map((group) => {
const totalChecks = group.checks.length;
const totalIncidents = group.checks.filter(
(check) => check.status === false
).length;
const avgResponseTime =
group.checks.reduce((sum, check) => sum + check.responseTime, 0) /
totalChecks;
return {
hour: group.hour,
day: group.day,
totalChecks,
totalIncidents,
avgResponseTime,
};
});
return stats;
} catch (error) {
throw error;
}
};
/**
* Get stats by monitor ID
* @async
@@ -200,6 +281,10 @@ const getMonitorStatsById = async (req) => {
...checksQuery,
createdAt: { $gte: filterLookup.day },
};
const checksQuery7Days = {
...checksQuery,
createdAt: { $gte: filterLookup.week },
};
const checksQuery30Days = {
...checksQuery,
createdAt: { $gte: filterLookup.month },
@@ -209,15 +294,18 @@ const getMonitorStatsById = async (req) => {
createdAt: { $gte: filterLookup.hour },
};
const [checks24Hours, checks30Days, checks60Mins] = await Promise.all([
model.find(checksQuery24Hours).sort({ createdAt: sortOrder }),
model.find(checksQuery30Days).sort({ createdAt: sortOrder }),
model.find(checksQuery60Mins).sort({ createdAt: sortOrder }),
]);
const [checks24Hours, checks7Days, checks30Days, checks60Mins] =
await Promise.all([
model.find(checksQuery24Hours).sort({ createdAt: sortOrder }),
model.find(checksQuery7Days).sort({ createdAt: sortOrder }),
model.find(checksQuery30Days).sort({ createdAt: sortOrder }),
model.find(checksQuery60Mins).sort({ createdAt: sortOrder }),
]);
// HTTP/PING Specific stats
monitorStats.avgResponseTime24hours =
getAverageResponseTime24Hours(checks24Hours);
monitorStats.uptime7Days = getUptimePercentage(checks7Days);
monitorStats.uptime24Hours = getUptimePercentage(checks24Hours);
monitorStats.uptime30Days = getUptimePercentage(checks30Days);
monitorStats.statusBar = getStatusBarValues(monitor, checks60Mins);
@@ -452,6 +540,7 @@ const editMonitor = async (candidateId, candidateMonitor) => {
module.exports = {
getAllMonitors,
getMonitorAggregateStats,
getMonitorStatsById,
getMonitorById,
getMonitorsByTeamId,
+835
View File
@@ -0,0 +1,835 @@
{
"openapi": "3.1.0",
"info": {
"title": "BlueWave Uptime",
"summary": "BlueWave Uptime OpenAPI Specifications",
"description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.",
"contact": {
"name": "API Support",
"url": "mailto:support@bluewavelabs.ca",
"email": "support@bluewavelabs.ca"
},
"license": {
"name": "AGPLv3",
"url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE"
},
"version": "1.0"
},
"servers": [
{
"url": "http://localhost:{PORT}/{API_PATH}",
"description": "Local Development Server",
"variables": {
"PORT": {
"description": "API Port",
"enum": ["5000", "3000", "8080"],
"default": "5000"
},
"API_PATH": {
"description": "API Base Path",
"enum": ["api/v1", "api/v1.1", "api/v2"],
"default": "api/v1"
}
}
}
],
"tags": [
{
"name": "auth",
"description": "Authentication"
},
{
"name": "invite",
"description": "Invite"
},
{
"name": "monitors",
"description": "Monitors"
},
{
"name": "checks",
"description": "Checks"
},
{
"name": "alerts",
"description": "Alerts"
},
{
"name": "pagespeed",
"description": "Page Speed"
},
{
"name": "maintenance-window",
"description": "Maintenance window"
},
{
"name": "healthy",
"description": "Health check"
},
{
"name": "mail",
"description": "Mail service"
}
],
"paths": {
"/auth/register": {
"post": {
"tags": ["auth"],
"description": "Register a new user",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["firstName", "lastName", "email", "password", "role", "teamId"],
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"format": "password"
},
"profileImage": {
"type": "file",
"format": "file"
},
"role": {
"type": "array",
"enum": [["user"], ["admin"], ["superadmin"]],
"default": ["superadmin"]
},
"teamId": {
"type": "string",
"format": "uuid"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/login": {
"post": {
"tags": ["auth"],
"description": "Login with credentials",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["email", "password"],
"properties": {
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"format": "password"
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/user/:userId": {
"put": {
"tags": ["auth"],
"description": "Change user informations",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
},
"delete": {
"tags": ["auth"],
"description": "Delete user",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/users/admin": {
"get": {
"tags": ["auth"],
"description": "Checks to see if an admin account exists",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/users": {
"get": {
"tags": ["auth"],
"description": "Get all users",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/invite": {
"post": {
"tags": ["auth"],
"description": "Invite people to application",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/invite/verify": {
"post": {
"tags": ["auth"],
"description": "Verify the invite",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/recovery/request": {
"post": {
"tags": ["auth"],
"description": "Request a recovery token",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/recovery/validate": {
"post": {
"tags": ["auth"],
"description": "Validate recovery token",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/auth/recovery/reset": {
"post": {
"tags": ["auth"],
"description": "Password reset",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UserSuccessResponse"
}
}
}
},
"422": {
"description": "Unprocessable Content",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/healthy": {
"get": {
"tags": ["healthy"],
"description": "Health check",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
},
"500": {
"description": "Internal Server Error",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ErrorResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
}
}
},
"SuccessResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"data": {
"type": "object"
}
}
},
"UserSuccessResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"data": {
"type": "object",
"$ref": "#/components/schemas/UserDataType"
}
}
},
"MonitorSuccessResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"data": {
"type": "object",
"$ref": "#/components/schemas/MonitorDataType"
}
}
},
"CheckDataSuccessResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"data": {
"type": "object",
"$ref": "#/components/schemas/CheckDataType"
}
}
},
"AlertDataSuccessResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"msg": {
"type": "string"
},
"data": {
"type": "object",
"$ref": "#/components/schemas/AlertDataType"
}
}
},
"UserDataType": {
"type": "object",
"required": [
"firstname",
"lastname",
"email",
"profilePicUrl",
"isActive",
"isVerified",
"updatedAt",
"createdAt"
],
"properties": {
"firstname": {
"type": "string"
},
"lastname": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"profilePicUrl": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"isVerified": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
"createdAt": {
"type": "string"
}
}
},
"MonitorDataType": {
"type": "object",
"required": [
"userId",
"name",
"description",
"url",
"isActive",
"interval",
"updatedAt",
"createdAt"
],
"properties": {
"userId": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"url": {
"type": "string"
},
"isActive": {
"type": "boolean"
},
"interval": {
"type": "integer"
},
"updatedAt": {
"type": "string"
},
"createdAt": {
"type": "string"
}
}
},
"CheckDataType": {
"type": "object",
"required": [
"monitorId",
"status",
"responseTime",
"statusCode",
"message",
"updatedAt",
"createdAt"
],
"properties": {
"monitorId": {
"type": "string"
},
"status": {
"type": "string"
},
"responseTime": {
"type": "integer"
},
"statusCode": {
"type": "integer"
},
"message": {
"type": "string"
},
"updatedAt": {
"type": "string"
},
"createdAt": {
"type": "string"
}
}
},
"AlertDataType": {
"type": "object",
"required": [
"checkId",
"monitorId",
"userId",
"status",
"message",
"notifiedStatus",
"acknowledgedStatus",
"updatedAt",
"createdAt"
],
"properties": {
"checkId": {
"type": "string"
},
"monitorId": {
"type": "string"
},
"userId": {
"type": "string"
},
"status": {
"type": "string"
},
"message": {
"type": "string"
},
"notifiedStatus": {
"type": "boolean"
},
"acknowledgedStatus": {
"type": "boolean"
},
"updatedAt": {
"type": "string"
},
"createdAt": {
"type": "string"
}
}
}
}
}
}
+1
View File
@@ -3,6 +3,7 @@ const monitorController = require("../controllers/monitorController");
const { isAllowed } = require("../middleware/isAllowed");
router.get("/", monitorController.getAllMonitors);
router.get("/aggregate/:monitorId", monitorController.getMonitorAggregateStats);
router.get("/stats/:monitorId", monitorController.getMonitorStatsById);
router.get("/certificate/:monitorId", monitorController.getMonitorCertificate);
router.get("/:monitorId", monitorController.getMonitorById);
+9
View File
@@ -201,6 +201,13 @@ const pauseMonitorParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorAggregateStatsParamValidation = joi.object({
monitorId: joi.string().required(),
});
const getMonitorAggregateStatsQueryValidation = joi.object({
dateRange: joi.string().valid("day", "week", "month"),
});
//****************************************
// Alerts
//****************************************
@@ -357,6 +364,8 @@ module.exports = {
getMonitorByIdQueryValidation,
getMonitorsByTeamIdValidation,
getMonitorsByTeamIdQueryValidation,
getMonitorAggregateStatsParamValidation,
getMonitorAggregateStatsQueryValidation,
editMonitorBodyValidation,
pauseMonitorParamValidation,
editUserParamValidation,