mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-25 03:09:32 -06:00
Merge branch 'develop' into fix-incidents-creation
This commit is contained in:
1
.github/workflows/staging-deploy.yml
vendored
1
.github/workflows/staging-deploy.yml
vendored
@@ -88,3 +88,4 @@ jobs:
|
||||
docker compose down
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker system prune -af
|
||||
|
||||
11325
Checkmate.CodeCanvas
Normal file
11325
Checkmate.CodeCanvas
Normal file
File diff suppressed because one or more lines are too long
@@ -162,6 +162,7 @@ Here's how you can contribute:
|
||||
4. Open an issue if you believe you've encountered a bug.
|
||||
5. Check for good-first-issue's if you are a newcomer.
|
||||
6. Make a pull request to add new features/make quality-of-life improvements/fix bugs.
|
||||
7. Check out this interactive walkthrough of the `Checkmate` codebase on CodeCanvas [here](https://www.code-canvas.com/?session=unauthenticatedGithub&repo=Checkmate&owner=bluewave-labs&branch=develop&OnboardingTutorial=true). To refine existing dataflow simulation or create new ones, follow the quick tutorial [here](https://docs.code-canvas.com/updating-diagram).
|
||||
|
||||
<a href="https://github.com/bluewave-labs/checkmate/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=bluewave-labs/checkmate" />
|
||||
|
||||
@@ -21,6 +21,10 @@ cd checkmate/charts/helm/checkmate
|
||||
Edit `values.yaml` to update:
|
||||
- `client.ingress.host` and `server.ingress.host` with your domain names
|
||||
- `server.protocol` (usually http or https)
|
||||
- **If upgrading**: Migrate persistence settings from flat structure to nested:
|
||||
- Old: `persistence.mongodbSize` → New: `persistence.mongo.size`
|
||||
- Old: `persistence.redisSize` → New: `persistence.redis.size`
|
||||
- Add: `persistence.mongo.storageClass` and `persistence.redis.storageClass` (leave empty for default)
|
||||
- Secrets under the `secrets` section (`JWT_SECRET`, email credentials, API keys, etc.) — replace all change_me values
|
||||
|
||||
### 3. Deploy the Helm chart
|
||||
|
||||
@@ -37,8 +37,8 @@ spec:
|
||||
- metadata:
|
||||
name: checkmate-mongo-persistent-storage
|
||||
spec:
|
||||
storageClassName: "gp3"
|
||||
storageClassName: {{ .Values.persistence.mongo.storageClass | quote }}
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.mongodbSize }}
|
||||
storage: {{ .Values.persistence.mongo.size | default "5Gi" | quote }}
|
||||
@@ -25,9 +25,9 @@ spec:
|
||||
- metadata:
|
||||
name: checkmate-redis-persistent-storage
|
||||
spec:
|
||||
storageClassName: "gp3"
|
||||
storageClassName: {{ .Values.persistence.redis.storageClass | quote }}
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.redisSize }}
|
||||
storage: {{ .Values.persistence.redis.size | default "1Gi" | quote }}
|
||||
{{- end }}
|
||||
@@ -44,5 +44,9 @@ secrets:
|
||||
# REFRESH_TOKEN_TTL: 99d
|
||||
|
||||
persistence:
|
||||
mongodbSize: 5Gi
|
||||
redisSize: 1Gi
|
||||
mongo:
|
||||
size: 5Gi
|
||||
storageClass: ""
|
||||
redis:
|
||||
size: 1Gi
|
||||
storageClass: ""
|
||||
@@ -223,18 +223,54 @@ const CreateNotifications = () => {
|
||||
<Typography component="p">{t(DESCRIPTION_MAP[type])}</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(12)}>
|
||||
<TextInput
|
||||
label={t(LABEL_MAP[type])}
|
||||
name="address"
|
||||
placeholder={t(PLACEHOLDER_MAP[type])}
|
||||
value={notification.address}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.address)}
|
||||
helperText={errors["address"]}
|
||||
/>
|
||||
{type === "matrix" ? (
|
||||
<>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.homeserverLabel")}
|
||||
name="homeserverUrl"
|
||||
placeholder={t(
|
||||
"createNotifications.matrixSettings.homeserverPlaceholder"
|
||||
)}
|
||||
value={notification.homeserverUrl || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.homeserverUrl)}
|
||||
helperText={errors["homeserverUrl"]}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.roomIdLabel")}
|
||||
name="roomId"
|
||||
placeholder={t("createNotifications.matrixSettings.roomIdPlaceholder")}
|
||||
value={notification.roomId || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.roomId)}
|
||||
helperText={errors["roomId"]}
|
||||
/>
|
||||
<TextInput
|
||||
label={t("createNotifications.matrixSettings.accessTokenLabel")}
|
||||
name="accessToken"
|
||||
type="password"
|
||||
placeholder={t(
|
||||
"createNotifications.matrixSettings.accessTokenPlaceholder"
|
||||
)}
|
||||
value={notification.accessToken || ""}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.accessToken)}
|
||||
helperText={errors["accessToken"]}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TextInput
|
||||
label={t(LABEL_MAP[type])}
|
||||
name="address"
|
||||
placeholder={t(PLACEHOLDER_MAP[type])}
|
||||
value={notification.address}
|
||||
onChange={onChange}
|
||||
error={Boolean(errors.address)}
|
||||
helperText={errors["address"]}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
|
||||
</ConfigBox>{" "}
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
|
||||
@@ -4,6 +4,7 @@ export const NOTIFICATION_TYPES = [
|
||||
{ _id: 3, name: "PagerDuty", value: "pager_duty" },
|
||||
{ _id: 4, name: "Webhook", value: "webhook" },
|
||||
{ _id: 5, name: "Discord", value: "discord" },
|
||||
{ _id: 6, name: "Matrix", value: "matrix" },
|
||||
];
|
||||
|
||||
export const TITLE_MAP = {
|
||||
@@ -12,6 +13,7 @@ export const TITLE_MAP = {
|
||||
pager_duty: "createNotifications.pagerdutySettings.title",
|
||||
webhook: "createNotifications.webhookSettings.title",
|
||||
discord: "createNotifications.discordSettings.title",
|
||||
matrix: "createNotifications.matrixSettings.title",
|
||||
};
|
||||
|
||||
export const DESCRIPTION_MAP = {
|
||||
@@ -20,6 +22,7 @@ export const DESCRIPTION_MAP = {
|
||||
pager_duty: "createNotifications.pagerdutySettings.description",
|
||||
webhook: "createNotifications.webhookSettings.description",
|
||||
discord: "createNotifications.discordSettings.description",
|
||||
matrix: "createNotifications.matrixSettings.description",
|
||||
};
|
||||
|
||||
export const LABEL_MAP = {
|
||||
@@ -28,6 +31,7 @@ export const LABEL_MAP = {
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyLabel",
|
||||
webhook: "createNotifications.webhookSettings.webhookLabel",
|
||||
discord: "createNotifications.discordSettings.webhookLabel",
|
||||
matrix: "createNotifications.matrixSettings.homeserverLabel",
|
||||
};
|
||||
|
||||
export const PLACEHOLDER_MAP = {
|
||||
@@ -36,4 +40,5 @@ export const PLACEHOLDER_MAP = {
|
||||
pager_duty: "createNotifications.pagerdutySettings.integrationKeyPlaceholder",
|
||||
webhook: "createNotifications.webhookSettings.webhookPlaceholder",
|
||||
discord: "createNotifications.discordSettings.webhookPlaceholder",
|
||||
matrix: "createNotifications.matrixSettings.homeserverPlaceholder",
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ const useMonitorsFetch = () => {
|
||||
try {
|
||||
const response = await networkService.getMonitorsByTeamId({
|
||||
limit: null, // donot return any checks for the monitors
|
||||
types: ["http", "ping", "port"], // status page is available for uptime, ping, and port monitors
|
||||
types: ["http", "ping", "port", "game"], // include game servers in status page monitor selection
|
||||
});
|
||||
setMonitors(response.data.data.monitors);
|
||||
} catch (error) {
|
||||
|
||||
@@ -7,6 +7,80 @@ import { StatusLabel } from "@/Components/v1/Label/index.jsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDateWithTz } from "../../../../../../Utils/timeUtils.js";
|
||||
import SkeletonLayout from "./skeleton.jsx";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import { lighten, useTheme } from "@mui/material";
|
||||
|
||||
/**
|
||||
* Creates tooltip content with detailed timing breakdown
|
||||
* Following the pattern from IncidentTable's GetTooltip function
|
||||
* @param {Object} timings - Timing object (guaranteed to have phases by caller)
|
||||
* @param {Object} theme - MUI theme object
|
||||
* @param {Function} t - Translation function
|
||||
* @returns {JSX.Element} Tooltip content
|
||||
*/
|
||||
const GetTooltip = (timings, theme, t) => {
|
||||
const phases = timings.phases;
|
||||
const timingDetails = [
|
||||
{ label: t("dnsLookup"), value: phases.dns },
|
||||
{ label: t("tcpConnection"), value: phases.tcp },
|
||||
{ label: t("tlsHandshake"), value: phases.tls },
|
||||
{ label: t("waitTime"), value: phases.wait },
|
||||
{ label: t("timeToFirstByte"), value: phases.firstByte },
|
||||
{ label: t("download"), value: phases.download },
|
||||
{ label: t("total"), value: phases.total },
|
||||
].filter((item) => item.value > 0);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
py: theme.spacing(2),
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
marginBottom: theme.spacing(1),
|
||||
color: theme.palette.primary.contrastText,
|
||||
}}
|
||||
>
|
||||
{t("responseTimeBreakdown")}
|
||||
</Typography>
|
||||
{timingDetails.map((detail, index, array) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
gap: theme.spacing(4),
|
||||
marginBottom: index < array.length - 1 ? theme.spacing(0.5) : 0,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: theme.palette.primary.contrastText }}
|
||||
>
|
||||
{detail.label}:
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: index === array.length - 1 ? 600 : 400,
|
||||
color: theme.palette.primary.contrastText,
|
||||
}}
|
||||
>
|
||||
{Math.round(detail.value)} ms
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const ResponseTable = ({
|
||||
isLoading = false,
|
||||
checks = [],
|
||||
@@ -18,6 +92,8 @@ const ResponseTable = ({
|
||||
setRowsPerPage,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
}
|
||||
@@ -54,6 +130,56 @@ const ResponseTable = ({
|
||||
content: t("message"),
|
||||
render: (row) => row.message,
|
||||
},
|
||||
{
|
||||
id: "responseTime",
|
||||
content: t("responseTime"),
|
||||
render: (row) => {
|
||||
const hasTimings = row.timings && row.timings.phases;
|
||||
const responseTime = row.responseTime;
|
||||
const responseTimeDisplay =
|
||||
responseTime !== null && responseTime !== undefined
|
||||
? `${Math.round(responseTime)} ms`
|
||||
: "N/A";
|
||||
|
||||
if (!hasTimings) {
|
||||
return responseTimeDisplay;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={GetTooltip(row.timings, theme, t)}
|
||||
placement="top"
|
||||
arrow
|
||||
enterDelay={300}
|
||||
enterNextDelay={300}
|
||||
slotProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
backgroundColor: lighten(theme.palette.primary.main, 0.1),
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
"& .MuiTooltip-arrow": {
|
||||
color: lighten(theme.palette.primary.main, 0.1),
|
||||
"&::before": {
|
||||
border: `1px solid ${theme.palette.primary.lowContrast}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
cursor: "help",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{responseTimeDisplay}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -458,7 +458,7 @@ const notificationValidation = joi.object({
|
||||
|
||||
type: joi
|
||||
.string()
|
||||
.valid("email", "webhook", "slack", "discord", "pager_duty")
|
||||
.valid("email", "webhook", "slack", "discord", "pager_duty", "matrix")
|
||||
.required()
|
||||
.messages({
|
||||
"string.empty": "Notification type is required",
|
||||
@@ -495,8 +495,40 @@ const notificationValidation = joi.object({
|
||||
"string.uri": "Please enter a valid Webhook URL",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "matrix",
|
||||
then: joi.string().allow("").optional(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
homeserverUrl: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Homeserver URL cannot be empty",
|
||||
"any.required": "Homeserver URL is required",
|
||||
"string.uri": "Please enter a valid Homeserver URL",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
roomId: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Room ID cannot be empty",
|
||||
"any.required": "Room ID is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
accessToken: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Access Token cannot be empty",
|
||||
"any.required": "Access Token is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const editUserValidation = joi.object({
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
"delete": "Delete",
|
||||
"configure": "Configure",
|
||||
"responseTime": "Response time",
|
||||
"responseTimeBreakdown": "Response time breakdown",
|
||||
"dnsLookup": "DNS lookup",
|
||||
"tcpConnection": "TCP connection",
|
||||
"tlsHandshake": "TLS handshake",
|
||||
"waitTime": "Wait time",
|
||||
"timeToFirstByte": "Time to first byte",
|
||||
"download": "Download",
|
||||
"ms": "ms",
|
||||
"bar": "Bar",
|
||||
"area": "Area",
|
||||
|
||||
@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default defineConfig(({}) => {
|
||||
let version = "3.2.0";
|
||||
let version = "3.2.1";
|
||||
|
||||
return {
|
||||
base: "/",
|
||||
|
||||
2316
server/package-lock.json
generated
2316
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
"axios": "^1.7.2",
|
||||
"bcryptjs": "3.0.2",
|
||||
"bullmq": "5.41.2",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"compression": "1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
@@ -51,7 +52,7 @@
|
||||
"ping": "0.4.4",
|
||||
"sharp": "0.33.5",
|
||||
"ssl-checker": "2.0.10",
|
||||
"super-simple-scheduler": "1.4.1",
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ const NotificationSchema = mongoose.Schema(
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ["email", "slack", "discord", "webhook", "pager_duty"],
|
||||
enum: ["email", "slack", "discord", "webhook", "pager_duty", "matrix"],
|
||||
},
|
||||
notificationName: {
|
||||
type: String,
|
||||
@@ -28,6 +28,16 @@ const NotificationSchema = mongoose.Schema(
|
||||
phone: {
|
||||
type: String,
|
||||
},
|
||||
// Matrix-specific fields
|
||||
homeserverUrl: {
|
||||
type: String,
|
||||
},
|
||||
roomId: {
|
||||
type: String,
|
||||
},
|
||||
accessToken: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Router } from "express";
|
||||
import MaintenanceWindow from "../../db/v1/models/MaintenanceWindow.js";
|
||||
|
||||
class MaintenanceWindowRoutes {
|
||||
constructor(maintenanceWindowController) {
|
||||
this.router = Router();
|
||||
|
||||
@@ -35,8 +35,12 @@ class SuperSimpleQueue {
|
||||
this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob());
|
||||
const monitors = await this.db.monitorModule.getAllMonitors();
|
||||
for (const monitor of monitors) {
|
||||
await this.addJob(monitor._id, monitor);
|
||||
const randomOffset = Math.floor(Math.random() * 100);
|
||||
setTimeout(() => {
|
||||
this.addJob(monitor._id, monitor);
|
||||
}, randomOffset);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
|
||||
@@ -46,7 +46,7 @@ class EmailService {
|
||||
*/
|
||||
this.loadTemplate = (templateName) => {
|
||||
try {
|
||||
const templatePath = this.path.join(__dirname, `../../templates/${templateName}.mjml`);
|
||||
const templatePath = this.path.join(__dirname, `../../../templates/${templateName}.mjml`);
|
||||
const templateContent = this.fs.readFileSync(templatePath, "utf8");
|
||||
return this.compile(templateContent);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import CacheableLookup from "cacheable-lookup";
|
||||
const SERVICE_NAME = "NetworkService";
|
||||
|
||||
class NetworkService {
|
||||
@@ -15,7 +16,6 @@ class NetworkService {
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.PING_ERROR = 5001;
|
||||
this.axios = axios;
|
||||
this.got = got;
|
||||
this.https = https;
|
||||
this.jmespath = jmespath;
|
||||
this.GameDig = GameDig;
|
||||
@@ -26,6 +26,16 @@ class NetworkService {
|
||||
this.net = net;
|
||||
this.stringService = stringService;
|
||||
this.settingsService = settingsService;
|
||||
|
||||
const cacheable = new CacheableLookup();
|
||||
|
||||
this.got = got.extend({
|
||||
dnsCache: cacheable,
|
||||
timeout: {
|
||||
request: 30000,
|
||||
},
|
||||
retry: { limit: 1 },
|
||||
});
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
@@ -529,6 +539,42 @@ class NetworkService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async requestMatrix({ homeserverUrl, accessToken, roomId, message }) {
|
||||
try {
|
||||
const url = `${homeserverUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message?access_token=${accessToken}`;
|
||||
const body = {
|
||||
msgtype: "m.text",
|
||||
body: message,
|
||||
format: "org.matrix.custom.html",
|
||||
formatted_body: message,
|
||||
};
|
||||
const response = await this.axios.post(url, body, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
status: true,
|
||||
code: response.status,
|
||||
message: "Successfully sent Matrix notification",
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "requestMatrix",
|
||||
});
|
||||
|
||||
return {
|
||||
status: false,
|
||||
code: error.response?.status || this.NETWORK_ERROR,
|
||||
message: "Failed to send Matrix notification",
|
||||
payload: error.response?.data,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NetworkService;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
const SERVICE_NAME = "Matrix";
|
||||
|
||||
class Matrix {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor({ networkService, logger }) {
|
||||
this.networkService = networkService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return Matrix.SERVICE_NAME;
|
||||
}
|
||||
|
||||
async send({ friendlyName, homeserverUrl, accessToken, roomId, message, monitorName }) {
|
||||
const title = `Checkmate status for ${monitorName}`;
|
||||
const formattedMessage = `## ${title}\n${message}`;
|
||||
try {
|
||||
await this.networkService.requestMatrix({
|
||||
homeserverUrl,
|
||||
accessToken,
|
||||
roomId,
|
||||
message: formattedMessage,
|
||||
});
|
||||
this.logger.info(`Successfully sent Matrix notification for ${friendlyName}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to send Matrix notification for ${friendlyName}: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Matrix;
|
||||
@@ -1,4 +1,5 @@
|
||||
const SERVICE_NAME = "NotificationService";
|
||||
import Matrix from "./notificationProviders/matrix.js";
|
||||
|
||||
class NotificationService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
@@ -46,6 +47,14 @@ class NotificationService {
|
||||
|
||||
return response;
|
||||
}
|
||||
if (type === "matrix") {
|
||||
const { friendlyName, homeserverUrl, accessToken, roomId } = notification;
|
||||
const monitorName = subject;
|
||||
const message = content;
|
||||
const matrix = new Matrix({ networkService: this.networkService, logger: this.logger });
|
||||
const success = await matrix.send({ friendlyName, homeserverUrl, accessToken, roomId, message, monitorName });
|
||||
return success;
|
||||
}
|
||||
};
|
||||
|
||||
async handleNotifications(networkResponse) {
|
||||
|
||||
@@ -381,7 +381,7 @@ class MonitorService implements IMonitorService {
|
||||
// Get monitor stats
|
||||
const monitorStats = await MonitorStats.findOne({
|
||||
monitorId: monitor._id,
|
||||
}).lean();
|
||||
});
|
||||
|
||||
if (!monitorStats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
|
||||
@@ -575,10 +575,10 @@ const createNotificationBodyValidation = joi.object({
|
||||
"any.required": "Notification name is required",
|
||||
}),
|
||||
|
||||
type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty").required().messages({
|
||||
type: joi.string().valid("email", "webhook", "slack", "discord", "pager_duty", "matrix").required().messages({
|
||||
"string.empty": "Notification type is required",
|
||||
"any.required": "Notification type is required",
|
||||
"any.only": "Notification type must be email, webhook, or pager_duty",
|
||||
"any.only": "Notification type must be email, webhook, slack, discord, pager_duty, or matrix",
|
||||
}),
|
||||
|
||||
address: joi.when("type", {
|
||||
@@ -606,8 +606,40 @@ const createNotificationBodyValidation = joi.object({
|
||||
"string.uri": "Please enter a valid Webhook URL",
|
||||
}),
|
||||
},
|
||||
{
|
||||
is: "matrix",
|
||||
then: joi.string().allow("").optional(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
homeserverUrl: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().uri().required().messages({
|
||||
"string.empty": "Homeserver URL cannot be empty",
|
||||
"any.required": "Homeserver URL is required",
|
||||
"string.uri": "Please enter a valid Homeserver URL",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
roomId: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Room ID cannot be empty",
|
||||
"any.required": "Room ID is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
|
||||
accessToken: joi.when("type", {
|
||||
is: "matrix",
|
||||
then: joi.string().required().messages({
|
||||
"string.empty": "Access Token cannot be empty",
|
||||
"any.required": "Access Token is required",
|
||||
}),
|
||||
otherwise: joi.string().allow("").optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
//****************************************
|
||||
|
||||
Reference in New Issue
Block a user