Skip to content

Commit 1f2dabb

Browse files
authored
Merge pull request #2429 from CatPaulKatze/feat/ntfy
feat(notification): add ntfy notifications
2 parents 02685fd + ffb69fe commit 1f2dabb

File tree

14 files changed

+7032
-6
lines changed

14 files changed

+7032
-6
lines changed

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

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ export const notificationSchema = z.discriminatedUnion("type", [
101101
decoration: z.boolean().default(true),
102102
})
103103
.merge(notificationBaseSchema),
104+
z
105+
.object({
106+
type: z.literal("ntfy"),
107+
serverUrl: z.string().min(1, { message: "Server URL is required" }),
108+
topic: z.string().min(1, { message: "Topic is required" }),
109+
accessToken: z.string().min(1, { message: "Access Token is required" }),
110+
priority: z.number().min(1).max(5).default(3),
111+
})
112+
.merge(notificationBaseSchema),
104113
]);
105114

106115
export const notificationsMap = {
@@ -124,6 +133,10 @@ export const notificationsMap = {
124133
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
125134
label: "Gotify",
126135
},
136+
ntfy: {
137+
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
138+
label: "ntfy",
139+
},
127140
};
128141

129142
export type NotificationSchema = z.infer<typeof notificationSchema>;
@@ -155,6 +168,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
155168
api.notification.testEmailConnection.useMutation();
156169
const { mutateAsync: testGotifyConnection, isLoading: isLoadingGotify } =
157170
api.notification.testGotifyConnection.useMutation();
171+
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
172+
api.notification.testNtfyConnection.useMutation();
158173
const slackMutation = notificationId
159174
? api.notification.updateSlack.useMutation()
160175
: api.notification.createSlack.useMutation();
@@ -170,6 +185,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
170185
const gotifyMutation = notificationId
171186
? api.notification.updateGotify.useMutation()
172187
: api.notification.createGotify.useMutation();
188+
const ntfyMutation = notificationId
189+
? api.notification.updateNtfy.useMutation()
190+
: api.notification.createNtfy.useMutation();
173191

174192
const form = useForm<NotificationSchema>({
175193
defaultValues: {
@@ -266,6 +284,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
266284
name: notification.name,
267285
dockerCleanup: notification.dockerCleanup,
268286
});
287+
} else if (notification.notificationType === "ntfy") {
288+
form.reset({
289+
appBuildError: notification.appBuildError,
290+
appDeploy: notification.appDeploy,
291+
dokployRestart: notification.dokployRestart,
292+
databaseBackup: notification.databaseBackup,
293+
type: notification.notificationType,
294+
accessToken: notification.ntfy?.accessToken,
295+
topic: notification.ntfy?.topic,
296+
priority: notification.ntfy?.priority,
297+
serverUrl: notification.ntfy?.serverUrl,
298+
name: notification.name,
299+
dockerCleanup: notification.dockerCleanup,
300+
});
269301
}
270302
} else {
271303
form.reset();
@@ -278,6 +310,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
278310
discord: discordMutation,
279311
email: emailMutation,
280312
gotify: gotifyMutation,
313+
ntfy: ntfyMutation,
281314
};
282315

283316
const onSubmit = async (data: NotificationSchema) => {
@@ -366,6 +399,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
366399
notificationId: notificationId || "",
367400
gotifyId: notification?.gotifyId || "",
368401
});
402+
} else if (data.type === "ntfy") {
403+
promise = ntfyMutation.mutateAsync({
404+
appBuildError: appBuildError,
405+
appDeploy: appDeploy,
406+
dokployRestart: dokployRestart,
407+
databaseBackup: databaseBackup,
408+
serverUrl: data.serverUrl,
409+
accessToken: data.accessToken,
410+
topic: data.topic,
411+
priority: data.priority,
412+
name: data.name,
413+
dockerCleanup: dockerCleanup,
414+
notificationId: notificationId || "",
415+
ntfyId: notification?.ntfyId || "",
416+
});
369417
}
370418

