mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(notifications): add emails and methos for each action
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
DiscordIcon,
|
||||||
|
SlackIcon,
|
||||||
|
TelegramIcon,
|
||||||
|
} from "@/components/icons/notification-icons";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -18,31 +23,26 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AlertTriangle, Mail } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useFieldArray, useForm } from "react-hook-form";
|
import { useFieldArray, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { AlertTriangle, Mail } from "lucide-react";
|
|
||||||
import {
|
|
||||||
DiscordIcon,
|
|
||||||
SlackIcon,
|
|
||||||
TelegramIcon,
|
|
||||||
} from "@/components/icons/notification-icons";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
|
|
||||||
const notificationBaseSchema = z.object({
|
const notificationBaseSchema = z.object({
|
||||||
name: z.string().min(1, {
|
name: z.string().min(1, {
|
||||||
message: "Name is required",
|
message: "Name is required",
|
||||||
}),
|
}),
|
||||||
appDeploy: z.boolean().default(false),
|
appDeploy: z.boolean().default(false),
|
||||||
userJoin: z.boolean().default(false),
|
appBuildError: z.boolean().default(false),
|
||||||
appBuilderError: z.boolean().default(false),
|
|
||||||
databaseBackup: z.boolean().default(false),
|
databaseBackup: z.boolean().default(false),
|
||||||
dokployRestart: z.boolean().default(false),
|
dokployRestart: z.boolean().default(false),
|
||||||
|
dockerCleanup: z.boolean().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const notificationSchema = z.discriminatedUnion("type", [
|
export const notificationSchema = z.discriminatedUnion("type", [
|
||||||
@@ -159,52 +159,51 @@ export const AddNotification = () => {
|
|||||||
|
|
||||||
const onSubmit = async (data: NotificationSchema) => {
|
const onSubmit = async (data: NotificationSchema) => {
|
||||||
const {
|
const {
|
||||||
appBuilderError,
|
appBuildError,
|
||||||
appDeploy,
|
appDeploy,
|
||||||
dokployRestart,
|
dokployRestart,
|
||||||
databaseBackup,
|
databaseBackup,
|
||||||
userJoin,
|
dockerCleanup,
|
||||||
} = data;
|
} = data;
|
||||||
let promise: Promise<unknown> | null = null;
|
let promise: Promise<unknown> | null = null;
|
||||||
if (data.type === "slack") {
|
if (data.type === "slack") {
|
||||||
promise = slackMutation.mutateAsync({
|
promise = slackMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
webhookUrl: data.webhookUrl,
|
webhookUrl: data.webhookUrl,
|
||||||
channel: data.channel,
|
channel: data.channel,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (data.type === "telegram") {
|
} else if (data.type === "telegram") {
|
||||||
promise = telegramMutation.mutateAsync({
|
promise = telegramMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
botToken: data.botToken,
|
botToken: data.botToken,
|
||||||
chatId: data.chatId,
|
chatId: data.chatId,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (data.type === "discord") {
|
} else if (data.type === "discord") {
|
||||||
promise = discordMutation.mutateAsync({
|
promise = discordMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
webhookUrl: data.webhookUrl,
|
webhookUrl: data.webhookUrl,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (data.type === "email") {
|
} else if (data.type === "email") {
|
||||||
promise = emailMutation.mutateAsync({
|
promise = emailMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
smtpServer: data.smtpServer,
|
smtpServer: data.smtpServer,
|
||||||
smtpPort: data.smtpPort,
|
smtpPort: data.smtpPort,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
@@ -212,6 +211,7 @@ export const AddNotification = () => {
|
|||||||
fromAddress: data.fromAddress,
|
fromAddress: data.fromAddress,
|
||||||
toAddresses: data.toAddresses,
|
toAddresses: data.toAddresses,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,13 +598,13 @@ export const AddNotification = () => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="userJoin"
|
name="appBuildError"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>User Join</FormLabel>
|
<FormLabel>App Build Error</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when a user joins the app.
|
Trigger the action when the build fails.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -616,6 +616,7 @@ export const AddNotification = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="databaseBackup"
|
name="databaseBackup"
|
||||||
@@ -639,13 +640,14 @@ export const AddNotification = () => {
|
|||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="dokployRestart"
|
name="dockerCleanup"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Deploy Restart</FormLabel>
|
<FormLabel>Docker Cleanup</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when a deploy is restarted.
|
Trigger the action when the docker cleanup is
|
||||||
|
performed.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -657,15 +659,16 @@ export const AddNotification = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="appBuilderError"
|
name="dokployRestart"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>App Builder Error</FormLabel>
|
<FormLabel>Dokploy Restart</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when the build fails.
|
Trigger the action when a dokploy is restarted.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
DiscordIcon,
|
||||||
|
SlackIcon,
|
||||||
|
TelegramIcon,
|
||||||
|
} from "@/components/icons/notification-icons";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -18,21 +23,16 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Mail, PenBoxIcon } from "lucide-react";
|
import { Mail, PenBoxIcon } from "lucide-react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { FieldErrors, useFieldArray, useForm } from "react-hook-form";
|
import { FieldErrors, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import {
|
import {
|
||||||
TelegramIcon,
|
|
||||||
DiscordIcon,
|
|
||||||
SlackIcon,
|
|
||||||
} from "@/components/icons/notification-icons";
|
|
||||||
import {
|
|
||||||
notificationSchema,
|
|
||||||
type NotificationSchema,
|
type NotificationSchema,
|
||||||
|
notificationSchema,
|
||||||
} from "./add-notification";
|
} from "./add-notification";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -82,11 +82,11 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
if (data) {
|
if (data) {
|
||||||
if (data.notificationType === "slack") {
|
if (data.notificationType === "slack") {
|
||||||
form.reset({
|
form.reset({
|
||||||
appBuilderError: data.appBuildError,
|
appBuildError: data.appBuildError,
|
||||||
appDeploy: data.appDeploy,
|
appDeploy: data.appDeploy,
|
||||||
dokployRestart: data.dokployRestart,
|
dokployRestart: data.dokployRestart,
|
||||||
databaseBackup: data.databaseBackup,
|
databaseBackup: data.databaseBackup,
|
||||||
userJoin: data.userJoin,
|
dockerCleanup: data.dockerCleanup,
|
||||||
webhookUrl: data.slack?.webhookUrl,
|
webhookUrl: data.slack?.webhookUrl,
|
||||||
channel: data.slack?.channel || "",
|
channel: data.slack?.channel || "",
|
||||||
name: data.name,
|
name: data.name,
|
||||||
@@ -94,35 +94,34 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
});
|
});
|
||||||
} else if (data.notificationType === "telegram") {
|
} else if (data.notificationType === "telegram") {
|
||||||
form.reset({
|
form.reset({
|
||||||
appBuilderError: data.appBuildError,
|
appBuildError: data.appBuildError,
|
||||||
appDeploy: data.appDeploy,
|
appDeploy: data.appDeploy,
|
||||||
dokployRestart: data.dokployRestart,
|
dokployRestart: data.dokployRestart,
|
||||||
databaseBackup: data.databaseBackup,
|
databaseBackup: data.databaseBackup,
|
||||||
userJoin: data.userJoin,
|
|
||||||
botToken: data.telegram?.botToken,
|
botToken: data.telegram?.botToken,
|
||||||
chatId: data.telegram?.chatId,
|
chatId: data.telegram?.chatId,
|
||||||
type: data.notificationType,
|
type: data.notificationType,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: data.dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (data.notificationType === "discord") {
|
} else if (data.notificationType === "discord") {
|
||||||
form.reset({
|
form.reset({
|
||||||
appBuilderError: data.appBuildError,
|
appBuildError: data.appBuildError,
|
||||||
appDeploy: data.appDeploy,
|
appDeploy: data.appDeploy,
|
||||||
dokployRestart: data.dokployRestart,
|
dokployRestart: data.dokployRestart,
|
||||||
databaseBackup: data.databaseBackup,
|
databaseBackup: data.databaseBackup,
|
||||||
userJoin: data.userJoin,
|
|
||||||
type: data.notificationType,
|
type: data.notificationType,
|
||||||
webhookUrl: data.discord?.webhookUrl,
|
webhookUrl: data.discord?.webhookUrl,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: data.dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (data.notificationType === "email") {
|
} else if (data.notificationType === "email") {
|
||||||
form.reset({
|
form.reset({
|
||||||
appBuilderError: data.appBuildError,
|
appBuildError: data.appBuildError,
|
||||||
appDeploy: data.appDeploy,
|
appDeploy: data.appDeploy,
|
||||||
dokployRestart: data.dokployRestart,
|
dokployRestart: data.dokployRestart,
|
||||||
databaseBackup: data.databaseBackup,
|
databaseBackup: data.databaseBackup,
|
||||||
type: data.notificationType,
|
type: data.notificationType,
|
||||||
userJoin: data.userJoin,
|
|
||||||
smtpServer: data.email?.smtpServer,
|
smtpServer: data.email?.smtpServer,
|
||||||
smtpPort: data.email?.smtpPort,
|
smtpPort: data.email?.smtpPort,
|
||||||
username: data.email?.username,
|
username: data.email?.username,
|
||||||
@@ -130,6 +129,7 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
toAddresses: data.email?.toAddresses,
|
toAddresses: data.email?.toAddresses,
|
||||||
fromAddress: data.email?.fromAddress,
|
fromAddress: data.email?.fromAddress,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
|
dockerCleanup: data.dockerCleanup,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,58 +137,57 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
|
|
||||||
const onSubmit = async (formData: NotificationSchema) => {
|
const onSubmit = async (formData: NotificationSchema) => {
|
||||||
const {
|
const {
|
||||||
appBuilderError,
|
appBuildError,
|
||||||
appDeploy,
|
appDeploy,
|
||||||
dokployRestart,
|
dokployRestart,
|
||||||
databaseBackup,
|
databaseBackup,
|
||||||
userJoin,
|
dockerCleanup,
|
||||||
} = formData;
|
} = formData;
|
||||||
let promise: Promise<unknown> | null = null;
|
let promise: Promise<unknown> | null = null;
|
||||||
if (formData?.type === "slack" && data?.slackId) {
|
if (formData?.type === "slack" && data?.slackId) {
|
||||||
promise = slackMutation.mutateAsync({
|
promise = slackMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
webhookUrl: formData.webhookUrl,
|
webhookUrl: formData.webhookUrl,
|
||||||
channel: formData.channel,
|
channel: formData.channel,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
notificationId: notificationId,
|
notificationId: notificationId,
|
||||||
slackId: data?.slackId,
|
slackId: data?.slackId,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (formData.type === "telegram" && data?.telegramId) {
|
} else if (formData.type === "telegram" && data?.telegramId) {
|
||||||
promise = telegramMutation.mutateAsync({
|
promise = telegramMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
botToken: formData.botToken,
|
botToken: formData.botToken,
|
||||||
chatId: formData.chatId,
|
chatId: formData.chatId,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
notificationId: notificationId,
|
notificationId: notificationId,
|
||||||
telegramId: data?.telegramId,
|
telegramId: data?.telegramId,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (formData.type === "discord" && data?.discordId) {
|
} else if (formData.type === "discord" && data?.discordId) {
|
||||||
promise = discordMutation.mutateAsync({
|
promise = discordMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
webhookUrl: formData.webhookUrl,
|
webhookUrl: formData.webhookUrl,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
notificationId: notificationId,
|
notificationId: notificationId,
|
||||||
discordId: data?.discordId,
|
discordId: data?.discordId,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
} else if (formData.type === "email" && data?.emailId) {
|
} else if (formData.type === "email" && data?.emailId) {
|
||||||
promise = emailMutation.mutateAsync({
|
promise = emailMutation.mutateAsync({
|
||||||
appBuildError: appBuilderError,
|
appBuildError: appBuildError,
|
||||||
appDeploy: appDeploy,
|
appDeploy: appDeploy,
|
||||||
dokployRestart: dokployRestart,
|
dokployRestart: dokployRestart,
|
||||||
databaseBackup: databaseBackup,
|
databaseBackup: databaseBackup,
|
||||||
userJoin: userJoin,
|
|
||||||
smtpServer: formData.smtpServer,
|
smtpServer: formData.smtpServer,
|
||||||
smtpPort: formData.smtpPort,
|
smtpPort: formData.smtpPort,
|
||||||
username: formData.username,
|
username: formData.username,
|
||||||
@@ -198,6 +197,7 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
name: formData.name,
|
name: formData.name,
|
||||||
notificationId: notificationId,
|
notificationId: notificationId,
|
||||||
emailId: data?.emailId,
|
emailId: data?.emailId,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,14 +554,14 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
defaultValue={form.control._defaultValues.userJoin}
|
defaultValue={form.control._defaultValues.appBuildError}
|
||||||
name="userJoin"
|
name="appBuildError"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>User Join</FormLabel>
|
<FormLabel>App Builder Error</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when a user joins the app.
|
Trigger the action when the build fails.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -573,6 +573,7 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="databaseBackup"
|
name="databaseBackup"
|
||||||
@@ -596,14 +597,14 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
defaultValue={form.control._defaultValues.dokployRestart}
|
name="dockerCleanup"
|
||||||
name="dokployRestart"
|
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Deploy Restart</FormLabel>
|
<FormLabel>Docker Cleanup</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when a deploy is restarted.
|
Trigger the action when the docker cleanup is
|
||||||
|
performed.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -617,14 +618,14 @@ export const UpdateNotification = ({ notificationId }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
defaultValue={form.control._defaultValues.appBuilderError}
|
defaultValue={form.control._defaultValues.dokployRestart}
|
||||||
name="appBuilderError"
|
name="dokployRestart"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
<FormItem className=" flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm gap-2">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>App Builder Error</FormLabel>
|
<FormLabel>Dokploy Restart</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
Trigger the action when the build fails.
|
Trigger the action when a dokploy is restarted.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</div>
|
</div>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
1
drizzle/0020_fantastic_slapstick.sql
Normal file
1
drizzle/0020_fantastic_slapstick.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "notification" ADD COLUMN "dockerCleanup" boolean DEFAULT false NOT NULL;
|
||||||
1
drizzle/0021_premium_sebastian_shaw.sql
Normal file
1
drizzle/0021_premium_sebastian_shaw.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "notification" DROP COLUMN IF EXISTS "userJoin";
|
||||||
2926
drizzle/meta/0020_snapshot.json
Normal file
2926
drizzle/meta/0020_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
2919
drizzle/meta/0021_snapshot.json
Normal file
2919
drizzle/meta/0021_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -141,6 +141,20 @@
|
|||||||
"when": 1721110706912,
|
"when": 1721110706912,
|
||||||
"tag": "0019_heavy_freak",
|
"tag": "0019_heavy_freak",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1721363861686,
|
||||||
|
"tag": "0020_fantastic_slapstick",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1721370423752,
|
||||||
|
"tag": "0021_premium_sebastian_shaw",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import * as React from "react";
|
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Button,
|
Button,
|
||||||
Container,
|
Container,
|
||||||
Head,
|
Head,
|
||||||
|
Heading,
|
||||||
Html,
|
Html,
|
||||||
|
Img,
|
||||||
Link,
|
Link,
|
||||||
Preview,
|
Preview,
|
||||||
Section,
|
Section,
|
||||||
Text,
|
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Img,
|
Text,
|
||||||
Heading,
|
|
||||||
} from "@react-email/components";
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
export type TemplateProps = {
|
export type TemplateProps = {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -20,6 +20,7 @@ export type TemplateProps = {
|
|||||||
applicationType: string;
|
applicationType: string;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
buildLink: string;
|
buildLink: string;
|
||||||
|
date: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BuildFailedEmail = ({
|
export const BuildFailedEmail = ({
|
||||||
@@ -28,6 +29,7 @@ export const BuildFailedEmail = ({
|
|||||||
applicationType = "application",
|
applicationType = "application",
|
||||||
errorMessage = "Error array.length is not a function",
|
errorMessage = "Error array.length is not a function",
|
||||||
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
|
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
|
||||||
|
date = "2023-05-01T00:00:00.000Z",
|
||||||
}: TemplateProps) => {
|
}: TemplateProps) => {
|
||||||
const previewText = `Build failed for ${applicationName}`;
|
const previewText = `Build failed for ${applicationName}`;
|
||||||
return (
|
return (
|
||||||
@@ -79,6 +81,9 @@ export const BuildFailedEmail = ({
|
|||||||
<Text className="!leading-3">
|
<Text className="!leading-3">
|
||||||
Application Type: <strong>{applicationType}</strong>
|
Application Type: <strong>{applicationType}</strong>
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Date: <strong>{date}</strong>
|
||||||
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
<Text className="!leading-3 font-bold">Reason: </Text>
|
<Text className="!leading-3 font-bold">Reason: </Text>
|
||||||
|
|||||||
106
emails/emails/build-success.tsx
Normal file
106
emails/emails/build-success.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Link,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export type TemplateProps = {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
applicationType: string;
|
||||||
|
buildLink: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BuildSuccessEmail = ({
|
||||||
|
projectName = "dokploy",
|
||||||
|
applicationName = "frontend",
|
||||||
|
applicationType = "application",
|
||||||
|
buildLink = "https://dokploy.com/projects/dokploy-test/applications/dokploy-test",
|
||||||
|
date = "2023-05-01T00:00:00.000Z",
|
||||||
|
}: TemplateProps) => {
|
||||||
|
const previewText = `Build success for ${applicationName}`;
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: "#007291",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||||
|
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||||
|
<Section className="mt-[32px]">
|
||||||
|
<Img
|
||||||
|
src={
|
||||||
|
"https://avatars.githubusercontent.com/u/156882017?s=200&v=4"
|
||||||
|
}
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
alt="Dokploy"
|
||||||
|
className="my-0 mx-auto"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||||
|
Build success for <strong>{applicationName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Hello,
|
||||||
|
</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Your build for <strong>{applicationName}</strong> was successful
|
||||||
|
</Text>
|
||||||
|
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
|
<Text className="!leading-3 font-bold">Details: </Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Project Name: <strong>{projectName}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Application Name: <strong>{applicationName}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Application Type: <strong>{applicationType}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Date: <strong>{date}</strong>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
<Section className="text-center mt-[32px] mb-[32px]">
|
||||||
|
<Button
|
||||||
|
href={buildLink}
|
||||||
|
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
|
||||||
|
>
|
||||||
|
View build
|
||||||
|
</Button>
|
||||||
|
</Section>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
or copy and paste this URL into your browser:{" "}
|
||||||
|
<Link href={buildLink} className="text-blue-600 no-underline">
|
||||||
|
{buildLink}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BuildSuccessEmail;
|
||||||
105
emails/emails/database-backup.tsx
Normal file
105
emails/emails/database-backup.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export type TemplateProps = {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
||||||
|
type: "error" | "success";
|
||||||
|
errorMessage?: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DatabaseBackupEmail = ({
|
||||||
|
projectName = "dokploy",
|
||||||
|
applicationName = "frontend",
|
||||||
|
databaseType = "postgres",
|
||||||
|
type = "success",
|
||||||
|
errorMessage,
|
||||||
|
date = "2023-05-01T00:00:00.000Z",
|
||||||
|
}: TemplateProps) => {
|
||||||
|
const previewText = `Database backup for ${applicationName} was ${type === "success" ? "successful ✅" : "failed ❌"}`;
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: "#007291",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Head />
|
||||||
|
|
||||||
|
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||||
|
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||||
|
<Section className="mt-[32px]">
|
||||||
|
<Img
|
||||||
|
src={
|
||||||
|
"https://avatars.githubusercontent.com/u/156882017?s=200&v=4"
|
||||||
|
}
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
alt="Dokploy"
|
||||||
|
className="my-0 mx-auto"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||||
|
Database backup for <strong>{applicationName}</strong>
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Hello,
|
||||||
|
</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Your database backup for <strong>{applicationName}</strong> was{" "}
|
||||||
|
{type === "success"
|
||||||
|
? "successful ✅"
|
||||||
|
: "failed Please check the error message below. ❌"}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
|
<Text className="!leading-3 font-bold">Details: </Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Project Name: <strong>{projectName}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Application Name: <strong>{applicationName}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Database Type: <strong>{databaseType}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Date: <strong>{date}</strong>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
{type === "error" && errorMessage ? (
|
||||||
|
<Section className="flex text-black text-[14px] mt-4 leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
|
<Text className="!leading-3 font-bold">Reason: </Text>
|
||||||
|
<Text className="text-[12px] leading-[24px]">
|
||||||
|
{errorMessage || "Error message not provided"}
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
) : null}
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DatabaseBackupEmail;
|
||||||
81
emails/emails/docker-cleanup.tsx
Normal file
81
emails/emails/docker-cleanup.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export type TemplateProps = {
|
||||||
|
message: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DockerCleanupEmail = ({
|
||||||
|
message = "Docker cleanup for dokploy",
|
||||||
|
date = "2023-05-01T00:00:00.000Z",
|
||||||
|
}: TemplateProps) => {
|
||||||
|
const previewText = "Docker cleanup for dokploy";
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: "#007291",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Head />
|
||||||
|
|
||||||
|
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||||
|
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||||
|
<Section className="mt-[32px]">
|
||||||
|
<Img
|
||||||
|
src={
|
||||||
|
"https://avatars.githubusercontent.com/u/156882017?s=200&v=4"
|
||||||
|
}
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
alt="Dokploy"
|
||||||
|
className="my-0 mx-auto"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||||
|
Docker cleanup for <strong>dokploy</strong>
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Hello,
|
||||||
|
</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
The docker cleanup for <strong>dokploy</strong> was successful ✅
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
|
<Text className="!leading-3 font-bold">Details: </Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Message: <strong>{message}</strong>
|
||||||
|
</Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Date: <strong>{date}</strong>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DockerCleanupEmail;
|
||||||
75
emails/emails/dokploy-restart.tsx
Normal file
75
emails/emails/dokploy-restart.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Heading,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from "@react-email/components";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export type TemplateProps = {
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DokployRestartEmail = ({
|
||||||
|
date = "2023-05-01T00:00:00.000Z",
|
||||||
|
}: TemplateProps) => {
|
||||||
|
const previewText = "Your dokploy server was restarted";
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
brand: "#007291",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Head />
|
||||||
|
|
||||||
|
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||||
|
<Container className="border border-solid border-[#eaeaea] rounded-lg my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||||
|
<Section className="mt-[32px]">
|
||||||
|
<Img
|
||||||
|
src={
|
||||||
|
"https://avatars.githubusercontent.com/u/156882017?s=200&v=4"
|
||||||
|
}
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
alt="Dokploy"
|
||||||
|
className="my-0 mx-auto"
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||||
|
Dokploy Server Restart
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Hello,
|
||||||
|
</Text>
|
||||||
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
|
Your dokploy server was restarted ✅
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Section className="flex text-black text-[14px] leading-[24px] bg-[#F4F4F5] rounded-lg p-2">
|
||||||
|
<Text className="!leading-3 font-bold">Details: </Text>
|
||||||
|
<Text className="!leading-3">
|
||||||
|
Date: <strong>{date}</strong>
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DokployRestartEmail;
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
import { findMariadbByBackupId } from "../services/mariadb";
|
import { findMariadbByBackupId } from "../services/mariadb";
|
||||||
import { findMongoByBackupId } from "../services/mongo";
|
import { findMongoByBackupId } from "../services/mongo";
|
||||||
import { findMySqlByBackupId } from "../services/mysql";
|
import { findMySqlByBackupId } from "../services/mysql";
|
||||||
|
import { sendDatabaseBackupNotifications } from "../services/notification";
|
||||||
import { findPostgresByBackupId } from "../services/postgres";
|
import { findPostgresByBackupId } from "../services/postgres";
|
||||||
|
|
||||||
export const backupRouter = createTRPCRouter({
|
export const backupRouter = createTRPCRouter({
|
||||||
@@ -90,6 +91,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const postgres = await findPostgresByBackupId(backup.backupId);
|
const postgres = await findPostgresByBackupId(backup.backupId);
|
||||||
await runPostgresBackup(postgres, backup);
|
await runPostgresBackup(postgres, backup);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { scheduleJob, scheduledJobs } from "node-schedule";
|
import { scheduleJob, scheduledJobs } from "node-schedule";
|
||||||
import { appRouter } from "../root";
|
import { appRouter } from "../root";
|
||||||
import { findAdmin, updateAdmin } from "../services/admin";
|
import { findAdmin, updateAdmin } from "../services/admin";
|
||||||
|
import { sendDockerCleanupNotifications } from "../services/notification";
|
||||||
import {
|
import {
|
||||||
getDokployImage,
|
getDokployImage,
|
||||||
getDokployVersion,
|
getDokployVersion,
|
||||||
@@ -86,6 +87,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
await cleanUpUnusedImages();
|
await cleanUpUnusedImages();
|
||||||
await cleanUpDockerBuilder();
|
await cleanUpDockerBuilder();
|
||||||
await cleanUpSystemPrune();
|
await cleanUpSystemPrune();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
cleanMonitoring: adminProcedure.mutation(async () => {
|
cleanMonitoring: adminProcedure.mutation(async () => {
|
||||||
@@ -144,6 +146,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
await cleanUpUnusedImages();
|
await cleanUpUnusedImages();
|
||||||
await cleanUpDockerBuilder();
|
await cleanUpDockerBuilder();
|
||||||
await cleanUpSystemPrune();
|
await cleanUpSystemPrune();
|
||||||
|
await sendDockerCleanupNotifications();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const currentJob = scheduledJobs["docker-cleanup"];
|
const currentJob = scheduledJobs["docker-cleanup"];
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import { TRPCError } from "@trpc/server";
|
|||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { findAdmin } from "./admin";
|
import { findAdmin } from "./admin";
|
||||||
import { createDeployment, updateDeploymentStatus } from "./deployment";
|
import { createDeployment, updateDeploymentStatus } from "./deployment";
|
||||||
import { sendBuildErrorNotifications } from "./notification";
|
import {
|
||||||
|
sendBuildErrorNotifications,
|
||||||
|
sendBuildSuccessNotifications,
|
||||||
|
} from "./notification";
|
||||||
import { validUniqueServerAppName } from "./project";
|
import { validUniqueServerAppName } from "./project";
|
||||||
export type Application = typeof applications.$inferSelect;
|
export type Application = typeof applications.$inferSelect;
|
||||||
|
|
||||||
@@ -157,13 +160,20 @@ export const deployApplication = async ({
|
|||||||
}
|
}
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "done");
|
await updateDeploymentStatus(deployment.deploymentId, "done");
|
||||||
await updateApplicationStatus(applicationId, "done");
|
await updateApplicationStatus(applicationId, "done");
|
||||||
|
|
||||||
|
await sendBuildSuccessNotifications({
|
||||||
|
projectName: application.project.name,
|
||||||
|
applicationName: application.name,
|
||||||
|
applicationType: "application",
|
||||||
|
buildLink: deployment.logPath,
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error on build", error);
|
console.log("Error on build", error);
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
await updateApplicationStatus(applicationId, "error");
|
await updateApplicationStatus(applicationId, "error");
|
||||||
await sendBuildErrorNotifications({
|
await sendBuildErrorNotifications({
|
||||||
projectName: application.project.name,
|
projectName: application.project.name,
|
||||||
applicationName: application.appName,
|
applicationName: application.name,
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
errorMessage: error?.message || "Error to build",
|
errorMessage: error?.message || "Error to build",
|
||||||
buildLink: deployment.logPath,
|
buildLink: deployment.logPath,
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { BuildFailedEmail } from "@/emails/emails/build-failed";
|
||||||
|
import BuildSuccessEmail from "@/emails/emails/build-success";
|
||||||
|
import DatabaseBackupEmail from "@/emails/emails/database-backup";
|
||||||
|
import DockerCleanupEmail from "@/emails/emails/docker-cleanup";
|
||||||
|
import DokployRestartEmail from "@/emails/emails/dokploy-restart";
|
||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import {
|
import {
|
||||||
type apiCreateDiscord,
|
type apiCreateDiscord,
|
||||||
@@ -20,10 +25,9 @@ import {
|
|||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import { render } from "@react-email/components";
|
import { render } from "@react-email/components";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
import { and, eq, isNotNull } from "drizzle-orm";
|
|
||||||
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||||
import { BuildFailedEmail } from "@/emails/emails/build-failed";
|
|
||||||
|
|
||||||
export type Notification = typeof notifications.$inferSelect;
|
export type Notification = typeof notifications.$inferSelect;
|
||||||
|
|
||||||
@@ -53,10 +57,10 @@ export const createSlackNotification = async (
|
|||||||
slackId: newSlack.slackId,
|
slackId: newSlack.slackId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "slack",
|
notificationType: "slack",
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
@@ -82,10 +86,10 @@ export const updateSlackNotification = async (
|
|||||||
.set({
|
.set({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
})
|
})
|
||||||
.where(eq(notifications.notificationId, input.notificationId))
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -138,10 +142,10 @@ export const createTelegramNotification = async (
|
|||||||
telegramId: newTelegram.telegramId,
|
telegramId: newTelegram.telegramId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "telegram",
|
notificationType: "telegram",
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
@@ -167,10 +171,10 @@ export const updateTelegramNotification = async (
|
|||||||
.set({
|
.set({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
})
|
})
|
||||||
.where(eq(notifications.notificationId, input.notificationId))
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -222,10 +226,10 @@ export const createDiscordNotification = async (
|
|||||||
discordId: newDiscord.discordId,
|
discordId: newDiscord.discordId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "discord",
|
notificationType: "discord",
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
@@ -251,10 +255,10 @@ export const updateDiscordNotification = async (
|
|||||||
.set({
|
.set({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
})
|
})
|
||||||
.where(eq(notifications.notificationId, input.notificationId))
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -310,10 +314,10 @@ export const createEmailNotification = async (
|
|||||||
emailId: newEmail.emailId,
|
emailId: newEmail.emailId,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
notificationType: "email",
|
notificationType: "email",
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
@@ -339,10 +343,10 @@ export const updateEmailNotification = async (
|
|||||||
.set({
|
.set({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
appDeploy: input.appDeploy,
|
appDeploy: input.appDeploy,
|
||||||
userJoin: input.userJoin,
|
|
||||||
appBuildError: input.appBuildError,
|
appBuildError: input.appBuildError,
|
||||||
databaseBackup: input.databaseBackup,
|
databaseBackup: input.databaseBackup,
|
||||||
dokployRestart: input.dokployRestart,
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
})
|
})
|
||||||
.where(eq(notifications.notificationId, input.notificationId))
|
.where(eq(notifications.notificationId, input.notificationId))
|
||||||
.returning()
|
.returning()
|
||||||
@@ -507,69 +511,13 @@ export const sendEmailTestNotification = async (
|
|||||||
console.log("Email notification sent successfully");
|
console.log("Email notification sent successfully");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendBuildFailedEmail = async ({
|
interface BuildFailedEmailProps {
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
errorMessage,
|
|
||||||
buildLink,
|
|
||||||
}: {
|
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
applicationType: string;
|
applicationType: string;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
buildLink: string;
|
buildLink: string;
|
||||||
}) => {
|
}
|
||||||
const notificationList = await db.query.notifications.findMany({
|
|
||||||
where: and(
|
|
||||||
isNotNull(notifications.emailId),
|
|
||||||
eq(notifications.appBuildError, true),
|
|
||||||
),
|
|
||||||
with: {
|
|
||||||
email: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
|
||||||
const { email } = notification;
|
|
||||||
if (email) {
|
|
||||||
const {
|
|
||||||
smtpServer,
|
|
||||||
smtpPort,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
fromAddress,
|
|
||||||
toAddresses,
|
|
||||||
} = email;
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
host: smtpServer,
|
|
||||||
port: smtpPort,
|
|
||||||
secure: smtpPort === 465,
|
|
||||||
auth: {
|
|
||||||
user: username,
|
|
||||||
pass: password,
|
|
||||||
},
|
|
||||||
} as SMTPTransport.Options);
|
|
||||||
const mailOptions = {
|
|
||||||
from: fromAddress,
|
|
||||||
to: toAddresses?.join(", "),
|
|
||||||
subject: "Build failed for dokploy",
|
|
||||||
html: render(
|
|
||||||
BuildFailedEmail({
|
|
||||||
projectName,
|
|
||||||
applicationName,
|
|
||||||
applicationType,
|
|
||||||
errorMessage,
|
|
||||||
buildLink,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
await transporter.sendMail(mailOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// export const
|
|
||||||
|
|
||||||
export const sendBuildErrorNotifications = async ({
|
export const sendBuildErrorNotifications = async ({
|
||||||
projectName,
|
projectName,
|
||||||
@@ -577,13 +525,7 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
applicationType,
|
applicationType,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
buildLink,
|
buildLink,
|
||||||
}: {
|
}: BuildFailedEmailProps) => {
|
||||||
projectName: string;
|
|
||||||
applicationName: string;
|
|
||||||
applicationType: string;
|
|
||||||
errorMessage: string;
|
|
||||||
buildLink: string;
|
|
||||||
}) => {
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const notificationList = await db.query.notifications.findMany({
|
const notificationList = await db.query.notifications.findMany({
|
||||||
where: eq(notifications.appBuildError, true),
|
where: eq(notifications.appBuildError, true),
|
||||||
@@ -626,6 +568,7 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
applicationType,
|
applicationType,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
buildLink,
|
buildLink,
|
||||||
|
date: date.toLocaleString(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -636,7 +579,7 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
const { webhookUrl } = discord;
|
const { webhookUrl } = discord;
|
||||||
const embed = {
|
const embed = {
|
||||||
title: "⚠️ Build Failed",
|
title: "⚠️ Build Failed",
|
||||||
color: 0xff0000, // Rojo
|
color: 0xff0000,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: "Project",
|
name: "Project",
|
||||||
@@ -775,3 +718,791 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface BuildSuccessEmailProps {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
applicationType: string;
|
||||||
|
buildLink: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendBuildSuccessNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
buildLink,
|
||||||
|
}: BuildSuccessEmailProps) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.appDeploy, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const {
|
||||||
|
smtpServer,
|
||||||
|
smtpPort,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
fromAddress,
|
||||||
|
toAddresses,
|
||||||
|
} = email;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: {
|
||||||
|
user: username,
|
||||||
|
pass: password,
|
||||||
|
},
|
||||||
|
} as SMTPTransport.Options);
|
||||||
|
const mailOptions = {
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses?.join(", "),
|
||||||
|
subject: "Build success for dokploy",
|
||||||
|
html: render(
|
||||||
|
BuildSuccessEmail({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
applicationType,
|
||||||
|
buildLink,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
const { webhookUrl } = discord;
|
||||||
|
const embed = {
|
||||||
|
title: "✅ Build Success",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Project",
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Build Link",
|
||||||
|
value: buildLink,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Build Notification",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
embeds: [embed],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const { botToken, chatId } = telegram;
|
||||||
|
const messageText = `
|
||||||
|
<b>✅ Build Success</b>
|
||||||
|
|
||||||
|
<b>Project:</b> ${projectName}
|
||||||
|
<b>Application:</b> ${applicationName}
|
||||||
|
<b>Type:</b> ${applicationType}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
|
||||||
|
<b>Build Details:</b> ${buildLink}
|
||||||
|
`;
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { webhookUrl, channel } = slack;
|
||||||
|
const message = {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#00FF00",
|
||||||
|
pretext: ":white_check_mark: *Build Success*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
value: projectName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: applicationType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Build Link",
|
||||||
|
value: buildLink,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDatabaseBackupNotifications = async ({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
databaseType,
|
||||||
|
type,
|
||||||
|
errorMessage,
|
||||||
|
}: {
|
||||||
|
projectName: string;
|
||||||
|
applicationName: string;
|
||||||
|
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
|
||||||
|
type: "error" | "success";
|
||||||
|
errorMessage?: string;
|
||||||
|
}) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.databaseBackup, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const {
|
||||||
|
smtpServer,
|
||||||
|
smtpPort,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
fromAddress,
|
||||||
|
toAddresses,
|
||||||
|
} = email;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: {
|
||||||
|
user: username,
|
||||||
|
pass: password,
|
||||||
|
},
|
||||||
|
} as SMTPTransport.Options);
|
||||||
|
const mailOptions = {
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses?.join(", "),
|
||||||
|
subject: "Database backup for dokploy",
|
||||||
|
html: render(
|
||||||
|
DatabaseBackupEmail({
|
||||||
|
projectName,
|
||||||
|
applicationName,
|
||||||
|
databaseType,
|
||||||
|
type,
|
||||||
|
errorMessage,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
const { webhookUrl } = discord;
|
||||||
|
const embed = {
|
||||||
|
title:
|
||||||
|
type === "success"
|
||||||
|
? "✅ Database Backup Successful"
|
||||||
|
: "❌ Database Backup Failed",
|
||||||
|
color: type === "success" ? 0x00ff00 : 0xff0000,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Project",
|
||||||
|
value: projectName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: databaseType,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type",
|
||||||
|
value: type,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Database Backup Notification",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "error" && errorMessage) {
|
||||||
|
embed.fields.push({
|
||||||
|
name: "Error Message",
|
||||||
|
value: errorMessage as unknown as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
embeds: [embed],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const { botToken, chatId } = telegram;
|
||||||
|
const statusEmoji = type === "success" ? "✅" : "❌";
|
||||||
|
const messageText = `
|
||||||
|
<b>${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"}</b>
|
||||||
|
|
||||||
|
<b>Project:</b> ${projectName}
|
||||||
|
<b>Application:</b> ${applicationName}
|
||||||
|
<b>Type:</b> ${databaseType}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
|
||||||
|
<b>Status:</b> ${type === "success" ? "Successful" : "Failed"}
|
||||||
|
${type === "error" && errorMessage ? `<b>Error:</b> ${errorMessage}` : ""}
|
||||||
|
`;
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { webhookUrl, channel } = slack;
|
||||||
|
const message = {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: type === "success" ? "#00FF00" : "#FF0000",
|
||||||
|
pretext:
|
||||||
|
type === "success"
|
||||||
|
? ":white_check_mark: *Database Backup Successful*"
|
||||||
|
: ":x: *Database Backup Failed*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Project",
|
||||||
|
value: projectName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Application",
|
||||||
|
value: applicationName,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: databaseType,
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Type",
|
||||||
|
value: type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
value: type === "success" ? "Successful" : "Failed",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === "error" && errorMessage) {
|
||||||
|
message.attachments[0].fields.push({
|
||||||
|
title: "Error Message",
|
||||||
|
value: errorMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDockerCleanupNotifications = async (
|
||||||
|
message = "Docker cleanup for dokploy",
|
||||||
|
) => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.dockerCleanup, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const {
|
||||||
|
smtpServer,
|
||||||
|
smtpPort,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
fromAddress,
|
||||||
|
toAddresses,
|
||||||
|
} = email;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: {
|
||||||
|
user: username,
|
||||||
|
pass: password,
|
||||||
|
},
|
||||||
|
} as SMTPTransport.Options);
|
||||||
|
const mailOptions = {
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses?.join(", "),
|
||||||
|
subject: "Docker cleanup for dokploy",
|
||||||
|
html: render(
|
||||||
|
DockerCleanupEmail({
|
||||||
|
message,
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
const { webhookUrl } = discord;
|
||||||
|
const embed = {
|
||||||
|
title: "✅ Docker Cleanup",
|
||||||
|
color: 0x00ff00,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Message",
|
||||||
|
value: message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Docker Cleanup Notification",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
embeds: [embed],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const { botToken, chatId } = telegram;
|
||||||
|
const messageText = `
|
||||||
|
<b>✅ Docker Cleanup</b>
|
||||||
|
<b>Message:</b> ${message}
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
`;
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { webhookUrl, channel } = slack;
|
||||||
|
const messageResponse = {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#00FF00",
|
||||||
|
pretext: ":white_check_mark: *Docker Cleanup*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Message",
|
||||||
|
value: message,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(messageResponse),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendEmailNotification = async (
|
||||||
|
connection: typeof email.$inferSelect,
|
||||||
|
subject: string,
|
||||||
|
htmlContent: string,
|
||||||
|
) => {
|
||||||
|
const { smtpServer, smtpPort, username, password, fromAddress, toAddresses } =
|
||||||
|
connection;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: { user: username, pass: password },
|
||||||
|
});
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses.join(", "),
|
||||||
|
subject,
|
||||||
|
html: htmlContent,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDiscordNotification = async (
|
||||||
|
connection: typeof discord.$inferSelect,
|
||||||
|
embed: any,
|
||||||
|
) => {
|
||||||
|
const response = await fetch(connection.webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ embeds: [embed] }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Discord notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendTelegramNotification = async (
|
||||||
|
connection: typeof telegram.$inferSelect,
|
||||||
|
messageText: string,
|
||||||
|
) => {
|
||||||
|
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: connection.chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Telegram notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendSlackNotification = async (
|
||||||
|
connection: typeof slack.$inferSelect,
|
||||||
|
message: any,
|
||||||
|
) => {
|
||||||
|
const response = await fetch(connection.webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Failed to send Slack notification");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendDokployRestartNotifications = async () => {
|
||||||
|
const date = new Date();
|
||||||
|
const notificationList = await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.dokployRestart, true),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
discord: true,
|
||||||
|
telegram: true,
|
||||||
|
slack: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const notification of notificationList) {
|
||||||
|
const { email, discord, telegram, slack } = notification;
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
const {
|
||||||
|
smtpServer,
|
||||||
|
smtpPort,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
fromAddress,
|
||||||
|
toAddresses,
|
||||||
|
} = email;
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: smtpServer,
|
||||||
|
port: smtpPort,
|
||||||
|
secure: smtpPort === 465,
|
||||||
|
auth: {
|
||||||
|
user: username,
|
||||||
|
pass: password,
|
||||||
|
},
|
||||||
|
} as SMTPTransport.Options);
|
||||||
|
const mailOptions = {
|
||||||
|
from: fromAddress,
|
||||||
|
to: toAddresses?.join(", "),
|
||||||
|
subject: "Dokploy Server Restarted",
|
||||||
|
html: render(
|
||||||
|
DokployRestartEmail({
|
||||||
|
date: date.toLocaleString(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discord) {
|
||||||
|
const { webhookUrl } = discord;
|
||||||
|
const embed = {
|
||||||
|
title: "✅ Dokploy Server Restarted",
|
||||||
|
color: 0xff0000,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
timestamp: date.toISOString(),
|
||||||
|
footer: {
|
||||||
|
text: "Dokploy Restart Notification",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
embeds: [embed],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telegram) {
|
||||||
|
const { botToken, chatId } = telegram;
|
||||||
|
const messageText = `
|
||||||
|
<b>✅ Dokploy Serverd Restarted</b>
|
||||||
|
<b>Time:</b> ${date.toLocaleString()}
|
||||||
|
`;
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chat_id: chatId,
|
||||||
|
text: messageText,
|
||||||
|
parse_mode: "HTML",
|
||||||
|
disable_web_page_preview: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slack) {
|
||||||
|
const { webhookUrl, channel } = slack;
|
||||||
|
const message = {
|
||||||
|
channel: channel,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
color: "#FF0000",
|
||||||
|
pretext: ":white_check_mark: *Dokploy Server Restarted*",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
title: "Time",
|
||||||
|
value: date.toLocaleString(),
|
||||||
|
short: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
text: "View Build Details",
|
||||||
|
url: "https://doks.dev/build-details",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(message),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Error to send test notification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { nanoid } from "nanoid";
|
|
||||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
|
||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm";
|
||||||
import { z } from "zod";
|
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const notificationType = pgEnum("notificationType", [
|
export const notificationType = pgEnum("notificationType", [
|
||||||
"slack",
|
"slack",
|
||||||
@@ -18,10 +18,10 @@ export const notifications = pgTable("notification", {
|
|||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
appDeploy: boolean("appDeploy").notNull().default(false),
|
appDeploy: boolean("appDeploy").notNull().default(false),
|
||||||
userJoin: boolean("userJoin").notNull().default(false),
|
|
||||||
appBuildError: boolean("appBuildError").notNull().default(false),
|
appBuildError: boolean("appBuildError").notNull().default(false),
|
||||||
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
databaseBackup: boolean("databaseBackup").notNull().default(false),
|
||||||
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
dokployRestart: boolean("dokployRestart").notNull().default(false),
|
||||||
|
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
||||||
notificationType: notificationType("notificationType").notNull(),
|
notificationType: notificationType("notificationType").notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -107,7 +107,7 @@ export const apiCreateSlack = notificationsSchema
|
|||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
userJoin: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
webhookUrl: z.string().min(1),
|
webhookUrl: z.string().min(1),
|
||||||
@@ -132,7 +132,7 @@ export const apiCreateTelegram = notificationsSchema
|
|||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
userJoin: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
botToken: z.string().min(1),
|
botToken: z.string().min(1),
|
||||||
@@ -157,7 +157,7 @@ export const apiCreateDiscord = notificationsSchema
|
|||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
userJoin: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
webhookUrl: z.string().min(1),
|
webhookUrl: z.string().min(1),
|
||||||
@@ -180,7 +180,7 @@ export const apiCreateEmail = notificationsSchema
|
|||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
userJoin: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
smtpServer: z.string().min(1),
|
smtpServer: z.string().min(1),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import http from "node:http";
|
|||||||
import { migration } from "@/server/db/migration";
|
import { migration } from "@/server/db/migration";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
import next from "next";
|
import next from "next";
|
||||||
|
import { sendDokployRestartNotifications } from "./api/services/notification";
|
||||||
import { deploymentWorker } from "./queues/deployments-queue";
|
import { deploymentWorker } from "./queues/deployments-queue";
|
||||||
import { setupDirectories } from "./setup/config-paths";
|
import { setupDirectories } from "./setup/config-paths";
|
||||||
import { initializePostgres } from "./setup/postgres-setup";
|
import { initializePostgres } from "./setup/postgres-setup";
|
||||||
@@ -57,6 +58,8 @@ void app.prepare().then(async () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 7000));
|
await new Promise((resolve) => setTimeout(resolve, 7000));
|
||||||
await migration();
|
await migration();
|
||||||
}
|
}
|
||||||
|
await sendDokployRestartNotifications();
|
||||||
|
|
||||||
server.listen(PORT);
|
server.listen(PORT);
|
||||||
console.log("Server Started:", PORT);
|
console.log("Server Started:", PORT);
|
||||||
deploymentWorker.run();
|
deploymentWorker.run();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { Mariadb } from "@/server/api/services/mariadb";
|
import type { Mariadb } from "@/server/api/services/mariadb";
|
||||||
|
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
||||||
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
@@ -10,7 +12,8 @@ export const runMariadbBackup = async (
|
|||||||
mariadb: Mariadb,
|
mariadb: Mariadb,
|
||||||
backup: BackupSchedule,
|
backup: BackupSchedule,
|
||||||
) => {
|
) => {
|
||||||
const { appName, databasePassword, databaseUser } = mariadb;
|
const { appName, databasePassword, databaseUser, projectId, name } = mariadb;
|
||||||
|
const project = await findProjectById(projectId);
|
||||||
const { prefix, database } = backup;
|
const { prefix, database } = backup;
|
||||||
const destination = backup.destination;
|
const destination = backup.destination;
|
||||||
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
||||||
@@ -31,8 +34,22 @@ export const runMariadbBackup = async (
|
|||||||
`docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
|
`docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
|
||||||
);
|
);
|
||||||
await uploadToS3(destination, bucketDestination, hostPath);
|
await uploadToS3(destination, bucketDestination, hostPath);
|
||||||
|
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mariadb",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mariadb",
|
||||||
|
type: "error",
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await unlink(hostPath);
|
await unlink(hostPath);
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { Mongo } from "@/server/api/services/mongo";
|
import type { Mongo } from "@/server/api/services/mongo";
|
||||||
|
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
||||||
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
// mongodb://mongo:Bqh7AQl-PRbnBu@localhost:27017/?tls=false&directConnection=true
|
// mongodb://mongo:Bqh7AQl-PRbnBu@localhost:27017/?tls=false&directConnection=true
|
||||||
export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
||||||
const { appName, databasePassword, databaseUser } = mongo;
|
const { appName, databasePassword, databaseUser, projectId, name } = mongo;
|
||||||
|
const project = await findProjectById(projectId);
|
||||||
const { prefix, database } = backup;
|
const { prefix, database } = backup;
|
||||||
const destination = backup.destination;
|
const destination = backup.destination;
|
||||||
const backupFileName = `${new Date().toISOString()}.dump.gz`;
|
const backupFileName = `${new Date().toISOString()}.dump.gz`;
|
||||||
@@ -27,8 +30,22 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
|||||||
);
|
);
|
||||||
await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
|
await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
|
||||||
await uploadToS3(destination, bucketDestination, hostPath);
|
await uploadToS3(destination, bucketDestination, hostPath);
|
||||||
|
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mongodb",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mongodb",
|
||||||
|
type: "error",
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await unlink(hostPath);
|
await unlink(hostPath);
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import { unlink } from "node:fs/promises";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
import type { MySql } from "@/server/api/services/mysql";
|
import type { MySql } from "@/server/api/services/mysql";
|
||||||
|
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
||||||
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
|
|
||||||
export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
|
export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
|
||||||
const { appName, databaseRootPassword } = mysql;
|
const { appName, databaseRootPassword, projectId, name } = mysql;
|
||||||
|
const project = await findProjectById(projectId);
|
||||||
const { prefix, database } = backup;
|
const { prefix, database } = backup;
|
||||||
const destination = backup.destination;
|
const destination = backup.destination;
|
||||||
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
||||||
@@ -29,8 +32,21 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
|
|||||||
`docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
|
`docker cp ${containerId}:/backup/${backupFileName} ${hostPath}`,
|
||||||
);
|
);
|
||||||
await uploadToS3(destination, bucketDestination, hostPath);
|
await uploadToS3(destination, bucketDestination, hostPath);
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mysql",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "mysql",
|
||||||
|
type: "error",
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await unlink(hostPath);
|
await unlink(hostPath);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { unlink } from "node:fs/promises";
|
import { unlink } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "@/server/api/services/backup";
|
import type { BackupSchedule } from "@/server/api/services/backup";
|
||||||
|
import { sendDatabaseBackupNotifications } from "@/server/api/services/notification";
|
||||||
import type { Postgres } from "@/server/api/services/postgres";
|
import type { Postgres } from "@/server/api/services/postgres";
|
||||||
|
import { findProjectById } from "@/server/api/services/project";
|
||||||
import { getServiceContainer } from "../docker/utils";
|
import { getServiceContainer } from "../docker/utils";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { uploadToS3 } from "./utils";
|
import { uploadToS3 } from "./utils";
|
||||||
@@ -10,7 +12,9 @@ export const runPostgresBackup = async (
|
|||||||
postgres: Postgres,
|
postgres: Postgres,
|
||||||
backup: BackupSchedule,
|
backup: BackupSchedule,
|
||||||
) => {
|
) => {
|
||||||
const { appName, databaseUser } = postgres;
|
const { appName, databaseUser, name, projectId } = postgres;
|
||||||
|
const project = await findProjectById(projectId);
|
||||||
|
|
||||||
const { prefix, database } = backup;
|
const { prefix, database } = backup;
|
||||||
const destination = backup.destination;
|
const destination = backup.destination;
|
||||||
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
const backupFileName = `${new Date().toISOString()}.sql.gz`;
|
||||||
@@ -29,8 +33,21 @@ export const runPostgresBackup = async (
|
|||||||
await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
|
await execAsync(`docker cp ${containerId}:${containerPath} ${hostPath}`);
|
||||||
|
|
||||||
await uploadToS3(destination, bucketDestination, hostPath);
|
await uploadToS3(destination, bucketDestination, hostPath);
|
||||||
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "postgres",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
await sendDatabaseBackupNotifications({
|
||||||
|
applicationName: name,
|
||||||
|
projectName: project.name,
|
||||||
|
databaseType: "postgres",
|
||||||
|
type: "error",
|
||||||
|
errorMessage: error?.message || "Error message not provided",
|
||||||
|
});
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await unlink(hostPath);
|
await unlink(hostPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user