mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-05 22:15:22 +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,
|
GotifyIcon,
|
||||||
LarkIcon,
|
LarkIcon,
|
||||||
NtfyIcon,
|
NtfyIcon,
|
||||||
|
PushoverIcon,
|
||||||
SlackIcon,
|
SlackIcon,
|
||||||
TelegramIcon,
|
TelegramIcon,
|
||||||
} from "@/components/icons/notification-icons";
|
} from "@/components/icons/notification-icons";
|
||||||
@@ -114,6 +115,16 @@ 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("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
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("custom"),
|
type: z.literal("custom"),
|
||||||
@@ -166,6 +177,10 @@ export const notificationsMap = {
|
|||||||
icon: <NtfyIcon />,
|
icon: <NtfyIcon />,
|
||||||
label: "ntfy",
|
label: "ntfy",
|
||||||
},
|
},
|
||||||
|
pushover: {
|
||||||
|
icon: <PushoverIcon />,
|
||||||
|
label: "Pushover",
|
||||||
|
},
|
||||||
custom: {
|
custom: {
|
||||||
icon: <PenBoxIcon size={29} className="text-muted-foreground" />,
|
icon: <PenBoxIcon size={29} className="text-muted-foreground" />,
|
||||||
label: "Custom",
|
label: "Custom",
|
||||||
@@ -209,6 +224,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
|
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
|
||||||
api.notification.testCustomConnection.useMutation();
|
api.notification.testCustomConnection.useMutation();
|
||||||
|
|
||||||
|
const { mutateAsync: testPushoverConnection, isLoading: isLoadingPushover } =
|
||||||
|
api.notification.testPushoverConnection.useMutation();
|
||||||
|
|
||||||
const customMutation = notificationId
|
const customMutation = notificationId
|
||||||
? api.notification.updateCustom.useMutation()
|
? api.notification.updateCustom.useMutation()
|
||||||
: api.notification.createCustom.useMutation();
|
: api.notification.createCustom.useMutation();
|
||||||
@@ -233,6 +251,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const larkMutation = notificationId
|
const larkMutation = notificationId
|
||||||
? api.notification.updateLark.useMutation()
|
? api.notification.updateLark.useMutation()
|
||||||
: api.notification.createLark.useMutation();
|
: api.notification.createLark.useMutation();
|
||||||
|
const pushoverMutation = notificationId
|
||||||
|
? api.notification.updatePushover.useMutation()
|
||||||
|
: api.notification.createPushover.useMutation();
|
||||||
|
|
||||||
const form = useForm<NotificationSchema>({
|
const form = useForm<NotificationSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -393,6 +414,23 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
serverThreshold: notification.serverThreshold,
|
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 {
|
} else {
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -408,6 +446,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
ntfy: ntfyMutation,
|
ntfy: ntfyMutation,
|
||||||
lark: larkMutation,
|
lark: larkMutation,
|
||||||
custom: customMutation,
|
custom: customMutation,
|
||||||
|
pushover: pushoverMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: NotificationSchema) => {
|
const onSubmit = async (data: NotificationSchema) => {
|
||||||
@@ -559,6 +598,28 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
customId: notification?.customId || "",
|
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) {
|
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>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -1428,7 +1630,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
isLoadingGotify ||
|
isLoadingGotify ||
|
||||||
isLoadingNtfy ||
|
isLoadingNtfy ||
|
||||||
isLoadingLark ||
|
isLoadingLark ||
|
||||||
isLoadingCustom
|
isLoadingCustom ||
|
||||||
|
isLoadingPushover
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1497,6 +1700,22 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
endpoint: data.endpoint,
|
endpoint: data.endpoint,
|
||||||
headers: headersRecord,
|
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");
|
toast.success("Connection Success");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -231,3 +231,29 @@ export const NtfyIcon = ({ className }: Props) => {
|
|||||||
</svg>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
12
apps/dokploy/drizzle/0135_illegal_magik.sql
Normal file
12
apps/dokploy/drizzle/0135_illegal_magik.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
ALTER TYPE "public"."notificationType" ADD VALUE 'pushover' BEFORE 'custom';--> statement-breakpoint
|
||||||
|
CREATE TABLE "pushover" (
|
||||||
|
"pushoverId" text PRIMARY KEY NOT NULL,
|
||||||
|
"userKey" text NOT NULL,
|
||||||
|
"apiToken" text NOT NULL,
|
||||||
|
"priority" integer DEFAULT 0 NOT NULL,
|
||||||
|
"retry" integer,
|
||||||
|
"expire" integer
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "notification" ADD COLUMN "pushoverId" text;--> statement-breakpoint
|
||||||
|
ALTER TABLE "notification" ADD CONSTRAINT "notification_pushoverId_pushover_pushoverId_fk" FOREIGN KEY ("pushoverId") REFERENCES "public"."pushover"("pushoverId") ON DELETE cascade ON UPDATE no action;
|
||||||
7038
apps/dokploy/drizzle/meta/0135_snapshot.json
Normal file
7038
apps/dokploy/drizzle/meta/0135_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -946,6 +946,13 @@
|
|||||||
"when": 1767871040249,
|
"when": 1767871040249,
|
||||||
"tag": "0134_strong_hercules",
|
"tag": "0134_strong_hercules",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 135,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1768271617042,
|
||||||
|
"tag": "0135_illegal_magik",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
createGotifyNotification,
|
createGotifyNotification,
|
||||||
createLarkNotification,
|
createLarkNotification,
|
||||||
createNtfyNotification,
|
createNtfyNotification,
|
||||||
|
createPushoverNotification,
|
||||||
createSlackNotification,
|
createSlackNotification,
|
||||||
createTelegramNotification,
|
createTelegramNotification,
|
||||||
findNotificationById,
|
findNotificationById,
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendServerThresholdNotifications,
|
sendServerThresholdNotifications,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -26,6 +28,7 @@ import {
|
|||||||
updateGotifyNotification,
|
updateGotifyNotification,
|
||||||
updateLarkNotification,
|
updateLarkNotification,
|
||||||
updateNtfyNotification,
|
updateNtfyNotification,
|
||||||
|
updatePushoverNotification,
|
||||||
updateSlackNotification,
|
updateSlackNotification,
|
||||||
updateTelegramNotification,
|
updateTelegramNotification,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
@@ -46,6 +49,7 @@ import {
|
|||||||
apiCreateGotify,
|
apiCreateGotify,
|
||||||
apiCreateLark,
|
apiCreateLark,
|
||||||
apiCreateNtfy,
|
apiCreateNtfy,
|
||||||
|
apiCreatePushover,
|
||||||
apiCreateSlack,
|
apiCreateSlack,
|
||||||
apiCreateTelegram,
|
apiCreateTelegram,
|
||||||
apiFindOneNotification,
|
apiFindOneNotification,
|
||||||
@@ -55,6 +59,7 @@ import {
|
|||||||
apiTestGotifyConnection,
|
apiTestGotifyConnection,
|
||||||
apiTestLarkConnection,
|
apiTestLarkConnection,
|
||||||
apiTestNtfyConnection,
|
apiTestNtfyConnection,
|
||||||
|
apiTestPushoverConnection,
|
||||||
apiTestSlackConnection,
|
apiTestSlackConnection,
|
||||||
apiTestTelegramConnection,
|
apiTestTelegramConnection,
|
||||||
apiUpdateCustom,
|
apiUpdateCustom,
|
||||||
@@ -63,6 +68,7 @@ import {
|
|||||||
apiUpdateGotify,
|
apiUpdateGotify,
|
||||||
apiUpdateLark,
|
apiUpdateLark,
|
||||||
apiUpdateNtfy,
|
apiUpdateNtfy,
|
||||||
|
apiUpdatePushover,
|
||||||
apiUpdateSlack,
|
apiUpdateSlack,
|
||||||
apiUpdateTelegram,
|
apiUpdateTelegram,
|
||||||
notifications,
|
notifications,
|
||||||
@@ -342,6 +348,7 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
orderBy: desc(notifications.createdAt),
|
orderBy: desc(notifications.createdAt),
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
@@ -634,6 +641,62 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
createPushover: adminProcedure
|
||||||
|
.input(apiCreatePushover)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
return await createPushoverNotification(
|
||||||
|
input,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error creating the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updatePushover: adminProcedure
|
||||||
|
.input(apiUpdatePushover)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const notification = await findNotificationById(input.notificationId);
|
||||||
|
if (
|
||||||
|
IS_CLOUD &&
|
||||||
|
notification.organizationId !== ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to update this notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await updatePushoverNotification({
|
||||||
|
...input,
|
||||||
|
organizationId: ctx.session.activeOrganizationId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
testPushoverConnection: adminProcedure
|
||||||
|
.input(apiTestPushoverConnection)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
input,
|
||||||
|
"Test Notification",
|
||||||
|
"Hi, From Dokploy 👋",
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error testing the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
||||||
return await db.query.notifications.findMany({
|
return await db.query.notifications.findMany({
|
||||||
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export const notificationType = pgEnum("notificationType", [
|
|||||||
"email",
|
"email",
|
||||||
"gotify",
|
"gotify",
|
||||||
"ntfy",
|
"ntfy",
|
||||||
|
"pushover",
|
||||||
"custom",
|
"custom",
|
||||||
"lark",
|
"lark",
|
||||||
]);
|
]);
|
||||||
@@ -64,6 +65,9 @@ export const notifications = pgTable("notification", {
|
|||||||
larkId: text("larkId").references(() => lark.larkId, {
|
larkId: text("larkId").references(() => lark.larkId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
pushoverId: text("pushoverId").references(() => pushover.pushoverId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
organizationId: text("organizationId")
|
organizationId: text("organizationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => organization.id, { onDelete: "cascade" }),
|
.references(() => organization.id, { onDelete: "cascade" }),
|
||||||
@@ -149,6 +153,18 @@ export const lark = pgTable("lark", {
|
|||||||
webhookUrl: text("webhookUrl").notNull(),
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const pushover = pgTable("pushover", {
|
||||||
|
pushoverId: text("pushoverId")
|
||||||
|
.notNull()
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
userKey: text("userKey").notNull(),
|
||||||
|
apiToken: text("apiToken").notNull(),
|
||||||
|
priority: integer("priority").notNull().default(0),
|
||||||
|
retry: integer("retry"),
|
||||||
|
expire: integer("expire"),
|
||||||
|
});
|
||||||
|
|
||||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||||
slack: one(slack, {
|
slack: one(slack, {
|
||||||
fields: [notifications.slackId],
|
fields: [notifications.slackId],
|
||||||
@@ -182,6 +198,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||||||
fields: [notifications.larkId],
|
fields: [notifications.larkId],
|
||||||
references: [lark.larkId],
|
references: [lark.larkId],
|
||||||
}),
|
}),
|
||||||
|
pushover: one(pushover, {
|
||||||
|
fields: [notifications.pushoverId],
|
||||||
|
references: [pushover.pushoverId],
|
||||||
|
}),
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [notifications.organizationId],
|
fields: [notifications.organizationId],
|
||||||
references: [organization.id],
|
references: [organization.id],
|
||||||
@@ -439,6 +459,69 @@ export const apiTestLarkConnection = apiCreateLark.pick({
|
|||||||
webhookUrl: true,
|
webhookUrl: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apiCreatePushover = notificationsSchema
|
||||||
|
.pick({
|
||||||
|
appBuildError: true,
|
||||||
|
databaseBackup: true,
|
||||||
|
volumeBackup: true,
|
||||||
|
dokployRestart: true,
|
||||||
|
name: true,
|
||||||
|
appDeploy: true,
|
||||||
|
dockerCleanup: true,
|
||||||
|
serverThreshold: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
userKey: z.string().min(1),
|
||||||
|
apiToken: z.string().min(1),
|
||||||
|
priority: z.number().min(-2).max(2).default(0),
|
||||||
|
retry: z.number().min(30).nullish(),
|
||||||
|
expire: z.number().min(1).max(10800).nullish(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) =>
|
||||||
|
data.priority !== 2 || (data.retry != null && data.expire != null),
|
||||||
|
{
|
||||||
|
message: "Retry and expire are required for emergency priority (2)",
|
||||||
|
path: ["retry"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const apiUpdatePushover = z.object({
|
||||||
|
notificationId: z.string().min(1),
|
||||||
|
pushoverId: z.string().min(1),
|
||||||
|
organizationId: z.string().optional(),
|
||||||
|
userKey: z.string().min(1).optional(),
|
||||||
|
apiToken: z.string().min(1).optional(),
|
||||||
|
priority: z.number().min(-2).max(2).optional(),
|
||||||
|
retry: z.number().min(30).nullish(),
|
||||||
|
expire: z.number().min(1).max(10800).nullish(),
|
||||||
|
appBuildError: z.boolean().optional(),
|
||||||
|
databaseBackup: z.boolean().optional(),
|
||||||
|
volumeBackup: z.boolean().optional(),
|
||||||
|
dokployRestart: z.boolean().optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
appDeploy: z.boolean().optional(),
|
||||||
|
dockerCleanup: z.boolean().optional(),
|
||||||
|
serverThreshold: z.boolean().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiTestPushoverConnection = z
|
||||||
|
.object({
|
||||||
|
userKey: z.string().min(1),
|
||||||
|
apiToken: z.string().min(1),
|
||||||
|
priority: z.number().min(-2).max(2),
|
||||||
|
retry: z.number().min(30).nullish(),
|
||||||
|
expire: z.number().min(1).max(10800).nullish(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(data) =>
|
||||||
|
data.priority !== 2 || (data.retry != null && data.expire != null),
|
||||||
|
{
|
||||||
|
message: "Retry and expire are required for emergency priority (2)",
|
||||||
|
path: ["retry"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const apiSendTest = notificationsSchema
|
export const apiSendTest = notificationsSchema
|
||||||
.extend({
|
.extend({
|
||||||
botToken: z.string(),
|
botToken: z.string(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type apiCreateGotify,
|
type apiCreateGotify,
|
||||||
type apiCreateLark,
|
type apiCreateLark,
|
||||||
type apiCreateNtfy,
|
type apiCreateNtfy,
|
||||||
|
type apiCreatePushover,
|
||||||
type apiCreateSlack,
|
type apiCreateSlack,
|
||||||
type apiCreateTelegram,
|
type apiCreateTelegram,
|
||||||
type apiUpdateCustom,
|
type apiUpdateCustom,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
type apiUpdateGotify,
|
type apiUpdateGotify,
|
||||||
type apiUpdateLark,
|
type apiUpdateLark,
|
||||||
type apiUpdateNtfy,
|
type apiUpdateNtfy,
|
||||||
|
type apiUpdatePushover,
|
||||||
type apiUpdateSlack,
|
type apiUpdateSlack,
|
||||||
type apiUpdateTelegram,
|
type apiUpdateTelegram,
|
||||||
custom,
|
custom,
|
||||||
@@ -23,6 +25,7 @@ import {
|
|||||||
lark,
|
lark,
|
||||||
notifications,
|
notifications,
|
||||||
ntfy,
|
ntfy,
|
||||||
|
pushover,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
@@ -694,6 +697,7 @@ export const findNotificationById = async (notificationId: string) => {
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!notification) {
|
if (!notification) {
|
||||||
@@ -817,3 +821,99 @@ export const updateNotificationById = async (
|
|||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createPushoverNotification = async (
|
||||||
|
input: typeof apiCreatePushover._type,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const newPushover = await tx
|
||||||
|
.insert(pushover)
|
||||||
|
.values({
|
||||||
|
userKey: input.userKey,
|
||||||
|
apiToken: input.apiToken,
|
||||||
|
priority: input.priority,
|
||||||
|
retry: input.retry,
|
||||||
|
expire: input.expire,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newPushover) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting pushover",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDestination = await tx
|
||||||
|
.insert(notifications)
|
||||||
|
.values({
|
||||||
|
pushoverId: newPushover.pushoverId,
|
||||||
|
name: input.name,
|
||||||
|
appDeploy: input.appDeploy,
|
||||||
|
appBuildError: input.appBuildError,
|
||||||
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
|
serverThreshold: input.serverThreshold,
|
||||||
|
notificationType: "pushover",
|
||||||
|
organizationId: organizationId,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newDestination) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDestination;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updatePushoverNotification = async (
|
||||||
|
input: typeof apiUpdatePushover._type,
|
||||||
|
) => {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const newDestination = await tx
|
||||||
|
.update(notifications)
|
||||||
|
.set({
|
||||||
|
name: input.name,
|
||||||
|
appDeploy: input.appDeploy,
|
||||||
|
appBuildError: input.appBuildError,
|
||||||
|
databaseBackup: input.databaseBackup,
|
||||||
|
volumeBackup: input.volumeBackup,
|
||||||
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
|
organizationId: input.organizationId,
|
||||||
|
serverThreshold: input.serverThreshold,
|
||||||
|
})
|
||||||
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newDestination) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error Updating notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tx
|
||||||
|
.update(pushover)
|
||||||
|
.set({
|
||||||
|
userKey: input.userKey,
|
||||||
|
apiToken: input.apiToken,
|
||||||
|
priority: input.priority,
|
||||||
|
retry: input.retry,
|
||||||
|
expire: input.expire,
|
||||||
|
})
|
||||||
|
.where(eq(pushover.pushoverId, input.pushoverId));
|
||||||
|
|
||||||
|
return newDestination;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -48,12 +49,22 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
const {
|
||||||
notification;
|
email,
|
||||||
|
discord,
|
||||||
|
telegram,
|
||||||
|
slack,
|
||||||
|
gotify,
|
||||||
|
ntfy,
|
||||||
|
custom,
|
||||||
|
lark,
|
||||||
|
pushover,
|
||||||
|
} = notification;
|
||||||
try {
|
try {
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -349,6 +360,14 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
"Build Failed",
|
||||||
|
`Project: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}\nError: ${errorMessage}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -51,12 +52,22 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
const {
|
||||||
notification;
|
email,
|
||||||
|
discord,
|
||||||
|
telegram,
|
||||||
|
slack,
|
||||||
|
gotify,
|
||||||
|
ntfy,
|
||||||
|
custom,
|
||||||
|
lark,
|
||||||
|
pushover,
|
||||||
|
} = notification;
|
||||||
try {
|
try {
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -363,6 +374,14 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
"Build Success",
|
||||||
|
`Project: ${projectName}\nApplication: ${applicationName}\nEnvironment: ${environmentName}\nType: ${applicationType}\nDate: ${date.toLocaleString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -48,12 +49,22 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
const {
|
||||||
notification;
|
email,
|
||||||
|
discord,
|
||||||
|
telegram,
|
||||||
|
slack,
|
||||||
|
gotify,
|
||||||
|
ntfy,
|
||||||
|
custom,
|
||||||
|
lark,
|
||||||
|
pushover,
|
||||||
|
} = notification;
|
||||||
try {
|
try {
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -377,6 +388,14 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
`Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
|
`Project: ${projectName}\nApplication: ${applicationName}\nDatabase: ${databaseType}\nDatabase Name: ${databaseName}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -35,12 +36,22 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
const {
|
||||||
notification;
|
email,
|
||||||
|
discord,
|
||||||
|
telegram,
|
||||||
|
slack,
|
||||||
|
gotify,
|
||||||
|
ntfy,
|
||||||
|
custom,
|
||||||
|
lark,
|
||||||
|
pushover,
|
||||||
|
} = notification;
|
||||||
try {
|
try {
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -230,6 +241,14 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
"Docker Cleanup",
|
||||||
|
`Date: ${date.toLocaleString()}\nMessage: ${message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -29,12 +30,22 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
const {
|
||||||
notification;
|
email,
|
||||||
|
discord,
|
||||||
|
telegram,
|
||||||
|
slack,
|
||||||
|
gotify,
|
||||||
|
ntfy,
|
||||||
|
custom,
|
||||||
|
lark,
|
||||||
|
pushover,
|
||||||
|
} = notification;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (email) {
|
if (email) {
|
||||||
@@ -219,6 +230,14 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
"Dokploy Server Restarted",
|
||||||
|
`Date: ${date.toLocaleString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
sendCustomNotification,
|
sendCustomNotification,
|
||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -38,6 +39,7 @@ export const sendServerThresholdNotifications = async (
|
|||||||
slack: true,
|
slack: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ export const sendServerThresholdNotifications = async (
|
|||||||
const typeColor = 0xff0000; // Rojo para indicar alerta
|
const typeColor = 0xff0000; // Rojo para indicar alerta
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { discord, telegram, slack, custom, lark } = notification;
|
const { discord, telegram, slack, custom, lark, pushover } = notification;
|
||||||
|
|
||||||
if (discord) {
|
if (discord) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
@@ -266,5 +268,13 @@ export const sendServerThresholdNotifications = async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
`Server ${payload.Type} Alert`,
|
||||||
|
`Server: ${payload.ServerName}\nType: ${payload.Type}\nCurrent: ${payload.Value.toFixed(2)}%\nThreshold: ${payload.Threshold.toFixed(2)}%\nMessage: ${payload.Message}\nTime: ${date.toLocaleString()}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
gotify,
|
gotify,
|
||||||
lark,
|
lark,
|
||||||
ntfy,
|
ntfy,
|
||||||
|
pushover,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
@@ -223,3 +224,33 @@ export const sendLarkNotification = async (
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendPushoverNotification = async (
|
||||||
|
connection: typeof pushover.$inferInsert,
|
||||||
|
title: string,
|
||||||
|
message: string,
|
||||||
|
) => {
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append("token", connection.apiToken);
|
||||||
|
formData.append("user", connection.userKey);
|
||||||
|
formData.append("title", title);
|
||||||
|
formData.append("message", message);
|
||||||
|
formData.append("priority", connection.priority?.toString() || "0");
|
||||||
|
|
||||||
|
// For emergency priority (2), retry and expire are required
|
||||||
|
if (connection.priority === 2) {
|
||||||
|
formData.append("retry", connection.retry?.toString() || "30");
|
||||||
|
formData.append("expire", connection.expire?.toString() || "3600");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch("https://api.pushover.net/1/messages.json", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to send Pushover notification: ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
|
sendPushoverNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
@@ -53,11 +54,13 @@ export const sendVolumeBackupNotifications = async ({
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
pushover: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy, pushover } =
|
||||||
|
notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
|
const subject = `Volume Backup ${type === "success" ? "Successful" : "Failed"} - ${applicationName}`;
|
||||||
@@ -270,5 +273,13 @@ export const sendVolumeBackupNotifications = async ({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pushover) {
|
||||||
|
await sendPushoverNotification(
|
||||||
|
pushover,
|
||||||
|
`Volume Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
|
`Project: ${projectName}\nApplication: ${applicationName}\nVolume: ${volumeName}\nService Type: ${serviceType}${backupSize ? `\nBackup Size: ${backupSize}` : ""}\nDate: ${date.toLocaleString()}${type === "error" && errorMessage ? `\nError: ${errorMessage}` : ""}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user