mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-13 23:45:17 +00:00
feat(notifications): implement custom notification form fields and connection testing for custom notifications
This commit is contained in:
@@ -39,6 +39,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
|
import { KeyValueInput } from "./key-value-input";
|
||||||
|
|
||||||
const notificationBaseSchema = z.object({
|
const notificationBaseSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
@@ -110,6 +111,13 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
|||||||
priority: z.number().min(1).max(5).default(3),
|
priority: z.number().min(1).max(5).default(3),
|
||||||
})
|
})
|
||||||
.merge(notificationBaseSchema),
|
.merge(notificationBaseSchema),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("custom"),
|
||||||
|
endpoint: z.string().min(1, { message: "Endpoint URL is required" }),
|
||||||
|
headers: z.string().optional(),
|
||||||
|
})
|
||||||
|
.merge(notificationBaseSchema),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notificationsMap = {
|
export const notificationsMap = {
|
||||||
@@ -137,6 +145,10 @@ export const notificationsMap = {
|
|||||||
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
icon: <MessageCircleMore size={29} className="text-muted-foreground" />,
|
||||||
label: "ntfy",
|
label: "ntfy",
|
||||||
},
|
},
|
||||||
|
custom: {
|
||||||
|
icon: <PenBoxIcon size={29} className="text-muted-foreground" />,
|
||||||
|
label: "Custom",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||||
@@ -170,6 +182,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
api.notification.testGotifyConnection.useMutation();
|
api.notification.testGotifyConnection.useMutation();
|
||||||
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
||||||
api.notification.testNtfyConnection.useMutation();
|
api.notification.testNtfyConnection.useMutation();
|
||||||
|
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
|
||||||
|
api.notification.testCustomConnection.useMutation();
|
||||||
const slackMutation = notificationId
|
const slackMutation = notificationId
|
||||||
? api.notification.updateSlack.useMutation()
|
? api.notification.updateSlack.useMutation()
|
||||||
: api.notification.createSlack.useMutation();
|
: api.notification.createSlack.useMutation();
|
||||||
@@ -188,6 +202,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const ntfyMutation = notificationId
|
const ntfyMutation = notificationId
|
||||||
? api.notification.updateNtfy.useMutation()
|
? api.notification.updateNtfy.useMutation()
|
||||||
: api.notification.createNtfy.useMutation();
|
: api.notification.createNtfy.useMutation();
|
||||||
|
const customMutation = notificationId
|
||||||
|
? api.notification.updateCustom.useMutation()
|
||||||
|
: api.notification.createCustom.useMutation();
|
||||||
|
|
||||||
const form = useForm<NotificationSchema>({
|
const form = useForm<NotificationSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -298,6 +315,19 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
name: notification.name,
|
name: notification.name,
|
||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
});
|
});
|
||||||
|
} else if (notification.notificationType === "custom") {
|
||||||
|
form.reset({
|
||||||
|
appBuildError: notification.appBuildError,
|
||||||
|
appDeploy: notification.appDeploy,
|
||||||
|
dokployRestart: notification.dokployRestart,
|
||||||
|
databaseBackup: notification.databaseBackup,
|
||||||
|
type: notification.notificationType,
|
||||||
|
endpoint: notification.custom?.endpoint || "",
|
||||||
|
headers: notification.custom?.headers || "",
|
||||||
|
name: notification.name,
|
||||||
|
dockerCleanup: notification.dockerCleanup,
|
||||||
|
serverThreshold: notification.serverThreshold,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -311,6 +341,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
email: emailMutation,
|
email: emailMutation,
|
||||||
gotify: gotifyMutation,
|
gotify: gotifyMutation,
|
||||||
ntfy: ntfyMutation,
|
ntfy: ntfyMutation,
|
||||||
|
custom: customMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: NotificationSchema) => {
|
const onSubmit = async (data: NotificationSchema) => {
|
||||||
@@ -414,6 +445,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
ntfyId: notification?.ntfyId || "",
|
ntfyId: notification?.ntfyId || "",
|
||||||
});
|
});
|
||||||
|
} else if (data.type === "custom") {
|
||||||
|
promise = customMutation.mutateAsync({
|
||||||
|
appBuildError: appBuildError,
|
||||||
|
appDeploy: appDeploy,
|
||||||
|
dokployRestart: dokployRestart,
|
||||||
|
databaseBackup: databaseBackup,
|
||||||
|
endpoint: data.endpoint,
|
||||||
|
headers: data.headers || "",
|
||||||
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
|
serverThreshold: serverThreshold,
|
||||||
|
notificationId: notificationId || "",
|
||||||
|
customId: notification?.customId || "",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise) {
|
if (promise) {
|
||||||
@@ -1000,6 +1045,49 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{type === "custom" && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="endpoint"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Webhook URL</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="https://api.example.com/webhook"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
The URL where POST requests will be sent with
|
||||||
|
notification data.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="headers"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<KeyValueInput
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={field.onChange}
|
||||||
|
label="Headers"
|
||||||
|
description="Optional. Custom headers for your POST request (e.g., Authorization, Content-Type)."
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -1150,7 +1238,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
isLoadingDiscord ||
|
isLoadingDiscord ||
|
||||||
isLoadingEmail ||
|
isLoadingEmail ||
|
||||||
isLoadingGotify ||
|
isLoadingGotify ||
|
||||||
isLoadingNtfy
|
isLoadingNtfy ||
|
||||||
|
isLoadingCustom
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -1194,6 +1283,11 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
accessToken: form.getValues("accessToken"),
|
accessToken: form.getValues("accessToken"),
|
||||||
priority: form.getValues("priority"),
|
priority: form.getValues("priority"),
|
||||||
});
|
});
|
||||||
|
} else if (type === "custom") {
|
||||||
|
await testCustomConnection({
|
||||||
|
endpoint: form.getValues("endpoint") as string,
|
||||||
|
headers: form.getValues("headers") as string | undefined,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
toast.success("Connection Success");
|
toast.success("Connection Success");
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user