17 files changed
+1361
-54
No files matched your search
@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
|
||||
const API_TOKEN = process.env.POEDITOR_API;
|
||||
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
|
||||
const LANGUAGES = (
|
||||
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
|
||||
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||
).split(",");
|
||||
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
languages:
|
||||
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
|
||||
required: false
|
||||
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
|
||||
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn,th"
|
||||
format:
|
||||
description: "Export format (key_value_json or json)"
|
||||
required: false
|
||||
|
||||
@@ -52,7 +52,7 @@ Usage instructions can be found [here](https://docs.checkmate.so/checkmate-2.1).
|
||||
|
||||
See installation instructions in [Checkmate documentation portal](https://docs.checkmate.so/checkmate-2.1/users-guide/quickstart).
|
||||
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
Alternatively, you can also use [Coolify](https://coolify.io/), [Elestio](https://elest.io/open-source/checkmate), [K8s](./charts/helm/checkmate/INSTALLATION.md), [Sive Host](https://sive.host) (South Africa) or [Pikapods](https://www.pikapods.com/) to quickly spin off a Checkmate instance. If you would like to monitor your server infrastructure, you'll need [Capture agent](https://github.com/bluewave-labs/capture). Capture repository also contains the installation instructions.
|
||||
|
||||
### Using a Custom CA
|
||||
|
||||
|
||||
Generated
+22
-8
@@ -45,7 +45,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"prettier": "^3.3.3",
|
||||
"vite": "6.3.5"
|
||||
"vite": "6.3.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -2422,13 +2422,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -3756,6 +3756,20 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -6375,9 +6389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
|
||||
+1
-1
@@ -58,6 +58,6 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"prettier": "^3.3.3",
|
||||
"vite": "6.3.5"
|
||||
"vite": "6.3.6"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useCallback } from "react";
|
||||
const useInfrastructureMonitorForm = () => {
|
||||
const [infrastructureMonitor, setInfrastructureMonitor] = useState({
|
||||
url: "",
|
||||
@@ -6,6 +6,8 @@ const useInfrastructureMonitorForm = () => {
|
||||
notifications: [],
|
||||
notify_email: false,
|
||||
interval: 0.25,
|
||||
statusWindowSize: 5,
|
||||
statusWindowThreshold: 60,
|
||||
cpu: false,
|
||||
usage_cpu: "",
|
||||
memory: false,
|
||||
@@ -29,9 +31,10 @@ const useInfrastructureMonitorForm = () => {
|
||||
[event.target.name]: event.target.checked,
|
||||
});
|
||||
};
|
||||
const initializeInfrastructureMonitorForCreate = (globalSettings) => {
|
||||
const initializeInfrastructureMonitorForCreate = useCallback((globalSettings) => {
|
||||
const gt = globalSettings?.data?.settings?.globalThresholds || {};
|
||||
setInfrastructureMonitor({
|
||||
setInfrastructureMonitor((prev) => ({
|
||||
...prev,
|
||||
url: "",
|
||||
name: "",
|
||||
notifications: [],
|
||||
@@ -45,17 +48,20 @@ const useInfrastructureMonitorForm = () => {
|
||||
temperature: gt.temperature !== undefined,
|
||||
usage_temperature: gt.temperature !== undefined ? gt.temperature.toString() : "",
|
||||
secret: "",
|
||||
});
|
||||
};
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const initializeInfrastructureMonitorForUpdate = (monitor) => {
|
||||
const initializeInfrastructureMonitorForUpdate = useCallback((monitor) => {
|
||||
const MS_PER_MINUTE = 60000;
|
||||
const { thresholds = {} } = monitor;
|
||||
setInfrastructureMonitor({
|
||||
setInfrastructureMonitor((prev) => ({
|
||||
...prev,
|
||||
url: monitor.url.replace(/^https?:\/\//, ""),
|
||||
name: monitor.name || "",
|
||||
notifications: monitor.notifications || [],
|
||||
interval: monitor.interval / MS_PER_MINUTE,
|
||||
statusWindowSize: monitor.statusWindowSize,
|
||||
statusWindowThreshold: monitor.statusWindowThreshold,
|
||||
cpu: thresholds.usage_cpu !== undefined,
|
||||
usage_cpu:
|
||||
thresholds.usage_cpu !== undefined ? (thresholds.usage_cpu * 100).toString() : "",
|
||||
@@ -75,8 +81,8 @@ const useInfrastructureMonitorForm = () => {
|
||||
? (thresholds.usage_temperature * 100).toString()
|
||||
: "",
|
||||
secret: monitor.secret || "",
|
||||
});
|
||||
};
|
||||
}));
|
||||
}, []);
|
||||
return {
|
||||
infrastructureMonitor,
|
||||
setInfrastructureMonitor,
|
||||
|
||||
@@ -12,6 +12,8 @@ const useInfrastructureSubmit = () => {
|
||||
? infrastructureMonitor.url
|
||||
: infrastructureMonitor.name,
|
||||
interval: infrastructureMonitor.interval * MS_PER_MINUTE,
|
||||
statusWindowSize: infrastructureMonitor.statusWindowSize,
|
||||
statusWindowThreshold: infrastructureMonitor.statusWindowThreshold,
|
||||
cpu: infrastructureMonitor.cpu,
|
||||
...(infrastructureMonitor.cpu
|
||||
? { usage_cpu: infrastructureMonitor.usage_cpu }
|
||||
|
||||
@@ -86,7 +86,14 @@ const CreateInfrastructureMonitor = () => {
|
||||
setHttps(monitor.url.startsWith("https"));
|
||||
initializeInfrastructureMonitorForUpdate(monitor);
|
||||
}
|
||||
}, [isCreate, monitor, globalSettings, globalSettingsLoading]);
|
||||
}, [
|
||||
isCreate,
|
||||
monitor,
|
||||
globalSettings,
|
||||
globalSettingsLoading,
|
||||
initializeInfrastructureMonitorForCreate,
|
||||
initializeInfrastructureMonitorForUpdate,
|
||||
]);
|
||||
|
||||
// Handlers
|
||||
const onSubmit = async (event) => {
|
||||
@@ -281,6 +288,39 @@ const CreateInfrastructureMonitor = () => {
|
||||
setNotifications={infrastructureMonitor.notifications}
|
||||
/>
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography
|
||||
component="h2"
|
||||
variant="h2"
|
||||
>
|
||||
{t("createMonitorPage.incidentConfigTitle")}
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
{t("createMonitorPage.incidentConfigDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
name="statusWindowSize"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowLabel")}
|
||||
type="number"
|
||||
value={infrastructureMonitor.statusWindowSize}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowSize"] ? true : false}
|
||||
helperText={errors["statusWindowSize"]}
|
||||
/>
|
||||
<TextInput
|
||||
name="statusWindowThreshold"
|
||||
label={t("createMonitorPage.incidentConfigStatusWindowThresholdLabel")}
|
||||
type="number"
|
||||
value={infrastructureMonitor.statusWindowThreshold}
|
||||
onChange={onChange}
|
||||
error={errors["statusWindowThreshold"] ? true : false}
|
||||
helperText={errors["statusWindowThreshold"]}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
<CustomAlertsSection
|
||||
errors={errors}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -437,6 +437,16 @@ const infrastructureMonitorValidation = joi.object({
|
||||
"number.base": "Frequency must be a number.",
|
||||
"any.required": "Frequency is required.",
|
||||
}),
|
||||
statusWindowSize: joi.number().min(1).max(20).messages({
|
||||
"number.base": "Status window size must be a number.",
|
||||
"number.min": "Status window size must be at least 1.",
|
||||
"number.max": "Status window size cannot exceed 20.",
|
||||
}),
|
||||
statusWindowThreshold: joi.number().min(1).max(100).messages({
|
||||
"number.base": "Status window threshold must be a number.",
|
||||
"number.min": "Status window threshold must be at least 1%.",
|
||||
"number.max": "Status window threshold cannot exceed 100%.",
|
||||
}),
|
||||
notifications: joi.array().items(joi.string()),
|
||||
});
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -5,12 +5,12 @@ import { execSync } from "child_process";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
let version = "3.1.8";
|
||||
let version = "3.2.0";
|
||||
|
||||
return {
|
||||
base: "/",
|
||||
plugins: [svgr(), react()],
|
||||
optimizeDeps: { Maybe this will be the straw th
|
||||
optimizeDeps: {
|
||||
include: ["@mui/material/Tooltip", "@emotion/styled"],
|
||||
},
|
||||
define: {
|
||||
|
||||
@@ -149,10 +149,12 @@
|
||||
"statusPageByTeamId": "Got status pages by team id successfully",
|
||||
"httpNetworkError": "Network error",
|
||||
"httpNotJson": "Response data is not json",
|
||||
"httpExpectedValueFail": "Expected value did not match",
|
||||
"httpJsonPathError": "Failed to parse json data",
|
||||
"httpEmptyResult": "Result is empty",
|
||||
"httpMatchSuccess": "Response data match successfully",
|
||||
"httpMatchFail": "Failed to match response data",
|
||||
"httpJsonPathFail": "JSONPath did not match expected value",
|
||||
"webhookSendSuccess": "Webhook notification sent successfully",
|
||||
"telegramRequiresBotTokenAndChatId": "Telegram notifications require both botToken and chatId",
|
||||
"webhookUrlRequired": "Webhook URL is required",
|
||||
@@ -162,5 +164,18 @@
|
||||
"monitorDownAlert": "Downtime Alert: One of your monitors went offline.\n📌 Monitor: {monitorName}\n📅 Time: {time}\n⚠️ Status: DOWN\n📟 Status Code: {code}\n\u200B\n",
|
||||
"sendTestEmail": "Test email sent successfully",
|
||||
"errorForValidEmailAddress": "A valid recipient email address is required.",
|
||||
"testEmailSubject": "Test E-mail from Checkmate"
|
||||
"testEmailSubject": "Test E-mail from Checkmate",
|
||||
"discordNotification": {
|
||||
"monitor": "Monitor",
|
||||
"status": "Status",
|
||||
"statusCode": "Status Code",
|
||||
"time": "Time",
|
||||
"up": "UP",
|
||||
"down": "DOWN",
|
||||
"checkmate": "Checkmate",
|
||||
"url": "URL",
|
||||
"unknown": "Unknown",
|
||||
"uptimeAlert": "Uptime Alert: One of your monitors is back online",
|
||||
"downtimeAlert": "Downtime Alert: One of your monitors went offline"
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,12 @@ class SuperSimpleQueueHelper {
|
||||
return async (monitor) => {
|
||||
try {
|
||||
const monitorId = monitor._id;
|
||||
const teamId = monitor.teamId;
|
||||
if (!monitorId) {
|
||||
throw new Error("No monitor id");
|
||||
}
|
||||
|
||||
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId);
|
||||
const maintenanceWindowActive = await this.isInMaintenanceWindow(monitorId, teamId);
|
||||
if (maintenanceWindowActive) {
|
||||
this.logger.info({
|
||||
message: `Monitor ${monitorId} is in maintenance window`,
|
||||
@@ -77,8 +78,11 @@ class SuperSimpleQueueHelper {
|
||||
};
|
||||
};
|
||||
|
||||
async isInMaintenanceWindow(monitorId) {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId(monitorId);
|
||||
async isInMaintenanceWindow(monitorId, teamId) {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({
|
||||
monitorId: monitorId.toString(),
|
||||
teamId: teamId.toString(),
|
||||
});
|
||||
// Check for active maintenance window:
|
||||
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc, window) => {
|
||||
if (window.active) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { match } from "assert";
|
||||
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
|
||||
class NetworkService {
|
||||
@@ -107,7 +105,7 @@ class NetworkService {
|
||||
}
|
||||
|
||||
async requestHttp(monitor) {
|
||||
const { url, secret, _id, name, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
const { url, secret, _id, teamId, type, ignoreTlsErrors, jsonPath, matchMethod, expectedValue } = monitor;
|
||||
const httpResponse = {
|
||||
monitorId: _id,
|
||||
teamId: teamId,
|
||||
@@ -156,33 +154,61 @@ class NetworkService {
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
if (expectedValue) {
|
||||
if (expectedValue && !jsonPath) {
|
||||
let ok = false;
|
||||
if (matchMethod === "equal") ok = payload === expectedValue;
|
||||
if (matchMethod === "include") ok = payload.includes(expectedValue);
|
||||
if (matchMethod === "regex") ok = new RegExp(expectedValue).test(payload);
|
||||
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
|
||||
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
|
||||
|
||||
if (ok === true) {
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.code = 500;
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpExpectedValueFail;
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonPath) {
|
||||
const contentType = response.headers["content-type"];
|
||||
|
||||
const isJson = contentType?.includes("application/json");
|
||||
if (!isJson) {
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpNotJson;
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
try {
|
||||
this.jmespath.search(payload, jsonPath);
|
||||
const extracted = this.jmespath.search(payload, jsonPath);
|
||||
if (expectedValue) {
|
||||
let ok = false;
|
||||
if (matchMethod === "equal") ok = extracted === expectedValue;
|
||||
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
|
||||
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
|
||||
|
||||
if (ok) {
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.status = false;
|
||||
httpResponse.code = 500;
|
||||
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
}
|
||||
} else {
|
||||
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
|
||||
if (!isFalsey) {
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
} else {
|
||||
httpResponse.status = false;
|
||||
httpResponse.code = 500;
|
||||
httpResponse.message = this.stringService.httpJsonPathFail;
|
||||
httpResponse.extracted = extracted;
|
||||
return httpResponse;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
httpResponse.status = false;
|
||||
httpResponse.message = this.stringService.httpJsonPathError;
|
||||
|
||||
@@ -16,7 +16,7 @@ class NotificationService {
|
||||
return NotificationService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
sendNotification = async ({ notification, subject, content, html }) => {
|
||||
sendNotification = async ({ notification, subject, content, html, discordContent = null, webhookBody = null }) => {
|
||||
const { type, address } = notification;
|
||||
|
||||
if (type === "email") {
|
||||
@@ -28,9 +28,11 @@ class NotificationService {
|
||||
// Create a body for webhooks
|
||||
let body = { text: content };
|
||||
if (type === "discord") {
|
||||
body = { content };
|
||||
body = !discordContent ? { content } : discordContent;
|
||||
}
|
||||
if (type === "webhook") {
|
||||
body = !webhookBody ? { content } : webhookBody;
|
||||
}
|
||||
|
||||
if (type === "slack" || type === "discord" || type === "webhook") {
|
||||
const response = await this.networkService.requestWebhook(type, address, body);
|
||||
return response.status;
|
||||
@@ -55,7 +57,6 @@ class NotificationService {
|
||||
|
||||
const notificationIDs = networkResponse.monitor?.notifications ?? [];
|
||||
if (notificationIDs.length === 0) return false;
|
||||
|
||||
if (networkResponse.monitor.type === "hardware") {
|
||||
const thresholds = networkResponse?.monitor?.thresholds;
|
||||
|
||||
@@ -63,30 +64,29 @@ class NotificationService {
|
||||
const metrics = networkResponse?.payload?.data ?? null;
|
||||
if (metrics === null) return false; // No metrics, we're done
|
||||
|
||||
const alerts = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
||||
const [alerts, discordContent] = await this.notificationUtils.buildHardwareAlerts(networkResponse);
|
||||
if (alerts.length === 0) return false;
|
||||
|
||||
const { subject, html } = await this.notificationUtils.buildHardwareEmail(networkResponse, alerts);
|
||||
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts);
|
||||
|
||||
const success = await this.notifyAll({ notificationIDs, subject, html, content });
|
||||
const content = await this.notificationUtils.buildHardwareNotificationMessage(alerts, monitor);
|
||||
const webhookBody = await this.notificationUtils.buildHardwareWebhookBody(alerts, monitor);
|
||||
const success = await this.notifyAll({ notificationIDs, subject, html, content, discordContent, webhookBody });
|
||||
return success;
|
||||
}
|
||||
|
||||
// Status monitors
|
||||
const { subject, html } = await this.notificationUtils.buildStatusEmail(networkResponse);
|
||||
const content = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
||||
const success = this.notifyAll({ notificationIDs, subject, html, content });
|
||||
const [content, discordContent] = await this.notificationUtils.buildWebhookMessage(networkResponse);
|
||||
const success = this.notifyAll({ notificationIDs, subject, html, content, discordContent });
|
||||
return success;
|
||||
}
|
||||
|
||||
async notifyAll({ notificationIDs, subject, html, content }) {
|
||||
async notifyAll({ notificationIDs, subject, html, content, discordContent = null, webhookBody = null }) {
|
||||
const notifications = await this.db.notificationModule.getNotificationsByIds(notificationIDs);
|
||||
|
||||
// Map each notification to a test promise
|
||||
const promises = notifications.map(async (notification) => {
|
||||
try {
|
||||
await this.sendNotification({ notification, subject, content, html });
|
||||
await this.sendNotification({ notification, subject, content, html, discordContent, webhookBody });
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
|
||||
@@ -70,7 +70,25 @@ class NotificationUtils {
|
||||
.replace("{time}", formattedTime)
|
||||
.replace("{code}", code || "Unknown");
|
||||
}
|
||||
return messageText;
|
||||
const dn = this.stringService.discordNotification;
|
||||
let discordMessageText = {
|
||||
embeds: [
|
||||
{
|
||||
title: status ? dn.uptimeAlert : dn.downtimeAlert,
|
||||
color: status ? 5763719 : 15548997,
|
||||
|
||||
fields: [
|
||||
{ name: dn.monitor, value: monitor?.name ?? dn.unknown, inline: true },
|
||||
{ name: dn.status, value: status ? dn.up : dn.down, inline: true },
|
||||
{ name: dn.statusCode, value: String(code ?? dn.unknown), inline: true },
|
||||
{ name: dn.time, value: formattedTime, inline: true },
|
||||
{ name: dn.url, value: monitor?.url ?? dn.unknown, inline: false },
|
||||
],
|
||||
footer: { text: dn.checkmate },
|
||||
},
|
||||
],
|
||||
};
|
||||
return [messageText, discordMessageText];
|
||||
};
|
||||
|
||||
buildHardwareAlerts = async (networkResponse) => {
|
||||
@@ -88,6 +106,43 @@ class NotificationUtils {
|
||||
};
|
||||
|
||||
const alertsToSend = [];
|
||||
const discordEmbeds = [];
|
||||
const monitorInfoFields = [
|
||||
{ name: "Monitor", value: monitor.name, inline: true },
|
||||
{ name: "URL", value: monitor.url, inline: false },
|
||||
];
|
||||
const formatDiscordAlert = {
|
||||
cpu: () => ({
|
||||
title: "CPU alert",
|
||||
description: `Your current CPU usage (${(cpuUsage * 100).toFixed(0)}%) is above your threshold (${(cpuThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
fields: monitorInfoFields,
|
||||
footer: { text: "Checkmate" },
|
||||
}),
|
||||
|
||||
memory: () => ({
|
||||
title: "Memory alert",
|
||||
description: `Your current memory usage (${(memoryUsage * 100).toFixed(0)}%) is above your threshold (${(memoryThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
fields: monitorInfoFields,
|
||||
footer: { text: "Checkmate" },
|
||||
}),
|
||||
|
||||
disk: () => ({
|
||||
title: "Disk alert",
|
||||
description: `Your current disk usage is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||
color: 15548997,
|
||||
footer: { text: "Checkmate" },
|
||||
fields: [
|
||||
...monitorInfoFields,
|
||||
...(Array.isArray(disk) ? disk : []).map((d, idx) => ({
|
||||
name: `Disk ${idx}`,
|
||||
value: `${(d?.usage_percent * 100).toFixed(0)}%`,
|
||||
inline: true,
|
||||
})),
|
||||
],
|
||||
}),
|
||||
};
|
||||
const alertTypes = ["cpu", "memory", "disk"];
|
||||
for (const type of alertTypes) {
|
||||
// Iterate over each alert type to see if any need to be decremented
|
||||
@@ -108,11 +163,13 @@ class NotificationUtils {
|
||||
.join(", ")} is above your threshold (${(diskThreshold * 100).toFixed(0)}%)`,
|
||||
};
|
||||
alertsToSend.push(formatAlert[type]());
|
||||
discordEmbeds.push(formatDiscordAlert[type]());
|
||||
}
|
||||
}
|
||||
}
|
||||
await monitor.save();
|
||||
return alertsToSend;
|
||||
const discordPayload = discordEmbeds.length ? { embeds: discordEmbeds } : null;
|
||||
return [alertsToSend, discordPayload];
|
||||
};
|
||||
|
||||
buildHardwareEmail = async (networkResponse, alerts) => {
|
||||
@@ -124,8 +181,15 @@ class NotificationUtils {
|
||||
return { subject, html };
|
||||
};
|
||||
|
||||
buildHardwareNotificationMessage = (alerts) => {
|
||||
return alerts.map((alert) => alert).join("\n");
|
||||
buildHardwareNotificationMessage = (alerts, monitor) => {
|
||||
const alertsHeader = [`Monitor: ${monitor.name}`, `URL: ${monitor.url}`];
|
||||
const alertText = alerts.length > 0 ? [...alertsHeader, ...alerts] : [];
|
||||
return alertText.map((alert) => alert).join("\n");
|
||||
};
|
||||
buildHardwareWebhookBody = (alerts, monitor) => {
|
||||
const content = alerts.map((alert) => alert).join("\n");
|
||||
const body = { text: content, name: monitor.name, url: monitor.url };
|
||||
return body;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,10 @@ class StringService {
|
||||
return this.translationService.getTranslation("monitorDownAlert");
|
||||
}
|
||||
|
||||
get discordNotification() {
|
||||
return this.translationService.getTranslation("discordNotification");
|
||||
}
|
||||
|
||||
getWebhookUnsupportedPlatform(platform) {
|
||||
return this.translationService.getTranslation("webhookUnsupportedPlatform").replace("{platform}", platform);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user