Skip to content

Commit 3846e41

Browse files
authored
Merge pull request #2728 from hoootan/feat/add-mattermost-notification-provider
feat: add mattermost notification provider
2 parents 3374737 + ac76f2d commit 3846e41

File tree

17 files changed

+9349
-455
lines changed

17 files changed

+9349
-455
lines changed

apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { toast } from "sonner";
1212
import { z } from "zod";
1313
import {
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,

apps/dokploy/components/dashboard/settings/notifications/show-notifications.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DiscordIcon,
55
GotifyIcon,
66
LarkIcon,
7+
MattermostIcon,
78
NtfyIcon,
89
ResendIcon,
910
SlackIcon,
@@ -121,6 +122,12 @@ export const ShowNotifications = () => {
121122
<TeamsIcon className="size-7 text-muted-foreground" />
122123
</div>
123124
)}
125+
{notification.notificationType ===
126+
"mattermost" && (
127+
<div className="flex items-center justify-center rounded-lg">
128+
<MattermostIcon className="size-7" />
129+
</div>
130+
)}
124131

125132
{notification.name}
126133
</span>

apps/dokploy/components/icons/notification-icons.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ export const DiscordIcon = ({ className }: Props) => {
8888
</svg>
8989
);
9090
};
91+
92+
export const MattermostIcon = ({ className }: Props) => {
93+
return (
94+
<svg
95+
fill="#0061ff"
96+
viewBox="0 0 501 501"
97+
xmlns="http://www.w3.org/2000/svg"
98+
className={cn("size-8", className)}
99+
>
100+
<path d="M236 .7C137.7 7.5 54 68.2 18.2 158.5c-32 81-19.6 172.8 33 242.5 39.8 53 97.2 87 164.3 97 16.5 2.7 48 3.2 63.5 1.2 48.7-6.3 92.2-24.6 129-54.2 13-10.5 33-31.2 42.2-43.7 26.4-35.5 42.8-75.8 49-120.3 1.6-12.3 1.6-48.7 0-61-4-28.3-12-54.8-24.2-79.5-12.8-26-26.5-45.3-46.8-65.8C417.8 64 400.2 49 398.4 49c-.6 0-.4 10.5.3 26l1.3 26 7 8.7c19 23.7 32.8 53.5 38.2 83 2.5 14 3 43 1 55.8-4.5 27.8-15.2 54-31 76.5-8.6 12.2-28 31.6-40.2 40.2-24 17-50 27.6-80 33-10 1.8-49 1.8-59 0-43-7.7-78.8-26-107.2-54.8-29.3-29.7-46.5-64-52.4-104.4-2-14-1.5-42 1-55C90 121.4 132 72 192 49.7c8-3 18.4-5.8 29.5-8.2 1.7-.4 34.4-38 35.3-40.6.3-1-10.2-1-20.8-.4z" />
101+
<path d="M322.2 24.6c-1.3.8-8.4 9.3-16 18.7-7.4 9.5-22.4 28-33.2 41.2-51 62.2-66 81.6-70.6 91-6 12-8.4 21-9 33-1.2 19.8 5 36 19 50C222 268 230 273 243 277.2c9 3 10.4 3.2 24 3.2 13.8 0 15 0 22.6-3 23.2-9 39-28.4 45-55.7 2-8.2 2-28.7.4-79.7l-2-72c-1-36.8-1.4-41.8-3-44-2-3-4.8-3.6-7.8-1.4z" />
102+
</svg>
103+
);
104+
};
105+
91106
export const TeamsIcon = ({ className }: Props) => {
92107
return (
93108
<svg
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ALTER TYPE "public"."notificationType" ADD VALUE 'mattermost' BEFORE 'pushover';--> statement-breakpoint
2+
CREATE TABLE "mattermost" (
3+
"mattermostId" text PRIMARY KEY NOT NULL,
4+
"webhookUrl" text NOT NULL,
5+
"channel" text,
6+
"username" text
7+
);
8+
--> statement-breakpoint
9+
ALTER TABLE "notification" ADD COLUMN "mattermostId" text;--> statement-breakpoint
10+
ALTER TABLE "notification" ADD CONSTRAINT "notification_mattermostId_mattermost_mattermostId_fk" FOREIGN KEY ("mattermostId") REFERENCES "public"."mattermost"("mattermostId") ON DELETE cascade ON UPDATE no action;

0 commit comments

Comments
 (0)