mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-15 16:35:17 +00:00
feat: add mattermost notification provider
Add comprehensive Mattermost integration as a new notification provider: ## Backend Implementation: - Add `mattermost` to notificationType enum and database schema - Create mattermost table with webhookUrl, channel, username fields - Implement CRUD operations: createMattermostNotification, updateMattermostNotification - Add API routes: createMattermost, updateMattermost, testMattermostConnection - Add sendMattermostNotification utility with proper payload formatting ## Frontend Implementation: - Add MattermostIcon component with provided SVG logo - Extend notification form with Mattermost schema validation - Add webhook URL (required), channel and username (optional) form fields - Integrate test connection functionality - Add Mattermost to provider selection UI ## Notification Integration: - Integrate across all notification types: - Build success/error notifications - Database backup notifications - Docker cleanup notifications - Dokploy restart notifications - Server threshold alerts - Format messages using Markdown for Mattermost compatibility - Handle optional channel (#prefix) and username override - Graceful fallback for empty optional fields ## Features: - Webhook-based messaging to Mattermost channels - Optional channel targeting and custom username display - Consistent formatting with other notification providers - Full CRUD support with proper validation - Test connection capability Closes: Support for Mattermost team communication platform # Conflicts: # apps/dokploy/components/dashboard/settings/notifications/handle-notifications.tsx # apps/dokploy/components/icons/notification-icons.tsx # apps/dokploy/server/api/routers/notification.ts # packages/server/src/db/schema/notification.ts # packages/server/src/services/notification.ts # packages/server/src/utils/notifications/build-error.ts # packages/server/src/utils/notifications/build-success.ts # packages/server/src/utils/notifications/database-backup.ts # packages/server/src/utils/notifications/docker-cleanup.ts # packages/server/src/utils/notifications/dokploy-restart.ts # packages/server/src/utils/notifications/server-threshold.ts # packages/server/src/utils/notifications/utils.ts
This commit is contained in:
@@ -6,6 +6,7 @@ import { toast } from "sonner";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
|
MattermostIcon,
|
||||||
GotifyIcon,
|
GotifyIcon,
|
||||||
LarkIcon,
|
LarkIcon,
|
||||||
NtfyIcon,
|
NtfyIcon,
|
||||||
@@ -108,6 +109,14 @@ export const notificationSchema = z.discriminatedUnion("type", [
|
|||||||
})
|
})
|
||||||
.merge(notificationBaseSchema),
|
.merge(notificationBaseSchema),
|
||||||
z
|
z
|
||||||
|
.object({
|
||||||
|
type: z.literal("mattermost"),
|
||||||
|
webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
|
||||||
|
channel: z.string().optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
})
|
||||||
|
.merge(notificationBaseSchema),
|
||||||
|
z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("lark"),
|
type: z.literal("lark"),
|
||||||
webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
|
webhookUrl: z.string().min(1, { message: "Webhook URL is required" }),
|
||||||
@@ -144,6 +153,10 @@ export const notificationsMap = {
|
|||||||
icon: <NtfyIcon />,
|
icon: <NtfyIcon />,
|
||||||
label: "ntfy",
|
label: "ntfy",
|
||||||
},
|
},
|
||||||
|
mattermost: {
|
||||||
|
icon: <MattermostIcon />,
|
||||||
|
label: "Mattermost",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
export type NotificationSchema = z.infer<typeof notificationSchema>;
|
||||||
@@ -177,6 +190,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
api.notification.testGotifyConnection.useMutation();
|
api.notification.testGotifyConnection.useMutation();
|
||||||
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
const { mutateAsync: testNtfyConnection, isLoading: isLoadingNtfy } =
|
||||||
api.notification.testNtfyConnection.useMutation();
|
api.notification.testNtfyConnection.useMutation();
|
||||||
|
const { mutateAsync: testMattermostConnection, isLoading: isLoadingMattermost } =
|
||||||
|
api.notification.testMattermostConnection.useMutation();
|
||||||
const { mutateAsync: testLarkConnection, isLoading: isLoadingLark } =
|
const { mutateAsync: testLarkConnection, isLoading: isLoadingLark } =
|
||||||
api.notification.testLarkConnection.useMutation();
|
api.notification.testLarkConnection.useMutation();
|
||||||
const slackMutation = notificationId
|
const slackMutation = notificationId
|
||||||
@@ -197,6 +212,9 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
const ntfyMutation = notificationId
|
const ntfyMutation = notificationId
|
||||||
? api.notification.updateNtfy.useMutation()
|
? api.notification.updateNtfy.useMutation()
|
||||||
: api.notification.createNtfy.useMutation();
|
: api.notification.createNtfy.useMutation();
|
||||||
|
const mattermostMutation = notificationId
|
||||||
|
? api.notification.updateMattermost.useMutation()
|
||||||
|
: api.notification.createMattermost.useMutation();
|
||||||
const larkMutation = notificationId
|
const larkMutation = notificationId
|
||||||
? api.notification.updateLark.useMutation()
|
? api.notification.updateLark.useMutation()
|
||||||
: api.notification.createLark.useMutation();
|
: api.notification.createLark.useMutation();
|
||||||
@@ -323,6 +341,20 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
dockerCleanup: notification.dockerCleanup,
|
dockerCleanup: notification.dockerCleanup,
|
||||||
serverThreshold: notification.serverThreshold,
|
serverThreshold: notification.serverThreshold,
|
||||||
});
|
});
|
||||||
|
} else if (notification.notificationType === "mattermost") {
|
||||||
|
form.reset({
|
||||||
|
appBuildError: notification.appBuildError,
|
||||||
|
appDeploy: notification.appDeploy,
|
||||||
|
dokployRestart: notification.dokployRestart,
|
||||||
|
databaseBackup: notification.databaseBackup,
|
||||||
|
type: notification.notificationType,
|
||||||
|
webhookUrl: notification.mattermost?.webhookUrl,
|
||||||
|
channel: notification.mattermost?.channel || "",
|
||||||
|
username: notification.mattermost?.username || "",
|
||||||
|
name: notification.name,
|
||||||
|
dockerCleanup: notification.dockerCleanup,
|
||||||
|
serverThreshold: notification.serverThreshold,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -336,6 +368,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
email: emailMutation,
|
email: emailMutation,
|
||||||
gotify: gotifyMutation,
|
gotify: gotifyMutation,
|
||||||
ntfy: ntfyMutation,
|
ntfy: ntfyMutation,
|
||||||
|
mattermost: mattermostMutation,
|
||||||
lark: larkMutation,
|
lark: larkMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -440,6 +473,21 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
ntfyId: notification?.ntfyId || "",
|
ntfyId: notification?.ntfyId || "",
|
||||||
});
|
});
|
||||||
|
} else if (data.type === "mattermost") {
|
||||||
|
promise = mattermostMutation.mutateAsync({
|
||||||
|
appBuildError: appBuildError,
|
||||||
|
appDeploy: appDeploy,
|
||||||
|
dokployRestart: dokployRestart,
|
||||||
|
databaseBackup: databaseBackup,
|
||||||
|
webhookUrl: data.webhookUrl,
|
||||||
|
channel: data.channel || undefined,
|
||||||
|
username: data.username || undefined,
|
||||||
|
name: data.name,
|
||||||
|
dockerCleanup: dockerCleanup,
|
||||||
|
notificationId: notificationId || "",
|
||||||
|
mattermostId: notification?.mattermostId || "",
|
||||||
|
serverThreshold: serverThreshold,
|
||||||
|
});
|
||||||
} else if (data.type === "lark") {
|
} else if (data.type === "lark") {
|
||||||
promise = larkMutation.mutateAsync({
|
promise = larkMutation.mutateAsync({
|
||||||
appBuildError: appBuildError,
|
appBuildError: appBuildError,
|
||||||
@@ -451,6 +499,8 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
dockerCleanup: dockerCleanup,
|
dockerCleanup: dockerCleanup,
|
||||||
notificationId: notificationId || "",
|
notificationId: notificationId || "",
|
||||||
larkId: notification?.larkId || "",
|
larkId: notification?.larkId || "",
|
||||||
|
|
||||||
|
|
||||||
serverThreshold: serverThreshold,
|
serverThreshold: serverThreshold,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1040,6 +1090,60 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{type === "mattermost" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="webhookUrl"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Webhook URL</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="https://your-mattermost.com/hooks/xxx-generatedkey-xxx"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="channel"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Channel</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="deployments" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Optional. Channel to post to (without #).
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Dokploy" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Optional. Display name for the webhook.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{type === "lark" && (
|
{type === "lark" && (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -1211,6 +1315,7 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
isLoadingEmail ||
|
isLoadingEmail ||
|
||||||
isLoadingGotify ||
|
isLoadingGotify ||
|
||||||
isLoadingNtfy ||
|
isLoadingNtfy ||
|
||||||
|
isLoadingMattermost ||
|
||||||
isLoadingLark
|
isLoadingLark
|
||||||
}
|
}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@@ -1255,6 +1360,12 @@ export const HandleNotifications = ({ notificationId }: Props) => {
|
|||||||
accessToken: form.getValues("accessToken"),
|
accessToken: form.getValues("accessToken"),
|
||||||
priority: form.getValues("priority"),
|
priority: form.getValues("priority"),
|
||||||
});
|
});
|
||||||
|
} else if (type === "mattermost") {
|
||||||
|
await testMattermostConnection({
|
||||||
|
webhookUrl: form.getValues("webhookUrl"),
|
||||||
|
channel: form.getValues("channel") || undefined,
|
||||||
|
username: form.getValues("username") || undefined,
|
||||||
|
});
|
||||||
} else if (type === "lark") {
|
} else if (type === "lark") {
|
||||||
await testLarkConnection({
|
await testLarkConnection({
|
||||||
webhookUrl: form.getValues("webhookUrl"),
|
webhookUrl: form.getValues("webhookUrl"),
|
||||||
|
|||||||
@@ -88,6 +88,20 @@ export const DiscordIcon = ({ className }: Props) => {
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MattermostIcon = ({ className }: Props) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="#0061ff"
|
||||||
|
viewBox="0 0 501 501"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn("size-8", className)}
|
||||||
|
>
|
||||||
|
<path d="M236 .7C137.7 7.5 54 68.2 18.2 158.5c-32 81-19.6 172.8 33 242.5 39.8 53 97.2 87 164.3 97 16.5 2.7 48 3.2 63.5 1.2 48.7-6.3 92.2-24.6 129-54.2 13-10.5 33-31.2 42.2-43.7 26.4-35.5 42.8-75.8 49-120.3 1.6-12.3 1.6-48.7 0-61-4-28.3-12-54.8-24.2-79.5-12.8-26-26.5-45.3-46.8-65.8C417.8 64 400.2 49 398.4 49c-.6 0-.4 10.5.3 26l1.3 26 7 8.7c19 23.7 32.8 53.5 38.2 83 2.5 14 3 43 1 55.8-4.5 27.8-15.2 54-31 76.5-8.6 12.2-28 31.6-40.2 40.2-24 17-50 27.6-80 33-10 1.8-49 1.8-59 0-43-7.7-78.8-26-107.2-54.8-29.3-29.7-46.5-64-52.4-104.4-2-14-1.5-42 1-55C90 121.4 132 72 192 49.7c8-3 18.4-5.8 29.5-8.2 1.7-.4 34.4-38 35.3-40.6.3-1-10.2-1-20.8-.4z"/>
|
||||||
|
<path d="M322.2 24.6c-1.3.8-8.4 9.3-16 18.7-7.4 9.5-22.4 28-33.2 41.2-51 62.2-66 81.6-70.6 91-6 12-8.4 21-9 33-1.2 19.8 5 36 19 50C222 268 230 273 243 277.2c9 3 10.4 3.2 24 3.2 13.8 0 15 0 22.6-3 23.2-9 39-28.4 45-55.7 2-8.2 2-28.7.4-79.7l-2-72c-1-36.8-1.4-41.8-3-44-2-3-4.8-3.6-7.8-1.4z"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
export const LarkIcon = ({ className }: Props) => {
|
export const LarkIcon = ({ className }: Props) => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
createEmailNotification,
|
createEmailNotification,
|
||||||
createLarkNotification,
|
createLarkNotification,
|
||||||
createGotifyNotification,
|
createGotifyNotification,
|
||||||
|
createMattermostNotification,
|
||||||
createNtfyNotification,
|
createNtfyNotification,
|
||||||
createSlackNotification,
|
createSlackNotification,
|
||||||
createTelegramNotification,
|
createTelegramNotification,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendServerThresholdNotifications,
|
sendServerThresholdNotifications,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
@@ -21,6 +23,7 @@ import {
|
|||||||
updateEmailNotification,
|
updateEmailNotification,
|
||||||
updateLarkNotification,
|
updateLarkNotification,
|
||||||
updateGotifyNotification,
|
updateGotifyNotification,
|
||||||
|
updateMattermostNotification,
|
||||||
updateNtfyNotification,
|
updateNtfyNotification,
|
||||||
updateSlackNotification,
|
updateSlackNotification,
|
||||||
updateTelegramNotification,
|
updateTelegramNotification,
|
||||||
@@ -40,6 +43,7 @@ import {
|
|||||||
apiCreateEmail,
|
apiCreateEmail,
|
||||||
apiCreateLark,
|
apiCreateLark,
|
||||||
apiCreateGotify,
|
apiCreateGotify,
|
||||||
|
apiCreateMattermost,
|
||||||
apiCreateNtfy,
|
apiCreateNtfy,
|
||||||
apiCreateSlack,
|
apiCreateSlack,
|
||||||
apiCreateTelegram,
|
apiCreateTelegram,
|
||||||
@@ -48,6 +52,7 @@ import {
|
|||||||
apiTestEmailConnection,
|
apiTestEmailConnection,
|
||||||
apiTestLarkConnection,
|
apiTestLarkConnection,
|
||||||
apiTestGotifyConnection,
|
apiTestGotifyConnection,
|
||||||
|
apiTestMattermostConnection,
|
||||||
apiTestNtfyConnection,
|
apiTestNtfyConnection,
|
||||||
apiTestSlackConnection,
|
apiTestSlackConnection,
|
||||||
apiTestTelegramConnection,
|
apiTestTelegramConnection,
|
||||||
@@ -55,6 +60,7 @@ import {
|
|||||||
apiUpdateEmail,
|
apiUpdateEmail,
|
||||||
apiUpdateLark,
|
apiUpdateLark,
|
||||||
apiUpdateGotify,
|
apiUpdateGotify,
|
||||||
|
apiUpdateMattermost,
|
||||||
apiUpdateNtfy,
|
apiUpdateNtfy,
|
||||||
apiUpdateSlack,
|
apiUpdateSlack,
|
||||||
apiUpdateTelegram,
|
apiUpdateTelegram,
|
||||||
@@ -334,6 +340,7 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
email: true,
|
email: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
orderBy: desc(notifications.createdAt),
|
orderBy: desc(notifications.createdAt),
|
||||||
@@ -518,7 +525,63 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createLark: adminProcedure
|
createMattermost: adminProcedure
|
||||||
|
.input(apiCreateMattermost)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
return await createMattermostNotification(
|
||||||
|
input,
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error creating the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
updateMattermost: adminProcedure
|
||||||
|
.input(apiUpdateMattermost)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
|
const notification = await findNotificationById(input.notificationId);
|
||||||
|
if (
|
||||||
|
IS_CLOUD &&
|
||||||
|
notification.organizationId !== ctx.session.activeOrganizationId
|
||||||
|
) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "UNAUTHORIZED",
|
||||||
|
message: "You are not authorized to update this notification",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await updateMattermostNotification({
|
||||||
|
...input,
|
||||||
|
organizationId: ctx.session.activeOrganizationId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
testMattermostConnection: adminProcedure
|
||||||
|
.input(apiTestMattermostConnection)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
await sendMattermostNotification(input, {
|
||||||
|
text: "Hi, From Dokploy 👋",
|
||||||
|
channel: input.channel,
|
||||||
|
username: input.username || "Dokploy Bot",
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error testing the notification",
|
||||||
|
cause: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
createLark: adminProcedure
|
||||||
.input(apiCreateLark)
|
.input(apiCreateLark)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const notificationType = pgEnum("notificationType", [
|
|||||||
"email",
|
"email",
|
||||||
"gotify",
|
"gotify",
|
||||||
"ntfy",
|
"ntfy",
|
||||||
|
"mattermost",
|
||||||
"lark",
|
"lark",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ export const notifications = pgTable("notification", {
|
|||||||
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
ntfyId: text("ntfyId").references(() => ntfy.ntfyId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
mattermostId: text("mattermostId").references(() => mattermost.mattermostId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
larkId: text("larkId").references(() => lark.larkId, {
|
larkId: text("larkId").references(() => lark.larkId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
@@ -120,6 +124,16 @@ export const ntfy = pgTable("ntfy", {
|
|||||||
priority: integer("priority").notNull().default(3),
|
priority: integer("priority").notNull().default(3),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const mattermost = pgTable("mattermost", {
|
||||||
|
mattermostId: text("mattermostId")
|
||||||
|
.notNull()
|
||||||
|
.primaryKey()
|
||||||
|
.$defaultFn(() => nanoid()),
|
||||||
|
webhookUrl: text("webhookUrl").notNull(),
|
||||||
|
channel: text("channel"),
|
||||||
|
username: text("username"),
|
||||||
|
});
|
||||||
|
|
||||||
export const lark = pgTable("lark", {
|
export const lark = pgTable("lark", {
|
||||||
larkId: text("larkId")
|
larkId: text("larkId")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -153,6 +167,10 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
|
|||||||
fields: [notifications.ntfyId],
|
fields: [notifications.ntfyId],
|
||||||
references: [ntfy.ntfyId],
|
references: [ntfy.ntfyId],
|
||||||
}),
|
}),
|
||||||
|
mattermost: one(mattermost, {
|
||||||
|
fields: [notifications.mattermostId],
|
||||||
|
references: [mattermost.mattermostId],
|
||||||
|
}),
|
||||||
lark: one(lark, {
|
lark: one(lark, {
|
||||||
fields: [notifications.larkId],
|
fields: [notifications.larkId],
|
||||||
references: [lark.larkId],
|
references: [lark.larkId],
|
||||||
@@ -349,6 +367,49 @@ export const apiTestNtfyConnection = apiCreateNtfy.pick({
|
|||||||
priority: true,
|
priority: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apiCreateMattermost = notificationsSchema
|
||||||
|
.pick({
|
||||||
|
appBuildError: true,
|
||||||
|
databaseBackup: true,
|
||||||
|
dokployRestart: true,
|
||||||
|
name: true,
|
||||||
|
appDeploy: true,
|
||||||
|
dockerCleanup: true,
|
||||||
|
serverThreshold: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
webhookUrl: z.string().min(1),
|
||||||
|
channel: z.string().optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
})
|
||||||
|
.required({
|
||||||
|
name: true,
|
||||||
|
webhookUrl: true,
|
||||||
|
appBuildError: true,
|
||||||
|
databaseBackup: true,
|
||||||
|
dokployRestart: true,
|
||||||
|
appDeploy: true,
|
||||||
|
dockerCleanup: true,
|
||||||
|
serverThreshold: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiUpdateMattermost = apiCreateMattermost.partial().extend({
|
||||||
|
notificationId: z.string().min(1),
|
||||||
|
mattermostId: z.string().min(1),
|
||||||
|
organizationId: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiTestMattermostConnection = apiCreateMattermost
|
||||||
|
.pick({
|
||||||
|
webhookUrl: true,
|
||||||
|
channel: true,
|
||||||
|
username: true,
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
channel: z.string().optional(),
|
||||||
|
username: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const apiFindOneNotification = notificationsSchema
|
export const apiFindOneNotification = notificationsSchema
|
||||||
.pick({
|
.pick({
|
||||||
notificationId: true,
|
notificationId: true,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type apiCreateEmail,
|
type apiCreateEmail,
|
||||||
type apiCreateLark,
|
type apiCreateLark,
|
||||||
type apiCreateGotify,
|
type apiCreateGotify,
|
||||||
|
type apiCreateMattermost,
|
||||||
type apiCreateNtfy,
|
type apiCreateNtfy,
|
||||||
type apiCreateSlack,
|
type apiCreateSlack,
|
||||||
type apiCreateTelegram,
|
type apiCreateTelegram,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
type apiUpdateEmail,
|
type apiUpdateEmail,
|
||||||
type apiUpdateLark,
|
type apiUpdateLark,
|
||||||
type apiUpdateGotify,
|
type apiUpdateGotify,
|
||||||
|
type apiUpdateMattermost,
|
||||||
type apiUpdateNtfy,
|
type apiUpdateNtfy,
|
||||||
type apiUpdateSlack,
|
type apiUpdateSlack,
|
||||||
type apiUpdateTelegram,
|
type apiUpdateTelegram,
|
||||||
@@ -18,6 +20,7 @@ import {
|
|||||||
email,
|
email,
|
||||||
lark,
|
lark,
|
||||||
gotify,
|
gotify,
|
||||||
|
mattermost,
|
||||||
notifications,
|
notifications,
|
||||||
ntfy,
|
ntfy,
|
||||||
slack,
|
slack,
|
||||||
@@ -588,6 +591,7 @@ export const findNotificationById = async (notificationId: string) => {
|
|||||||
email: true,
|
email: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -711,3 +715,95 @@ export const updateNotificationById = async (
|
|||||||
|
|
||||||
return result[0];
|
return result[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createMattermostNotification = async (
|
||||||
|
input: typeof apiCreateMattermost._type,
|
||||||
|
organizationId: string,
|
||||||
|
) => {
|
||||||
|
await db.transaction(async (tx) => {
|
||||||
|
const newMattermost = await tx
|
||||||
|
.insert(mattermost)
|
||||||
|
.values({
|
||||||
|
webhookUrl: input.webhookUrl,
|
||||||
|
channel: input.channel,
|
||||||
|
username: input.username,
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
if (!newMattermost) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Error input: Inserting mattermost",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDestination = await tx
|
||||||
|
.insert(notifications)
|
||||||
|
.values({
|
||||||
|
mattermostId: newMattermost.mattermostId,
|
||||||
|
name: input.name,
|
||||||
|
appDeploy: input.appDeploy,
|
||||||
|
appBuildError: input.appBuildError,
|
||||||
|
databaseBackup: input.databaseBackup,
|
||||||
|
dokployRestart: input.dokployRestart,
|
||||||
|
dockerCleanup: input.dockerCleanup,
|
||||||
|
notificationType: "mattermost",
|
||||||
|
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 updateMattermostNotification = async (
|
||||||
|
input: typeof apiUpdateMattermost._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(mattermost)
|
||||||
|
.set({
|
||||||
|
webhookUrl: input.webhookUrl,
|
||||||
|
channel: input.channel,
|
||||||
|
username: input.username,
|
||||||
|
})
|
||||||
|
.where(eq(mattermost.mattermostId, input.mattermostId))
|
||||||
|
.returning()
|
||||||
|
.then((value) => value[0]);
|
||||||
|
|
||||||
|
return newDestination;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -45,13 +46,13 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
const { email, discord, telegram, slack, gotify, ntfy, mattermost, lark } = notification;
|
||||||
notification;
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
BuildFailedEmail({
|
BuildFailedEmail({
|
||||||
@@ -215,6 +216,26 @@ export const sendBuildErrorNotifications = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mattermost) {
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `:warning: **Build Failed**
|
||||||
|
|
||||||
|
**Project:** ${projectName}
|
||||||
|
**Application:** ${applicationName}
|
||||||
|
**Type:** ${applicationType}
|
||||||
|
**Time:** ${date.toLocaleString()}
|
||||||
|
|
||||||
|
**Error:**
|
||||||
|
\`\`\`
|
||||||
|
${errorMessage}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
[View Build Details](${buildLink})`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy Bot",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (lark) {
|
if (lark) {
|
||||||
const limitCharacter = 800;
|
const limitCharacter = 800;
|
||||||
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
const truncatedErrorMessage = errorMessage.substring(0, limitCharacter);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -46,13 +47,13 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
const { email, discord, telegram, slack, gotify, ntfy, mattermost, lark } = notification;
|
||||||
notification;
|
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -317,5 +318,13 @@ export const sendBuildSuccessNotifications = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mattermost) {
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `**✅ Build Success**\n\n**Project:** ${projectName}\n**Application:** ${applicationName}\n**Type:** ${applicationType}\n**Date:** ${format(date, "PP")}\n**Time:** ${format(date, "pp")}\n\n[View Build Details](${buildLink})`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -45,13 +46,13 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
const { email, discord, telegram, slack, gotify, ntfy, mattermost, lark } = notification;
|
||||||
notification;
|
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -356,5 +357,19 @@ export const sendDatabaseBackupNotifications = async ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mattermost) {
|
||||||
|
const statusEmoji = type === "success" ? "✅" : "❌";
|
||||||
|
const typeStatus = type === "success" ? "Successful" : "Failed";
|
||||||
|
const errorMsg = type === "error" && errorMessage
|
||||||
|
? `\n\n**Error:**\n\`\`\`\n${errorMessage}\n\`\`\``
|
||||||
|
: "";
|
||||||
|
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `**${statusEmoji} Database Backup ${typeStatus}**\n\n**Project:** ${projectName}\n**Application:** ${applicationName}\n**Type:** ${databaseType}\n**Database Name:** ${databaseName}\n**Date:** ${format(date, "PP")}\n**Time:** ${format(date, "pp")}${errorMsg}`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -32,13 +33,13 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
const { email, discord, telegram, slack, gotify, ntfy, mattermost, lark } = notification;
|
||||||
notification;
|
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -139,7 +140,15 @@ export const sendDockerCleanupNotifications = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lark) {
|
if (mattermost) {
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `**✅ Docker Cleanup**\n\n**Message:** ${message}\n**Date:** ${format(date, "PP")}\n**Time:** ${format(date, "pp")}`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lark) {
|
||||||
await sendLarkNotification(lark, {
|
await sendLarkNotification(lark, {
|
||||||
msg_type: "interactive",
|
msg_type: "interactive",
|
||||||
card: {
|
card: {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendGotifyNotification,
|
sendGotifyNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendNtfyNotification,
|
sendNtfyNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -26,13 +27,13 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
slack: true,
|
slack: true,
|
||||||
gotify: true,
|
gotify: true,
|
||||||
ntfy: true,
|
ntfy: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { email, discord, telegram, slack, gotify, ntfy, lark } =
|
const { email, discord, telegram, slack, gotify, ntfy, mattermost, lark } = notification;
|
||||||
notification;
|
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
const template = await renderAsync(
|
const template = await renderAsync(
|
||||||
@@ -139,6 +140,18 @@ export const sendDokployRestartNotifications = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mattermost) {
|
||||||
|
try {
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `**✅ Dokploy Server Restarted**\n\n**Date:** ${format(date, "PP")}\n**Time:** ${format(date, "pp")}`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lark) {
|
if (lark) {
|
||||||
try {
|
try {
|
||||||
await sendLarkNotification(lark, {
|
await sendLarkNotification(lark, {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { db } from "../../db";
|
|||||||
import { notifications } from "../../db/schema";
|
import { notifications } from "../../db/schema";
|
||||||
import {
|
import {
|
||||||
sendDiscordNotification,
|
sendDiscordNotification,
|
||||||
|
sendMattermostNotification,
|
||||||
sendLarkNotification,
|
sendLarkNotification,
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
sendTelegramNotification,
|
sendTelegramNotification,
|
||||||
@@ -35,6 +36,7 @@ export const sendServerThresholdNotifications = async (
|
|||||||
discord: true,
|
discord: true,
|
||||||
telegram: true,
|
telegram: true,
|
||||||
slack: true,
|
slack: true,
|
||||||
|
mattermost: true,
|
||||||
lark: true,
|
lark: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -43,7 +45,7 @@ export const sendServerThresholdNotifications = async (
|
|||||||
const typeColor = 0xff0000; // Rojo para indicar alerta
|
const typeColor = 0xff0000; // Rojo para indicar alerta
|
||||||
|
|
||||||
for (const notification of notificationList) {
|
for (const notification of notificationList) {
|
||||||
const { discord, telegram, slack, lark } = notification;
|
const { discord, telegram, slack, mattermost, lark } = notification;
|
||||||
|
|
||||||
if (discord) {
|
if (discord) {
|
||||||
const decorate = (decoration: string, text: string) =>
|
const decorate = (decoration: string, text: string) =>
|
||||||
@@ -154,7 +156,15 @@ export const sendServerThresholdNotifications = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lark) {
|
if (mattermost) {
|
||||||
|
await sendMattermostNotification(mattermost, {
|
||||||
|
text: `**⚠️ Server ${payload.Type} Alert**\n\n**Server Name:** ${payload.ServerName}\n**Type:** ${payload.Type}\n**Current Value:** ${payload.Value.toFixed(2)}%\n**Threshold:** ${payload.Threshold.toFixed(2)}%\n**Message:** ${payload.Message}\n**Time:** ${date.toLocaleString()}`,
|
||||||
|
channel: mattermost.channel,
|
||||||
|
username: mattermost.username || "Dokploy",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lark) {
|
||||||
await sendLarkNotification(lark, {
|
await sendLarkNotification(lark, {
|
||||||
msg_type: "interactive",
|
msg_type: "interactive",
|
||||||
card: {
|
card: {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type {
|
|||||||
email,
|
email,
|
||||||
lark,
|
lark,
|
||||||
gotify,
|
gotify,
|
||||||
|
mattermost,
|
||||||
ntfy,
|
ntfy,
|
||||||
slack,
|
slack,
|
||||||
telegram,
|
telegram,
|
||||||
@@ -154,6 +155,28 @@ export const sendNtfyNotification = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendMattermostNotification = async (
|
||||||
|
connection: typeof mattermost.$inferInsert,
|
||||||
|
message: any,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
...message,
|
||||||
|
// Only include username if it's provided and not empty
|
||||||
|
...(message.username && message.username.trim() && { username: message.username }),
|
||||||
|
// Only include wchannel if it's provided and not empty
|
||||||
|
...(message.channel && message.channel.trim() && { channel: `#${message.channel.replace('#', '')}` }),
|
||||||
|
};
|
||||||
|
|
||||||
|
await fetch(connection.webhookUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
|
||||||
export const sendLarkNotification = async (
|
export const sendLarkNotification = async (
|
||||||
connection: typeof lark.$inferInsert,
|
connection: typeof lark.$inferInsert,
|
||||||
message: any,
|
message: any,
|
||||||
|
|||||||
Reference in New Issue
Block a user