feat: add Matrix chat notification support (#211)
Implements native Matrix notification channel support, allowing users to receive service alerts in any Matrix room via the Client-Server API. Changes: - server/service-operation/notification/matrix.go: new Matrix service implementing the NotificationService interface (PUT /_matrix/client/v3/...) - server/service-operation/notification/types.go: add MatrixHomeserver, MatrixRoomID, MatrixAccessToken fields to AlertConfiguration - server/service-operation/notification/manager.go: register MatrixService - server/pb_migrations/1772200000_updated_alert_configurations_matrix.js: PocketBase migration adding matrix fields and notification_type value - application: Matrix option in channel dialog with homeserver/room/token fields, tab filter, list display, type definitions and translations (en, km) - application/public/upload/notification/matrix.png: Matrix channel icon
This commit is contained in:
@@ -60,7 +60,7 @@ CheckCle is an Open Source solution for seamless, real-time monitoring of full-s
|
|||||||
- Infrastructure Server Monitoring, Supports Linux (🐧 Debian, Ubuntu, CentOS, Red Hat, etc.) and Windows (Beta). And Servers metrics like CPU, RAM, disk usage, and network activity) with an one-line installation angent script.
|
- Infrastructure Server Monitoring, Supports Linux (🐧 Debian, Ubuntu, CentOS, Red Hat, etc.) and Windows (Beta). And Servers metrics like CPU, RAM, disk usage, and network activity) with an one-line installation angent script.
|
||||||
- Schedule Maintenance & Incident Management
|
- Schedule Maintenance & Incident Management
|
||||||
- Operational Status / Public Status Pages
|
- Operational Status / Public Status Pages
|
||||||
- Notifications via email, Telegram, Discord, and Slack
|
- Notifications via email, Telegram, Discord, Slack, Matrix, and more
|
||||||
- Reports & Analytics
|
- Reports & Analytics
|
||||||
- Settings Panel (User Management, Data Retention, Multi-language, Themes (Dark & Light Mode), Notification and channels and alert templates).
|
- Settings Panel (User Management, Data Retention, Multi-language, Themes (Dark & Light Mode), Notification and channels and alert templates).
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 250 B |
+71
-4
@@ -38,7 +38,7 @@ interface NotificationChannelDialogProps {
|
|||||||
|
|
||||||
const baseSchema = z.object({
|
const baseSchema = z.object({
|
||||||
notify_name: z.string().min(1, "Name is required"),
|
notify_name: z.string().min(1, "Name is required"),
|
||||||
notification_type: z.enum(["telegram", "discord", "slack", "signal", "google_chat", "email", "ntfy", "pushover", "notifiarr", "gotify", "webhook"]),
|
notification_type: z.enum(["telegram", "discord", "slack", "signal", "google_chat", "email", "ntfy", "pushover", "notifiarr", "gotify", "webhook", "matrix"]),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
service_id: z.string().default("global"),
|
service_id: z.string().default("global"),
|
||||||
template_id: z.string().optional(),
|
template_id: z.string().optional(),
|
||||||
@@ -110,6 +110,13 @@ const gotifySchema = baseSchema.extend({
|
|||||||
server_url: z.string().url("Must be a valid server URL"),
|
server_url: z.string().url("Must be a valid server URL"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const matrixSchema = baseSchema.extend({
|
||||||
|
notification_type: z.literal("matrix"),
|
||||||
|
matrix_homeserver: z.string().url("Must be a valid homeserver URL"),
|
||||||
|
matrix_room_id: z.string().min(1, "Room ID is required"),
|
||||||
|
matrix_access_token: z.string().min(1, "Access token is required"),
|
||||||
|
});
|
||||||
|
|
||||||
const formSchema = z.discriminatedUnion("notification_type", [
|
const formSchema = z.discriminatedUnion("notification_type", [
|
||||||
telegramSchema,
|
telegramSchema,
|
||||||
discordSchema,
|
discordSchema,
|
||||||
@@ -122,6 +129,7 @@ const formSchema = z.discriminatedUnion("notification_type", [
|
|||||||
notifiarrSchema,
|
notifiarrSchema,
|
||||||
gotifySchema,
|
gotifySchema,
|
||||||
webhookSchema,
|
webhookSchema,
|
||||||
|
matrixSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
type FormValues = z.infer<typeof formSchema>;
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
@@ -188,12 +196,18 @@ const notificationTypeOptions = [
|
|||||||
description: "Send push notifications via Gotify",
|
description: "Send push notifications via Gotify",
|
||||||
icon: "/upload/notification/gotify.png"
|
icon: "/upload/notification/gotify.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "webhook",
|
value: "webhook",
|
||||||
label: "Webhook",
|
label: "Webhook",
|
||||||
description: "Send notifications to custom webhook",
|
description: "Send notifications to custom webhook",
|
||||||
icon: "/upload/notification/webhook.png"
|
icon: "/upload/notification/webhook.png"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "matrix",
|
||||||
|
label: "Matrix",
|
||||||
|
description: "Send notifications to a Matrix chat room",
|
||||||
|
icon: "/upload/notification/matrix.png"
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const webhookPayloadTemplates = {
|
const webhookPayloadTemplates = {
|
||||||
@@ -832,6 +846,59 @@ export const NotificationChannelDialog = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{notificationType === "matrix" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="matrix_homeserver"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("matrixHomeserver")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("matrixHomeserverPlaceholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("matrixHomeserverDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="matrix_room_id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("matrixRoomId")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("matrixRoomIdPlaceholder")} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("matrixRoomIdDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="matrix_access_token"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("matrixAccessToken")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder={t("matrixAccessTokenPlaceholder")} {...field} type="password" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t("matrixAccessTokenDesc")}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="enabled"
|
name="enabled"
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const NotificationChannelList = ({
|
|||||||
case "pushover": return "Pushover";
|
case "pushover": return "Pushover";
|
||||||
case "notifiarr": return "Notifiarr";
|
case "notifiarr": return "Notifiarr";
|
||||||
case "webhook": return "Webhook";
|
case "webhook": return "Webhook";
|
||||||
|
case "matrix": return "Matrix";
|
||||||
default: return type || "Unknown";
|
default: return type || "Unknown";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -102,6 +103,8 @@ export const NotificationChannelList = ({
|
|||||||
return config.signal_number || '';
|
return config.signal_number || '';
|
||||||
case "email":
|
case "email":
|
||||||
return config.email_address || '';
|
return config.email_address || '';
|
||||||
|
case "matrix":
|
||||||
|
return config.matrix_room_id || '';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ const NotificationSettings = () => {
|
|||||||
<TabsTrigger value="google_chat">{t("googleChat")}</TabsTrigger>
|
<TabsTrigger value="google_chat">{t("googleChat")}</TabsTrigger>
|
||||||
<TabsTrigger value="email">{t("email")}</TabsTrigger>
|
<TabsTrigger value="email">{t("email")}</TabsTrigger>
|
||||||
<TabsTrigger value="webhook">{t("webhook")}</TabsTrigger>
|
<TabsTrigger value="webhook">{t("webhook")}</TabsTrigger>
|
||||||
|
<TabsTrigger value="matrix">{t("matrix")}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value={currentTab} className="mt-0">
|
<TabsContent value={currentTab} className="mt-0">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export interface AlertConfiguration {
|
|||||||
collectionId?: string;
|
collectionId?: string;
|
||||||
collectionName?: string;
|
collectionName?: string;
|
||||||
service_id: string;
|
service_id: string;
|
||||||
notification_type: "telegram" | "discord" | "slack" | "signal" | "google_chat" | "email" | "ntfy" | "pushover" | "notifiarr" | "gotify" | "webhook";
|
notification_type: "telegram" | "discord" | "slack" | "signal" | "google_chat" | "email" | "ntfy" | "pushover" | "notifiarr" | "gotify" | "webhook" | "matrix";
|
||||||
telegram_chat_id?: string;
|
telegram_chat_id?: string;
|
||||||
discord_webhook_url?: string;
|
discord_webhook_url?: string;
|
||||||
signal_number?: string;
|
signal_number?: string;
|
||||||
@@ -34,6 +34,9 @@ export interface AlertConfiguration {
|
|||||||
server_url?: string;
|
server_url?: string;
|
||||||
webhook_url?: string;
|
webhook_url?: string;
|
||||||
webhook_payload_template?: string;
|
webhook_payload_template?: string;
|
||||||
|
matrix_homeserver?: string;
|
||||||
|
matrix_room_id?: string;
|
||||||
|
matrix_access_token?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const alertConfigService = {
|
export const alertConfigService = {
|
||||||
@@ -105,8 +108,13 @@ export const alertConfigService = {
|
|||||||
} else if (config.notification_type === "webhook") {
|
} else if (config.notification_type === "webhook") {
|
||||||
cleanConfig.webhook_url = config.webhook_url || "";
|
cleanConfig.webhook_url = config.webhook_url || "";
|
||||||
cleanConfig.webhook_payload_template = config.webhook_payload_template || "";
|
cleanConfig.webhook_payload_template = config.webhook_payload_template || "";
|
||||||
|
|
||||||
}
|
} else if (config.notification_type === "matrix") {
|
||||||
|
cleanConfig.matrix_homeserver = config.matrix_homeserver || "";
|
||||||
|
cleanConfig.matrix_room_id = config.matrix_room_id || "";
|
||||||
|
cleanConfig.matrix_access_token = config.matrix_access_token || "";
|
||||||
|
|
||||||
|
}
|
||||||
const result = await pb.collection('alert_configurations').create(cleanConfig);
|
const result = await pb.collection('alert_configurations').create(cleanConfig);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const settingsTranslations: SettingsTranslations = {
|
|||||||
googleChat: "Google Chat",
|
googleChat: "Google Chat",
|
||||||
email: "Email",
|
email: "Email",
|
||||||
webhook: "Webhook",
|
webhook: "Webhook",
|
||||||
|
matrix: "Matrix",
|
||||||
|
|
||||||
// NotificationChannelDialog.tsx
|
// NotificationChannelDialog.tsx
|
||||||
editChannel: "Edit Notification Channel",
|
editChannel: "Edit Notification Channel",
|
||||||
@@ -129,6 +130,12 @@ export const settingsTranslations: SettingsTranslations = {
|
|||||||
notifiarrChannelIdDesc: "The Discord channel ID where notifications will be sent",
|
notifiarrChannelIdDesc: "The Discord channel ID where notifications will be sent",
|
||||||
gotifyServerUrl: "Server URL",
|
gotifyServerUrl: "Server URL",
|
||||||
gotifyServerUrlDesc: "The URL of your Gotify server",
|
gotifyServerUrlDesc: "The URL of your Gotify server",
|
||||||
|
matrixHomeserver: "Homeserver URL",
|
||||||
|
matrixHomeserverDesc: "The URL of your Matrix homeserver (e.g. https://matrix.org)",
|
||||||
|
matrixRoomId: "Room ID",
|
||||||
|
matrixRoomIdDesc: "The Matrix room ID to send notifications to (e.g. !abc123:matrix.org)",
|
||||||
|
matrixAccessToken: "Access Token",
|
||||||
|
matrixAccessTokenDesc: "The access token of your Matrix bot account",
|
||||||
errorSaveChannel: "Failed to save notification channel",
|
errorSaveChannel: "Failed to save notification channel",
|
||||||
|
|
||||||
channelNamePlaceholder: "My Notification Channel",
|
channelNamePlaceholder: "My Notification Channel",
|
||||||
@@ -150,6 +157,9 @@ export const settingsTranslations: SettingsTranslations = {
|
|||||||
notifiarrChannelIdPlaceholder: "Discord Channel ID",
|
notifiarrChannelIdPlaceholder: "Discord Channel ID",
|
||||||
gotifyServerUrlPlaceholder: "https://your-gotify-server.com",
|
gotifyServerUrlPlaceholder: "https://your-gotify-server.com",
|
||||||
webhookUrlPlaceholder: "https://api.example.com/webhook",
|
webhookUrlPlaceholder: "https://api.example.com/webhook",
|
||||||
|
matrixHomeserverPlaceholder: "https://matrix.org",
|
||||||
|
matrixRoomIdPlaceholder: "!roomid:matrix.org",
|
||||||
|
matrixAccessTokenPlaceholder: "syt_...",
|
||||||
|
|
||||||
// DataRetentionSettings.tsx
|
// DataRetentionSettings.tsx
|
||||||
// permissionNotice: "Permission Notice:",
|
// permissionNotice: "Permission Notice:",
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ descriptionChannelsServices: "កំណត់រចនាសម្ព័ន្
|
|||||||
googleChat: "Google Chat",
|
googleChat: "Google Chat",
|
||||||
email: "អ៊ីមែល",
|
email: "អ៊ីមែល",
|
||||||
webhook: "Webhook",
|
webhook: "Webhook",
|
||||||
|
matrix: "Matrix",
|
||||||
|
|
||||||
// NotificationChannelDialog.tsx
|
// NotificationChannelDialog.tsx
|
||||||
editChannel: "កែសម្រួលបណ្តាញជូនដំណឹង",
|
editChannel: "កែសម្រួលបណ្តាញជូនដំណឹង",
|
||||||
@@ -129,6 +130,12 @@ descriptionChannelsServices: "កំណត់រចនាសម្ព័ន្
|
|||||||
notifiarrChannelIdDesc: "លេខសម្គាល់បណ្តាញ Discord ដែលការជូនដំណឹងនឹងត្រូវបានផ្ញើទៅ",
|
notifiarrChannelIdDesc: "លេខសម្គាល់បណ្តាញ Discord ដែលការជូនដំណឹងនឹងត្រូវបានផ្ញើទៅ",
|
||||||
gotifyServerUrl: "URL នៃម៉ាស៊ីនបម្រើ",
|
gotifyServerUrl: "URL នៃម៉ាស៊ីនបម្រើ",
|
||||||
gotifyServerUrlDesc: "URL នៃម៉ាស៊ីនបម្រើ Gotify របស់អ្នក",
|
gotifyServerUrlDesc: "URL នៃម៉ាស៊ីនបម្រើ Gotify របស់អ្នក",
|
||||||
|
matrixHomeserver: "Homeserver URL",
|
||||||
|
matrixHomeserverDesc: "URL នៃ Matrix homeserver របស់អ្នក (ឧ. https://matrix.org)",
|
||||||
|
matrixRoomId: "Room ID",
|
||||||
|
matrixRoomIdDesc: "Matrix room ID ដែលការជូនដំណឹងនឹងត្រូវបានផ្ញើទៅ (ឧ. !abc123:matrix.org)",
|
||||||
|
matrixAccessToken: "Access Token",
|
||||||
|
matrixAccessTokenDesc: "Access token នៃ Matrix bot account របស់អ្នក",
|
||||||
errorSaveChannel: "បរាជ័យក្នុងការរក្សាទុកបណ្តាញជូនដំណឹង",
|
errorSaveChannel: "បរាជ័យក្នុងការរក្សាទុកបណ្តាញជូនដំណឹង",
|
||||||
|
|
||||||
channelNamePlaceholder: "ប៉ុស្តិ៍ផ្ទាល់សារជូនដំណឹងរបស់ខ្ញុំ",
|
channelNamePlaceholder: "ប៉ុស្តិ៍ផ្ទាល់សារជូនដំណឹងរបស់ខ្ញុំ",
|
||||||
@@ -150,6 +157,9 @@ descriptionChannelsServices: "កំណត់រចនាសម្ព័ន្
|
|||||||
notifiarrChannelIdPlaceholder: "ID ប៉ុស្តិ៍ផ្ទាល់សារ Discord",
|
notifiarrChannelIdPlaceholder: "ID ប៉ុស្តិ៍ផ្ទាល់សារ Discord",
|
||||||
gotifyServerUrlPlaceholder: "https://your-gotify-server.com",
|
gotifyServerUrlPlaceholder: "https://your-gotify-server.com",
|
||||||
webhookUrlPlaceholder: "https://api.example.com/webhook",
|
webhookUrlPlaceholder: "https://api.example.com/webhook",
|
||||||
|
matrixHomeserverPlaceholder: "https://matrix.org",
|
||||||
|
matrixRoomIdPlaceholder: "!roomid:matrix.org",
|
||||||
|
matrixAccessTokenPlaceholder: "syt_...",
|
||||||
|
|
||||||
// DataRetentionSettings.tsx
|
// DataRetentionSettings.tsx
|
||||||
permissionNoticeDataRetention: "ជាអ្នកប្រើប្រាស់អ្នកគ្រប់គ្រង អ្នកមិនមានសិទ្ធចូលដំណើរការការកំណត់រក្សាទុកទិន្នន័យទេ។ ការកំណត់ទាំងនេះអាចត្រូវបានចូលដំណើរការនិងកែប្រែដោយតែអ្នកគ្រប់គ្រងដ៏ខ្ពស់ប៉ុណ្ណោះ។",
|
permissionNoticeDataRetention: "ជាអ្នកប្រើប្រាស់អ្នកគ្រប់គ្រង អ្នកមិនមានសិទ្ធចូលដំណើរការការកំណត់រក្សាទុកទិន្នន័យទេ។ ការកំណត់ទាំងនេះអាចត្រូវបានចូលដំណើរការនិងកែប្រែដោយតែអ្នកគ្រប់គ្រងដ៏ខ្ពស់ប៉ុណ្ណោះ។",
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export interface SettingsTranslations {
|
|||||||
googleChat: string;
|
googleChat: string;
|
||||||
email: string;
|
email: string;
|
||||||
webhook: string;
|
webhook: string;
|
||||||
|
matrix: string;
|
||||||
|
|
||||||
// NotificationChannelDialog.tsx
|
// NotificationChannelDialog.tsx
|
||||||
editChannel: string;
|
editChannel: string;
|
||||||
@@ -127,6 +128,12 @@ export interface SettingsTranslations {
|
|||||||
notifiarrChannelIdDesc: string;
|
notifiarrChannelIdDesc: string;
|
||||||
gotifyServerUrl: string;
|
gotifyServerUrl: string;
|
||||||
gotifyServerUrlDesc: string;
|
gotifyServerUrlDesc: string;
|
||||||
|
matrixHomeserver: string;
|
||||||
|
matrixHomeserverDesc: string;
|
||||||
|
matrixRoomId: string;
|
||||||
|
matrixRoomIdDesc: string;
|
||||||
|
matrixAccessToken: string;
|
||||||
|
matrixAccessTokenDesc: string;
|
||||||
errorSaveChannel: string;
|
errorSaveChannel: string;
|
||||||
|
|
||||||
channelNamePlaceholder: string;
|
channelNamePlaceholder: string;
|
||||||
@@ -148,6 +155,9 @@ export interface SettingsTranslations {
|
|||||||
notifiarrChannelIdPlaceholder: string;
|
notifiarrChannelIdPlaceholder: string;
|
||||||
gotifyServerUrlPlaceholder: string;
|
gotifyServerUrlPlaceholder: string;
|
||||||
webhookUrlPlaceholder: string;
|
webhookUrlPlaceholder: string;
|
||||||
|
matrixHomeserverPlaceholder: string;
|
||||||
|
matrixRoomIdPlaceholder: string;
|
||||||
|
matrixAccessTokenPlaceholder: string;
|
||||||
|
|
||||||
// DataRetentionSettings.tsx
|
// DataRetentionSettings.tsx
|
||||||
// permissionNotice: string;
|
// permissionNotice: string;
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/// <reference path="../pb_data/types.d.ts" />
|
||||||
|
migrate((app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_1938176441")
|
||||||
|
|
||||||
|
// add matrix_homeserver field
|
||||||
|
collection.fields.addAt(collection.fields.length, new Field({
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text_matrix_homeserver",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "matrix_homeserver",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}))
|
||||||
|
|
||||||
|
// add matrix_room_id field
|
||||||
|
collection.fields.addAt(collection.fields.length, new Field({
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text_matrix_room_id",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "matrix_room_id",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}))
|
||||||
|
|
||||||
|
// add matrix_access_token field
|
||||||
|
collection.fields.addAt(collection.fields.length, new Field({
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text_matrix_access_token",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "matrix_access_token",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
}))
|
||||||
|
|
||||||
|
// add "matrix" to the notification_type select field values
|
||||||
|
const notifTypeField = collection.fields.getByName("notification_type")
|
||||||
|
if (notifTypeField && notifTypeField.values) {
|
||||||
|
notifTypeField.values.push("matrix")
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
}, (app) => {
|
||||||
|
const collection = app.findCollectionByNameOrId("pbc_1938176441")
|
||||||
|
|
||||||
|
collection.fields.removeById("text_matrix_homeserver")
|
||||||
|
collection.fields.removeById("text_matrix_room_id")
|
||||||
|
collection.fields.removeById("text_matrix_access_token")
|
||||||
|
|
||||||
|
const notifTypeField = collection.fields.getByName("notification_type")
|
||||||
|
if (notifTypeField && notifTypeField.values) {
|
||||||
|
notifTypeField.values = notifTypeField.values.filter(v => v !== "matrix")
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.save(collection)
|
||||||
|
})
|
||||||
@@ -33,6 +33,7 @@ func NewNotificationManager(pbClient *pocketbase.PocketBaseClient) *Notification
|
|||||||
services["pushover"] = NewPushoverService()
|
services["pushover"] = NewPushoverService()
|
||||||
services["notifiarr"] = NewNotifiarrService()
|
services["notifiarr"] = NewNotifiarrService()
|
||||||
services["gotify"] = NewGotifyService()
|
services["gotify"] = NewGotifyService()
|
||||||
|
services["matrix"] = NewMatrixService()
|
||||||
|
|
||||||
// log.Printf("✅ Notification services initialized: %v", getKeys(services))
|
// log.Printf("✅ Notification services initialized: %v", getKeys(services))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,301 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MatrixService handles Matrix chat notifications
|
||||||
|
type MatrixService struct{}
|
||||||
|
|
||||||
|
// NewMatrixService creates a new Matrix notification service
|
||||||
|
func NewMatrixService() *MatrixService {
|
||||||
|
return &MatrixService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// matrixMessagePayload represents the payload for the Matrix Client-Server API
|
||||||
|
type matrixMessagePayload struct {
|
||||||
|
MsgType string `json:"msgtype"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
Format string `json:"format,omitempty"`
|
||||||
|
FormattedBody string `json:"formatted_body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNotification sends a plain message to a Matrix room
|
||||||
|
func (ms *MatrixService) SendNotification(config *AlertConfiguration, message string) error {
|
||||||
|
if config.MatrixHomeserver == "" || config.MatrixRoomID == "" || config.MatrixAccessToken == "" {
|
||||||
|
return fmt.Errorf("matrix homeserver, room ID and access token are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the room ID for use in the URL path
|
||||||
|
encodedRoomID := url.PathEscape(config.MatrixRoomID)
|
||||||
|
|
||||||
|
// Use Unix nanoseconds as a unique transaction ID to prevent duplicate messages
|
||||||
|
txnID := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
|
|
||||||
|
apiURL := fmt.Sprintf("%s/_matrix/client/v3/rooms/%s/send/m.room.message/%s",
|
||||||
|
strings.TrimRight(config.MatrixHomeserver, "/"),
|
||||||
|
encodedRoomID,
|
||||||
|
txnID,
|
||||||
|
)
|
||||||
|
|
||||||
|
payload := matrixMessagePayload{
|
||||||
|
MsgType: "m.text",
|
||||||
|
Body: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("matrix: failed to marshal payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPut, apiURL, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("matrix: failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Authorization", "Bearer "+config.MatrixAccessToken)
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("matrix: HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("matrix: API returned status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendServerNotification sends a server-specific notification via Matrix
|
||||||
|
func (ms *MatrixService) SendServerNotification(config *AlertConfiguration, payload *NotificationPayload, template *ServerNotificationTemplate, resourceType string) error {
|
||||||
|
message := ms.generateServerMessage(payload, template, resourceType)
|
||||||
|
return ms.SendNotification(config, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendServiceNotification sends a service-specific notification via Matrix
|
||||||
|
func (ms *MatrixService) SendServiceNotification(config *AlertConfiguration, payload *NotificationPayload, template *ServiceNotificationTemplate) error {
|
||||||
|
message := ms.generateServiceMessage(payload, template)
|
||||||
|
return ms.SendNotification(config, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) generateServerMessage(payload *NotificationPayload, template *ServerNotificationTemplate, resourceType string) string {
|
||||||
|
var templateMessage string
|
||||||
|
|
||||||
|
switch strings.ToLower(payload.Status) {
|
||||||
|
case "down":
|
||||||
|
templateMessage = template.DownMessage
|
||||||
|
case "up":
|
||||||
|
templateMessage = template.UpMessage
|
||||||
|
case "warning":
|
||||||
|
templateMessage = template.WarningMessage
|
||||||
|
case "paused":
|
||||||
|
templateMessage = template.PausedMessage
|
||||||
|
default:
|
||||||
|
switch resourceType {
|
||||||
|
case "cpu":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreCPUMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.CPUMessage
|
||||||
|
}
|
||||||
|
case "ram", "memory":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreRAMMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.RAMMessage
|
||||||
|
}
|
||||||
|
case "disk":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreDiskMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.DiskMessage
|
||||||
|
}
|
||||||
|
case "network":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreNetworkMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.NetworkMessage
|
||||||
|
}
|
||||||
|
case "cpu_temp", "cpu_temperature":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreCPUTempMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.CPUTempMessage
|
||||||
|
}
|
||||||
|
case "disk_io":
|
||||||
|
if strings.Contains(strings.ToLower(payload.Status), "restore") {
|
||||||
|
templateMessage = template.RestoreDiskIOMessage
|
||||||
|
} else {
|
||||||
|
templateMessage = template.DiskIOMessage
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
templateMessage = template.WarningMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateMessage == "" {
|
||||||
|
templateMessage = ms.generateDefaultServerMessage(payload, resourceType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ms.replacePlaceholders(templateMessage, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) generateServiceMessage(payload *NotificationPayload, template *ServiceNotificationTemplate) string {
|
||||||
|
var templateMessage string
|
||||||
|
|
||||||
|
switch strings.ToLower(payload.Status) {
|
||||||
|
case "up":
|
||||||
|
templateMessage = template.UpMessage
|
||||||
|
case "down":
|
||||||
|
templateMessage = template.DownMessage
|
||||||
|
case "maintenance":
|
||||||
|
templateMessage = template.MaintenanceMessage
|
||||||
|
case "incident":
|
||||||
|
templateMessage = template.IncidentMessage
|
||||||
|
case "resolved":
|
||||||
|
templateMessage = template.ResolvedMessage
|
||||||
|
case "warning":
|
||||||
|
templateMessage = template.WarningMessage
|
||||||
|
default:
|
||||||
|
templateMessage = template.WarningMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
if templateMessage == "" {
|
||||||
|
templateMessage = ms.generateDefaultUptimeMessage(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ms.replacePlaceholders(templateMessage, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) replacePlaceholders(message string, payload *NotificationPayload) string {
|
||||||
|
message = strings.ReplaceAll(message, "${service_name}", payload.ServiceName)
|
||||||
|
message = strings.ReplaceAll(message, "${status}", strings.ToUpper(payload.Status))
|
||||||
|
message = strings.ReplaceAll(message, "${host}", ms.safeString(payload.Host))
|
||||||
|
message = strings.ReplaceAll(message, "${hostname}", ms.safeString(payload.Hostname))
|
||||||
|
|
||||||
|
u := ms.safeString(payload.URL)
|
||||||
|
if u == "N/A" && payload.Host != "" {
|
||||||
|
u = payload.Host
|
||||||
|
}
|
||||||
|
message = strings.ReplaceAll(message, "${url}", u)
|
||||||
|
message = strings.ReplaceAll(message, "${domain}", ms.safeString(payload.Domain))
|
||||||
|
|
||||||
|
if payload.ServiceType != "" {
|
||||||
|
message = strings.ReplaceAll(message, "${service_type}", strings.ToUpper(payload.ServiceType))
|
||||||
|
} else {
|
||||||
|
message = strings.ReplaceAll(message, "${service_type}", "N/A")
|
||||||
|
}
|
||||||
|
|
||||||
|
message = strings.ReplaceAll(message, "${region_name}", ms.safeString(payload.RegionName))
|
||||||
|
message = strings.ReplaceAll(message, "${agent_id}", ms.safeString(payload.AgentID))
|
||||||
|
|
||||||
|
if payload.Port > 0 {
|
||||||
|
message = strings.ReplaceAll(message, "${port}", fmt.Sprintf("%d", payload.Port))
|
||||||
|
} else {
|
||||||
|
message = strings.ReplaceAll(message, "${port}", "N/A")
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.ResponseTime > 0 {
|
||||||
|
message = strings.ReplaceAll(message, "${response_time}", fmt.Sprintf("%dms", payload.ResponseTime))
|
||||||
|
} else {
|
||||||
|
message = strings.ReplaceAll(message, "${response_time}", "N/A")
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Uptime > 0 {
|
||||||
|
message = strings.ReplaceAll(message, "${uptime}", fmt.Sprintf("%d%%", payload.Uptime))
|
||||||
|
} else {
|
||||||
|
message = strings.ReplaceAll(message, "${uptime}", "N/A")
|
||||||
|
}
|
||||||
|
|
||||||
|
message = strings.ReplaceAll(message, "${cpu_usage}", ms.safeString(payload.CPUUsage))
|
||||||
|
message = strings.ReplaceAll(message, "${ram_usage}", ms.safeString(payload.RAMUsage))
|
||||||
|
message = strings.ReplaceAll(message, "${disk_usage}", ms.safeString(payload.DiskUsage))
|
||||||
|
message = strings.ReplaceAll(message, "${network_usage}", ms.safeString(payload.NetworkUsage))
|
||||||
|
message = strings.ReplaceAll(message, "${cpu_temp}", ms.safeString(payload.CPUTemp))
|
||||||
|
message = strings.ReplaceAll(message, "${disk_io}", ms.safeString(payload.DiskIO))
|
||||||
|
message = strings.ReplaceAll(message, "${threshold}", ms.safeString(payload.Threshold))
|
||||||
|
message = strings.ReplaceAll(message, "${error_message}", ms.safeString(payload.ErrorMessage))
|
||||||
|
message = strings.ReplaceAll(message, "${error}", ms.safeString(payload.ErrorMessage))
|
||||||
|
message = strings.ReplaceAll(message, "${time}", payload.Timestamp.Format("2006-01-02 15:04:05"))
|
||||||
|
message = strings.ReplaceAll(message, "${timestamp}", payload.Timestamp.Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) safeString(value string) string {
|
||||||
|
if value == "" {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) generateDefaultUptimeMessage(payload *NotificationPayload) string {
|
||||||
|
statusEmoji := "🔵"
|
||||||
|
switch strings.ToLower(payload.Status) {
|
||||||
|
case "up":
|
||||||
|
statusEmoji = "🟢"
|
||||||
|
case "down":
|
||||||
|
statusEmoji = "🔴"
|
||||||
|
case "warning":
|
||||||
|
statusEmoji = "🟡"
|
||||||
|
case "maintenance", "paused":
|
||||||
|
statusEmoji = "🟠"
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf("%s Service %s is %s.", statusEmoji, payload.ServiceName, strings.ToUpper(payload.Status))
|
||||||
|
|
||||||
|
details := []string{}
|
||||||
|
if payload.URL != "" {
|
||||||
|
details = append(details, fmt.Sprintf(" - Host URL: %s", payload.URL))
|
||||||
|
} else if payload.Host != "" {
|
||||||
|
details = append(details, fmt.Sprintf(" - Host: %s", payload.Host))
|
||||||
|
}
|
||||||
|
if payload.ServiceType != "" {
|
||||||
|
details = append(details, fmt.Sprintf(" - Type: %s", strings.ToUpper(payload.ServiceType)))
|
||||||
|
}
|
||||||
|
if payload.Port > 0 {
|
||||||
|
details = append(details, fmt.Sprintf(" - Port: %d", payload.Port))
|
||||||
|
}
|
||||||
|
if payload.Domain != "" {
|
||||||
|
details = append(details, fmt.Sprintf(" - Domain: %s", payload.Domain))
|
||||||
|
}
|
||||||
|
if payload.ResponseTime > 0 {
|
||||||
|
details = append(details, fmt.Sprintf(" - Response time: %dms", payload.ResponseTime))
|
||||||
|
} else {
|
||||||
|
details = append(details, " - Response time: N/A")
|
||||||
|
}
|
||||||
|
if payload.RegionName != "" {
|
||||||
|
details = append(details, fmt.Sprintf(" - Region: %s", payload.RegionName))
|
||||||
|
}
|
||||||
|
if payload.Uptime > 0 {
|
||||||
|
details = append(details, fmt.Sprintf(" - Uptime: %d%%", payload.Uptime))
|
||||||
|
}
|
||||||
|
details = append(details, fmt.Sprintf(" - Time: %s", payload.Timestamp.Format("2006-01-02 15:04:05")))
|
||||||
|
|
||||||
|
if len(details) > 0 {
|
||||||
|
message += "\n" + strings.Join(details, "\n")
|
||||||
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MatrixService) generateDefaultServerMessage(payload *NotificationPayload, resourceType string) string {
|
||||||
|
statusEmoji := "🔵"
|
||||||
|
switch strings.ToLower(payload.Status) {
|
||||||
|
case "up":
|
||||||
|
statusEmoji = "🟢"
|
||||||
|
case "down":
|
||||||
|
statusEmoji = "🔴"
|
||||||
|
case "warning":
|
||||||
|
statusEmoji = "🟡"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s 🖥️ Server %s (%s) status: %s", statusEmoji, payload.ServiceName, payload.Hostname, strings.ToUpper(payload.Status))
|
||||||
|
}
|
||||||
@@ -67,6 +67,9 @@ type AlertConfiguration struct {
|
|||||||
APIToken string `json:"api_token"`
|
APIToken string `json:"api_token"`
|
||||||
UserKey string `json:"user_key"`
|
UserKey string `json:"user_key"`
|
||||||
ServerURL string `json:"server_url"`
|
ServerURL string `json:"server_url"`
|
||||||
|
MatrixHomeserver string `json:"matrix_homeserver"`
|
||||||
|
MatrixRoomID string `json:"matrix_room_id"`
|
||||||
|
MatrixAccessToken string `json:"matrix_access_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerNotificationTemplate represents a server notification template
|
// ServerNotificationTemplate represents a server notification template
|
||||||
|
|||||||
Reference in New Issue
Block a user