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:
MrSchneemann
2026-03-01 10:39:46 +01:00
parent f2c338ced9
commit 91ceddc489
13 changed files with 495 additions and 8 deletions
+1 -1
View File
@@ -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

@@ -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>;
@@ -194,6 +202,12 @@ const notificationTypeOptions = [
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 = {
@@ -106,6 +109,11 @@ export const alertConfigService = {
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);
@@ -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