@@ -12,6 +12,7 @@ import { toast } from "sonner";
1212import { z } from "zod" ;
1313import {
1414 DiscordIcon ,
15+ MattermostIcon ,
1516 GotifyIcon ,
1617 LarkIcon ,
1718 NtfyIcon ,
@@ -134,6 +135,14 @@ export const notificationSchema = z.discriminatedUnion("type", [
134135 priority : z . number ( ) . min ( 1 ) . max ( 5 ) . default ( 3 ) ,
135136 } )
136137 . merge ( notificationBaseSchema ) ,
138+ z
139+ . object ( {
140+ type : z . literal ( "mattermost" ) ,
141+ webhookUrl : z . string ( ) . min ( 1 , { message : "Webhook URL is required" } ) ,
142+ channel : z . string ( ) . optional ( ) ,
143+ username : z . string ( ) . optional ( ) ,
144+ } )
145+ . merge ( notificationBaseSchema ) ,
137146 z
138147 . object ( {
139148 type : z . literal ( "pushover" ) ,
@@ -210,6 +219,10 @@ export const notificationsMap = {
210219 icon : < NtfyIcon /> ,
211220 label : "ntfy" ,
212221 } ,
222+ mattermost : {
223+ icon : < MattermostIcon /> ,
224+ label : "Mattermost" ,
225+ } ,
213226 pushover : {
214227 icon : < PushoverIcon /> ,
215228 label : "Pushover" ,
@@ -253,14 +266,16 @@ export const HandleNotifications = ({ notificationId }: Props) => {
253266 api . notification . testGotifyConnection . useMutation ( ) ;
254267 const { mutateAsync : testNtfyConnection , isPending : isLoadingNtfy } =
255268 api . notification . testNtfyConnection . useMutation ( ) ;
269+ const {
270+ mutateAsync : testMattermostConnection ,
271+ isPending : isLoadingMattermost ,
272+ } = api . notification . testMattermostConnection . useMutation ( ) ;
256273 const { mutateAsync : testLarkConnection , isPending : isLoadingLark } =
257274 api . notification . testLarkConnection . useMutation ( ) ;
258275 const { mutateAsync : testTeamsConnection , isPending : isLoadingTeams } =
259276 api . notification . testTeamsConnection . useMutation ( ) ;
260-
261277 const { mutateAsync : testCustomConnection , isPending : isLoadingCustom } =
262278 api . notification . testCustomConnection . useMutation ( ) ;
263-
264279 const { mutateAsync : testPushoverConnection , isPending : isLoadingPushover } =
265280 api . notification . testPushoverConnection . useMutation ( ) ;
266281
@@ -288,6 +303,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
288303 const ntfyMutation = notificationId
289304 ? api . notification . updateNtfy . useMutation ( )
290305 : api . notification . createNtfy . useMutation ( ) ;
306+ const mattermostMutation = notificationId
307+ ? api . notification . updateMattermost . useMutation ( )
308+ : api . notification . createMattermost . useMutation ( ) ;
291309 const larkMutation = notificationId
292310 ? api . notification . updateLark . useMutation ( )
293311 : api . notification . createLark . useMutation ( ) ;
@@ -438,6 +456,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
438456 dockerCleanup : notification . dockerCleanup ,
439457 serverThreshold : notification . serverThreshold ,
440458 } ) ;
459+ } else if ( notification . notificationType === "mattermost" ) {
460+ form . reset ( {
461+ appBuildError : notification . appBuildError ,
462+ appDeploy : notification . appDeploy ,
463+ dokployRestart : notification . dokployRestart ,
464+ databaseBackup : notification . databaseBackup ,
465+ volumeBackup : notification . volumeBackup ,
466+ type : notification . notificationType ,
467+ webhookUrl : notification . mattermost ?. webhookUrl ,
468+ channel : notification . mattermost ?. channel || "" ,
469+ username : notification . mattermost ?. username || "" ,
470+ name : notification . name ,
471+ dockerCleanup : notification . dockerCleanup ,
472+ serverThreshold : notification . serverThreshold ,
473+ } ) ;
441474 } else if ( notification . notificationType === "lark" ) {
442475 form . reset ( {
443476 appBuildError : notification . appBuildError ,
@@ -516,6 +549,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
516549 resend : resendMutation ,
517550 gotify : gotifyMutation ,
518551 ntfy : ntfyMutation ,
552+ mattermost : mattermostMutation ,
519553 lark : larkMutation ,
520554 teams : teamsMutation ,
521555 custom : customMutation ,
@@ -646,6 +680,22 @@ export const HandleNotifications = ({ notificationId }: Props) => {
646680 notificationId : notificationId || "" ,
647681 ntfyId : notification ?. ntfyId || "" ,
648682 } ) ;
683+ } else if ( data . type === "mattermost" ) {
684+ promise = mattermostMutation . mutateAsync ( {
685+ appBuildError : appBuildError ,
686+ appDeploy : appDeploy ,
687+ dokployRestart : dokployRestart ,
688+ databaseBackup : databaseBackup ,
689+ volumeBackup : volumeBackup ,
690+ webhookUrl : data . webhookUrl ,
691+ channel : data . channel || undefined ,
692+ username : data . username || undefined ,
693+ name : data . name ,
694+ dockerCleanup : dockerCleanup ,
695+ notificationId : notificationId || "" ,
696+ mattermostId : notification ?. mattermostId || "" ,
697+ serverThreshold : serverThreshold ,
698+ } ) ;
649699 } else if ( data . type === "lark" ) {
650700 promise = larkMutation . mutateAsync ( {
651701 appBuildError : appBuildError ,
@@ -1406,6 +1456,62 @@ export const HandleNotifications = ({ notificationId }: Props) => {
14061456 />
14071457 </ >
14081458 ) }
1459+
1460+ { type === "mattermost" && (
1461+ < >
1462+ < FormField
1463+ control = { form . control }
1464+ name = "webhookUrl"
1465+ render = { ( { field } ) => (
1466+ < FormItem >
1467+ < FormLabel > Webhook URL</ FormLabel >
1468+ < FormControl >
1469+ < Input
1470+ placeholder = "https://your-mattermost.com/hooks/xxx-generatedkey-xxx"
1471+ { ...field }
1472+ />
1473+ </ FormControl >
1474+ < FormMessage />
1475+ </ FormItem >
1476+ ) }
1477+ />
1478+
1479+ < FormField
1480+ control = { form . control }
1481+ name = "channel"
1482+ render = { ( { field } ) => (
1483+ < FormItem >
1484+ < FormLabel > Channel</ FormLabel >
1485+ < FormControl >
1486+ < Input placeholder = "deployments" { ...field } />
1487+ </ FormControl >
1488+ < FormDescription >
1489+ Optional. Channel to post to (without #).
1490+ </ FormDescription >
1491+ < FormMessage />
1492+ </ FormItem >
1493+ ) }
1494+ />
1495+
1496+ < FormField
1497+ control = { form . control }
1498+ name = "username"
1499+ render = { ( { field } ) => (
1500+ < FormItem >
1501+ < FormLabel > Username</ FormLabel >
1502+ < FormControl >
1503+ < Input placeholder = "Dokploy" { ...field } />
1504+ </ FormControl >
1505+ < FormDescription >
1506+ Optional. Display name for the webhook.
1507+ </ FormDescription >
1508+ < FormMessage />
1509+ </ FormItem >
1510+ ) }
1511+ />
1512+ </ >
1513+ ) }
1514+
14091515 { type === "custom" && (
14101516 < div className = "space-y-4" >
14111517 < FormField
@@ -1492,6 +1598,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
14921598 </ div >
14931599 </ div >
14941600 ) }
1601+
14951602 { type === "lark" && (
14961603 < >
14971604 < FormField
@@ -1852,6 +1959,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
18521959 isLoadingResend ||
18531960 isLoadingGotify ||
18541961 isLoadingNtfy ||
1962+ isLoadingMattermost ||
18551963 isLoadingLark ||
18561964 isLoadingTeams ||
18571965 isLoadingCustom ||
@@ -1911,6 +2019,12 @@ export const HandleNotifications = ({ notificationId }: Props) => {
19112019 accessToken : data . accessToken || "" ,
19122020 priority : data . priority ?? 0 ,
19132021 } ) ;
2022+ } else if ( data . type === "mattermost" ) {
2023+ await testMattermostConnection ( {
2024+ webhookUrl : data . webhookUrl ,
2025+ channel : data . channel || undefined ,
2026+ username : data . username || undefined ,
2027+ } ) ;
19142028 } else if ( data . type === "lark" ) {
19152029 await testLarkConnection ( {
19162030 webhookUrl : data . webhookUrl ,
0 commit comments