mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Merge main into feature/pushover-notifications, add Microsoft Teams provider
Both branches added a migration at index 0062. Resolved by keeping upstream's teams migration at 0062 and promoting pushover to 0063. Both providers are now included in the enum, types, handlers, and forms.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."provider_kind" ADD VALUE 'teams';
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "93d81732-8e94-4c2e-be9d-79c7bacc398e",
|
||||
"id": "679bcdd7-cf48-43d5-965d-0814b49196e1",
|
||||
"prevId": "e6d15bce-96b6-468c-8853-08efe5a18ecd",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -2844,7 +2844,7 @@
|
||||
"ntfy",
|
||||
"webhook",
|
||||
"nextcloud",
|
||||
"pushover"
|
||||
"teams"
|
||||
]
|
||||
},
|
||||
"public.event_kind": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -439,8 +439,15 @@
|
||||
{
|
||||
"idx": 62,
|
||||
"version": "7",
|
||||
"when": 1780934519843,
|
||||
"tag": "0062_futuristic_dragon_lord",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 63,
|
||||
"version": "7",
|
||||
"when": 1781854323074,
|
||||
"tag": "0062_open_proudstar",
|
||||
"tag": "0063_open_proudstar",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,7 +7,7 @@ import {z} from "zod";
|
||||
import {OrganizationInvitation} from "@/db/schema/05_invitation";
|
||||
|
||||
|
||||
export const providerKindEnum = pgEnum('provider_kind', ['slack', 'smtp', 'discord', 'telegram', 'gotify', 'ntfy', 'webhook', 'nextcloud', 'pushover']);
|
||||
export const providerKindEnum = pgEnum('provider_kind', ['slack', 'smtp', 'discord', 'telegram', 'gotify', 'ntfy', 'webhook', 'nextcloud', 'teams', 'pushover']);
|
||||
|
||||
export const notificationChannel = pgTable('notification_channel', {
|
||||
id: uuid("id").defaultRandom().primaryKey(),
|
||||
|
||||
@@ -11,6 +11,7 @@ import {PushoverChannelConfigSchema} from "@/features/channel/notifications/push
|
||||
import {S3ChannelConfigSchema} from "@/features/channel/storages/s3.schema";
|
||||
import {GoogleDriveChannelConfigSchema} from "@/features/channel/storages/google-drive/google-drive.schema";
|
||||
import {LocalChannelConfigSchema} from "@/features/channel/storages/local.schema";
|
||||
import {TeamsChannelConfigSchema} from "@/features/channel/notifications/teams.schema";
|
||||
|
||||
|
||||
const BaseChannelFormSchema = z.object({
|
||||
@@ -54,6 +55,10 @@ export const NotificationChannelFormSchema = z.discriminatedUnion("provider", [
|
||||
provider: z.literal("nextcloud"),
|
||||
config: NextcloudChannelConfigSchema,
|
||||
}),
|
||||
BaseChannelFormSchema.extend({
|
||||
provider: z.literal("teams"),
|
||||
config: TeamsChannelConfigSchema,
|
||||
}),
|
||||
BaseChannelFormSchema.extend({
|
||||
provider: z.literal("pushover"),
|
||||
config: PushoverChannelConfigSchema,
|
||||
|
||||
@@ -29,6 +29,9 @@ import {
|
||||
import {
|
||||
notificationProviders,
|
||||
} from "@/features/channel/channels-notification-helper";
|
||||
import {
|
||||
NotifierTeamsForm
|
||||
} from "@/features/channel/notifications/teams.form";
|
||||
import {storageProviders} from "@/features/channel/channels-storage-helper";
|
||||
import {ForwardRefExoticComponent, JSX, RefAttributes, SVGProps} from "react";
|
||||
import {LucideProps} from "lucide-react";
|
||||
@@ -95,6 +98,8 @@ export const renderChannelForm = (provider: string | undefined, form: UseFormRet
|
||||
return <NotifierWebhookForm form={form}/>;
|
||||
case "nextcloud":
|
||||
return <NotifierNextcloudForm form={form}/>;
|
||||
case "teams":
|
||||
return <NotifierTeamsForm form={form}/>;
|
||||
case "pushover":
|
||||
return <NotifierPushoverForm form={form}/>;
|
||||
case "s3":
|
||||
|
||||
@@ -14,8 +14,8 @@ export const notificationProviders: ProviderIconTypes[] = [
|
||||
{value: "ntfy", label: "ntfy.sh", icon: NtfyIcon},
|
||||
{value: "webhook", label: "Webhook", icon: WebhookIcon},
|
||||
{value: "nextcloud", label: "Nextcloud Talk", icon: NextcloudIcon},
|
||||
{value: "teams", label: "Microsoft Teams", icon: MSTeamsIcon},
|
||||
{value: "pushover", label: "Pushover", icon: PushoverIcon},
|
||||
{value: "microsoft-teams", label: "Microsoft Teams", icon: MSTeamsIcon, preview: true}
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {sendNtfy} from "@/features/channel/notifications/ntfy";
|
||||
import {sendWebhook} from "@/features/channel/notifications/webhook";
|
||||
import {sendNextcloud} from "@/features/channel/notifications/nextcloud";
|
||||
import {sendPushover} from "@/features/channel/notifications/pushover";
|
||||
import {sendTeams} from "@/features/channel/notifications/teams"
|
||||
|
||||
const handlers: Record<
|
||||
ProviderKind,
|
||||
@@ -22,6 +23,7 @@ const handlers: Record<
|
||||
ntfy: sendNtfy,
|
||||
webhook: sendWebhook,
|
||||
nextcloud: sendNextcloud,
|
||||
teams: sendTeams,
|
||||
pushover: sendPushover,
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
|
||||
type NotifierTeamsFormProps = {
|
||||
form: UseFormReturn<any, any, any>;
|
||||
};
|
||||
|
||||
export const NotifierTeamsForm = ({ form }: NotifierTeamsFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Separator className="my-1" />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.teamsWebhook"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Teams Webhook URL *</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
{...field}
|
||||
placeholder="e.g. https://xxxxx.logic.azure.com:443/xxxxx"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import {z} from "zod";
|
||||
|
||||
export const TeamsChannelConfigSchema = z.object({
|
||||
teamsWebhook: z.string().url("Must be a valid URL"),
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import type { EventPayload, DispatchResult } from '@/features/notifications/notifications.types';
|
||||
|
||||
|
||||
|
||||
export async function sendTeams(
|
||||
config: { teamsWebhook: string },
|
||||
payload: EventPayload
|
||||
): Promise<DispatchResult> {
|
||||
const { teamsWebhook: webhookUrl } = config;
|
||||
const levelColor: Record<string, string> = {
|
||||
critical: 'attention',
|
||||
warning: 'warning',
|
||||
info: 'good',
|
||||
};
|
||||
const accentColor = levelColor[payload.level] ?? 'default';
|
||||
const bodyItems: object[] = [
|
||||
{
|
||||
type: 'TextBlock',
|
||||
text: `**[${payload.level.toUpperCase()}] ${payload.title}**`,
|
||||
wrap: true,
|
||||
color: accentColor,
|
||||
size: 'Medium',
|
||||
weight: 'Bolder',
|
||||
},
|
||||
{
|
||||
type: 'TextBlock',
|
||||
text: payload.message,
|
||||
wrap: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (payload.data) {
|
||||
bodyItems.push(
|
||||
{
|
||||
type: 'TextBlock',
|
||||
text: '**Data:**',
|
||||
wrap: true,
|
||||
weight: 'Bolder',
|
||||
},
|
||||
{
|
||||
type: 'TextBlock',
|
||||
text: JSON.stringify(payload.data, null, 2).substring(0, 1000),
|
||||
wrap: true,
|
||||
fontType: 'Monospace',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const adaptiveCard = {
|
||||
type: 'AdaptiveCard',
|
||||
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
|
||||
version: '1.2',
|
||||
body: bodyItems,
|
||||
};
|
||||
|
||||
const body = {
|
||||
type: 'message',
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'application/vnd.microsoft.card.adaptive',
|
||||
contentUrl: null,
|
||||
content: adaptiveCard,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 10_000);
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
signal: controller.signal,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
throw new Error(`Teams error: ${res.status} ${err}`);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
provider: 'teams',
|
||||
message: 'Sent to Microsoft Teams',
|
||||
response: res.statusText,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ProviderKind = 'slack' | 'smtp' | 'discord' | 'telegram' | 'gotify' | 'ntfy' | 'webhook' | 'nextcloud' | 'pushover';
|
||||
export type ProviderKind = 'slack' | 'smtp' | 'discord' | 'telegram' | 'gotify' | 'ntfy' | 'webhook' | 'nextcloud' | 'teams' | 'pushover';
|
||||
|
||||
export interface DispatchResult {
|
||||
success: boolean;
|
||||
|
||||
Reference in New Issue
Block a user