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
@@ -38,7 +38,7 @@ interface NotificationChannelDialogProps {
const baseSchema = z.object({
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),
service_id: z.string().default("global"),
template_id: z.string().optional(),
@@ -110,6 +110,13 @@ const gotifySchema = baseSchema.extend({
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", [
telegramSchema,
discordSchema,
@@ -122,6 +129,7 @@ const formSchema = z.discriminatedUnion("notification_type", [
notifiarrSchema,
gotifySchema,
webhookSchema,
matrixSchema,
]);
type FormValues = z.infer<typeof formSchema>;
@@ -188,12 +196,18 @@ const notificationTypeOptions = [
description: "Send push notifications via Gotify",
icon: "/upload/notification/gotify.png"
},
{
value: "webhook",
label: "Webhook",
{
value: "webhook",
label: "Webhook",
description: "Send notifications to custom webhook",
icon: "/upload/notification/webhook.png"
},
{
value: "matrix",
label: "Matrix",
description: "Send notifications to a Matrix chat room",
icon: "/upload/notification/matrix.png"
},
];
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
control={form.control}
name="enabled"
@@ -82,6 +82,7 @@ export const NotificationChannelList = ({
case "pushover": return "Pushover";
case "notifiarr": return "Notifiarr";
case "webhook": return "Webhook";
case "matrix": return "Matrix";
default: return type || "Unknown";
}
};
@@ -102,6 +103,8 @@ export const NotificationChannelList = ({
return config.signal_number || '';
case "email":
return config.email_address || '';
case "matrix":
return config.matrix_room_id || '';
default:
return '';
}
@@ -154,6 +154,7 @@ const NotificationSettings = () => {
<TabsTrigger value="google_chat">{t("googleChat")}</TabsTrigger>
<TabsTrigger value="email">{t("email")}</TabsTrigger>
<TabsTrigger value="webhook">{t("webhook")}</TabsTrigger>
<TabsTrigger value="matrix">{t("matrix")}</TabsTrigger>
</TabsList>
<TabsContent value={currentTab} className="mt-0">