371419
if (promise) {
@@ -875,6 +923,83 @@ export const HandleNotifications = ({ notificationId }: Props) => {
875923
/>
876924
</>
877925
)}
926+
927+
{type === "ntfy" && (
928+
<>
929+
<FormField
930+
control={form.control}
931+
name="serverUrl"
932+
render={({ field }) => (
933+
<FormItem>
934+
<FormLabel>Server URL</FormLabel>
935+
<FormControl>
936+
<Input placeholder="https://ntfy.sh" {...field} />
937+
</FormControl>
938+
<FormMessage />
939+
</FormItem>
940+
)}
941+
/>
942+
<FormField
943+
control={form.control}
944+
name="topic"
945+
render={({ field }) => (
946+
<FormItem>
947+
<FormLabel>Topic</FormLabel>
948+
<FormControl>
949+
<Input placeholder="deployments" {...field} />
950+
</FormControl>
951+
<FormMessage />
952+
</FormItem>
953+
)}
954+
/>
955+
<FormField
956+
control={form.control}
957+
name="accessToken"
958+
render={({ field }) => (
959+
<FormItem>
960+
<FormLabel>Access Token</FormLabel>
961+
<FormControl>
962+
<Input
963+
placeholder="AzxcvbnmKjhgfdsa..."
964+
{...field}
965+
/>
966+
</FormControl>
967+
<FormMessage />
968+
</FormItem>
969+
)}
970+
/>
971+
<FormField
972+
control={form.control}
973+
name="priority"
974+
defaultValue={3}
975+
render={({ field }) => (
976+
<FormItem className="w-full">
977+
<FormLabel>Priority</FormLabel>
978+
<FormControl>
979+
<Input
980+
placeholder="3"
981+
{...field}
982+
onChange={(e) => {
983+
const value = e.target.value;
984+
if (value) {
985+
const port = Number.parseInt(value);
986+
if (port > 0 && port <= 5) {
987+
field.onChange(port);
988+
}
989+
}
990+
}}
991+
type="number"
992+
/>
993+
</FormControl>
994+
<FormDescription>
995+
Message priority (1-5, default: 3)
996+
</FormDescription>
997+
<FormMessage />
998+
</FormItem>
999+
)}
1000+
/>
1001+
</>
1002+
)}
8781003
</div>
8791004
</div>
8801005
<div className="flex flex-col gap-4">
@@ -1024,7 +1149,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
10241149
isLoadingTelegram ||
10251150
isLoadingDiscord ||
10261151
isLoadingEmail ||
1027-
isLoadingGotify
1152+
isLoadingGotify ||
1153+
isLoadingNtfy
10281154
}
10291155
variant="secondary"
10301156
onClick={async () => {
@@ -1061,6 +1187,13 @@ export const HandleNotifications = ({ notificationId }: Props) => {
10611187
priority: form.getValues("priority"),
10621188
decoration: form.getValues("decoration"),
10631189
});
1190+
} else if (type === "ntfy") {
1191+
await testNtfyConnection({
1192+
serverUrl: form.getValues("serverUrl"),
1193+
topic: form.getValues("topic"),
1194+
accessToken: form.getValues("accessToken"),
1195+
priority: form.getValues("priority"),
1196+
});
10641197
}
10651198
toast.success("Connection Success");
10661199
} catch {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ export const ShowNotifications = () => {
8888
<MessageCircleMore className="size-6 text-muted-foreground" />
8989
</div>
9090
)}
91+
{notification.notificationType === "ntfy" && (
92+
<div className="flex items-center justify-center rounded-lg ">
93+
<MessageCircleMore className="size-6 text-muted-foreground" />
94+
</div>
95+
)}
9196

9297
{notification.name}
9398
</span>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ALTER TYPE "public"."notificationType" ADD VALUE 'ntfy';--> statement-breakpoint
2+
CREATE TABLE "ntfy" (
3+
"ntfyId" text PRIMARY KEY NOT NULL,
4+
"serverUrl" text NOT NULL,
5+
"topic" text NOT NULL,
6+
"accessToken" text NOT NULL,
7+
"priority" integer DEFAULT 3 NOT NULL
8+
);
9+
--> statement-breakpoint
10+
ALTER TABLE "notification" ADD COLUMN "ntfyId" text;--> statement-breakpoint
11+
ALTER TABLE "notification" ADD CONSTRAINT "notification_ntfyId_ntfy_ntfyId_fk" FOREIGN KEY ("ntfyId") REFERENCES "public"."ntfy"("ntfyId") ON DELETE cascade ON UPDATE no action;

0 commit comments

Comments
 (0)