mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-02 02:02:18 +00:00
Merge branch 'feature/add-custom-webhook-notification-provider' of github.com:ChristoferMendes/dokploy into feature/add-custom-webhook-notification-provider
This commit is contained in:
@@ -6,157 +6,160 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
interface KeyValuePair {
|
interface KeyValuePair {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeyValueInputProps {
|
interface KeyValueInputProps {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEmptyPair = (): KeyValuePair => ({
|
const createEmptyPair = (): KeyValuePair => ({
|
||||||
key: "",
|
key: "",
|
||||||
value: "",
|
value: "",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const parseJsonToPairs = (jsonString: string): KeyValuePair[] => {
|
const parseJsonToPairs = (jsonString: string): KeyValuePair[] => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonString);
|
const parsed = JSON.parse(jsonString);
|
||||||
const pairs = Object.entries(parsed).map(([key, val]) => ({
|
const pairs = Object.entries(parsed).map(([key, val]) => ({
|
||||||
key,
|
key,
|
||||||
value: String(val),
|
value: String(val),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
}));
|
}));
|
||||||
return pairs.length > 0 ? pairs : [createEmptyPair()];
|
return pairs.length > 0 ? pairs : [createEmptyPair()];
|
||||||
} catch {
|
} catch {
|
||||||
return [createEmptyPair()];
|
return [createEmptyPair()];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const convertPairsToJson = (pairs: KeyValuePair[]): string => {
|
const convertPairsToJson = (pairs: KeyValuePair[]): string => {
|
||||||
const enabledPairs = pairs.filter((pair) => pair.enabled && pair.key.trim());
|
const enabledPairs = pairs.filter((pair) => pair.enabled && pair.key.trim());
|
||||||
|
|
||||||
if (enabledPairs.length === 0) {
|
if (enabledPairs.length === 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = enabledPairs.reduce((acc, pair) => {
|
const obj = enabledPairs.reduce(
|
||||||
acc[pair.key.trim()] = pair.value;
|
(acc, pair) => {
|
||||||
return acc;
|
acc[pair.key.trim()] = pair.value;
|
||||||
}, {} as Record<string, string>);
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
|
||||||
return JSON.stringify(obj, null, 2);
|
return JSON.stringify(obj, null, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const KeyValueInput = ({
|
export const KeyValueInput = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
label,
|
label,
|
||||||
description,
|
description,
|
||||||
}: KeyValueInputProps) => {
|
}: KeyValueInputProps) => {
|
||||||
const [pairs, setPairs] = useState<KeyValuePair[]>([]);
|
const [pairs, setPairs] = useState<KeyValuePair[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newPairs = value ? parseJsonToPairs(value) : [createEmptyPair()];
|
const newPairs = value ? parseJsonToPairs(value) : [createEmptyPair()];
|
||||||
setPairs(newPairs);
|
setPairs(newPairs);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const syncPairsWithParent = (newPairs: KeyValuePair[]) => {
|
const syncPairsWithParent = (newPairs: KeyValuePair[]) => {
|
||||||
setPairs(newPairs);
|
setPairs(newPairs);
|
||||||
onChange(convertPairsToJson(newPairs));
|
onChange(convertPairsToJson(newPairs));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addPair = () => {
|
const addPair = () => {
|
||||||
syncPairsWithParent([...pairs, createEmptyPair()]);
|
syncPairsWithParent([...pairs, createEmptyPair()]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removePair = (index: number) => {
|
const removePair = (index: number) => {
|
||||||
const filteredPairs = pairs.filter((_, i) => i !== index);
|
const filteredPairs = pairs.filter((_, i) => i !== index);
|
||||||
syncPairsWithParent(filteredPairs);
|
syncPairsWithParent(filteredPairs);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePair = (
|
const updatePair = (
|
||||||
index: number,
|
index: number,
|
||||||
field: keyof KeyValuePair,
|
field: keyof KeyValuePair,
|
||||||
newValue: string | boolean
|
newValue: string | boolean,
|
||||||
) => {
|
) => {
|
||||||
const updatedPairs = pairs.map((pair, i) =>
|
const updatedPairs = pairs.map((pair, i) =>
|
||||||
i === index ? { ...pair, [field]: newValue } : pair
|
i === index ? { ...pair, [field]: newValue } : pair,
|
||||||
);
|
);
|
||||||
syncPairsWithParent(updatedPairs);
|
syncPairsWithParent(updatedPairs);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-sm font-medium">{label}</Label>
|
<Label className="text-sm font-medium">{label}</Label>
|
||||||
{description && (
|
{description && (
|
||||||
<p className="text-sm text-muted-foreground mt-1">{description}</p>
|
<p className="text-sm text-muted-foreground mt-1">{description}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{pairs.map((pair, index) => (
|
{pairs.map((pair, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center gap-2 p-2 border rounded-md bg-muted/50"
|
className="flex items-center gap-2 p-2 border rounded-md bg-muted/50"
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={pair.enabled}
|
checked={pair.enabled}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
updatePair(index, "enabled", checked)
|
updatePair(index, "enabled", checked)
|
||||||
}
|
}
|
||||||
className="mr-2"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
value={pair.key}
|
value={pair.key}
|
||||||
onChange={(e) => updatePair(index, "key", e.target.value)}
|
onChange={(e) => updatePair(index, "key", e.target.value)}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
disabled={!pair.enabled}
|
disabled={!pair.enabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-[2]">
|
<div className="flex-[2]">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
value={pair.value}
|
value={pair.value}
|
||||||
onChange={(e) => updatePair(index, "value", e.target.value)}
|
onChange={(e) => updatePair(index, "value", e.target.value)}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
disabled={!pair.enabled}
|
disabled={!pair.enabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => removePair(index)}
|
onClick={() => removePair(index)}
|
||||||
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={addPair}
|
onClick={addPair}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Add {label.toLowerCase()}
|
Add {label.toLowerCase()}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,164 +1,164 @@
|
|||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
Loader2,
|
Loader2,
|
||||||
Mail,
|
Mail,
|
||||||
MessageCircleMore,
|
MessageCircleMore,
|
||||||
PenBoxIcon,
|
PenBoxIcon,
|
||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
SlackIcon,
|
SlackIcon,
|
||||||
TelegramIcon,
|
TelegramIcon,
|
||||||
} from "@/components/icons/notification-icons";
|
} from "@/components/icons/notification-icons";
|
||||||
import { DialogAction } from "@/components/shared/dialog-action";
|
import { DialogAction } from "@/components/shared/dialog-action";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { HandleNotifications } from "./handle-notifications";
|
import { HandleNotifications } from "./handle-notifications";
|
||||||
|
|
||||||
export const ShowNotifications = () => {
|
export const ShowNotifications = () => {
|
||||||
const { data, isLoading, refetch } = api.notification.all.useQuery();
|
const { data, isLoading, refetch } = api.notification.all.useQuery();
|
||||||
const { mutateAsync, isLoading: isRemoving } =
|
const { mutateAsync, isLoading: isRemoving } =
|
||||||
api.notification.remove.useMutation();
|
api.notification.remove.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
<Card className="h-full bg-sidebar p-2.5 rounded-xl max-w-5xl mx-auto">
|
||||||
<div className="rounded-xl bg-background shadow-md ">
|
<div className="rounded-xl bg-background shadow-md ">
|
||||||
<CardHeader className="">
|
<CardHeader className="">
|
||||||
<CardTitle className="text-xl flex flex-row gap-2">
|
<CardTitle className="text-xl flex flex-row gap-2">
|
||||||
<Bell className="size-6 text-muted-foreground self-center" />
|
<Bell className="size-6 text-muted-foreground self-center" />
|
||||||
Notifications
|
Notifications
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Add your providers to receive notifications, like Discord, Slack,
|
Add your providers to receive notifications, like Discord, Slack,
|
||||||
Telegram, Email.
|
Telegram, Email.
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 py-8 border-t">
|
<CardContent className="space-y-2 py-8 border-t">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[25vh]">
|
<div className="flex flex-row gap-2 items-center justify-center text-sm text-muted-foreground min-h-[25vh]">
|
||||||
<span>Loading...</span>
|
<span>Loading...</span>
|
||||||
<Loader2 className="animate-spin size-4" />
|
<Loader2 className="animate-spin size-4" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{data?.length === 0 ? (
|
{data?.length === 0 ? (
|
||||||
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
|
<div className="flex flex-col items-center gap-3 min-h-[25vh] justify-center">
|
||||||
<Bell />
|
<Bell />
|
||||||
<span className="text-base text-muted-foreground text-center">
|
<span className="text-base text-muted-foreground text-center">
|
||||||
To send notifications it is required to set at least 1
|
To send notifications it is required to set at least 1
|
||||||
provider.
|
provider.
|
||||||
</span>
|
</span>
|
||||||
<HandleNotifications />
|
<HandleNotifications />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-4 min-h-[25vh]">
|
<div className="flex flex-col gap-4 min-h-[25vh]">
|
||||||
<div className="flex flex-col gap-4 rounded-lg ">
|
<div className="flex flex-col gap-4 rounded-lg ">
|
||||||
{data?.map((notification, _index) => (
|
{data?.map((notification, _index) => (
|
||||||
<div
|
<div
|
||||||
key={notification.notificationId}
|
key={notification.notificationId}
|
||||||
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
className="flex items-center justify-between bg-sidebar p-1 w-full rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
<div className="flex items-center justify-between p-3.5 rounded-lg bg-background border w-full">
|
||||||
<span className="text-sm flex flex-row items-center gap-4">
|
<span className="text-sm flex flex-row items-center gap-4">
|
||||||
{notification.notificationType === "slack" && (
|
{notification.notificationType === "slack" && (
|
||||||
<div className="flex items-center justify-center rounded-lg">
|
<div className="flex items-center justify-center rounded-lg">
|
||||||
<SlackIcon className="size-6" />
|
<SlackIcon className="size-6" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "telegram" && (
|
{notification.notificationType === "telegram" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<TelegramIcon className="size-7 " />
|
<TelegramIcon className="size-7 " />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "discord" && (
|
{notification.notificationType === "discord" && (
|
||||||
<div className="flex items-center justify-center rounded-lg">
|
<div className="flex items-center justify-center rounded-lg">
|
||||||
<DiscordIcon className="size-7 " />
|
<DiscordIcon className="size-7 " />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "email" && (
|
{notification.notificationType === "email" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<Mail className="size-6 text-muted-foreground" />
|
<Mail className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "gotify" && (
|
{notification.notificationType === "gotify" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "ntfy" && (
|
{notification.notificationType === "ntfy" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<MessageCircleMore className="size-6 text-muted-foreground" />
|
<MessageCircleMore className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{notification.notificationType === "custom" && (
|
{notification.notificationType === "custom" && (
|
||||||
<div className="flex items-center justify-center rounded-lg ">
|
<div className="flex items-center justify-center rounded-lg ">
|
||||||
<PenBoxIcon className="size-6 text-muted-foreground" />
|
<PenBoxIcon className="size-6 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{notification.name}
|
{notification.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex flex-row gap-1">
|
<div className="flex flex-row gap-1">
|
||||||
<HandleNotifications
|
<HandleNotifications
|
||||||
notificationId={notification.notificationId}
|
notificationId={notification.notificationId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DialogAction
|
<DialogAction
|
||||||
title="Delete Notification"
|
title="Delete Notification"
|
||||||
description="Are you sure you want to delete this notification?"
|
description="Are you sure you want to delete this notification?"
|
||||||
type="destructive"
|
type="destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
notificationId: notification.notificationId,
|
notificationId: notification.notificationId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success(
|
toast.success(
|
||||||
"Notification deleted successfully"
|
"Notification deleted successfully",
|
||||||
);
|
);
|
||||||
refetch();
|
refetch();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error(
|
toast.error(
|
||||||
"Error deleting notification"
|
"Error deleting notification",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="group hover:bg-red-500/10 "
|
className="group hover:bg-red-500/10 "
|
||||||
isLoading={isRemoving}
|
isLoading={isRemoving}
|
||||||
>
|
>
|
||||||
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
<Trash2 className="size-4 text-primary group-hover:text-red-500" />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogAction>
|
</DialogAction>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
|
<div className="flex flex-row gap-2 flex-wrap w-full justify-end mr-4">
|
||||||
<HandleNotifications />
|
<HandleNotifications />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,400 +6,400 @@ import { z } from "zod";
|
|||||||
import { organization } from "./account";
|
import { organization } from "./account";
|
||||||
|
|
||||||
export const notificationType = pgEnum("notificationType", [
|
export const notificationType = pgEnum("notificationType", [
|
||||||
"slack",
|
"slack",
|
||||||
"telegram",
|
"telegram",
|
||||||
"discord",
|
"discord",
|
||||||
"email",
|
"email",
|
||||||
"gotify",
|
"gotify",
|
||||||
"ntfy",
|
"ntfy",
|
||||||
"custom",
|
"custom",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const notifications = pgTable("notification", {
|
export const notifications = pgTable("notification", {
|
||||||
notificationId: text("notificationId")
|
notificationId: text("notificationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
appDeploy: boolean("appDeploy").notNull().default(false),
|
appDeploy: boolean("appDeploy").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),
|
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
|
||||||
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
serverThreshold: boolean("serverThreshold").notNull().default(false),
|
||||||
notificationType: notificationType("notificationType").notNull(),
|
notificationType: notificationType("notificationType").notNull(),
|
||||||
createdAt: text("createdAt")
|
createdAt: text("createdAt")
|
||||||
.notNull()
|
.notNull()
|
||||||
.$defaultFn(() => new Date().toISOString()),
|
.$defaultFn(() => new Date().toISOString()),
|
||||||
slackId: text("slackId").references(() => slack.slackId, {
|
slackId: text("slackId").references(() => slack.slackId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
telegramId: text("telegramId").references(() => telegram.telegramId, {
|
telegramId: text("telegramId").references(() => telegram.telegramId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
discordId: text("discordId").references(() => discord.discordId, {
|
discordId: text("discordId").references(() => discord.discordId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
emailId: text("emailId").references(() => email.emailId, {
|
emailId: text("emailId").references(() => email.emailId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
customId: text("customId").references(() => custom.customId, {
|
customId: text("customId").references(() => custom.customId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
organizationId: text("organizationId")
|
organizationId: text("organizationId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => organization.id, { onDelete: "cascade" }),
|
.references(() => organization.id, { onDelete: "cascade" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const slack = pgTable("slack", {
|
export const slack = pgTable("slack", {
|
||||||
slackId: text("slackId")
|
slackId: text("slackId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
webhookUrl: text("webhookUrl").notNull(),
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
channel: text("channel"),
|
channel: text("channel"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const telegram = pgTable("telegram", {
|
export const telegram = pgTable("telegram", {
|
||||||
telegramId: text("telegramId")
|
telegramId: text("telegramId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
botToken: text("botToken").notNull(),
|
botToken: text("botToken").notNull(),
|
||||||
chatId: text("chatId").notNull(),
|
chatId: text("chatId").notNull(),
|
||||||
messageThreadId: text("messageThreadId"),
|
messageThreadId: text("messageThreadId"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const discord = pgTable("discord", {
|
export const discord = pgTable("discord", {
|
||||||
discordId: text("discordId")
|
discordId: text("discordId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
webhookUrl: text("webhookUrl").notNull(),
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
decoration: boolean("decoration"),
|
decoration: boolean("decoration"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const email = pgTable("email", {
|
export const email = pgTable("email", {
|
||||||
emailId: text("emailId")
|
emailId: text("emailId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
smtpServer: text("smtpServer").notNull(),
|
smtpServer: text("smtpServer").notNull(),
|
||||||
smtpPort: integer("smtpPort").notNull(),
|
smtpPort: integer("smtpPort").notNull(),
|
||||||
username: text("username").notNull(),
|
username: text("username").notNull(),
|
||||||
password: text("password").notNull(),
|
password: text("password").notNull(),
|
||||||
fromAddress: text("fromAddress").notNull(),
|
fromAddress: text("fromAddress").notNull(),
|
||||||
toAddresses: text("toAddress").array().notNull(),
|
toAddresses: text("toAddress").array().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gotify = pgTable("gotify", {
|
export const gotify = pgTable("gotify", {
|
||||||
gotifyId: text("gotifyId")
|
gotifyId: text("gotifyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
serverUrl: text("serverUrl").notNull(),
|
serverUrl: text("serverUrl").notNull(),
|
||||||
appToken: text("appToken").notNull(),
|
appToken: text("appToken").notNull(),
|
||||||
priority: integer("priority").notNull().default(5),
|
priority: integer("priority").notNull().default(5),
|
||||||
decoration: boolean("decoration"),
|
decoration: boolean("decoration"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ntfy = pgTable("ntfy", {
|
export const ntfy = pgTable("ntfy", {
|
||||||
ntfyId: text("ntfyId")
|
ntfyId: text("ntfyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
serverUrl: text("serverUrl").notNull(),
|
serverUrl: text("serverUrl").notNull(),
|
||||||
topic: text("topic").notNull(),
|
topic: text("topic").notNull(),
|
||||||
accessToken: text("accessToken").notNull(),
|
accessToken: text("accessToken").notNull(),
|
||||||
priority: integer("priority").notNull().default(3),
|
priority: integer("priority").notNull().default(3),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const custom = pgTable("custom", {
|
export const custom = pgTable("custom", {
|
||||||
customId: text("customId")
|
customId: text("customId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
endpoint: text("endpoint").notNull(),
|
endpoint: text("endpoint").notNull(),
|
||||||
headers: text("headers"), // JSON string
|
headers: text("headers"), // JSON string
|
||||||
});
|
});
|
||||||
|
|
||||||
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||||
slack: one(slack, {
|
slack: one(slack, {
|
||||||
fields: [notifications.slackId],
|
fields: [notifications.slackId],
|
||||||
references: [slack.slackId],
|
references: [slack.slackId],
|
||||||
}),
|
}),
|
||||||
telegram: one(telegram, {
|
telegram: one(telegram, {
|
||||||
fields: [notifications.telegramId],
|
fields: [notifications.telegramId],
|
||||||
references: [telegram.telegramId],
|
references: [telegram.telegramId],
|
||||||
}),
|
}),
|
||||||
discord: one(discord, {
|
discord: one(discord, {
|
||||||
fields: [notifications.discordId],
|
fields: [notifications.discordId],
|
||||||
references: [discord.discordId],
|
references: [discord.discordId],
|
||||||
}),
|
}),
|
||||||
email: one(email, {
|
email: one(email, {
|
||||||
fields: [notifications.emailId],
|
fields: [notifications.emailId],
|
||||||
references: [email.emailId],
|
references: [email.emailId],
|
||||||
}),
|
}),
|
||||||
gotify: one(gotify, {
|
gotify: one(gotify, {
|
||||||
fields: [notifications.gotifyId],
|
fields: [notifications.gotifyId],
|
||||||
references: [gotify.gotifyId],
|
references: [gotify.gotifyId],
|
||||||
}),
|
}),
|
||||||
ntfy: one(ntfy, {
|
ntfy: one(ntfy, {
|
||||||
fields: [notifications.ntfyId],
|
fields: [notifications.ntfyId],
|
||||||
references: [ntfy.ntfyId],
|
references: [ntfy.ntfyId],
|
||||||
}),
|
}),
|
||||||
custom: one(custom, {
|
custom: one(custom, {
|
||||||
fields: [notifications.customId],
|
fields: [notifications.customId],
|
||||||
references: [custom.customId],
|
references: [custom.customId],
|
||||||
}),
|
}),
|
||||||
organization: one(organization, {
|
organization: one(organization, {
|
||||||
fields: [notifications.organizationId],
|
fields: [notifications.organizationId],
|
||||||
references: [organization.id],
|
references: [organization.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const notificationsSchema = createInsertSchema(notifications);
|
export const notificationsSchema = createInsertSchema(notifications);
|
||||||
|
|
||||||
export const apiCreateSlack = notificationsSchema
|
export const apiCreateSlack = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
serverThreshold: true,
|
serverThreshold: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
webhookUrl: z.string().min(1),
|
webhookUrl: z.string().min(1),
|
||||||
channel: z.string(),
|
channel: z.string(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateSlack = apiCreateSlack.partial().extend({
|
export const apiUpdateSlack = apiCreateSlack.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
slackId: z.string(),
|
slackId: z.string(),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestSlackConnection = apiCreateSlack.pick({
|
export const apiTestSlackConnection = apiCreateSlack.pick({
|
||||||
webhookUrl: true,
|
webhookUrl: true,
|
||||||
channel: true,
|
channel: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateTelegram = notificationsSchema
|
export const apiCreateTelegram = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
serverThreshold: true,
|
serverThreshold: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
botToken: z.string().min(1),
|
botToken: z.string().min(1),
|
||||||
chatId: z.string().min(1),
|
chatId: z.string().min(1),
|
||||||
messageThreadId: z.string(),
|
messageThreadId: z.string(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
|
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
telegramId: z.string().min(1),
|
telegramId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestTelegramConnection = apiCreateTelegram.pick({
|
export const apiTestTelegramConnection = apiCreateTelegram.pick({
|
||||||
botToken: true,
|
botToken: true,
|
||||||
chatId: true,
|
chatId: true,
|
||||||
messageThreadId: true,
|
messageThreadId: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateDiscord = notificationsSchema
|
export const apiCreateDiscord = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
serverThreshold: true,
|
serverThreshold: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
webhookUrl: z.string().min(1),
|
webhookUrl: z.string().min(1),
|
||||||
decoration: z.boolean(),
|
decoration: z.boolean(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
|
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
discordId: z.string().min(1),
|
discordId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestDiscordConnection = apiCreateDiscord
|
export const apiTestDiscordConnection = apiCreateDiscord
|
||||||
.pick({
|
.pick({
|
||||||
webhookUrl: true,
|
webhookUrl: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
decoration: z.boolean().optional(),
|
decoration: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateEmail = notificationsSchema
|
export const apiCreateEmail = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
serverThreshold: true,
|
serverThreshold: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
smtpServer: z.string().min(1),
|
smtpServer: z.string().min(1),
|
||||||
smtpPort: z.number().min(1),
|
smtpPort: z.number().min(1),
|
||||||
username: z.string().min(1),
|
username: z.string().min(1),
|
||||||
password: z.string().min(1),
|
password: z.string().min(1),
|
||||||
fromAddress: z.string().min(1),
|
fromAddress: z.string().min(1),
|
||||||
toAddresses: z.array(z.string()).min(1),
|
toAddresses: z.array(z.string()).min(1),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateEmail = apiCreateEmail.partial().extend({
|
export const apiUpdateEmail = apiCreateEmail.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
emailId: z.string().min(1),
|
emailId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestEmailConnection = apiCreateEmail.pick({
|
export const apiTestEmailConnection = apiCreateEmail.pick({
|
||||||
smtpServer: true,
|
smtpServer: true,
|
||||||
smtpPort: true,
|
smtpPort: true,
|
||||||
username: true,
|
username: true,
|
||||||
password: true,
|
password: true,
|
||||||
toAddresses: true,
|
toAddresses: true,
|
||||||
fromAddress: true,
|
fromAddress: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateGotify = notificationsSchema
|
export const apiCreateGotify = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
serverUrl: z.string().min(1),
|
serverUrl: z.string().min(1),
|
||||||
appToken: z.string().min(1),
|
appToken: z.string().min(1),
|
||||||
priority: z.number().min(1),
|
priority: z.number().min(1),
|
||||||
decoration: z.boolean(),
|
decoration: z.boolean(),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateGotify = apiCreateGotify.partial().extend({
|
export const apiUpdateGotify = apiCreateGotify.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
gotifyId: z.string().min(1),
|
gotifyId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestGotifyConnection = apiCreateGotify
|
export const apiTestGotifyConnection = apiCreateGotify
|
||||||
.pick({
|
.pick({
|
||||||
serverUrl: true,
|
serverUrl: true,
|
||||||
appToken: true,
|
appToken: true,
|
||||||
priority: true,
|
priority: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
decoration: z.boolean().optional(),
|
decoration: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateNtfy = notificationsSchema
|
export const apiCreateNtfy = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
serverUrl: z.string().min(1),
|
serverUrl: z.string().min(1),
|
||||||
topic: z.string().min(1),
|
topic: z.string().min(1),
|
||||||
accessToken: z.string().min(1),
|
accessToken: z.string().min(1),
|
||||||
priority: z.number().min(1),
|
priority: z.number().min(1),
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiUpdateNtfy = apiCreateNtfy.partial().extend({
|
export const apiUpdateNtfy = apiCreateNtfy.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
ntfyId: z.string().min(1),
|
ntfyId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
||||||
serverUrl: true,
|
serverUrl: true,
|
||||||
topic: true,
|
topic: true,
|
||||||
accessToken: true,
|
accessToken: true,
|
||||||
priority: true,
|
priority: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiFindOneNotification = notificationsSchema
|
export const apiFindOneNotification = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
notificationId: true,
|
notificationId: true,
|
||||||
})
|
})
|
||||||
.required();
|
.required();
|
||||||
|
|
||||||
export const apiCreateCustom = notificationsSchema
|
export const apiCreateCustom = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
appBuildError: true,
|
appBuildError: true,
|
||||||
databaseBackup: true,
|
databaseBackup: true,
|
||||||
dokployRestart: true,
|
dokployRestart: true,
|
||||||
name: true,
|
name: true,
|
||||||
appDeploy: true,
|
appDeploy: true,
|
||||||
dockerCleanup: true,
|
dockerCleanup: true,
|
||||||
serverThreshold: true,
|
serverThreshold: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
endpoint: z.string().min(1),
|
endpoint: z.string().min(1),
|
||||||
headers: z.string().optional(),
|
headers: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiUpdateCustom = apiCreateCustom.partial().extend({
|
export const apiUpdateCustom = apiCreateCustom.partial().extend({
|
||||||
notificationId: z.string().min(1),
|
notificationId: z.string().min(1),
|
||||||
customId: z.string().min(1),
|
customId: z.string().min(1),
|
||||||
organizationId: z.string().optional(),
|
organizationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiTestCustomConnection = z.object({
|
export const apiTestCustomConnection = z.object({
|
||||||
endpoint: z.string().min(1),
|
endpoint: z.string().min(1),
|
||||||
headers: z.string().optional(),
|
headers: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiSendTest = notificationsSchema
|
export const apiSendTest = notificationsSchema
|
||||||
.extend({
|
.extend({
|
||||||
botToken: z.string(),
|
botToken: z.string(),
|
||||||
chatId: z.string(),
|
chatId: z.string(),
|
||||||
webhookUrl: z.string(),
|
webhookUrl: z.string(),
|
||||||
channel: z.string(),
|
channel: z.string(),
|
||||||
smtpServer: z.string(),
|
smtpServer: z.string(),
|
||||||
smtpPort: z.number(),
|
smtpPort: z.number(),
|
||||||
fromAddress: z.string(),
|
fromAddress: z.string(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
toAddresses: z.array(z.string()),
|
toAddresses: z.array(z.string()),
|
||||||
serverUrl: z.string(),
|
serverUrl: z.string(),
|
||||||
topic: z.string(),
|
topic: z.string(),
|
||||||
appToken: z.string(),
|
appToken: z.string(),
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
priority: z.number(),
|
priority: z.number(),
|
||||||
endpoint: z.string(),
|
endpoint: z.string(),
|
||||||
headers: z.string(),
|
headers: z.string(),
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,236 +5,236 @@ import { renderAsync } from "@react-email/components";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
sendCustomNotification,
|
sendCustomNotification,
|
||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
applicationType: string;
|
applicationType: string;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
buildLink: string;
|
buildLink: string;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendBuildErrorNotifications = async ({
|
export const sendBuildErrorNotifications = async ({
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
buildLink,
|
buildLink,
|
||||||
organizationId,
|
organizationId,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const unixDate = ~~(Number(date) / 1000);
|
const unixDate = ~~(Number(date) / 1000);
|
||||||
const notificationList = await db.query.notifications.findMany({
|
const notificationList = await db.query.notifications.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
eq(notifications.appBuildError, true),
|
eq(notifications.appBuildError, true),
|
||||||
eq(notifications.organizationId, organizationId)
|
eq(notifications.organizationId, organizationId),
|
||||||
),
|
),
|
||||||
with: {
|
with: {
|
||||||
email: true,
|
email: true,
|
||||||
discord: true,
|
discord: true,
|
||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
||||||
notification;
|
notification;
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
BuildFailedEmail({
|
BuildFailedEmail({
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorMessage,
|
||||||
buildLink,
|
buildLink,
|
||||||
date: date.toLocaleString(),
|
date: date.toLocaleString(),
|
||||||
})
|
}),
|
||||||
).catch();
|
).catch();
|
||||||
await sendEmailNotification(email, "Build failed for dokploy", template);
|
await sendEmailNotification(email, "Build failed for dokploy", template);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discord) {
|
if (discord) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
||||||
|
|
||||||
const limitCharacter = 800;
|
const limitCharacter = 800;
|
||||||
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
||||||
await sendDiscordNotification(discord, {
|
await sendDiscordNotification(discord, {
|
||||||
title: decorate(">", "`⚠️` Build Failed"),
|
title: decorate(">", "`⚠️` Build Failed"),
|
||||||
color: 0xed4245,
|
color: 0xed4245,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: decorate("`🛠️`", "Project"),
|
name: decorate("`🛠️`", "Project"),
|
||||||
value: projectName,
|
value: projectName,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`⚙️`", "Application"),
|
name: decorate("`⚙️`", "Application"),
|
||||||
value: applicationName,
|
value: applicationName,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`❔`", "Type"),
|
name: decorate("`❔`", "Type"),
|
||||||
value: applicationType,
|
value: applicationType,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`📅`", "Date"),
|
name: decorate("`📅`", "Date"),
|
||||||
value: `<t:${unixDate}:D>`,
|
value: `<t:${unixDate}:D>`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`⌚`", "Time"),
|
name: decorate("`⌚`", "Time"),
|
||||||
value: `<t:${unixDate}:t>`,
|
value: `<t:${unixDate}:t>`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`❓`", "Type"),
|
name: decorate("`❓`", "Type"),
|
||||||
value: "Failed",
|
value: "Failed",
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`⚠️`", "Error Message"),
|
name: decorate("`⚠️`", "Error Message"),
|
||||||
value: `\`\`\`${truncatedErrorMessage}\`\`\``,
|
value: `\`\`\`${truncatedErrorMessage}\`\`\``,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`🧷`", "Build Link"),
|
name: decorate("`🧷`", "Build Link"),
|
||||||
value: `[Click here to access build link](${buildLink})`,
|
value: `[Click here to access build link](${buildLink})`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
timestamp: date.toISOString(),
|
timestamp: date.toISOString(),
|
||||||
footer: {
|
footer: {
|
||||||
text: "Dokploy Build Notification",
|
text: "Dokploy Build Notification",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gotify) {
|
if (gotify) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||||
await sendGotifyNotification(
|
await sendGotifyNotification(
|
||||||
gotify,
|
gotify,
|
||||||
decorate("⚠️", "Build Failed"),
|
decorate("⚠️", "Build Failed"),
|
||||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||||
`${decorate("❔", `Type: ${applicationType}`)}` +
|
`${decorate("❔", `Type: ${applicationType}`)}` +
|
||||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||||
`${decorate("⚠️", `Error:\n${errorMessage}`)}` +
|
`${decorate("⚠️", `Error:\n${errorMessage}`)}` +
|
||||||
`${decorate("🔗", `Build details:\n${buildLink}`)}`
|
`${decorate("🔗", `Build details:\n${buildLink}`)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ntfy) {
|
if (ntfy) {
|
||||||
await sendNtfyNotification(
|
await sendNtfyNotification(
|
||||||
ntfy,
|
ntfy,
|
||||||
"Build Failed",
|
"Build Failed",
|
||||||
"warning",
|
"warning",
|
||||||
`view, Build details, ${buildLink}, clear=true;`,
|
`view, Build details, ${buildLink}, clear=true;`,
|
||||||
`🛠️Project: ${projectName}\n` +
|
`🛠️Project: ${projectName}\n` +
|
||||||
`⚙️Application: ${applicationName}\n` +
|
`⚙️Application: ${applicationName}\n` +
|
||||||
`❔Type: ${applicationType}\n` +
|
`❔Type: ${applicationType}\n` +
|
||||||
`🕒Date: ${date.toLocaleString()}\n` +
|
`🕒Date: ${date.toLocaleString()}\n` +
|
||||||
`⚠️Error:\n${errorMessage}`
|
`⚠️Error:\n${errorMessage}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
const inlineButton = [
|
const inlineButton = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: "Deployment Logs",
|
text: "Deployment Logs",
|
||||||
url: buildLink,
|
url: buildLink,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
await sendTelegramNotification(
|
await sendTelegramNotification(
|
||||||
telegram,
|
telegram,
|
||||||
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(
|
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(
|
||||||
date,
|
date,
|
||||||
"PP"
|
"PP",
|
||||||
)}\n<b>Time:</b> ${format(
|
)}\n<b>Time:</b> ${format(
|
||||||
date,
|
date,
|
||||||
"pp"
|
"pp",
|
||||||
)}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
|
)}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
|
||||||
inlineButton
|
inlineButton,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slack) {
|
if (slack) {
|
||||||
const { channel } = slack;
|
const { channel } = slack;
|
||||||
await sendSlackNotification(slack, {
|
await sendSlackNotification(slack, {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
color: "#FF0000",
|
color: "#FF0000",
|
||||||
pretext: ":warning: *Build Failed*",
|
pretext: ":warning: *Build Failed*",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
title: "Project",
|
title: "Project",
|
||||||
value: projectName,
|
value: projectName,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Application",
|
title: "Application",
|
||||||
value: applicationName,
|
value: applicationName,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Type",
|
title: "Type",
|
||||||
value: applicationType,
|
value: applicationType,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Time",
|
title: "Time",
|
||||||
value: date.toLocaleString(),
|
value: date.toLocaleString(),
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Error",
|
title: "Error",
|
||||||
value: `\`\`\`${errorMessage}\`\`\``,
|
value: `\`\`\`${errorMessage}\`\`\``,
|
||||||
short: false,
|
short: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
text: "View Build Details",
|
text: "View Build Details",
|
||||||
url: buildLink,
|
url: buildLink,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (custom) {
|
if (custom) {
|
||||||
await sendCustomNotification(custom, {
|
await sendCustomNotification(custom, {
|
||||||
title: "Build Error",
|
title: "Build Error",
|
||||||
message: "Build failed with errors",
|
message: "Build failed with errors",
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
buildLink,
|
buildLink,
|
||||||
timestamp: date.toISOString(),
|
timestamp: date.toISOString(),
|
||||||
date: date.toLocaleString(),
|
date: date.toLocaleString(),
|
||||||
status: "error",
|
status: "error",
|
||||||
type: "build",
|
type: "build",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,231 +6,231 @@ import { renderAsync } from "@react-email/components";
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
sendCustomNotification,
|
sendCustomNotification,
|
||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
applicationType: string;
|
applicationType: string;
|
||||||
buildLink: string;
|
buildLink: string;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendBuildSuccessNotifications = async ({
|
export const sendBuildSuccessNotifications = async ({
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
buildLink,
|
buildLink,
|
||||||
organizationId,
|
organizationId,
|
||||||
domains,
|
domains,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
const unixDate = ~~(Number(date) / 1000);
|
const unixDate = ~~(Number(date) / 1000);
|
||||||
const notificationList = await db.query.notifications.findMany({
|
const notificationList = await db.query.notifications.findMany({
|
||||||
where: and(
|
where: and(
|
||||||
eq(notifications.appDeploy, true),
|
eq(notifications.appDeploy, true),
|
||||||
eq(notifications.organizationId, organizationId)
|
eq(notifications.organizationId, organizationId),
|
||||||
),
|
),
|
||||||
with: {
|
with: {
|
||||||
email: true,
|
email: true,
|
||||||
discord: true,
|
discord: true,
|
||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
custom: true,
|
custom: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
||||||
notification;
|
notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
BuildSuccessEmail({
|
BuildSuccessEmail({
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
buildLink,
|
buildLink,
|
||||||
date: date.toLocaleString(),
|
date: date.toLocaleString(),
|
||||||
})
|
}),
|
||||||
).catch();
|
).catch();
|
||||||
await sendEmailNotification(email, "Build success for dokploy", template);
|
await sendEmailNotification(email, "Build success for dokploy", template);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discord) {
|
if (discord) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
`${discord.decoration ? decoration : ""} ${text}`.trim();
|
||||||
|
|
||||||
await sendDiscordNotification(discord, {
|
await sendDiscordNotification(discord, {
|
||||||
title: decorate(">", "`✅` Build Success"),
|
title: decorate(">", "`✅` Build Success"),
|
||||||
color: 0x57f287,
|
color: 0x57f287,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: decorate("`🛠️`", "Project"),
|
name: decorate("`🛠️`", "Project"),
|
||||||
value: projectName,
|
value: projectName,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`⚙️`", "Application"),
|
name: decorate("`⚙️`", "Application"),
|
||||||
value: applicationName,
|
value: applicationName,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`❔`", "Type"),
|
name: decorate("`❔`", "Type"),
|
||||||
value: applicationType,
|
value: applicationType,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`📅`", "Date"),
|
name: decorate("`📅`", "Date"),
|
||||||
value: `<t:${unixDate}:D>`,
|
value: `<t:${unixDate}:D>`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`⌚`", "Time"),
|
name: decorate("`⌚`", "Time"),
|
||||||
value: `<t:${unixDate}:t>`,
|
value: `<t:${unixDate}:t>`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`❓`", "Type"),
|
name: decorate("`❓`", "Type"),
|
||||||
value: "Successful",
|
value: "Successful",
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: decorate("`🧷`", "Build Link"),
|
name: decorate("`🧷`", "Build Link"),
|
||||||
value: `[Click here to access build link](${buildLink})`,
|
value: `[Click here to access build link](${buildLink})`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
timestamp: date.toISOString(),
|
timestamp: date.toISOString(),
|
||||||
footer: {
|
footer: {
|
||||||
text: "Dokploy Build Notification",
|
text: "Dokploy Build Notification",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gotify) {
|
if (gotify) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
`${gotify.decoration ? decoration : ""} ${text}\n`;
|
||||||
await sendGotifyNotification(
|
await sendGotifyNotification(
|
||||||
gotify,
|
gotify,
|
||||||
decorate("✅", "Build Success"),
|
decorate("✅", "Build Success"),
|
||||||
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
`${decorate("🛠️", `Project: ${projectName}`)}` +
|
||||||
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
`${decorate("⚙️", `Application: ${applicationName}`)}` +
|
||||||
`${decorate("❔", `Type: ${applicationType}`)}` +
|
`${decorate("❔", `Type: ${applicationType}`)}` +
|
||||||
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
`${decorate("🕒", `Date: ${date.toLocaleString()}`)}` +
|
||||||
`${decorate("🔗", `Build details:\n${buildLink}`)}`
|
`${decorate("🔗", `Build details:\n${buildLink}`)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ntfy) {
|
if (ntfy) {
|
||||||
await sendNtfyNotification(
|
await sendNtfyNotification(
|
||||||
ntfy,
|
ntfy,
|
||||||
"Build Success",
|
"Build Success",
|
||||||
"white_check_mark",
|
"white_check_mark",
|
||||||
`view, Build details, ${buildLink}, clear=true;`,
|
`view, Build details, ${buildLink}, clear=true;`,
|
||||||
`🛠Project: ${projectName}\n` +
|
`🛠Project: ${projectName}\n` +
|
||||||
`⚙️Application: ${applicationName}\n` +
|
`⚙️Application: ${applicationName}\n` +
|
||||||
`❔Type: ${applicationType}\n` +
|
`❔Type: ${applicationType}\n` +
|
||||||
`🕒Date: ${date.toLocaleString()}`
|
`🕒Date: ${date.toLocaleString()}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (telegram) {
|
if (telegram) {
|
||||||
const chunkArray = <T>(array: T[], chunkSize: number): T[][] =>
|
const chunkArray = <T>(array: T[], chunkSize: number): T[][] =>
|
||||||
Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
|
Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) =>
|
||||||
array.slice(i * chunkSize, i * chunkSize + chunkSize)
|
array.slice(i * chunkSize, i * chunkSize + chunkSize),
|
||||||
);
|
);
|
||||||
|
|
||||||
const inlineButton = [
|
const inlineButton = [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
text: "Deployment Logs",
|
text: "Deployment Logs",
|
||||||
url: buildLink,
|
url: buildLink,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
...chunkArray(domains, 2).map((chunk) =>
|
...chunkArray(domains, 2).map((chunk) =>
|
||||||
chunk.map((data) => ({
|
chunk.map((data) => ({
|
||||||
text: data.host,
|
text: data.host,
|
||||||
url: `${data.https ? "https" : "http"}://${data.host}`,
|
url: `${data.https ? "https" : "http"}://${data.host}`,
|
||||||
}))
|
})),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
await sendTelegramNotification(
|
await sendTelegramNotification(
|
||||||
telegram,
|
telegram,
|
||||||
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(
|
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(
|
||||||
date,
|
date,
|
||||||
"PP"
|
"PP",
|
||||||
)}\n<b>Time:</b> ${format(date, "pp")}`,
|
)}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||||
inlineButton
|
inlineButton,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slack) {
|
if (slack) {
|
||||||
const { channel } = slack;
|
const { channel } = slack;
|
||||||
await sendSlackNotification(slack, {
|
await sendSlackNotification(slack, {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
color: "#00FF00",
|
color: "#00FF00",
|
||||||
pretext: ":white_check_mark: *Build Success*",
|
pretext: ":white_check_mark: *Build Success*",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
title: "Project",
|
title: "Project",
|
||||||
value: projectName,
|
value: projectName,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Application",
|
title: "Application",
|
||||||
value: applicationName,
|
value: applicationName,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Type",
|
title: "Type",
|
||||||
value: applicationType,
|
value: applicationType,
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Time",
|
title: "Time",
|
||||||
value: date.toLocaleString(),
|
value: date.toLocaleString(),
|
||||||
short: true,
|
short: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
text: "View Build Details",
|
text: "View Build Details",
|
||||||
url: buildLink,
|
url: buildLink,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (custom) {
|
if (custom) {
|
||||||
await sendCustomNotification(custom, {
|
await sendCustomNotification(custom, {
|
||||||
title: "Build Success",
|
title: "Build Success",
|
||||||
message: "Build completed successfully",
|
message: "Build completed successfully",
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
applicationType,
|
applicationType,
|
||||||
buildLink,
|
buildLink,
|
||||||
timestamp: date.toISOString(),
|
timestamp: date.toISOString(),
|
||||||
date: date.toLocaleString(),
|
date: date.toLocaleString(),
|
||||||
domains: domains.map((domain) => domain.host).join(", "),
|
domains: domains.map((domain) => domain.host).join(", "),
|
||||||
status: "success",
|
status: "success",
|
||||||
type: "build",
|
type: "build",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
||||||
|
notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -244,7 +245,10 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
if (custom) {
|
if (custom) {
|
||||||
await sendCustomNotification(custom, {
|
await sendCustomNotification(custom, {
|
||||||
title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||||
message: type === "success" ? "Database backup completed successfully" : "Database backup failed",
|
message:
|
||||||
|
type === "success"
|
||||||
|
? "Database backup completed successfully"
|
||||||
|
: "Database backup failed",
|
||||||
projectName,
|
projectName,
|
||||||
applicationName,
|
applicationName,
|
||||||
databaseType,
|
databaseType,
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
||||||
|
notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, custom } = notification;
|
const { email, discord, telegram, slack, gotify, ntfy, custom } =
|
||||||
|
notification;
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
|
|||||||
@@ -1,193 +1,193 @@
|
|||||||
import type {
|
import type {
|
||||||
custom,
|
custom,
|
||||||
discord,
|
discord,
|
||||||
email,
|
email,
|
||||||
gotify,
|
gotify,
|
||||||
ntfy,
|
ntfy,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
} from "@dokploy/server/db/schema";
|
} from "@dokploy/server/db/schema";
|
||||||
import nodemailer from "nodemailer";
|
import nodemailer from "nodemailer";
|
||||||
|
|
||||||
export const sendEmailNotification = async (
|
export const sendEmailNotification = async (
|
||||||
connection: typeof email.$inferInsert,
|
connection: typeof email.$inferInsert,
|
||||||
subject: string,
|
subject: string,
|
||||||
htmlContent: string
|
htmlContent: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
smtpServer,
|
smtpServer,
|
||||||
smtpPort,
|
smtpPort,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
fromAddress,
|
fromAddress,
|
||||||
toAddresses,
|
toAddresses,
|
||||||
} = connection;
|
} = connection;
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: smtpServer,
|
host: smtpServer,
|
||||||
port: smtpPort,
|
port: smtpPort,
|
||||||
auth: { user: username, pass: password },
|
auth: { user: username, pass: password },
|
||||||
});
|
});
|
||||||
|
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
to: toAddresses.join(", "),
|
to: toAddresses.join(", "),
|
||||||
subject,
|
subject,
|
||||||
html: htmlContent,
|
html: htmlContent,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendDiscordNotification = async (
|
export const sendDiscordNotification = async (
|
||||||
connection: typeof discord.$inferInsert,
|
connection: typeof discord.$inferInsert,
|
||||||
embed: any
|
embed: any,
|
||||||
) => {
|
) => {
|
||||||
// try {
|
// try {
|
||||||
await fetch(connection.webhookUrl, {
|
await fetch(connection.webhookUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ embeds: [embed] }),
|
body: JSON.stringify({ embeds: [embed] }),
|
||||||
});
|
});
|
||||||
// } catch (err) {
|
// } catch (err) {
|
||||||
// console.log(err);
|
// console.log(err);
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendTelegramNotification = async (
|
export const sendTelegramNotification = async (
|
||||||
connection: typeof telegram.$inferInsert,
|
connection: typeof telegram.$inferInsert,
|
||||||
messageText: string,
|
messageText: string,
|
||||||
inlineButton?: {
|
inlineButton?: {
|
||||||
text: string;
|
text: string;
|
||||||
url: string;
|
url: string;
|
||||||
}[][]
|
}[][],
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`;
|
||||||
await fetch(url, {
|
await fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
chat_id: connection.chatId,
|
chat_id: connection.chatId,
|
||||||
message_thread_id: connection.messageThreadId,
|
message_thread_id: connection.messageThreadId,
|
||||||
text: messageText,
|
text: messageText,
|
||||||
parse_mode: "HTML",
|
parse_mode: "HTML",
|
||||||
disable_web_page_preview: true,
|
disable_web_page_preview: true,
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: inlineButton,
|
inline_keyboard: inlineButton,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendSlackNotification = async (
|
export const sendSlackNotification = async (
|
||||||
connection: typeof slack.$inferInsert,
|
connection: typeof slack.$inferInsert,
|
||||||
message: any
|
message: any,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await fetch(connection.webhookUrl, {
|
await fetch(connection.webhookUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(message),
|
body: JSON.stringify(message),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendGotifyNotification = async (
|
export const sendGotifyNotification = async (
|
||||||
connection: typeof gotify.$inferInsert,
|
connection: typeof gotify.$inferInsert,
|
||||||
title: string,
|
title: string,
|
||||||
message: string
|
message: string,
|
||||||
) => {
|
) => {
|
||||||
const response = await fetch(`${connection.serverUrl}/message`, {
|
const response = await fetch(`${connection.serverUrl}/message`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-Gotify-Key": connection.appToken,
|
"X-Gotify-Key": connection.appToken,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title: title,
|
title: title,
|
||||||
message: message,
|
message: message,
|
||||||
priority: connection.priority,
|
priority: connection.priority,
|
||||||
extras: {
|
extras: {
|
||||||
"client::display": {
|
"client::display": {
|
||||||
contentType: "text/plain",
|
contentType: "text/plain",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to send Gotify notification: ${response.statusText}`
|
`Failed to send Gotify notification: ${response.statusText}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendNtfyNotification = async (
|
export const sendNtfyNotification = async (
|
||||||
connection: typeof ntfy.$inferInsert,
|
connection: typeof ntfy.$inferInsert,
|
||||||
title: string,
|
title: string,
|
||||||
tags: string,
|
tags: string,
|
||||||
actions: string,
|
actions: string,
|
||||||
message: string
|
message: string,
|
||||||
) => {
|
) => {
|
||||||
const response = await fetch(`${connection.serverUrl}/${connection.topic}`, {
|
const response = await fetch(`${connection.serverUrl}/${connection.topic}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${connection.accessToken}`,
|
Authorization: `Bearer ${connection.accessToken}`,
|
||||||
"X-Priority": connection.priority?.toString() || "3",
|
"X-Priority": connection.priority?.toString() || "3",
|
||||||
"X-Title": title,
|
"X-Title": title,
|
||||||
"X-Tags": tags,
|
"X-Tags": tags,
|
||||||
"X-Actions": actions,
|
"X-Actions": actions,
|
||||||
},
|
},
|
||||||
body: message,
|
body: message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to send ntfy notification: ${response.statusText}`);
|
throw new Error(`Failed to send ntfy notification: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendCustomNotification = async (
|
export const sendCustomNotification = async (
|
||||||
connection: typeof custom.$inferInsert,
|
connection: typeof custom.$inferInsert,
|
||||||
payload: Record<string, any>
|
payload: Record<string, any>,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
// Parse headers if provided
|
// Parse headers if provided
|
||||||
let headers: Record<string, string> = {
|
let headers: Record<string, string> = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
if (connection.headers) {
|
if (connection.headers) {
|
||||||
try {
|
try {
|
||||||
headers = { ...headers, ...JSON.parse(connection.headers) };
|
headers = { ...headers, ...JSON.parse(connection.headers) };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing headers:", error);
|
console.error("Error parsing headers:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default body with payload
|
// Default body with payload
|
||||||
const body = JSON.stringify(payload);
|
const body = JSON.stringify(payload);
|
||||||
|
|
||||||
const response = await fetch(connection.endpoint, {
|
const response = await fetch(connection.endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to send custom notification: ${response.statusText}`
|
`Failed to send custom notification: ${response.statusText}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending custom notification:", error);
|
console.error("Error sending custom notification:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user