mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Ready for release 1.1.4
This commit is contained in:
+1
-1
@@ -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",
|
||||||
|
|||||||
+6
-1
@@ -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 (
|
return (
|
||||||
<div>
|
<div className="text-muted-foreground italic">
|
||||||
{eventKinds.map((eventKind, idx) => (
|
No event
|
||||||
<div key={idx} className="flex items-center gap-2">
|
|
||||||
<Badge>{eventKind}</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge>{eventKind}</Badge>
|
||||||
</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,19 +33,25 @@ 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);
|
||||||
}}>
|
}}>
|
||||||
|
{trigger && (
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
{isCreate ?
|
{isCreate ?
|
||||||
<Button>
|
<Button>
|
||||||
@@ -59,6 +66,8 @@ export const NotifierAddEditModal = ({
|
|||||||
</Button>
|
</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,8 +26,16 @@ 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>
|
||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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
@@ -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,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);
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user