mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-22 01:29:57 -06:00
Merge branch 'develop' into feat/fe/details-empty-views
This commit is contained in:
@@ -548,23 +548,6 @@ const Login = () => {
|
||||
)
|
||||
)}
|
||||
</Stack>
|
||||
<Box
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Don't have an account? —</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.main}
|
||||
ml={theme.spacing(2)}
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
}}
|
||||
sx={{ userSelect: "none" }}
|
||||
>
|
||||
Sign Up
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
Object.keys(form)
|
||||
.filter((k) => k.startsWith(THRESHOLD_FIELD_PREFIX))
|
||||
.map((k) => {
|
||||
if (form[k]) thresholds[k] = form[k];
|
||||
if (form[k]) thresholds[k] = form[k] / 100;
|
||||
delete form[k];
|
||||
delete form[k.substring(THRESHOLD_FIELD_PREFIX.length)];
|
||||
});
|
||||
@@ -158,6 +158,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
: infrastructureMonitor.name,
|
||||
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
||||
};
|
||||
|
||||
delete form.notifications;
|
||||
if (hasValidationErrors(form, infrastructureMonitorValidation, setErrors)) {
|
||||
return;
|
||||
@@ -194,25 +195,6 @@ const CreateInfrastructureMonitor = () => {
|
||||
{ _id: 10, name: "10 minutes" },
|
||||
];
|
||||
|
||||
const NOTIFY_MULTIPLE_EMAIL_LABEL = (
|
||||
<Box>
|
||||
<Typography mb={theme.spacing(4)}>
|
||||
Also notify via email to multiple addresses (coming soon)
|
||||
</Typography>
|
||||
<Field
|
||||
id="notify-email-list"
|
||||
type="text"
|
||||
placeholder="name@gmail.com"
|
||||
value=""
|
||||
onChange={() => logger.warn("disabled")}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<Typography mt={theme.spacing(4)}>
|
||||
You can separate multiple emails with a comma
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className="create-infrastructure-monitor">
|
||||
<Breadcrumbs
|
||||
@@ -253,7 +235,8 @@ const CreateInfrastructureMonitor = () => {
|
||||
<Box>
|
||||
<Typography component="h2">General settings</Typography>
|
||||
<Typography component="p">
|
||||
Here you can select the URL of the host, together with the type of monitor.
|
||||
Here you can select the URL of the host, together with the friendly name and
|
||||
authorization secret to connect to the server agent.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(15)}>
|
||||
@@ -307,15 +290,6 @@ const CreateInfrastructureMonitor = () => {
|
||||
onChange={(e) => handleChange(e)}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<Checkbox
|
||||
id="notify-email"
|
||||
label={NOTIFY_MULTIPLE_EMAIL_LABEL}
|
||||
isChecked={false}
|
||||
value=""
|
||||
onChange={() => logger.warn("disabled")}
|
||||
onBlur={handleBlur}
|
||||
isDisabled={true}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
|
||||
@@ -323,8 +297,7 @@ const CreateInfrastructureMonitor = () => {
|
||||
<Box>
|
||||
<Typography component="h2">Customize alerts</Typography>
|
||||
<Typography component="p">
|
||||
Send a notification to user(s) When the thresholds exceed a certain number
|
||||
or percentage.
|
||||
Send a notification to user(s) when thresholds exceed a specified percentage.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
|
||||
@@ -11,6 +11,7 @@ import PulseDot from "../../../Components/Animated/PulseDot";
|
||||
import useUtils from "../../Monitors/utils";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Empty from "./empty";
|
||||
import { logger } from "../../../Utils/Logger";
|
||||
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
|
||||
import {
|
||||
TzTick,
|
||||
@@ -190,7 +191,7 @@ const InfrastructureDetails = () => {
|
||||
];
|
||||
const [monitor, setMonitor] = useState(null);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("all");
|
||||
const { statusColor, determineState } = useUtils();
|
||||
// These calculations are needed because ResponsiveContainer
|
||||
// doesn't take padding of parent/siblings into account
|
||||
@@ -210,23 +211,23 @@ const InfrastructureDetails = () => {
|
||||
const response = await networkService.getStatsByMonitorId({
|
||||
authToken: authToken,
|
||||
monitorId: monitorId,
|
||||
sortOrder: null,
|
||||
sortOrder: "asc",
|
||||
limit: null,
|
||||
dateRange: dateRange,
|
||||
numToDisplay: 50,
|
||||
normalize: true,
|
||||
normalize: false,
|
||||
});
|
||||
|
||||
setMonitor(response.data.data);
|
||||
} catch (error) {
|
||||
navigate("/not-found", { replace: true });
|
||||
|
||||
console.error(error);
|
||||
logger.error(error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, [authToken, monitorId, dateRange]);
|
||||
|
||||
|
||||
const statBoxConfigs = [
|
||||
{
|
||||
id: 0,
|
||||
|
||||
@@ -44,6 +44,14 @@ class Logger {
|
||||
this.log = NO_OP;
|
||||
return;
|
||||
}
|
||||
|
||||
if (logLevel === "debug") {
|
||||
this.error = console.error.bind(console);
|
||||
this.warn = console.warn.bind(console);
|
||||
this.info = console.info.bind(console);
|
||||
this.log = console.log.bind(console);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -16,6 +16,28 @@ const NotificationSchema = mongoose.Schema(
|
||||
phone: {
|
||||
type: String,
|
||||
},
|
||||
alertThreshold: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
cpuAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
},
|
||||
memoryAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
},
|
||||
diskAlertThreshold: {
|
||||
type: Number,
|
||||
default: function () {
|
||||
return this.alertThreshold;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -198,7 +198,7 @@ const getIncidents = (checks) => {
|
||||
|
||||
/**
|
||||
* Get date range parameters
|
||||
* @param {string} dateRange - 'day' | 'week' | 'month'
|
||||
* @param {string} dateRange - 'day' | 'week' | 'month' | 'all'
|
||||
* @returns {Object} Start and end dates
|
||||
*/
|
||||
const getDateRange = (dateRange) => {
|
||||
@@ -206,6 +206,7 @@ const getDateRange = (dateRange) => {
|
||||
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)),
|
||||
all: new Date(0),
|
||||
};
|
||||
return {
|
||||
start: startDates[dateRange],
|
||||
|
||||
8
Server/package-lock.json
generated
8
Server/package-lock.json
generated
@@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "5.28.2",
|
||||
"bullmq": "5.29.1",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "4.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -1217,9 +1217,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.28.2",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.28.2.tgz",
|
||||
"integrity": "sha512-2F94CJnOKP2kyf29v8bxpNNyF7IsG+z0RY80gP7dAvUprcEbb3CAvyCKMcbkHeF/kBKkcJCNQOlVY/+JY8dsUQ==",
|
||||
"version": "5.29.1",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.29.1.tgz",
|
||||
"integrity": "sha512-TZWiwRlPnpaN+Qwh4D8IQf2cYLpkiDX1LbaaWEabc6y37ojIttWOSynxDewpVHyW233LssSIC4+aLMSvAjtpmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.6.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"bullmq": "5.28.2",
|
||||
"bullmq": "5.29.1",
|
||||
"cors": "^2.8.5",
|
||||
"dockerode": "4.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
|
||||
@@ -65,7 +65,7 @@ class EmailService {
|
||||
serverIsDownTemplate: this.loadTemplate("serverIsDown"),
|
||||
serverIsUpTemplate: this.loadTemplate("serverIsUp"),
|
||||
passwordResetTemplate: this.loadTemplate("passwordReset"),
|
||||
thresholdViolatedTemplate: this.loadTemplate("thresholdViolated"),
|
||||
hardwareIncidentTemplate: this.loadTemplate("hardwareIncident"),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -165,17 +165,12 @@ class JobQueue {
|
||||
// Handle status change
|
||||
const { monitor, statusChanged, prevStatus } =
|
||||
await this.statusService.updateStatus(networkResponse);
|
||||
|
||||
//If status hasn't changed, we're done
|
||||
if (statusChanged === false) return;
|
||||
|
||||
// if prevStatus is undefined, monitor is resuming, we're done
|
||||
if (prevStatus === undefined) return;
|
||||
|
||||
// Handle notifications
|
||||
this.notificationService.handleNotifications({
|
||||
...networkResponse,
|
||||
monitor,
|
||||
prevStatus,
|
||||
statusChanged,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
|
||||
@@ -14,15 +14,34 @@ class NotificationService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email notification based on the network response.
|
||||
* Sends an email notification for hardware infrastructure alerts
|
||||
*
|
||||
* @param {Object} networkResponse - The response from the network monitor.
|
||||
* @param {Object} networkResponse.monitor - The monitor object containing details about the monitored service.
|
||||
* @param {string} networkResponse.monitor.name - The name of the monitor.
|
||||
* @param {string} networkResponse.monitor.url - The URL of the monitor.
|
||||
* @param {boolean} networkResponse.status - The current status of the monitor (true for up, false for down).
|
||||
* @param {boolean} networkResponse.prevStatus - The previous status of the monitor (true for up, false for down).
|
||||
* @param {string} address - The email address to send the notification to.
|
||||
* @async
|
||||
* @function sendHardwareEmail
|
||||
* @param {Object} networkResponse - Response object containing monitor information
|
||||
* @param {string} address - Email address to send the notification to
|
||||
* @param {Array} [alerts=[]] - List of hardware alerts to include in the email
|
||||
* @returns {Promise<boolean>} - Indicates whether email was sent successfully
|
||||
* @throws {Error}
|
||||
*/
|
||||
async sendHardwareEmail(networkResponse, address, alerts = []) {
|
||||
if (alerts.length === 0) return false;
|
||||
const { monitor, status, prevStatus } = networkResponse;
|
||||
const template = "hardwareIncidentTemplate";
|
||||
const context = { monitor: monitor.name, url: monitor.url, alerts };
|
||||
const subject = `Monitor ${monitor.name} infrastructure alerts`;
|
||||
this.emailService.buildAndSendEmail(template, context, address, subject);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email notification about monitor status change
|
||||
*
|
||||
* @async
|
||||
* @function sendEmail
|
||||
* @param {Object} networkResponse - Response object containing monitor status information
|
||||
* @param {string} address - Email address to send the notification to
|
||||
* @returns {Promise<boolean>} - Indicates email was sent successfully
|
||||
*/
|
||||
async sendEmail(networkResponse, address) {
|
||||
const { monitor, status, prevStatus } = networkResponse;
|
||||
@@ -30,25 +49,133 @@ class NotificationService {
|
||||
const context = { monitor: monitor.name, url: monitor.url };
|
||||
const subject = `Monitor ${monitor.name} is ${status === true ? "up" : "down"}`;
|
||||
this.emailService.buildAndSendEmail(template, context, address, subject);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications based on the network response.
|
||||
*
|
||||
* @param {Object} networkResponse - The response from the network monitor.
|
||||
* @param {string} networkResponse.monitorId - The ID of the monitor.
|
||||
*/
|
||||
async handleNotifications(networkResponse) {
|
||||
async handleStatusNotifications(networkResponse) {
|
||||
try {
|
||||
//If status hasn't changed, we're done
|
||||
if (networkResponse.statusChanged === false) return false;
|
||||
|
||||
// if prevStatus is undefined, monitor is resuming, we're done
|
||||
if (networkResponse.prevStatus === undefined) return false;
|
||||
const notifications = await this.db.getNotificationsByMonitorId(
|
||||
networkResponse.monitorId
|
||||
);
|
||||
|
||||
for (const notification of notifications) {
|
||||
if (notification.type === "email") {
|
||||
this.sendEmail(networkResponse, notification.address);
|
||||
}
|
||||
// Handle other types of notifications here
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "handleNotifications",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handles status change notifications for a monitor
|
||||
*
|
||||
* @async
|
||||
* @function handleStatusNotifications
|
||||
* @param {Object} networkResponse - Response object containing monitor status information
|
||||
* @returns {Promise<boolean>} - Indicates whether notifications were processed
|
||||
* @throws {Error}
|
||||
*/
|
||||
async handleHardwareNotifications(networkResponse) {
|
||||
const thresholds = networkResponse?.monitor?.thresholds;
|
||||
if (thresholds === undefined) return false; // No thresholds set, we're done
|
||||
|
||||
// Get thresholds from monitor
|
||||
const {
|
||||
usage_cpu: cpuThreshold = -1,
|
||||
usage_memory: memoryThreshold = -1,
|
||||
usage_disk: diskThreshold = -1,
|
||||
} = thresholds;
|
||||
|
||||
// Get metrics from response
|
||||
const metrics = networkResponse?.payload?.data ?? null;
|
||||
if (metrics === null) return false;
|
||||
|
||||
const {
|
||||
cpu: { usage_percent: cpuUsage = -1 } = {},
|
||||
memory: { usage_percent: memoryUsage = -1 } = {},
|
||||
disk = [],
|
||||
} = metrics;
|
||||
|
||||
const alerts = {
|
||||
cpu: cpuThreshold !== -1 && cpuUsage > cpuThreshold ? true : false,
|
||||
memory: memoryThreshold !== -1 && memoryUsage > memoryThreshold ? true : false,
|
||||
disk: disk.some((d) => diskThreshold !== -1 && d.usage_percent > diskThreshold)
|
||||
? true
|
||||
: false,
|
||||
};
|
||||
|
||||
const notifications = await this.db.getNotificationsByMonitorId(
|
||||
networkResponse.monitorId
|
||||
);
|
||||
for (const notification of notifications) {
|
||||
const alertsToSend = [];
|
||||
const alertTypes = ["cpu", "memory", "disk"];
|
||||
|
||||
for (const type of alertTypes) {
|
||||
// Iterate over each alert type to see if any need to be decremented
|
||||
if (alerts[type] === true) {
|
||||
notification[`${type}AlertThreshold`]--; // Decrement threshold if an alert is triggered
|
||||
|
||||
if (notification[`${type}AlertThreshold`] <= 0) {
|
||||
// If threshold drops below 0, reset and send notification
|
||||
notification[`${type}AlertThreshold`] = notification.alertThreshold;
|
||||
|
||||
const formatAlert = {
|
||||
cpu: () =>
|
||||
`Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
|
||||
memory: () =>
|
||||
`Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
|
||||
disk: () =>
|
||||
`Your current disk usage: ${disk
|
||||
.map((d, idx) => `(Disk${idx}: ${(d.usage_percent * 100).toFixed(0)}%)`)
|
||||
.join(
|
||||
", "
|
||||
)} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||
};
|
||||
alertsToSend.push(formatAlert[type]());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await notification.save();
|
||||
|
||||
if (alertsToSend.length === 0) continue; // No alerts to send, we're done
|
||||
|
||||
if (notification.type === "email") {
|
||||
this.sendHardwareEmail(networkResponse, notification.address, alertsToSend);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifications for different monitor types
|
||||
*
|
||||
* @async
|
||||
* @function handleNotifications
|
||||
* @param {Object} networkResponse - Response object containing monitor information
|
||||
* @returns {Promise<boolean>} - Indicates whether notifications were processed successfully
|
||||
*/
|
||||
async handleNotifications(networkResponse) {
|
||||
try {
|
||||
if (networkResponse.monitor.type === "hardware") {
|
||||
this.handleHardwareNotifications(networkResponse);
|
||||
}
|
||||
this.handleStatusNotifications(networkResponse);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
|
||||
@@ -29,7 +29,8 @@ class StatusService {
|
||||
const { monitorId, status } = networkResponse;
|
||||
const monitor = await this.db.getMonitorById(monitorId);
|
||||
// No change in monitor status, return early
|
||||
if (monitor.status === status) return { statusChanged: false };
|
||||
if (monitor.status === status)
|
||||
return { monitor, statusChanged: false, prevStatus: monitor.status };
|
||||
// Monitor status changed, save prev status and update monitor
|
||||
|
||||
this.logger.info({
|
||||
@@ -103,7 +104,7 @@ class StatusService {
|
||||
|
||||
if (type === "hardware") {
|
||||
const { cpu, memory, disk, host } = payload?.data ?? {};
|
||||
const { errors } = payload;
|
||||
const { errors } = payload?.errors ?? [];
|
||||
check.cpu = cpu ?? {};
|
||||
check.memory = memory ?? {};
|
||||
check.disk = disk ?? {};
|
||||
|
||||
43
Server/templates/hardwareIncident.mjml
Normal file
43
Server/templates/hardwareIncident.mjml
Normal file
@@ -0,0 +1,43 @@
|
||||
<mjml>
|
||||
<mj-head>
|
||||
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
|
||||
<mj-attributes>
|
||||
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
|
||||
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
|
||||
<mj-section padding="0px"></mj-section>
|
||||
</mj-attributes>
|
||||
</mj-head>
|
||||
<mj-body>
|
||||
<mj-section padding="20px 0">
|
||||
<mj-column width="100%">
|
||||
<mj-text align="left" font-size="10px">
|
||||
Message from BlueWave Infrastructure Monitoring
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
<mj-column width="45%" padding-top="20px">
|
||||
<mj-text align="center" font-weight="500" padding="0px" font-size="18px" color="red">
|
||||
Infrastructure Alerts
|
||||
</mj-text>
|
||||
<mj-divider border-width="2px" border-color="#616161"></mj-divider>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
<mj-section>
|
||||
<mj-column width="100%">
|
||||
<mj-text>
|
||||
<p>Hello {{name}}!</p>
|
||||
<p>{{monitor}} at {{url}} has the following infrastructure alerts:</p>
|
||||
{{#each alerts}}
|
||||
<p>• {{this}}</p>
|
||||
{{/each}}
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
<mj-column width="100%">
|
||||
<mj-divider border-width="1px" border-color="#E0E0E0"></mj-divider>
|
||||
<mj-button background-color="#1570EF"> View Infrastructure Details </mj-button>
|
||||
<mj-text font-size="12px">
|
||||
<p>This email was sent by BlueWave Infrastructure Monitoring.</p>
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
@@ -1,40 +0,0 @@
|
||||
<mjml>
|
||||
<mj-head>
|
||||
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto:300,500"></mj-font>
|
||||
<mj-attributes>
|
||||
<mj-all font-family="Roboto, Helvetica, sans-serif"></mj-all>
|
||||
<mj-text font-weight="300" font-size="16px" color="#616161" line-height="24px"></mj-text>
|
||||
<mj-section padding="0px"></mj-section>
|
||||
</mj-attributes>
|
||||
</mj-head>
|
||||
<mj-body>
|
||||
<mj-section padding="20px 0">
|
||||
<mj-column width="100%">
|
||||
<mj-text align="left" font-size="10px">
|
||||
Message from BlueWave Uptime Service
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
<mj-column width="45%" padding-top="20px">
|
||||
<mj-text font-weight="500" padding="0px" font-size="18px">
|
||||
{{message}}
|
||||
</mj-text>
|
||||
<mj-text font-weight="500" padding="0px" font-size="18px">
|
||||
{{#if cpu}}
|
||||
{{cpu}}
|
||||
{{/if}}
|
||||
</mj-text>
|
||||
<mj-text font-weight="500" padding="0px" font-size="18px">
|
||||
{{#if disk}}
|
||||
{{disk}}
|
||||
{{/if}}
|
||||
</mj-text>
|
||||
<mj-text font-weight="500" padding="0px" font-size="18px">
|
||||
{{#if memory}}
|
||||
{{memory}}
|
||||
{{/if}}
|
||||
</mj-text>
|
||||
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
</mj-body>
|
||||
</mjml>
|
||||
@@ -78,11 +78,23 @@ describe("NotificationService", () => {
|
||||
describe("handleNotifications", async () => {
|
||||
it("should handle notifications based on the network response", async () => {
|
||||
notificationService.sendEmail = sinon.stub();
|
||||
notificationService.db.getNotificationsByMonitorId.resolves([
|
||||
{ type: "email", address: "www.google.com" },
|
||||
]);
|
||||
await notificationService.handleNotifications({ monitorId: "123" });
|
||||
expect(notificationService.sendEmail.calledOnce).to.be.true;
|
||||
const res = await notificationService.handleNotifications({
|
||||
monitor: {
|
||||
type: "email",
|
||||
address: "www.google.com",
|
||||
},
|
||||
});
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
it("should handle hardware notifications", async () => {
|
||||
notificationService.sendEmail = sinon.stub();
|
||||
const res = await notificationService.handleNotifications({
|
||||
monitor: {
|
||||
type: "hardware",
|
||||
address: "www.google.com",
|
||||
},
|
||||
});
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle an error when getting notifications", async () => {
|
||||
@@ -92,4 +104,184 @@ describe("NotificationService", () => {
|
||||
expect(notificationService.logger.warn.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendHardwareEmail", async () => {
|
||||
let networkResponse, address, alerts;
|
||||
beforeEach(() => {
|
||||
networkResponse = {
|
||||
monitor: {
|
||||
name: "Test Monitor",
|
||||
url: "http://test.com",
|
||||
},
|
||||
status: true,
|
||||
prevStatus: false,
|
||||
};
|
||||
address = "test@test.com";
|
||||
alerts = ["test"];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
it("should send an email notification with Hardware Template", async () => {
|
||||
emailService.buildAndSendEmail.resolves(true);
|
||||
const res = await notificationService.sendHardwareEmail(
|
||||
networkResponse,
|
||||
address,
|
||||
alerts
|
||||
);
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
it("should return false if no alerts are provided", async () => {
|
||||
alerts = [];
|
||||
emailService.buildAndSendEmail.resolves(true);
|
||||
const res = await notificationService.sendHardwareEmail(
|
||||
networkResponse,
|
||||
address,
|
||||
alerts
|
||||
);
|
||||
expect(res).to.be.false;
|
||||
});
|
||||
});
|
||||
describe("handleStatusNotifications", async () => {
|
||||
let networkResponse;
|
||||
beforeEach(() => {
|
||||
networkResponse = {
|
||||
monitor: {
|
||||
name: "Test Monitor",
|
||||
url: "http://test.com",
|
||||
},
|
||||
statusChanged: true,
|
||||
status: true,
|
||||
prevStatus: false,
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should handle status notifications", async () => {
|
||||
db.getNotificationsByMonitorId.resolves([
|
||||
{ type: "email", address: "test@test.com" },
|
||||
]);
|
||||
const res = await notificationService.handleStatusNotifications(networkResponse);
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
it("should return false if status hasn't changed", async () => {
|
||||
networkResponse.statusChanged = false;
|
||||
const res = await notificationService.handleStatusNotifications(networkResponse);
|
||||
expect(res).to.be.false;
|
||||
});
|
||||
it("should return false if prevStatus is undefined", async () => {
|
||||
networkResponse.prevStatus = undefined;
|
||||
const res = await notificationService.handleStatusNotifications(networkResponse);
|
||||
expect(res).to.be.false;
|
||||
});
|
||||
it("should handle an error", async () => {
|
||||
const testError = new Error("Test Error");
|
||||
db.getNotificationsByMonitorId.rejects(testError);
|
||||
try {
|
||||
await notificationService.handleStatusNotifications(networkResponse);
|
||||
} catch (error) {
|
||||
expect(error).to.be.an.instanceOf(Error);
|
||||
expect(error.message).to.equal("Test Error");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleHardwareNotifications", async () => {
|
||||
let networkResponse;
|
||||
beforeEach(() => {
|
||||
networkResponse = {
|
||||
monitor: {
|
||||
name: "Test Monitor",
|
||||
url: "http://test.com",
|
||||
thresholds: {
|
||||
usage_cpu: 1,
|
||||
usage_memory: 1,
|
||||
usage_disk: 1,
|
||||
},
|
||||
},
|
||||
payload: {
|
||||
data: {
|
||||
cpu: {
|
||||
usage_percent: 0.655,
|
||||
},
|
||||
memory: {
|
||||
usage_percent: 0.783,
|
||||
},
|
||||
disk: [
|
||||
{
|
||||
name: "/dev/sda1",
|
||||
usage_percent: 0.452,
|
||||
},
|
||||
{
|
||||
name: "/dev/sdb1",
|
||||
usage_percent: 0.627,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe("it should return false if no thresholds are set", () => {
|
||||
it("should return false if no thresholds are set", async () => {
|
||||
networkResponse.monitor.thresholds = undefined;
|
||||
const res =
|
||||
await notificationService.handleHardwareNotifications(networkResponse);
|
||||
expect(res).to.be.false;
|
||||
});
|
||||
|
||||
it("should return false if metrics are null", async () => {
|
||||
networkResponse.payload.data = null;
|
||||
const res =
|
||||
await notificationService.handleHardwareNotifications(networkResponse);
|
||||
expect(res).to.be.false;
|
||||
});
|
||||
|
||||
it("should return true if request is well formed and thresholds > 0", async () => {
|
||||
db.getNotificationsByMonitorId.resolves([
|
||||
{
|
||||
type: "email",
|
||||
address: "test@test.com",
|
||||
alertThreshold: 1,
|
||||
cpuAlertThreshold: 1,
|
||||
memoryAlertThreshold: 1,
|
||||
diskAlertThreshold: 1,
|
||||
save: sinon.stub().resolves(),
|
||||
},
|
||||
]);
|
||||
const res =
|
||||
await notificationService.handleHardwareNotifications(networkResponse);
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
|
||||
it("should return true if thresholds are exceeded", async () => {
|
||||
db.getNotificationsByMonitorId.resolves([
|
||||
{
|
||||
type: "email",
|
||||
address: "test@test.com",
|
||||
alertThreshold: 1,
|
||||
cpuAlertThreshold: 1,
|
||||
memoryAlertThreshold: 1,
|
||||
diskAlertThreshold: 1,
|
||||
save: sinon.stub().resolves(),
|
||||
},
|
||||
]);
|
||||
networkResponse.monitor.thresholds = {
|
||||
usage_cpu: 0.01,
|
||||
usage_memory: 0.01,
|
||||
usage_disk: 0.01,
|
||||
};
|
||||
const res =
|
||||
await notificationService.handleHardwareNotifications(networkResponse);
|
||||
expect(res).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -178,7 +178,7 @@ const getMonitorStatsByIdQueryValidation = joi.object({
|
||||
status: joi.string(),
|
||||
limit: joi.number(),
|
||||
sortOrder: joi.string().valid("asc", "desc"),
|
||||
dateRange: joi.string().valid("day", "week", "month"),
|
||||
dateRange: joi.string().valid("day", "week", "month", "all"),
|
||||
numToDisplay: joi.number(),
|
||||
normalize: joi.boolean(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user