Ready for release 1.1.4

This commit is contained in:
charlesgauthereau
2025-12-07 10:53:08 +01:00
parent ca232250cc
commit e1bc1ec6c8
16 changed files with 1944 additions and 82 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "portabase", "name": "portabase",
"version": "1.1.3", "version": "1.1.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack -p 8887", "dev": "next dev --turbopack -p 8887",
@@ -5,6 +5,7 @@ import {NotifierCard} from "@/components/wrappers/dashboard/common/notifier/noti
import {useState} from "react"; import {useState} from "react";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder"; import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal";
type NotificationChannelsSectionProps = { type NotificationChannelsSectionProps = {
notificationChannels: NotificationChannelWith[] notificationChannels: NotificationChannelWith[]
@@ -22,8 +23,10 @@ export const NotificationChannelsSection = ({
return ( return (
<div className="h-full"> <div className="h-full">
<NotifierAddEditModal open={isAddModalOpen} onOpenChangeAction={setIsAddModalOpen} adminView={false} trigger={false}/>
{hasNotifiers ? ( {hasNotifiers ? (
<div className="h-full"> <div className="h-full">
<CardsWithPagination <CardsWithPagination
data={notificationChannels} data={notificationChannels}
cardItem={NotifierCard} cardItem={NotifierCard}
@@ -36,7 +39,9 @@ export const NotificationChannelsSection = ({
) : ( ) : (
<EmptyStatePlaceholder <EmptyStatePlaceholder
text="No notification channels configured yet" text="No notification channels configured yet"
onClick={() => setIsAddModalOpen(true)} onClick={() => {
setIsAddModalOpen(true)
}}
className="h-full" className="h-full"
/> />
)} )}
@@ -54,16 +54,21 @@ export function notificationLogsColumns(): ColumnDef<NotificationLogWithRelation
}, },
{ {
accessorKey: "policy", accessorKey: "policy",
header: "Event Kinds", header: "Event Kind",
cell: ({row}) => { cell: ({ row }) => {
const eventKinds = row.original.policy?.eventKinds ?? []; const eventKind = row.original.policy?.event;
if (!eventKind) {
return (
<div className="text-muted-foreground italic">
No event
</div>
);
}
return ( return (
<div> <div className="flex items-center gap-2">
{eventKinds.map((eventKind, idx) => ( <Badge>{eventKind}</Badge>
<div key={idx} className="flex items-center gap-2">
<Badge>{eventKind}</Badge>
</div>
))}
</div> </div>
); );
} }
@@ -14,7 +14,7 @@ import {Input} from "@/components/ui/input";
import {Form} from "@/components/ui/form"; import {Form} from "@/components/ui/form";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button";
import {useMutation} from "@tanstack/react-query"; import {useMutation} from "@tanstack/react-query";
import {TooltipProvider} from "@/components/ui/tooltip"; import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip";
import { import {
EmailFormSchema, EmailFormSchema,
@@ -26,6 +26,11 @@ import {
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action"; } from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action";
import {toast} from "sonner"; import {toast} from "sonner";
import {useRouter} from "next/navigation"; import {useRouter} from "next/navigation";
import {sendEmail} from "@/lib/email/email-helper";
import {render} from "@react-email/components";
import EmailSettingsTest from "@/components/emails/email-settings-test";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {Send} from "lucide-react";
export type EmailFormProps = { export type EmailFormProps = {
defaultValues?: EmailFormType; defaultValues?: EmailFormType;
@@ -36,6 +41,8 @@ export const EmailForm = (props: EmailFormProps) => {
schema: EmailFormSchema, schema: EmailFormSchema,
defaultValues: props.defaultValues, defaultValues: props.defaultValues,
}); });
const isDirty = form.formState.isDirty;
const router = useRouter(); const router = useRouter();
const mutation = useMutation({ const mutation = useMutation({
@@ -47,10 +54,45 @@ export const EmailForm = (props: EmailFormProps) => {
return; return;
} }
toast.success(`Success updating email informations`); toast.success(`Success updating email informations`);
form.reset(data);
router.refresh(); router.refresh();
}, },
}); });
const mutationSendEmailTest = useMutation({
mutationFn: async () => {
if (!props.defaultValues?.smtpUser || !props.defaultValues?.smtpFrom) {
toast.error("SMTP is not configured");
return;
}
try {
const email = await sendEmail({
to: props.defaultValues.smtpUser,
subject: "Portabase",
html: await render(EmailSettingsTest(), {}),
from: props.defaultValues.smtpFrom,
});
if (email?.response) {
toast.success("Test Email successfully sent!");
}
return email;
} catch (error: unknown) {
if (error instanceof Error) {
toast.error(`Email sending failed: ${error.message}`);
} else {
toast.error("Unknown error while sending test email");
}
throw error;
}
},
});
return ( return (
<TooltipProvider> <TooltipProvider>
<Card> <Card>
@@ -139,8 +181,56 @@ export const EmailForm = (props: EmailFormProps) => {
</FormItem> </FormItem>
)} )}
/> />
<div className="flex justify-end gap-4"> <div className="flex justify-between gap-4">
<Button>Save</Button>
{/*{props.defaultValues?.smtpFrom && (*/}
{/* <ButtonWithLoading*/}
{/* type="button"*/}
{/* isPending={mutationSendEmailTest.isPending}*/}
{/* onClick={async () => {*/}
{/* await mutationSendEmailTest.mutateAsync();*/}
{/* }}*/}
{/* icon={<Send/>}*/}
{/* size="default"*/}
{/* className="bg-green-600 hover:bg-green-700 text-white font-medium shadow-sm transition-all"*/}
{/* >Send email test</ButtonWithLoading>*/}
{/*)}*/}
{props.defaultValues?.smtpFrom && (
<Tooltip>
<TooltipTrigger asChild>
<div>
<ButtonWithLoading
type="button"
disabled={isDirty || mutationSendEmailTest.isPending}
isPending={mutationSendEmailTest.isPending}
onClick={async () => {
await mutationSendEmailTest.mutateAsync();
}}
icon={<Send />}
size="default"
className="bg-green-600 hover:bg-green-700 text-white font-medium shadow-sm transition-all"
>
Send email test
</ButtonWithLoading>
</div>
</TooltipTrigger>
{isDirty && (
<TooltipContent>
You must save changes before testing the email settings.
</TooltipContent>
)}
</Tooltip>
)}
<Button
type="submit"
>Save</Button>
</div> </div>
</Form> </Form>
</CardContent> </CardContent>
@@ -10,7 +10,7 @@ import {NotifierForm} from "@/components/wrappers/dashboard/common/notifier/noti
import {OrganizationWithMembers} from "@/db/schema/03_organization"; import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel"; import {NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {useIsMobile} from "@/hooks/use-mobile"; import {useIsMobile} from "@/hooks/use-mobile";
import {useState} from "react"; import {useEffect, useState} from "react";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs"; import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import { import {
NotifierChannelOrganisationForm NotifierChannelOrganisationForm
@@ -23,6 +23,7 @@ type OrganizationNotifierAddModalProps = {
onOpenChangeAction?: (open: boolean) => void; onOpenChangeAction?: (open: boolean) => void;
adminView?: boolean; adminView?: boolean;
organizations?: OrganizationWithMembers[] organizations?: OrganizationWithMembers[]
trigger?: boolean;
} }
@@ -32,33 +33,41 @@ export const NotifierAddEditModal = ({
open = false, open = false,
onOpenChangeAction, onOpenChangeAction,
adminView, adminView,
organizations organizations ,
trigger= true
}: OrganizationNotifierAddModalProps) => { }: OrganizationNotifierAddModalProps) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [openInternal, setOpen] = useState(open); const [openInternal, setOpen] = useState(open);
const isCreate = !Boolean(notificationChannel); const isCreate = !Boolean(notificationChannel);
useEffect(() => {
setOpen(open);
},[open])
return ( return (
<Dialog open={openInternal} onOpenChange={(state) => { <Dialog open={openInternal} onOpenChange={(state) => {
onOpenChangeAction?.(state); onOpenChangeAction?.(state);
setOpen(state); setOpen(state);
}}> }}>
<DialogTrigger asChild> {trigger && (
{isCreate ? <DialogTrigger asChild>
<Button> {isCreate ?
<Plus/>{!isMobile && `Add notification channel`} <Button>
</Button> <Plus/>{!isMobile && `Add notification channel`}
: </Button>
<Button :
variant="ghost" <Button
size="icon" variant="ghost"
> size="icon"
<Pencil className="h-4 w-4"/> >
</Button> <Pencil className="h-4 w-4"/>
} </Button>
</DialogTrigger> }
</DialogTrigger>
)}
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}> <DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
<DialogHeader> <DialogHeader>
<DialogTitle> {isCreate ? "Add" : "Edit"} Notification Channel</DialogTitle> <DialogTitle> {isCreate ? "Add" : "Edit"} Notification Channel</DialogTitle>
@@ -35,8 +35,10 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
const router = useRouter() const router = useRouter()
const organizationNotificationChannels = notificationChannels.map(channel => channel.id) ?? []; const organizationNotificationChannels = notificationChannels.map(channel => channel.id) ?? [];
const formattedAlertPoliciesList = (alertPolicies: AlertPolicy[]) => { const formattedAlertPoliciesList = (alertPolicies: AlertPolicy[]) => {
return alertPolicies.map((alertPolicy) => ({ return alertPolicies.filter((alertPolicy)=> organizationNotificationChannels.includes(alertPolicy.notificationChannelId)).map((alertPolicy) => ({
notificationChannelId: alertPolicy.notificationChannelId, notificationChannelId: alertPolicy.notificationChannelId,
eventKinds: alertPolicy.eventKinds, eventKinds: alertPolicy.eventKinds,
enabled: alertPolicy.enabled enabled: alertPolicy.enabled
@@ -273,11 +275,6 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div tabIndex={0} className="inline-flex rounded-md"> <div tabIndex={0} className="inline-flex rounded-md">
{/*<Switch*/}
{/* checked={true}*/}
{/* onCheckedChange={async () => {*/}
{/* }}*/}
{/*/>*/}
<FormField <FormField
@@ -26,14 +26,22 @@ type AlertPolicyModalProps = {
export const AlertPolicyModal = ({database, notificationChannels, organizationId}: AlertPolicyModalProps) => { export const AlertPolicyModal = ({database, notificationChannels, organizationId}: AlertPolicyModalProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const notificationsChannelsIds = notificationChannels.map(channel => channel.id);
const activePolicies = database.alertPolicies?.filter((policy) => notificationsChannelsIds.some(()=> policy.notificationChannelId));
const notificationsChannelsFiltered = notificationChannels
.filter((channel) => channel.enabled)
const notificationsChannelsIds = notificationsChannelsFiltered
.map(channel => channel.id);
const activePolicies = database.alertPolicies?.filter((policy) => notificationsChannelsIds.includes(policy.notificationChannelId));
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" onClick={() => setOpen(true)} className="relative"> <Button variant="outline" onClick={() => setOpen(true)} className="relative">
<Megaphone/> <Megaphone/>
{ activePolicies && activePolicies.length > 0 && ( {activePolicies && activePolicies.length > 0 && (
<Badge <Badge
className="absolute -top-1.5 -right-1.5 h-4 w-4 rounded-full p-0 text-[10px] flex items-center justify-center" className="absolute -top-1.5 -right-1.5 h-4 w-4 rounded-full p-0 text-[10px] flex items-center justify-center"
> >
@@ -52,7 +60,7 @@ export const AlertPolicyModal = ({database, notificationChannels, organizationId
<Separator className="mt-3 mb-3"/> <Separator className="mt-3 mb-3"/>
<AlertPolicyForm <AlertPolicyForm
organizationId={organizationId} organizationId={organizationId}
notificationChannels={notificationChannels} notificationChannels={notificationsChannelsFiltered}
database={database} database={database}
onSuccess={() => setOpen(false)} onSuccess={() => setOpen(false)}
/> />
+9
View File
@@ -0,0 +1,9 @@
ALTER TABLE "notification_log" DROP CONSTRAINT "notification_log_channel_id_notification_channel_id_fk";
--> statement-breakpoint
ALTER TABLE "notification_log" DROP CONSTRAINT "notification_log_policy_id_alert_policy_id_fk";
--> statement-breakpoint
ALTER TABLE "notification_log" DROP CONSTRAINT "notification_log_organization_id_organization_id_fk";
--> statement-breakpoint
ALTER TABLE "notification_log" ADD COLUMN "event" text;--> statement-breakpoint
ALTER TABLE "notification_log" ADD COLUMN "provider" text NOT NULL;--> statement-breakpoint
ALTER TABLE "notification_log" ADD COLUMN "provider_name" text NOT NULL;
File diff suppressed because it is too large Load Diff
+7
View File
@@ -78,6 +78,13 @@
"when": 1764497732581, "when": 1764497732581,
"tag": "0010_past_trauma", "tag": "0010_past_trauma",
"breakpoints": true "breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1765097874110,
"tag": "0011_outgoing_blob",
"breakpoints": true
} }
] ]
} }
+2 -4
View File
@@ -2,7 +2,7 @@ import {boolean, pgEnum, pgTable, uuid} from "drizzle-orm/pg-core";
import {notificationChannel} from "@/db/schema/09_notification-channel"; import {notificationChannel} from "@/db/schema/09_notification-channel";
import {timestamps} from "@/db/schema/00_common"; import {timestamps} from "@/db/schema/00_common";
import {relations} from "drizzle-orm"; import {relations} from "drizzle-orm";
import {database, retentionPolicy} from "@/db/schema/07_database"; import {database} from "@/db/schema/07_database";
import {createSelectSchema} from "drizzle-zod"; import {createSelectSchema} from "drizzle-zod";
import {z} from "zod"; import {z} from "zod";
@@ -32,9 +32,7 @@ export const alertPolicyRelations = relations(alertPolicy, ({one}) => ({
}), }),
})); }));
export const notificationChannelsToAlertPoliciesRelations = relations(notificationChannel, ({many}) => ({
alertPolicies: many(alertPolicy),
}));
export const alertPolicySchema = createSelectSchema(alertPolicy); export const alertPolicySchema = createSelectSchema(alertPolicy);
+13 -8
View File
@@ -1,10 +1,10 @@
import {pgTable, uuid, timestamp, jsonb, varchar, boolean, text, pgEnum} from 'drizzle-orm/pg-core'; import {pgTable, uuid, timestamp, jsonb, varchar, boolean, text, pgEnum} from 'drizzle-orm/pg-core';
import {notificationChannel} from "@/db/schema/09_notification-channel"; import {notificationChannel} from "@/db/schema/09_notification-channel";
import {alertPolicy} from "@/db/schema/10_alert-policy"; import {alertPolicy} from "@/db/schema/10_alert-policy";
import {organization} from "@/db/schema/03_organization";
import {timestamps} from "@/db/schema/00_common"; import {timestamps} from "@/db/schema/00_common";
import {createSelectSchema} from "drizzle-zod"; import {createSelectSchema} from "drizzle-zod";
import {z} from "zod"; import {z} from "zod";
import {relations} from "drizzle-orm";
export const levelEnum = pgEnum('level', ['critical', 'warning', 'info']); export const levelEnum = pgEnum('level', ['critical', 'warning', 'info']);
@@ -13,13 +13,13 @@ export const levelEnum = pgEnum('level', ['critical', 'warning', 'info']);
export const notificationLog = pgTable('notification_log', { export const notificationLog = pgTable('notification_log', {
id: uuid('id').defaultRandom().primaryKey(), id: uuid('id').defaultRandom().primaryKey(),
channelId: uuid('channel_id') channelId: uuid('channel_id').notNull(),
.notNull() policyId: uuid('policy_id'),
.references(() => notificationChannel.id, {onDelete: 'restrict'}), organizationId: uuid('organization_id'),
policyId: uuid('policy_id')
.references(() => alertPolicy.id, {onDelete: 'restrict'}), event: text("event"),
organizationId: uuid('organization_id') provider: text("provider").notNull(),
.references(() => organization.id, {onDelete: 'set null'}), providerName: text("provider_name").notNull(),
title: varchar('title', {length: 255}).notNull(), title: varchar('title', {length: 255}).notNull(),
message: text('message').notNull(), message: text('message').notNull(),
@@ -35,6 +35,11 @@ export const notificationLog = pgTable('notification_log', {
...timestamps ...timestamps
}); });
export const notificationChannelsToAlertPoliciesRelations = relations(notificationChannel, ({many}) => ({
alertPolicies: many(alertPolicy),
}));
export const notificationLogSchema = createSelectSchema(notificationLog); export const notificationLogSchema = createSelectSchema(notificationLog);
export type NotificationLog = z.infer<typeof notificationLogSchema>; export type NotificationLog = z.infer<typeof notificationLogSchema>;
+5 -10
View File
@@ -2,7 +2,6 @@ import {and, desc, eq, gte, lte} from 'drizzle-orm';
import {NotificationLevel, notificationLog} from "@/db/schema/11_notification-log"; import {NotificationLevel, notificationLog} from "@/db/schema/11_notification-log";
import {notificationChannel} from "@/db/schema/09_notification-channel"; import {notificationChannel} from "@/db/schema/09_notification-channel";
import {db} from "@/db"; import {db} from "@/db";
import {alertPolicy} from "@/db/schema/10_alert-policy";
import {Json} from "drizzle-zod"; import {Json} from "drizzle-zod";
export type NotificationLogWithRelations = { export type NotificationLogWithRelations = {
@@ -18,13 +17,11 @@ export type NotificationLogWithRelations = {
message: string; message: string;
}, },
channel: { channel: {
id: string;
name: string; name: string;
provider: string; provider: string;
} | null; } | null;
policy: { policy: {
id: string; event: string | null;
eventKinds: string[];
} | null; } | null;
}; };
@@ -63,18 +60,16 @@ export async function getNotificationHistory(
message: notificationLog.message, message: notificationLog.message,
}, },
channel: { channel: {
id: notificationChannel.id, name: notificationLog.providerName,
name: notificationChannel.name, provider: notificationLog.provider,
provider: notificationChannel.provider,
}, },
policy: { policy: {
id: alertPolicy.id, event: notificationLog.event,
eventKinds: alertPolicy.eventKinds,
}, },
}) })
.from(notificationLog) .from(notificationLog)
.leftJoin(notificationChannel, eq(notificationLog.channelId, notificationChannel.id)) .leftJoin(notificationChannel, eq(notificationLog.channelId, notificationChannel.id))
.leftJoin(alertPolicy, eq(notificationLog.policyId, alertPolicy.id)) // .leftJoin(alertPolicy, eq(notificationLog.policyId, alertPolicy.id))
.where(and(...where)) .where(and(...where))
.orderBy(desc(notificationLog.sentAt)) .orderBy(desc(notificationLog.sentAt))
.limit(filters?.limit || 100); .limit(filters?.limit || 100);
+22 -16
View File
@@ -1,12 +1,13 @@
"use server"; "use server";
import { eq } from "drizzle-orm"; import {eq} from "drizzle-orm";
import { dispatchViaProvider } from "./providers"; import {dispatchViaProvider} from "./providers";
import type {EventPayload, DispatchResult} from "./types"; import type {EventPayload, DispatchResult, EventKind} from "./types";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import { db } from "@/db"; import {db} from "@/db";
import { notificationLog } from "@/db/schema/11_notification-log"; import {notificationLog} from "@/db/schema/11_notification-log";
import {NotificationChannel} from "@/db/schema/09_notification-channel"; import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {Json} from "drizzle-zod"; import {Json} from "drizzle-zod";
import {AlertPolicy} from "@/db/schema/10_alert-policy";
export async function dispatchNotification( export async function dispatchNotification(
payload: EventPayload, payload: EventPayload,
@@ -18,14 +19,14 @@ export async function dispatchNotification(
let channel: NotificationChannel | null = null; let channel: NotificationChannel | null = null;
if (policyId) { if (policyId) {
const policy = await db.query.alertPolicy.findFirst({ const policyDb = await db.query.alertPolicy.findFirst({
where: eq(drizzleDb.schemas.alertPolicy.id, policyId), where: eq(drizzleDb.schemas.alertPolicy.id, policyId),
with: { with: {
notificationChannel: true notificationChannel: true
}, },
}); });
if (!policy || !policy.notificationChannel) { if (!policyDb || !policyDb.notificationChannel) {
return { return {
success: false, success: false,
channelId: "", channelId: "",
@@ -34,18 +35,18 @@ export async function dispatchNotification(
}; };
} }
if (!policy.enabled || !policy.notificationChannel.enabled) { if (!policyDb.enabled || !policyDb.notificationChannel.enabled) {
return { return {
success: false, success: false,
channelId: policy.notificationChannel.id, channelId: policyDb.notificationChannel.id,
provider: policy.notificationChannel.provider as any, provider: policyDb.notificationChannel.provider as any,
error: "Policy or channel is disabled", error: "Policy or channel is disabled",
}; };
} }
channel = { channel = {
...policy.notificationChannel, ...policyDb.notificationChannel,
config : policy.notificationChannel.config as Json, config: policyDb.notificationChannel.config as Json,
}; };
} }
@@ -65,7 +66,7 @@ export async function dispatchNotification(
channel = { channel = {
...fetchedChannel, ...fetchedChannel,
config : fetchedChannel.config as Json, config: fetchedChannel.config as Json,
}; };
} }
@@ -91,7 +92,7 @@ export async function dispatchNotification(
const result = await dispatchViaProvider( const result = await dispatchViaProvider(
channel.provider, channel.provider,
channel.config, channel.config,
{ ...payload, timestamp: payload.timestamp || new Date() }, {...payload, timestamp: payload.timestamp || new Date()},
channel.id channel.id
); );
@@ -101,6 +102,11 @@ export async function dispatchNotification(
channelId: channel.id, channelId: channel.id,
policyId: policyId || null, policyId: policyId || null,
organizationId: organizationId || null, organizationId: organizationId || null,
provider: channel.provider,
providerName: channel.name,
event: payload.event as EventKind,
title: payload.title, title: payload.title,
message: payload.message, message: payload.message,
level: payload.level, level: payload.level,
@@ -109,9 +115,9 @@ export async function dispatchNotification(
error: result.success ? null : result.error, error: result.success ? null : result.error,
providerResponse: result.response || null, providerResponse: result.response || null,
}) })
.returning({ id: notificationLog.id }); .returning({id: notificationLog.id});
return { ...result, channelId: channel.id }; return {...result, channelId: channel.id};
} catch (err: any) { } catch (err: any) {
return { return {
+2 -3
View File
@@ -1,10 +1,8 @@
import {DatabaseWith} from "@/db/schema/07_database"; import {DatabaseWith} from "@/db/schema/07_database";
import {EventPayload} from "@/features/notifications/types"; import {EventKind, EventPayload} from "@/features/notifications/types";
import {dispatchNotification} from "@/features/notifications/dispatch"; import {dispatchNotification} from "@/features/notifications/dispatch";
type EventKind = ("error_backup" | "error_restore" | "success_restore" | "success_backup" | "weekly_report")
export async function sendNotificationsBackupRestore(database: DatabaseWith, event: EventKind) { export async function sendNotificationsBackupRestore(database: DatabaseWith, event: EventKind) {
if (database.alertPolicies && database.alertPolicies.length > 0) { if (database.alertPolicies && database.alertPolicies.length > 0) {
@@ -52,6 +50,7 @@ export async function sendNotificationsBackupRestore(database: DatabaseWith, eve
title: titleMap[event], title: titleMap[event],
message, message,
level: level, level: level,
event: event,
data: { data: {
host: database.name, host: database.name,
id: database.id, id: database.id,
+3
View File
@@ -14,5 +14,8 @@ export interface EventPayload {
message: string; message: string;
level: 'critical' | 'warning' | 'info'; level: 'critical' | 'warning' | 'info';
timestamp?: Date; timestamp?: Date;
event?: EventKind
data?: Record<string, any>; data?: Record<string, any>;
} }
export type EventKind = ("error_backup" | "error_restore" | "success_restore" | "success_backup" | "weekly_report")