mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
Merge pull request #2699 from ChristoferMendes/feature/add-custom-webhook-notification-provider
feat(notifications): add custom webhook notification provider
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { ApplicationNested } from "@dokploy/server/utils/builders";
|
||||
import { mechanizeDockerContainer } from "@dokploy/server/utils/builders";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
type MockCreateServiceOptions = {
|
||||
TaskTemplate?: {
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import type { findEnvironmentsByProjectId } from "@dokploy/server";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
Terminal,
|
||||
TrashIcon,
|
||||
} from "lucide-react";
|
||||
import { ChevronDownIcon, PencilIcon, PlusIcon, TrashIcon } from "lucide-react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { EnvironmentVariables } from "@/components/dashboard/project/environment-variables";
|
||||
import { AlertBlock } from "@/components/shared/alert-block";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -246,20 +239,6 @@ export const AdvancedEnvironmentSelector = ({
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{/* Action buttons for non-production environments */}
|
||||
{/* <EnvironmentVariables environmentId={environment.environmentId}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Terminal className="h-3 w-3" />
|
||||
</Button>
|
||||
</EnvironmentVariables> */}
|
||||
{environment.name !== "production" && (
|
||||
<div className="flex items-center gap-1 px-2">
|
||||
<Button
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { AlertTriangle, Mail, PenBoxIcon, PlusIcon } from "lucide-react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
Mail,
|
||||
PenBoxIcon,
|
||||
PlusIcon,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
@@ -108,6 +114,21 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
||||
priority: z.number().min(1).max(5).default(3),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("custom"),
|
||||
endpoint: z.string().min(1, { message: "Endpoint URL is required" }),
|
||||
headers: z
|
||||
.array(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.default([]),
|
||||
})
|
||||
.merge(notificationBaseSchema),
|
||||
z
|
||||
.object({
|
||||
type: z.literal("lark"),
|
||||
@@ -145,6 +166,10 @@ export const notificationsMap = {
|
||||
icon: <NtfyIcon />,
|
||||
label: "ntfy",
|
||||
},
|
||||
custom: {
|
||||
icon: <PenBoxIcon size={29} className="text-muted-foreground" />,
|
||||
label: "Custom",
|
||||
},
|
||||
};
|
||||
|
||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||
@@ -180,6 +205,13 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
api.notification.testNtfyConnection.useMutation();
|
||||
const { mutateAsync: testLarkConnection, isLoading: isLoadingLark } =
|
||||
api.notification.testLarkConnection.useMutation();
|
||||
|
||||
const { mutateAsync: testCustomConnection, isLoading: isLoadingCustom } =
|
||||
api.notification.testCustomConnection.useMutation();
|
||||
|
||||
const customMutation = notificationId
|
||||
? api.notification.updateCustom.useMutation()
|
||||
: api.notification.createCustom.useMutation();
|
||||
const slackMutation = notificationId
|
||||
? api.notification.updateSlack.useMutation()
|
||||
: api.notification.createSlack.useMutation();
|
||||
@@ -218,6 +250,15 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
name: "toAddresses" as never,
|
||||
});
|
||||
|
||||
const {
|
||||
fields: headerFields,
|
||||
append: appendHeader,
|
||||
remove: removeHeader,
|
||||
} = useFieldArray({
|
||||
control: form.control,
|
||||
name: "headers" as never,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (type === "email" && fields.length === 0) {
|
||||
append("");
|
||||
@@ -330,6 +371,26 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
serverThreshold: notification.serverThreshold,
|
||||
});
|
||||
} else if (notification.notificationType === "custom") {
|
||||
form.reset({
|
||||
appBuildError: notification.appBuildError,
|
||||
appDeploy: notification.appDeploy,
|
||||
dokployRestart: notification.dokployRestart,
|
||||
databaseBackup: notification.databaseBackup,
|
||||
type: notification.notificationType,
|
||||
endpoint: notification.custom?.endpoint || "",
|
||||
headers: notification.custom?.headers
|
||||
? Object.entries(notification.custom.headers).map(
|
||||
([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
: [],
|
||||
name: notification.name,
|
||||
dockerCleanup: notification.dockerCleanup,
|
||||
serverThreshold: notification.serverThreshold,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.reset();
|
||||
@@ -344,6 +405,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
gotify: gotifyMutation,
|
||||
ntfy: ntfyMutation,
|
||||
lark: larkMutation,
|
||||
custom: customMutation,
|
||||
};
|
||||
|
||||
const onSubmit = async (data: NotificationSchema) => {
|
||||
@@ -467,6 +529,32 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
larkId: notification?.larkId || "",
|
||||
serverThreshold: serverThreshold,
|
||||
});
|
||||
} else if (data.type === "custom") {
|
||||
// Convert headers array to object
|
||||
const headersRecord =
|
||||
data.headers && data.headers.length > 0
|
||||
? data.headers.reduce(
|
||||
(acc, { key, value }) => {
|
||||
if (key.trim()) acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
promise = customMutation.mutateAsync({
|
||||
appBuildError: appBuildError,
|
||||
appDeploy: appDeploy,
|
||||
dokployRestart: dokployRestart,
|
||||
databaseBackup: databaseBackup,
|
||||
endpoint: data.endpoint,
|
||||
headers: headersRecord,
|
||||
name: data.name,
|
||||
dockerCleanup: dockerCleanup,
|
||||
serverThreshold: serverThreshold,
|
||||
notificationId: notificationId || "",
|
||||
customId: notification?.customId || "",
|
||||
});
|
||||
}
|
||||
|
||||
if (promise) {
|
||||
@@ -1057,7 +1145,92 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{type === "custom" && (
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="endpoint"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="https://api.example.com/webhook"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The URL where POST requests will be sent with
|
||||
notification data.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<FormLabel>Headers</FormLabel>
|
||||
<FormDescription>
|
||||
Optional. Custom headers for your POST request (e.g.,
|
||||
Authorization, Content-Type).
|
||||
</FormDescription>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
{headerFields.map((field, index) => (
|
||||
<div
|
||||
key={field.id}
|
||||
className="flex items-center gap-2 p-2 border rounded-md bg-muted/50"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`headers.${index}.key` as never}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Input placeholder="Key" {...field} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`headers.${index}.value` as never}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-[2]">
|
||||
<FormControl>
|
||||
<Input placeholder="Value" {...field} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => removeHeader(index)}
|
||||
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => appendHeader({ key: "", value: "" })}
|
||||
className="w-full"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 mr-2" />
|
||||
Add header
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{type === "lark" && (
|
||||
<>
|
||||
<FormField
|
||||
@@ -1250,7 +1423,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
isLoadingEmail ||
|
||||
isLoadingGotify ||
|
||||
isLoadingNtfy ||
|
||||
isLoadingLark
|
||||
isLoadingLark ||
|
||||
isLoadingCustom
|
||||
}
|
||||
variant="secondary"
|
||||
type="button"
|
||||
@@ -1304,6 +1478,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
||||
await testLarkConnection({
|
||||
webhookUrl: data.webhookUrl,
|
||||
});
|
||||
} else if (data.type === "custom") {
|
||||
const headersRecord =
|
||||
data.headers && data.headers.length > 0
|
||||
? data.headers.reduce(
|
||||
(acc, { key, value }) => {
|
||||
if (key.trim()) acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
)
|
||||
: undefined;
|
||||
await testCustomConnection({
|
||||
endpoint: data.endpoint,
|
||||
headers: headersRecord,
|
||||
});
|
||||
}
|
||||
toast.success("Connection Success");
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Bell, Loader2, Mail, Trash2 } from "lucide-react";
|
||||
import { Bell, Loader2, Mail, PenBoxIcon, Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
DiscordIcon,
|
||||
@@ -96,6 +96,11 @@ export const ShowNotifications = () => {
|
||||
<NtfyIcon className="size-6" />
|
||||
</div>
|
||||
)}
|
||||
{notification.notificationType === "custom" && (
|
||||
<div className="flex items-center justify-center rounded-lg ">
|
||||
<PenBoxIcon className="size-6 text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
{notification.notificationType === "lark" && (
|
||||
<div className="flex items-center justify-center rounded-lg">
|
||||
<LarkIcon className="size-7 text-muted-foreground" />
|
||||
|
||||
9
apps/dokploy/drizzle/0129_pale_roughhouse.sql
Normal file
9
apps/dokploy/drizzle/0129_pale_roughhouse.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
ALTER TYPE "public"."notificationType" ADD VALUE 'custom' BEFORE 'lark';--> statement-breakpoint
|
||||
CREATE TABLE "custom" (
|
||||
"customId" text PRIMARY KEY NOT NULL,
|
||||
"endpoint" text NOT NULL,
|
||||
"headers" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "notification" ADD COLUMN "customId" text;--> statement-breakpoint
|
||||
ALTER TABLE "notification" ADD CONSTRAINT "notification_customId_custom_customId_fk" FOREIGN KEY ("customId") REFERENCES "public"."custom"("customId") ON DELETE cascade ON UPDATE no action;
|
||||
6915
apps/dokploy/drizzle/meta/0129_snapshot.json
Normal file
6915
apps/dokploy/drizzle/meta/0129_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -904,6 +904,13 @@
|
||||
"when": 1765101709413,
|
||||
"tag": "0128_hard_falcon",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 129,
|
||||
"version": "7",
|
||||
"when": 1765136384035,
|
||||
"tag": "0129_pale_roughhouse",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
createMount,
|
||||
deployMariadb,
|
||||
findBackupsByDbId,
|
||||
findMariadbById,
|
||||
findEnvironmentById,
|
||||
findMariadbById,
|
||||
findProjectById,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
createMount,
|
||||
deployMongo,
|
||||
findBackupsByDbId,
|
||||
findMongoById,
|
||||
findEnvironmentById,
|
||||
findMongoById,
|
||||
findProjectById,
|
||||
IS_CLOUD,
|
||||
rebuildDatabase,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
createCustomNotification,
|
||||
createDiscordNotification,
|
||||
createEmailNotification,
|
||||
createGotifyNotification,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
findNotificationById,
|
||||
IS_CLOUD,
|
||||
removeNotificationById,
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -17,6 +19,7 @@ import {
|
||||
sendServerThresholdNotifications,
|
||||
sendSlackNotification,
|
||||
sendTelegramNotification,
|
||||
updateCustomNotification,
|
||||
updateDiscordNotification,
|
||||
updateEmailNotification,
|
||||
updateGotifyNotification,
|
||||
@@ -36,6 +39,7 @@ import {
|
||||
} from "@/server/api/trpc";
|
||||
import { db } from "@/server/db";
|
||||
import {
|
||||
apiCreateCustom,
|
||||
apiCreateDiscord,
|
||||
apiCreateEmail,
|
||||
apiCreateGotify,
|
||||
@@ -44,6 +48,7 @@ import {
|
||||
apiCreateSlack,
|
||||
apiCreateTelegram,
|
||||
apiFindOneNotification,
|
||||
apiTestCustomConnection,
|
||||
apiTestDiscordConnection,
|
||||
apiTestEmailConnection,
|
||||
apiTestGotifyConnection,
|
||||
@@ -51,6 +56,7 @@ import {
|
||||
apiTestNtfyConnection,
|
||||
apiTestSlackConnection,
|
||||
apiTestTelegramConnection,
|
||||
apiUpdateCustom,
|
||||
apiUpdateDiscord,
|
||||
apiUpdateEmail,
|
||||
apiUpdateGotify,
|
||||
@@ -334,6 +340,7 @@ export const notificationRouter = createTRPCRouter({
|
||||
email: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
orderBy: desc(notifications.createdAt),
|
||||
@@ -518,6 +525,59 @@ export const notificationRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}),
|
||||
createCustom: adminProcedure
|
||||
.input(apiCreateCustom)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
return await createCustomNotification(
|
||||
input,
|
||||
ctx.session.activeOrganizationId,
|
||||
);
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error creating the notification",
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
updateCustom: adminProcedure
|
||||
.input(apiUpdateCustom)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const notification = await findNotificationById(input.notificationId);
|
||||
if (notification.organizationId !== ctx.session.activeOrganizationId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to update this notification",
|
||||
});
|
||||
}
|
||||
return await updateCustomNotification({
|
||||
...input,
|
||||
organizationId: ctx.session.activeOrganizationId,
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
testCustomConnection: adminProcedure
|
||||
.input(apiTestCustomConnection)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
await sendCustomNotification(input, {
|
||||
title: "Test Notification",
|
||||
message: "Hi, From Dokploy 👋",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: `${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
createLark: adminProcedure
|
||||
.input(apiCreateLark)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"switch:prod": "node scripts/switchToDist.js",
|
||||
"dev": "rm -rf ./dist && pnpm esbuild && tsc --emitDeclarationOnly --outDir dist -p tsconfig.server.json",
|
||||
"esbuild": "tsx ./esbuild.config.ts && tsc --project tsconfig.server.json --emitDeclarationOnly ",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dbml:generate": "npx tsx src/db/schema/dbml.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.5",
|
||||
|
||||
1200
packages/server/schema.dbml
Normal file
1200
packages/server/schema.dbml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,12 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
text,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
@@ -12,6 +19,7 @@ export const notificationType = pgEnum("notificationType", [
|
||||
"email",
|
||||
"gotify",
|
||||
"ntfy",
|
||||
"custom",
|
||||
"lark",
|
||||
]);
|
||||
|
||||
@@ -50,6 +58,9 @@ export const notifications = pgTable("notification", {
|
||||
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
customId: text("customId").references(() => custom.customId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
larkId: text("larkId").references(() => lark.larkId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
@@ -121,6 +132,15 @@ export const ntfy = pgTable("ntfy", {
|
||||
priority: integer("priority").notNull().default(3),
|
||||
});
|
||||
|
||||
export const custom = pgTable("custom", {
|
||||
customId: text("customId")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
endpoint: text("endpoint").notNull(),
|
||||
headers: jsonb("headers").$type<Record<string, string>>(),
|
||||
});
|
||||
|
||||
export const lark = pgTable("lark", {
|
||||
larkId: text("larkId")
|
||||
.notNull()
|
||||
@@ -154,6 +174,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
||||
fields: [notifications.ntfyId],
|
||||
references: [ntfy.ntfyId],
|
||||
}),
|
||||
custom: one(custom, {
|
||||
fields: [notifications.customId],
|
||||
references: [custom.customId],
|
||||
}),
|
||||
lark: one(lark, {
|
||||
fields: [notifications.larkId],
|
||||
references: [lark.larkId],
|
||||
@@ -362,6 +386,32 @@ export const apiFindOneNotification = notificationsSchema
|
||||
})
|
||||
.required();
|
||||
|
||||
export const apiCreateCustom = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
databaseBackup: true,
|
||||
dokployRestart: true,
|
||||
name: true,
|
||||
appDeploy: true,
|
||||
dockerCleanup: true,
|
||||
serverThreshold: true,
|
||||
})
|
||||
.extend({
|
||||
endpoint: z.string().min(1),
|
||||
headers: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const apiUpdateCustom = apiCreateCustom.partial().extend({
|
||||
notificationId: z.string().min(1),
|
||||
customId: z.string().min(1),
|
||||
organizationId: z.string().optional(),
|
||||
});
|
||||
|
||||
export const apiTestCustomConnection = z.object({
|
||||
endpoint: z.string().min(1),
|
||||
headers: z.record(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const apiCreateLark = notificationsSchema
|
||||
.pick({
|
||||
appBuildError: true,
|
||||
@@ -404,5 +454,7 @@ export const apiSendTest = notificationsSchema
|
||||
appToken: z.string(),
|
||||
accessToken: z.string().optional(),
|
||||
priority: z.number(),
|
||||
endpoint: z.string(),
|
||||
headers: z.string(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
@@ -5,17 +5,24 @@ enum applicationStatus {
|
||||
error
|
||||
}
|
||||
|
||||
enum backupType {
|
||||
database
|
||||
compose
|
||||
}
|
||||
|
||||
enum buildType {
|
||||
dockerfile
|
||||
heroku_buildpacks
|
||||
paketo_buildpacks
|
||||
nixpacks
|
||||
static
|
||||
railpack
|
||||
}
|
||||
|
||||
enum certificateType {
|
||||
letsencrypt
|
||||
none
|
||||
custom
|
||||
}
|
||||
|
||||
enum composeType {
|
||||
@@ -28,6 +35,7 @@ enum databaseType {
|
||||
mariadb
|
||||
mysql
|
||||
mongo
|
||||
"web-server"
|
||||
}
|
||||
|
||||
enum deploymentStatus {
|
||||
@@ -61,6 +69,8 @@ enum notificationType {
|
||||
discord
|
||||
email
|
||||
gotify
|
||||
ntfy
|
||||
custom
|
||||
}
|
||||
|
||||
enum protocolType {
|
||||
@@ -68,14 +78,21 @@ enum protocolType {
|
||||
udp
|
||||
}
|
||||
|
||||
enum publishModeType {
|
||||
ingress
|
||||
host
|
||||
}
|
||||
|
||||
enum RegistryType {
|
||||
selfHosted
|
||||
cloud
|
||||
}
|
||||
|
||||
enum Roles {
|
||||
admin
|
||||
user
|
||||
enum scheduleType {
|
||||
application
|
||||
compose
|
||||
server
|
||||
"dokploy-server"
|
||||
}
|
||||
|
||||
enum serverStatus {
|
||||
@@ -93,6 +110,11 @@ enum serviceType {
|
||||
compose
|
||||
}
|
||||
|
||||
enum shellType {
|
||||
bash
|
||||
sh
|
||||
}
|
||||
|
||||
enum sourceType {
|
||||
docker
|
||||
git
|
||||
@@ -112,6 +134,11 @@ enum sourceTypeCompose {
|
||||
raw
|
||||
}
|
||||
|
||||
enum triggerType {
|
||||
push
|
||||
tag
|
||||
}
|
||||
|
||||
table account {
|
||||
id text [pk, not null]
|
||||
account_id text [not null]
|
||||
@@ -133,7 +160,39 @@ table account {
|
||||
confirmationExpiresAt text
|
||||
}
|
||||
|
||||
table admin {
|
||||
table ai {
|
||||
aiId text [pk, not null]
|
||||
name text [not null]
|
||||
apiUrl text [not null]
|
||||
apiKey text [not null]
|
||||
model text [not null]
|
||||
isEnabled boolean [not null, default: true]
|
||||
organizationId text [not null]
|
||||
createdAt text [not null]
|
||||
}
|
||||
|
||||
table apikey {
|
||||
id text [pk, not null]
|
||||
name text
|
||||
start text
|
||||
prefix text
|
||||
key text [not null]
|
||||
user_id text [not null]
|
||||
refill_interval integer
|
||||
refill_amount integer
|
||||
last_refill_at timestamp
|
||||
enabled boolean
|
||||
rate_limit_enabled boolean
|
||||
rate_limit_time_window integer
|
||||
rate_limit_max integer
|
||||
request_count integer
|
||||
remaining integer
|
||||
last_request timestamp
|
||||
expires_at timestamp
|
||||
created_at timestamp [not null]
|
||||
updated_at timestamp [not null]
|
||||
permissions text
|
||||
metadata text
|
||||
}
|
||||
|
||||
table application {
|
||||
@@ -143,14 +202,19 @@ table application {
|
||||
description text
|
||||
env text
|
||||
previewEnv text
|
||||
watchPaths text[]
|
||||
previewBuildArgs text
|
||||
previewLabels text[]
|
||||
previewWildcard text
|
||||
previewPort integer [default: 3000]
|
||||
previewHttps boolean [not null, default: false]
|
||||
previewPath text [default: '/']
|
||||
certificateType certificateType [not null, default: 'none']
|
||||
previewCustomCertResolver text
|
||||
previewLimit integer [default: 3]
|
||||
isPreviewDeploymentsActive boolean [default: false]
|
||||
previewRequireCollaboratorPermissions boolean [default: true]
|
||||
rollbackActive boolean [default: false]
|
||||
buildArgs text
|
||||
memoryReservation text
|
||||
memoryLimit text
|
||||
@@ -167,6 +231,7 @@ table application {
|
||||
owner text
|
||||
branch text
|
||||
buildPath text [default: '/']
|
||||
triggerType triggerType [default: 'push']
|
||||
autoDeploy boolean
|
||||
gitlabProjectId integer
|
||||
gitlabRepository text
|
||||
@@ -174,6 +239,10 @@ table application {
|
||||
gitlabBranch text
|
||||
gitlabBuildPath text [default: '/']
|
||||
gitlabPathNamespace text
|
||||
giteaRepository text
|
||||
giteaOwner text
|
||||
giteaBranch text
|
||||
giteaBuildPath text [default: '/']
|
||||
bitbucketRepository text
|
||||
bitbucketOwner text
|
||||
bitbucketBranch text
|
||||
@@ -186,6 +255,7 @@ table application {
|
||||
customGitBranch text
|
||||
customGitBuildPath text
|
||||
customGitSSHKeyId text
|
||||
enableSubmodules boolean [not null, default: false]
|
||||
dockerfile text
|
||||
dockerContextPath text
|
||||
dockerBuildStage text
|
||||
@@ -201,52 +271,47 @@ table application {
|
||||
replicas integer [not null, default: 1]
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
buildType buildType [not null, default: 'nixpacks']
|
||||
railpackVersion text [default: '0.2.2']
|
||||
herokuVersion text [default: '24']
|
||||
publishDirectory text
|
||||
isStaticSpa boolean
|
||||
createdAt text [not null]
|
||||
registryId text
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
githubId text
|
||||
gitlabId text
|
||||
bitbucketId text
|
||||
giteaId text
|
||||
bitbucketId text
|
||||
serverId text
|
||||
}
|
||||
|
||||
table auth {
|
||||
id text [pk, not null]
|
||||
email text [not null, unique]
|
||||
password text [not null]
|
||||
rol Roles [not null]
|
||||
image text
|
||||
secret text
|
||||
token text
|
||||
is2FAEnabled boolean [not null, default: false]
|
||||
createdAt text [not null]
|
||||
resetPasswordToken text
|
||||
resetPasswordExpiresAt text
|
||||
confirmationToken text
|
||||
confirmationExpiresAt text
|
||||
}
|
||||
|
||||
table backup {
|
||||
backupId text [pk, not null]
|
||||
appName text [not null, unique]
|
||||
schedule text [not null]
|
||||
enabled boolean
|
||||
database text [not null]
|
||||
prefix text [not null]
|
||||
serviceName text
|
||||
destinationId text [not null]
|
||||
keepLatestCount integer
|
||||
backupType backupType [not null, default: 'database']
|
||||
databaseType databaseType [not null]
|
||||
composeId text
|
||||
postgresId text
|
||||
mariadbId text
|
||||
mysqlId text
|
||||
mongoId text
|
||||
userId text
|
||||
metadata jsonb
|
||||
}
|
||||
|
||||
table bitbucket {
|
||||
bitbucketId text [pk, not null]
|
||||
bitbucketUsername text
|
||||
bitbucketEmail text
|
||||
appPassword text
|
||||
apiToken text
|
||||
bitbucketWorkspaceName text
|
||||
gitProviderId text [not null]
|
||||
}
|
||||
@@ -258,7 +323,7 @@ table certificate {
|
||||
privateKey text [not null]
|
||||
certificatePath text [not null, unique]
|
||||
autoRenew boolean
|
||||
userId text
|
||||
organizationId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -291,13 +356,17 @@ table compose {
|
||||
customGitBranch text
|
||||
customGitSSHKeyId text
|
||||
command text [not null, default: '']
|
||||
enableSubmodules boolean [not null, default: false]
|
||||
composePath text [not null, default: './docker-compose.yml']
|
||||
suffix text [not null, default: '']
|
||||
randomize boolean [not null, default: false]
|
||||
isolatedDeployment boolean [not null, default: false]
|
||||
isolatedDeploymentsVolume boolean [not null, default: false]
|
||||
triggerType triggerType [default: 'push']
|
||||
composeStatus applicationStatus [not null, default: 'idle']
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
createdAt text [not null]
|
||||
watchPaths text[]
|
||||
githubId text
|
||||
gitlabId text
|
||||
bitbucketId text
|
||||
@@ -305,19 +374,32 @@ table compose {
|
||||
serverId text
|
||||
}
|
||||
|
||||
table custom {
|
||||
customId text [pk, not null]
|
||||
endpoint text [not null]
|
||||
headers text
|
||||
}
|
||||
|
||||
table deployment {
|
||||
deploymentId text [pk, not null]
|
||||
title text [not null]
|
||||
description text
|
||||
status deploymentStatus [default: 'running']
|
||||
logPath text [not null]
|
||||
pid text
|
||||
applicationId text
|
||||
composeId text
|
||||
serverId text
|
||||
isPreviewDeployment boolean [default: false]
|
||||
previewDeploymentId text
|
||||
createdAt text [not null]
|
||||
startedAt text
|
||||
finishedAt text
|
||||
errorMessage text
|
||||
scheduleId text
|
||||
backupId text
|
||||
rollbackId text
|
||||
volumeBackupId text
|
||||
}
|
||||
|
||||
table destination {
|
||||
@@ -329,7 +411,8 @@ table destination {
|
||||
bucket text [not null]
|
||||
region text [not null]
|
||||
endpoint text [not null]
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
createdAt timestamp [not null, default: `now()`]
|
||||
}
|
||||
|
||||
table discord {
|
||||
@@ -349,9 +432,12 @@ table domain {
|
||||
uniqueConfigKey serial [not null, increment]
|
||||
createdAt text [not null]
|
||||
composeId text
|
||||
customCertResolver text
|
||||
applicationId text
|
||||
previewDeploymentId text
|
||||
certificateType certificateType [not null, default: 'none']
|
||||
internalPath text [default: '/']
|
||||
stripPath boolean [not null, default: false]
|
||||
}
|
||||
|
||||
table email {
|
||||
@@ -364,12 +450,36 @@ table email {
|
||||
toAddress text[] [not null]
|
||||
}
|
||||
|
||||
table environment {
|
||||
environmentId text [pk, not null]
|
||||
name text [not null]
|
||||
description text
|
||||
createdAt text [not null]
|
||||
env text [not null, default: '']
|
||||
projectId text [not null]
|
||||
}
|
||||
|
||||
table git_provider {
|
||||
gitProviderId text [pk, not null]
|
||||
name text [not null]
|
||||
providerType gitProviderType [not null, default: 'github']
|
||||
createdAt text [not null]
|
||||
userId text
|
||||
organizationId text [not null]
|
||||
userId text [not null]
|
||||
}
|
||||
|
||||
table gitea {
|
||||
giteaId text [pk, not null]
|
||||
giteaUrl text [not null, default: 'https://gitea.com']
|
||||
redirect_uri text
|
||||
client_id text
|
||||
client_secret text
|
||||
gitProviderId text [not null]
|
||||
access_token text
|
||||
refresh_token text
|
||||
expires_at integer
|
||||
scopes text [default: 'repo,repo:status,read:user,read:org']
|
||||
last_authenticated_at integer
|
||||
}
|
||||
|
||||
table github {
|
||||
@@ -397,20 +507,6 @@ table gitlab {
|
||||
gitProviderId text [not null]
|
||||
}
|
||||
|
||||
table gitea {
|
||||
giteaId text [pk, not null]
|
||||
giteaUrl text [not null, default: 'https://gitea.com']
|
||||
redirect_uri text
|
||||
client_id text [not null]
|
||||
client_secret text [not null]
|
||||
access_token text
|
||||
refresh_token text
|
||||
expires_at integer
|
||||
gitProviderId text [not null]
|
||||
scopes text [default: 'repo,repo:status,read:user,read:org']
|
||||
last_authenticated_at integer
|
||||
}
|
||||
|
||||
table gotify {
|
||||
gotifyId text [pk, not null]
|
||||
serverUrl text [not null]
|
||||
@@ -427,6 +523,7 @@ table invitation {
|
||||
status text [not null]
|
||||
expires_at timestamp [not null]
|
||||
inviter_id text [not null]
|
||||
team_id text
|
||||
}
|
||||
|
||||
table mariadb {
|
||||
@@ -447,8 +544,17 @@ table mariadb {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -458,6 +564,19 @@ table member {
|
||||
user_id text [not null]
|
||||
role text [not null]
|
||||
created_at timestamp [not null]
|
||||
team_id text
|
||||
canCreateProjects boolean [not null, default: false]
|
||||
canAccessToSSHKeys boolean [not null, default: false]
|
||||
canCreateServices boolean [not null, default: false]
|
||||
canDeleteProjects boolean [not null, default: false]
|
||||
canDeleteServices boolean [not null, default: false]
|
||||
canAccessToDocker boolean [not null, default: false]
|
||||
canAccessToAPI boolean [not null, default: false]
|
||||
canAccessToGitProviders boolean [not null, default: false]
|
||||
canAccessToTraefikFiles boolean [not null, default: false]
|
||||
accesedProjects text[] [not null, default: `ARRAY[]::text[]`]
|
||||
accessedEnvironments text[] [not null, default: `ARRAY[]::text[]`]
|
||||
accesedServices text[] [not null, default: `ARRAY[]::text[]`]
|
||||
}
|
||||
|
||||
table mongo {
|
||||
@@ -476,8 +595,17 @@ table mongo {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
replicaSets boolean [default: false]
|
||||
}
|
||||
@@ -518,8 +646,17 @@ table mysql {
|
||||
cpuLimit text
|
||||
externalPort integer
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -539,7 +676,17 @@ table notification {
|
||||
discordId text
|
||||
emailId text
|
||||
gotifyId text
|
||||
userId text
|
||||
ntfyId text
|
||||
customId text
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table ntfy {
|
||||
ntfyId text [pk, not null]
|
||||
serverUrl text [not null]
|
||||
topic text [not null]
|
||||
accessToken text [not null]
|
||||
priority integer [not null, default: 3]
|
||||
}
|
||||
|
||||
table organization {
|
||||
@@ -555,6 +702,7 @@ table organization {
|
||||
table port {
|
||||
portId text [pk, not null]
|
||||
publishedPort integer [not null]
|
||||
publishMode publishModeType [not null, default: 'host']
|
||||
targetPort integer [not null]
|
||||
protocol protocolType [not null]
|
||||
applicationId text [not null]
|
||||
@@ -577,8 +725,17 @@ table postgres {
|
||||
cpuReservation text
|
||||
cpuLimit text
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
createdAt text [not null]
|
||||
projectId text [not null]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -603,7 +760,7 @@ table project {
|
||||
name text [not null]
|
||||
description text
|
||||
createdAt text [not null]
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
env text [not null, default: '']
|
||||
}
|
||||
|
||||
@@ -633,7 +790,16 @@ table redis {
|
||||
externalPort integer
|
||||
createdAt text [not null]
|
||||
applicationStatus applicationStatus [not null, default: 'idle']
|
||||
projectId text [not null]
|
||||
healthCheckSwarm json
|
||||
restartPolicySwarm json
|
||||
placementSwarm json
|
||||
updateConfigSwarm json
|
||||
rollbackConfigSwarm json
|
||||
modeSwarm json
|
||||
labelsSwarm json
|
||||
networkSwarm json
|
||||
replicas integer [not null, default: 1]
|
||||
environmentId text [not null]
|
||||
serverId text
|
||||
}
|
||||
|
||||
@@ -646,7 +812,34 @@ table registry {
|
||||
registryUrl text [not null, default: '']
|
||||
createdAt text [not null]
|
||||
selfHosted RegistryType [not null, default: 'cloud']
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table rollback {
|
||||
rollbackId text [pk, not null]
|
||||
deploymentId text [not null]
|
||||
version serial [not null, increment]
|
||||
image text
|
||||
createdAt text [not null]
|
||||
fullContext jsonb
|
||||
}
|
||||
|
||||
table schedule {
|
||||
scheduleId text [pk, not null]
|
||||
name text [not null]
|
||||
cronExpression text [not null]
|
||||
appName text [not null]
|
||||
serviceName text
|
||||
shellType shellType [not null, default: 'bash']
|
||||
scheduleType scheduleType [not null, default: 'application']
|
||||
command text [not null]
|
||||
script text
|
||||
applicationId text
|
||||
composeId text
|
||||
serverId text
|
||||
userId text
|
||||
enabled boolean [not null, default: true]
|
||||
createdAt text [not null]
|
||||
}
|
||||
|
||||
table security {
|
||||
@@ -671,14 +864,14 @@ table server {
|
||||
appName text [not null]
|
||||
enableDockerCleanup boolean [not null, default: false]
|
||||
createdAt text [not null]
|
||||
userId text [not null]
|
||||
organizationId text [not null]
|
||||
serverStatus serverStatus [not null, default: 'active']
|
||||
command text [not null, default: '']
|
||||
sshKeyId text
|
||||
metricsConfig jsonb [not null, default: `{"server":{"type":"Remote","refreshRate":60,"port":4500,"token":"","urlCallback":"","cronJob":"","retentionDays":2,"thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
|
||||
}
|
||||
|
||||
table session {
|
||||
table session_temp {
|
||||
id text [pk, not null]
|
||||
expires_at timestamp [not null]
|
||||
token text [not null, unique]
|
||||
@@ -705,49 +898,49 @@ table "ssh-key" {
|
||||
description text
|
||||
createdAt text [not null]
|
||||
lastUsedAt text
|
||||
userId text
|
||||
organizationId text [not null]
|
||||
}
|
||||
|
||||
table telegram {
|
||||
telegramId text [pk, not null]
|
||||
botToken text [not null]
|
||||
chatId text [not null]
|
||||
messageThreadId text
|
||||
}
|
||||
|
||||
table user {
|
||||
table two_factor {
|
||||
id text [pk, not null]
|
||||
secret text [not null]
|
||||
backup_codes text [not null]
|
||||
user_id text [not null]
|
||||
}
|
||||
|
||||
table user_temp {
|
||||
id text [pk, not null]
|
||||
name text [not null, default: '']
|
||||
token text [not null]
|
||||
isRegistered boolean [not null, default: false]
|
||||
expirationDate text [not null]
|
||||
createdAt text [not null]
|
||||
canCreateProjects boolean [not null, default: false]
|
||||
canAccessToSSHKeys boolean [not null, default: false]
|
||||
canCreateServices boolean [not null, default: false]
|
||||
canDeleteProjects boolean [not null, default: false]
|
||||
canDeleteServices boolean [not null, default: false]
|
||||
canAccessToDocker boolean [not null, default: false]
|
||||
canAccessToAPI boolean [not null, default: false]
|
||||
canAccessToGitProviders boolean [not null, default: false]
|
||||
canAccessToTraefikFiles boolean [not null, default: false]
|
||||
accesedProjects text[] [not null, default: `ARRAY[]::text[]`]
|
||||
accesedServices text[] [not null, default: `ARRAY[]::text[]`]
|
||||
created_at timestamp [default: `now()`]
|
||||
two_factor_enabled boolean
|
||||
email text [not null, unique]
|
||||
email_verified boolean [not null]
|
||||
image text
|
||||
role text
|
||||
banned boolean
|
||||
ban_reason text
|
||||
ban_expires timestamp
|
||||
updated_at timestamp [not null]
|
||||
serverIp text
|
||||
certificateType certificateType [not null, default: 'none']
|
||||
https boolean [not null, default: false]
|
||||
host text
|
||||
letsEncryptEmail text
|
||||
sshPrivateKey text
|
||||
enableDockerCleanup boolean [not null, default: false]
|
||||
enableLogRotation boolean [not null, default: false]
|
||||
logCleanupCron text [default: '0 0 * * *']
|
||||
role text [not null, default: 'user']
|
||||
enablePaidFeatures boolean [not null, default: false]
|
||||
allowImpersonation boolean [not null, default: false]
|
||||
metricsConfig jsonb [not null, default: `{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
|
||||
cleanupCacheApplications boolean [not null, default: false]
|
||||
cleanupCacheOnPreviews boolean [not null, default: false]
|
||||
@@ -766,6 +959,29 @@ table verification {
|
||||
updated_at timestamp
|
||||
}
|
||||
|
||||
table volume_backup {
|
||||
volumeBackupId text [pk, not null]
|
||||
name text [not null]
|
||||
volumeName text [not null]
|
||||
prefix text [not null]
|
||||
serviceType serviceType [not null, default: 'application']
|
||||
appName text [not null]
|
||||
serviceName text
|
||||
turnOff boolean [not null, default: false]
|
||||
cronExpression text [not null]
|
||||
keepLatestCount integer
|
||||
enabled boolean
|
||||
applicationId text
|
||||
postgresId text
|
||||
mariadbId text
|
||||
mongoId text
|
||||
mysqlId text
|
||||
redisId text
|
||||
composeId text
|
||||
createdAt text [not null]
|
||||
destinationId text [not null]
|
||||
}
|
||||
|
||||
ref: mount.applicationId > application.applicationId
|
||||
|
||||
ref: mount.postgresId > postgres.postgresId
|
||||
@@ -780,7 +996,13 @@ ref: mount.redisId > redis.redisId
|
||||
|
||||
ref: mount.composeId > compose.composeId
|
||||
|
||||
ref: application.projectId > project.projectId
|
||||
ref: user_temp.id - account.user_id
|
||||
|
||||
ref: ai.organizationId - organization.id
|
||||
|
||||
ref: apikey.user_id > user_temp.id
|
||||
|
||||
ref: application.environmentId > environment.environmentId
|
||||
|
||||
ref: application.customGitSSHKeyId > "ssh-key".sshKeyId
|
||||
|
||||
@@ -790,6 +1012,8 @@ ref: application.githubId - github.githubId
|
||||
|
||||
ref: application.gitlabId - gitlab.gitlabId
|
||||
|
||||
ref: application.giteaId - gitea.giteaId
|
||||
|
||||
ref: application.bitbucketId - bitbucket.bitbucketId
|
||||
|
||||
ref: application.serverId > server.serverId
|
||||
@@ -804,13 +1028,17 @@ ref: backup.mysqlId > mysql.mysqlId
|
||||
|
||||
ref: backup.mongoId > mongo.mongoId
|
||||
|
||||
ref: backup.userId > user_temp.id
|
||||
|
||||
ref: backup.composeId > compose.composeId
|
||||
|
||||
ref: git_provider.gitProviderId - bitbucket.gitProviderId
|
||||
|
||||
ref: certificate.serverId > server.serverId
|
||||
|
||||
ref: certificate.userId - user.id
|
||||
ref: certificate.organizationId - organization.id
|
||||
|
||||
ref: compose.projectId > project.projectId
|
||||
ref: compose.environmentId > environment.environmentId
|
||||
|
||||
ref: compose.customGitSSHKeyId > "ssh-key".sshKeyId
|
||||
|
||||
@@ -820,6 +1048,8 @@ ref: compose.gitlabId - gitlab.gitlabId
|
||||
|
||||
ref: compose.bitbucketId - bitbucket.bitbucketId
|
||||
|
||||
ref: compose.giteaId - gitea.giteaId
|
||||
|
||||
ref: compose.serverId > server.serverId
|
||||
|
||||
ref: deployment.applicationId > application.applicationId
|
||||
@@ -830,7 +1060,15 @@ ref: deployment.serverId > server.serverId
|
||||
|
||||
ref: deployment.previewDeploymentId > preview_deployments.previewDeploymentId
|
||||
|
||||
ref: destination.userId - user.id
|
||||
ref: deployment.scheduleId > schedule.scheduleId
|
||||
|
||||
ref: deployment.backupId > backup.backupId
|
||||
|
||||
ref: rollback.deploymentId - deployment.deploymentId
|
||||
|
||||
ref: deployment.volumeBackupId > volume_backup.volumeBackupId
|
||||
|
||||
ref: destination.organizationId - organization.id
|
||||
|
||||
ref: domain.applicationId > application.applicationId
|
||||
|
||||
@@ -838,23 +1076,33 @@ ref: domain.composeId > compose.composeId
|
||||
|
||||
ref: preview_deployments.domainId - domain.domainId
|
||||
|
||||
ref: environment.projectId > project.projectId
|
||||
|
||||
ref: github.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: gitlab.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: gitea.gitProviderId - git_provider.gitProviderId
|
||||
|
||||
ref: git_provider.userId - user.id
|
||||
ref: git_provider.organizationId - organization.id
|
||||
|
||||
ref: mariadb.projectId > project.projectId
|
||||
ref: git_provider.userId - user_temp.id
|
||||
|
||||
ref: invitation.organization_id - organization.id
|
||||
|
||||
ref: mariadb.environmentId > environment.environmentId
|
||||
|
||||
ref: mariadb.serverId > server.serverId
|
||||
|
||||
ref: mongo.projectId > project.projectId
|
||||
ref: member.organization_id > organization.id
|
||||
|
||||
ref: member.user_id - user_temp.id
|
||||
|
||||
ref: mongo.environmentId > environment.environmentId
|
||||
|
||||
ref: mongo.serverId > server.serverId
|
||||
|
||||
ref: mysql.projectId > project.projectId
|
||||
ref: mysql.environmentId > environment.environmentId
|
||||
|
||||
ref: mysql.serverId > server.serverId
|
||||
|
||||
@@ -868,30 +1116,58 @@ ref: notification.emailId - email.emailId
|
||||
|
||||
ref: notification.gotifyId - gotify.gotifyId
|
||||
|
||||
ref: notification.userId - user.id
|
||||
ref: notification.ntfyId - ntfy.ntfyId
|
||||
|
||||
ref: notification.customId - custom.customId
|
||||
|
||||
ref: notification.organizationId - organization.id
|
||||
|
||||
ref: organization.owner_id > user_temp.id
|
||||
|
||||
ref: port.applicationId > application.applicationId
|
||||
|
||||
ref: postgres.projectId > project.projectId
|
||||
ref: postgres.environmentId > environment.environmentId
|
||||
|
||||
ref: postgres.serverId > server.serverId
|
||||
|
||||
ref: preview_deployments.applicationId > application.applicationId
|
||||
|
||||
ref: project.userId - user.id
|
||||
ref: project.organizationId > organization.id
|
||||
|
||||
ref: redirect.applicationId > application.applicationId
|
||||
|
||||
ref: redis.projectId > project.projectId
|
||||
ref: redis.environmentId > environment.environmentId
|
||||
|
||||
ref: redis.serverId > server.serverId
|
||||
|
||||
ref: registry.userId - user.id
|
||||
ref: schedule.applicationId - application.applicationId
|
||||
|
||||
ref: schedule.composeId > compose.composeId
|
||||
|
||||
ref: schedule.serverId > server.serverId
|
||||
|
||||
ref: schedule.userId > user_temp.id
|
||||
|
||||
ref: security.applicationId > application.applicationId
|
||||
|
||||
ref: server.userId - user.id
|
||||
|
||||
ref: server.sshKeyId > "ssh-key".sshKeyId
|
||||
|
||||
ref: "ssh-key".userId - user.id
|
||||
ref: server.organizationId > organization.id
|
||||
|
||||
ref: "ssh-key".organizationId - organization.id
|
||||
|
||||
ref: volume_backup.applicationId - application.applicationId
|
||||
|
||||
ref: volume_backup.postgresId - postgres.postgresId
|
||||
|
||||
ref: volume_backup.mariadbId - mariadb.mariadbId
|
||||
|
||||
ref: volume_backup.mongoId - mongo.mongoId
|
||||
|
||||
ref: volume_backup.mysqlId - mysql.mysqlId
|
||||
|
||||
ref: volume_backup.redisId - redis.redisId
|
||||
|
||||
ref: volume_backup.composeId - compose.composeId
|
||||
|
||||
ref: volume_backup.destinationId - destination.destinationId
|
||||
@@ -1,23 +1,26 @@
|
||||
import { db } from "@dokploy/server/db";
|
||||
import {
|
||||
type apiCreateCustom,
|
||||
type apiCreateDiscord,
|
||||
type apiCreateEmail,
|
||||
type apiCreateLark,
|
||||
type apiCreateGotify,
|
||||
type apiCreateLark,
|
||||
type apiCreateNtfy,
|
||||
type apiCreateSlack,
|
||||
type apiCreateTelegram,
|
||||
type apiUpdateCustom,
|
||||
type apiUpdateDiscord,
|
||||
type apiUpdateEmail,
|
||||
type apiUpdateLark,
|
||||
type apiUpdateGotify,
|
||||
type apiUpdateLark,
|
||||
type apiUpdateNtfy,
|
||||
type apiUpdateSlack,
|
||||
type apiUpdateTelegram,
|
||||
custom,
|
||||
discord,
|
||||
email,
|
||||
lark,
|
||||
gotify,
|
||||
lark,
|
||||
notifications,
|
||||
ntfy,
|
||||
slack,
|
||||
@@ -590,6 +593,94 @@ export const updateNtfyNotification = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const createCustomNotification = async (
|
||||
input: typeof apiCreateCustom._type,
|
||||
organizationId: string,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newCustom = await tx
|
||||
.insert(custom)
|
||||
.values({
|
||||
endpoint: input.endpoint,
|
||||
headers: input.headers,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newCustom) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error input: Inserting custom",
|
||||
});
|
||||
}
|
||||
|
||||
const newDestination = await tx
|
||||
.insert(notifications)
|
||||
.values({
|
||||
customId: newCustom.customId,
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
notificationType: "custom",
|
||||
organizationId: organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error input: Inserting notification",
|
||||
});
|
||||
}
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const updateCustomNotification = async (
|
||||
input: typeof apiUpdateCustom._type,
|
||||
) => {
|
||||
await db.transaction(async (tx) => {
|
||||
const newDestination = await tx
|
||||
.update(notifications)
|
||||
.set({
|
||||
name: input.name,
|
||||
appDeploy: input.appDeploy,
|
||||
appBuildError: input.appBuildError,
|
||||
databaseBackup: input.databaseBackup,
|
||||
dokployRestart: input.dokployRestart,
|
||||
dockerCleanup: input.dockerCleanup,
|
||||
organizationId: input.organizationId,
|
||||
serverThreshold: input.serverThreshold,
|
||||
})
|
||||
.where(eq(notifications.notificationId, input.notificationId))
|
||||
.returning()
|
||||
.then((value) => value[0]);
|
||||
|
||||
if (!newDestination) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Error Updating notification",
|
||||
});
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(custom)
|
||||
.set({
|
||||
endpoint: input.endpoint,
|
||||
headers: input.headers,
|
||||
})
|
||||
.where(eq(custom.customId, input.customId));
|
||||
|
||||
return newDestination;
|
||||
});
|
||||
};
|
||||
|
||||
export const findNotificationById = async (notificationId: string) => {
|
||||
const notification = await db.query.notifications.findFirst({
|
||||
where: eq(notifications.notificationId, notificationId),
|
||||
@@ -600,6 +691,7 @@ export const findNotificationById = async (notificationId: string) => {
|
||||
email: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
createDeploymentBackup,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server/services/deployment";
|
||||
import type { Mariadb } from "@dokploy/server/services/mariadb";
|
||||
import { findEnvironmentById } from "@dokploy/server/services/environment";
|
||||
import type { Mariadb } from "@dokploy/server/services/mariadb";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
createDeploymentBackup,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server/services/deployment";
|
||||
import type { Mongo } from "@dokploy/server/services/mongo";
|
||||
import { findEnvironmentById } from "@dokploy/server/services/environment";
|
||||
import type { Mongo } from "@dokploy/server/services/mongo";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
createDeploymentBackup,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server/services/deployment";
|
||||
import type { MySql } from "@dokploy/server/services/mysql";
|
||||
import { findEnvironmentById } from "@dokploy/server/services/environment";
|
||||
import type { MySql } from "@dokploy/server/services/mysql";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
@@ -3,8 +3,8 @@ import {
|
||||
createDeploymentBackup,
|
||||
updateDeploymentStatus,
|
||||
} from "@dokploy/server/services/deployment";
|
||||
import type { Postgres } from "@dokploy/server/services/postgres";
|
||||
import { findEnvironmentById } from "@dokploy/server/services/environment";
|
||||
import type { Postgres } from "@dokploy/server/services/postgres";
|
||||
import { findProjectById } from "@dokploy/server/services/project";
|
||||
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
|
||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||
|
||||
@@ -5,6 +5,7 @@ import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -45,12 +46,13 @@ export const sendBuildErrorNotifications = async ({
|
||||
slack: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
||||
notification;
|
||||
try {
|
||||
if (email) {
|
||||
@@ -220,6 +222,22 @@ export const sendBuildErrorNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
await sendCustomNotification(custom, {
|
||||
title: "Build Error",
|
||||
message: "Build failed with errors",
|
||||
projectName,
|
||||
applicationName,
|
||||
applicationType,
|
||||
errorMessage,
|
||||
buildLink,
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
status: "error",
|
||||
type: "build",
|
||||
});
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
const limitCharacter = 800;
|
||||
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -48,12 +49,13 @@ export const sendBuildSuccessNotifications = async ({
|
||||
slack: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
||||
notification;
|
||||
try {
|
||||
if (email) {
|
||||
@@ -181,7 +183,10 @@ export const sendBuildSuccessNotifications = async ({
|
||||
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Environment:</b> ${environmentName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
`<b>✅ Build Success</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Environment:</b> ${environmentName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(
|
||||
date,
|
||||
"PP",
|
||||
)}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
inlineButton,
|
||||
);
|
||||
}
|
||||
@@ -233,6 +238,22 @@ export const sendBuildSuccessNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
await sendCustomNotification(custom, {
|
||||
title: "Build Success",
|
||||
message: "Build completed successfully",
|
||||
projectName,
|
||||
applicationName,
|
||||
applicationType,
|
||||
buildLink,
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
domains: domains.map((domain) => domain.host).join(", "),
|
||||
status: "success",
|
||||
type: "build",
|
||||
});
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
await sendLarkNotification(lark, {
|
||||
msg_type: "interactive",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -45,12 +46,13 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
slack: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
||||
notification;
|
||||
try {
|
||||
if (email) {
|
||||
@@ -242,6 +244,25 @@ export const sendDatabaseBackupNotifications = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
await sendCustomNotification(custom, {
|
||||
title: `Database Backup ${type === "success" ? "Successful" : "Failed"}`,
|
||||
message:
|
||||
type === "success"
|
||||
? "Database backup completed successfully"
|
||||
: "Database backup failed",
|
||||
projectName,
|
||||
applicationName,
|
||||
databaseType,
|
||||
databaseName,
|
||||
type,
|
||||
errorMessage: errorMessage || "",
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
status: type,
|
||||
});
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
const limitCharacter = 800;
|
||||
const truncatedErrorMessage =
|
||||
|
||||
@@ -5,6 +5,7 @@ import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -32,12 +33,13 @@ export const sendDockerCleanupNotifications = async (
|
||||
slack: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
||||
notification;
|
||||
try {
|
||||
if (email) {
|
||||
@@ -139,6 +141,18 @@ export const sendDockerCleanupNotifications = async (
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
await sendCustomNotification(custom, {
|
||||
title: "Docker Cleanup",
|
||||
message: "Docker cleanup completed successfully",
|
||||
cleanupMessage: message,
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
status: "success",
|
||||
type: "docker-cleanup",
|
||||
});
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
await sendLarkNotification(lark, {
|
||||
msg_type: "interactive",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { renderAsync } from "@react-email/components";
|
||||
import { format } from "date-fns";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
sendGotifyNotification,
|
||||
@@ -26,12 +27,13 @@ export const sendDokployRestartNotifications = async () => {
|
||||
slack: true,
|
||||
gotify: true,
|
||||
ntfy: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
||||
const { email, discord, telegram, slack, gotify, ntfy, custom, lark } =
|
||||
notification;
|
||||
|
||||
try {
|
||||
@@ -101,7 +103,10 @@ export const sendDokployRestartNotifications = async () => {
|
||||
if (telegram) {
|
||||
await sendTelegramNotification(
|
||||
telegram,
|
||||
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
`<b>✅ Dokploy Server Restarted</b>\n\n<b>Date:</b> ${format(
|
||||
date,
|
||||
"PP",
|
||||
)}\n<b>Time:</b> ${format(date, "pp")}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,6 +130,21 @@ export const sendDokployRestartNotifications = async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
try {
|
||||
await sendCustomNotification(custom, {
|
||||
title: "Dokploy Server Restarted",
|
||||
message: "Dokploy server has been restarted successfully",
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
status: "success",
|
||||
type: "dokploy-restart",
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
await sendLarkNotification(lark, {
|
||||
msg_type: "interactive",
|
||||
@@ -181,7 +201,10 @@ export const sendDokployRestartNotifications = async () => {
|
||||
elements: [
|
||||
{
|
||||
tag: "markdown",
|
||||
content: `**Restart Time:**\n${format(date, "PP pp")}`,
|
||||
content: `**Restart Time:**\n${format(
|
||||
date,
|
||||
"PP pp",
|
||||
)}`,
|
||||
text_align: "left",
|
||||
text_size: "normal_v2",
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import { and, eq } from "drizzle-orm";
|
||||
import { db } from "../../db";
|
||||
import { notifications } from "../../db/schema";
|
||||
import {
|
||||
sendCustomNotification,
|
||||
sendDiscordNotification,
|
||||
sendLarkNotification,
|
||||
sendSlackNotification,
|
||||
@@ -35,6 +36,7 @@ export const sendServerThresholdNotifications = async (
|
||||
discord: true,
|
||||
telegram: true,
|
||||
slack: true,
|
||||
custom: true,
|
||||
lark: true,
|
||||
},
|
||||
});
|
||||
@@ -43,7 +45,7 @@ export const sendServerThresholdNotifications = async (
|
||||
const typeColor = 0xff0000; // Rojo para indicar alerta
|
||||
|
||||
for (const notification of notificationList) {
|
||||
const { discord, telegram, slack, lark } = notification;
|
||||
const { discord, telegram, slack, custom, lark } = notification;
|
||||
|
||||
if (discord) {
|
||||
const decorate = (decoration: string, text: string) =>
|
||||
@@ -154,6 +156,21 @@ export const sendServerThresholdNotifications = async (
|
||||
});
|
||||
}
|
||||
|
||||
if (custom) {
|
||||
await sendCustomNotification(custom, {
|
||||
title: `Server ${payload.Type} Alert`,
|
||||
message: payload.Message,
|
||||
serverName: payload.ServerName,
|
||||
type: payload.Type,
|
||||
currentValue: payload.Value,
|
||||
threshold: payload.Threshold,
|
||||
timestamp: date.toISOString(),
|
||||
date: date.toLocaleString(),
|
||||
status: "alert",
|
||||
alertType: "server-threshold",
|
||||
});
|
||||
}
|
||||
|
||||
if (lark) {
|
||||
await sendLarkNotification(lark, {
|
||||
msg_type: "interactive",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {
|
||||
custom,
|
||||
discord,
|
||||
email,
|
||||
gotify,
|
||||
@@ -175,6 +176,39 @@ export const sendNtfyNotification = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const sendCustomNotification = async (
|
||||
connection: typeof custom.$inferInsert,
|
||||
payload: Record<string, any>,
|
||||
) => {
|
||||
try {
|
||||
// Merge default headers with custom headers (now already an object from jsonb)
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
...(connection.headers || {}),
|
||||
};
|
||||
|
||||
// Default body with payload
|
||||
const body = JSON.stringify(payload);
|
||||
|
||||
const response = await fetch(connection.endpoint, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to send custom notification: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending custom notification:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const sendLarkNotification = async (
|
||||
connection: typeof lark.$inferInsert,
|
||||
message: any,
|
||||
|
||||
1194
schema.dbml
Normal file
1194
schema.dbml
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user