mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-20 14:45:42 +02:00
Merge pull request #3447 from pluisol/feature/pushover-notifications
feat: add Pushover notification provider
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
GotifyIcon,
|
||||
LarkIcon,
|
||||
NtfyIcon,
|
||||
PushoverIcon,
|
||||
SlackIcon,
|
||||
TelegramIcon,
|
||||
} from "@/components/icons/notification-icons";
|
||||
@@ -114,6 +115,16 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
||||
priority: z.number().min(1).max(5).default(3),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("pushover"),
|
||||
userKey: z.string().min(1, { message: "User Key is required" }),
|
||||
apiToken: z.string().min(1, { message: "API Token is required" }),
|
||||
priority: z.number().min(-2).max(2).default(0),
|
||||
retry: z.number().min(30).nullish(),
|
||||
expire: z.number().min(1).max(10800).nullish(),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("custom"),
|
||||
@@ -166,6 +177,10 @@ export const notificationsMap = {
|
||||
icon: <NtfyIcon />,
|
||||
label: "ntfy",
|
||||
},
|
||||
pushover: {
|
||||
icon: <PushoverIcon />,
|
||||
label: "Pushover",
|
||||
},
|
||||
custom: {
|
||||
icon: <PenBoxIcon size={29} className="text-muted-foreground" />,
|
||||
label: "Custom",
|
||||
@@ -209,6 +224,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
|
||||
api.notification.testCustomConnection.useMutation();
|
||||
|
||||
const { mutateAsync: testPushoverConnection, isLoading: isLoadingPushover } =
|
||||
api.notification.testPushoverConnection.useMutation();
|
||||
|
||||
const customMutation = notificationId
|
||||
? api.notification.updateCustom.useMutation()
|
||||
: api.notification.createCustom.useMutation();
|
||||
@@ -233,6 +251,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
const larkMutation = notificationId
|
||||
? api.notification.updateLark.useMutation()
|
||||
: api.notification.createLark.useMutation();
|
||||
const pushoverMutation = notificationId
|
||||
? api.notification.updatePushover.useMutation()
|
||||
: api.notification.createPushover.useMutation();
|
||||
|
||||
const form = useForm<NotificationSchema>({
|
||||
defaultValues: {
|
||||
@@ -393,6 +414,23 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
serverThreshold: notification.serverThreshold,
|
||||
});
|
||||
} else if (notification.notificationType === "pushover") {
|
||||
form.reset({
|
||||
appBuildError: notification.appBuildError,
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
volumeBackup: notification.volumeBackup,
|
||||
type: notification.notificationType,
|
||||
userKey: notification.pushover?.userKey,
|
||||
apiToken: notification.pushover?.apiToken,
|
||||
priority: notification.pushover?.priority,
|
||||
retry: notification.pushover?.retry ?? undefined,
|
||||
expire: notification.pushover?.expire ?? undefined,
|
||||
name: notification.name,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
serverThreshold: notification.serverThreshold,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset();
|
||||
@@ -408,6 +446,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
ntfy: ntfyMutation,
|
||||
lark: larkMutation,
|
||||
custom: customMutation,
|
||||
pushover: pushoverMutation,
|
||||
};
|
||||
|
||||
const onSubmit = async (data: NotificationSchema) => {
|
||||
@@ -559,6 +598,28 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
notificationId: notificationId || "",
|
||||
customId: notification?.customId || "",
|
||||
});
|
||||
} else if (data.type === "pushover") {
|
||||
if (data.priority === 2 && (data.retry == null || data.expire == null)) {
|
||||
toast.error("Retry and expire are required for emergency priority (2)");
|
||||
return;
|
||||
}
|
||||
promise = pushoverMutation.mutateAsync({
|
||||
appBuildError: appBuildError,
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
volumeBackup: volumeBackup,
|
||||
userKey: data.userKey,
|
||||
apiToken: data.apiToken,
|
||||
priority: data.priority,
|
||||
retry: data.priority === 2 ? data.retry : undefined,
|
||||
expire: data.priority === 2 ? data.expire : undefined,
|
||||
name: data.name,
|
||||
dockerCleanup: dockerCleanup,
|
||||
serverThreshold: serverThreshold,
|
||||
notificationId: notificationId || "",
|
||||
pushoverId: notification?.pushoverId || "",
|
||||
});
|
||||
}
|
||||
|
||||
if (promise) {
|
||||
@@ -1255,6 +1316,147 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{type === "pushover" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="userKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>User Key</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="ub3de9kl2q..." {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="apiToken"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>API Token</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="a3d9k2q7m4..." {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="priority"
|
||||
defaultValue={0}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Priority</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="0"
|
||||
value={field.value ?? 0}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "" || value === "-") {
|
||||
field.onChange(0);
|
||||
} else {
|
||||
const priority = Number.parseInt(value);
|
||||
if (
|
||||
!Number.isNaN(priority) &&
|
||||
priority >= -2 &&
|
||||
priority <= 2
|
||||
) {
|
||||
field.onChange(priority);
|
||||
}
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
min={-2}
|
||||
max={2}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Message priority (-2 to 2, default: 0, emergency: 2)
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{form.watch("priority") === 2 && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="retry"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Retry (seconds)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="30"
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "") {
|
||||
field.onChange(undefined);
|
||||
} else {
|
||||
const retry = Number.parseInt(value);
|
||||
if (!Number.isNaN(retry)) {
|
||||
field.onChange(retry);
|
||||
}
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
min={30}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
How often (in seconds) to retry. Minimum 30
|
||||
seconds.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="expire"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel>Expire (seconds)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="3600"
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
if (value === "") {
|
||||
field.onChange(undefined);
|
||||
} else {
|
||||
const expire = Number.parseInt(value);
|
||||
if (!Number.isNaN(expire)) {
|
||||
field.onChange(expire);
|
||||
}
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
min={1}
|
||||
max={10800}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
How long to keep retrying (max 10800 seconds / 3
|
||||
hours).
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -1428,7 +1630,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
isLoadingGotify ||
|
||||
isLoadingNtfy ||
|
||||
isLoadingLark ||
|
||||
isLoadingCustom
|
||||
isLoadingCustom ||
|
||||
isLoadingPushover
|
||||
}
|
||||
variant="secondary"
|
||||
type="button"
|
||||
@@ -1497,6 +1700,22 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
endpoint: data.endpoint,
|
||||
headers: headersRecord,
|
||||
});
|
||||
} else if (data.type === "pushover") {
|
||||
if (
|
||||
data.priority === 2 &&
|
||||
(data.retry == null || data.expire == null)
|
||||
) {
|
||||
throw new Error(
|
||||
"Retry and expire are required for emergency priority (2)",
|
||||
);
|
||||
}
|
||||
await testPushoverConnection({
|
||||
userKey: data.userKey,
|
||||
apiToken: data.apiToken,
|
||||
priority: data.priority,
|
||||
retry: data.priority === 2 ? data.retry : undefined,
|
||||
expire: data.priority === 2 ? data.expire : undefined,
|
||||
});
|
||||
}
|
||||
toast.success("Connection Success");
|
||||
} catch (error) {
|
||||
|
||||
@@ -231,3 +231,29 @@ export const NtfyIcon = ({ className }: Props) => {
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const PushoverIcon = ({ className }: Props) => {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 600 600"
|
||||
className={cn("size-8", className)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g stroke="none" strokeWidth="1">
|
||||
<ellipse
|
||||
style={{ fillRule: "evenodd" }}
|
||||
fill="#249DF1"
|
||||
transform="matrix(-0.674571, 0.73821, -0.73821, -0.674571, 556.833239, 241.613465)"
|
||||
cx="216.308"
|
||||
cy="152.076"
|
||||
rx="296.855"
|
||||
ry="296.855"
|
||||
/>
|
||||
<path
|
||||
fill="#FFFFFF"
|
||||
d="M 280.949 172.514 L 355.429 162.714 L 282.909 326.374 L 282.909 326.374 C 295.649 325.394 308.142 321.067 320.389 313.394 L 320.389 313.394 L 320.389 313.394 C 332.642 305.714 343.916 296.077 354.209 284.484 L 354.209 284.484 L 354.209 284.484 C 364.496 272.884 373.396 259.981 380.909 245.774 L 380.909 245.774 L 380.909 245.774 C 388.422 231.561 393.812 217.594 397.079 203.874 L 397.079 203.874 L 397.079 203.874 C 399.039 195.381 399.939 187.214 399.779 179.374 L 399.779 179.374 L 399.779 179.374 C 399.612 171.534 397.569 164.674 393.649 158.794 L 393.649 158.794 L 393.649 158.794 C 389.729 152.914 383.766 148.177 375.759 144.584 L 375.759 144.584 L 375.759 144.584 C 367.759 140.991 356.899 139.194 343.179 139.194 L 343.179 139.194 L 343.179 139.194 C 327.172 139.194 311.409 141.807 295.889 147.034 L 295.889 147.034 L 295.889 147.034 C 280.376 152.261 266.002 159.857 252.769 169.824 L 252.769 169.824 L 252.769 169.824 C 239.542 179.784 228.029 192.197 218.229 207.064 L 218.229 207.064 L 218.229 207.064 C 208.429 221.924 201.406 238.827 197.159 257.774 L 197.159 257.774 L 197.159 257.774 C 195.526 263.981 194.546 268.961 194.219 272.714 L 194.219 272.714 L 194.219 272.714 C 193.892 276.474 193.812 279.577 193.979 282.024 L 193.979 282.024 L 193.979 282.024 C 194.139 284.477 194.462 286.357 194.949 287.664 L 194.949 287.664 L 194.949 287.664 C 195.442 288.971 195.852 290.277 196.179 291.584 L 196.179 291.584 L 196.179 291.584 C 179.519 291.584 167.349 288.234 159.669 281.534 L 159.669 281.534 L 159.669 281.534 C 151.996 274.841 150.119 263.164 154.039 246.504 L 154.039 246.504 L 154.039 246.504 C 157.959 229.191 166.862 212.694 180.749 197.014 L 180.749 197.014 L 180.749 197.014 C 194.629 181.334 211.122 167.531 230.229 155.604 L 230.229 155.604 L 230.229 155.604 C 249.342 143.684 270.249 134.214 292.949 127.194 L 292.949 127.194 L 292.949 127.194 C 315.656 120.167 337.789 116.654 359.349 116.654 L 359.349 116.654 L 359.349 116.654 C 378.296 116.654 394.219 119.347 407.119 124.734 L 407.119 124.734 L 407.119 124.734 C 420.026 130.127 430.072 137.234 437.259 146.054 L 437.259 146.054 L 437.259 146.054 C 444.446 154.874 448.936 165.164 450.729 176.924 L 450.729 176.924 L 450.729 176.924 C 452.529 188.684 451.959 200.934 449.019 213.674 L 449.019 213.674 L 449.019 213.674 C 445.426 229.027 438.646 244.464 428.679 259.984 L 428.679 259.984 L 428.679 259.984 C 418.719 275.497 406.226 289.544 391.199 302.124 L 391.199 302.124 L 391.199 302.124 C 376.172 314.697 358.939 324.904 339.499 332.744 L 339.499 332.744 L 339.499 332.744 C 320.066 340.584 299.406 344.504 277.519 344.504 L 277.519 344.504 L 275.069 344.504 L 212.839 484.154 L 142.279 484.154 L 280.949 172.514 Z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user