mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-22 12:20:04 +00:00
message push config
This commit is contained in:
parent
5422f17fab
commit
4c9095400e
@ -25,6 +25,7 @@ import { update } from "@/repository/settings";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
import { PbErrorData } from "@/domain/base";
|
||||
import { useState } from "react";
|
||||
import { EmailsSetting } from "@/domain/settings";
|
||||
|
||||
type EmailsEditProps = {
|
||||
className?: string;
|
||||
@ -51,7 +52,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
});
|
||||
|
||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||
if (emails.content.emails.includes(data.email)) {
|
||||
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
|
||||
form.setError("email", {
|
||||
message: "邮箱已存在",
|
||||
});
|
||||
@ -59,7 +60,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
||||
}
|
||||
|
||||
// 保存到 config
|
||||
const newEmails = [...emails.content.emails, data.email];
|
||||
const newEmails = [...(emails.content as EmailsSetting).emails, data.email];
|
||||
|
||||
try {
|
||||
const resp = await update({
|
||||
|
146
ui/src/components/notify/DingTalk.tsx
Normal file
146
ui/src/components/notify/DingTalk.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { Label } from "../ui/label";
|
||||
import { useNotify } from "@/providers/notify";
|
||||
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
|
||||
import { useEffect, useState } from "react";
|
||||
import { update } from "@/repository/settings";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
type DingTalkSetting = {
|
||||
id: string;
|
||||
name: string;
|
||||
data: NotifyChannelDingTalk;
|
||||
};
|
||||
|
||||
const DingTalk = () => {
|
||||
const { config, setChannels } = useNotify();
|
||||
|
||||
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
accessToken: "",
|
||||
secret: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getDetailDingTalk = () => {
|
||||
const df: NotifyChannelDingTalk = {
|
||||
accessToken: "",
|
||||
secret: "",
|
||||
enabled: false,
|
||||
};
|
||||
if (!config.content) {
|
||||
return df;
|
||||
}
|
||||
const chanels = config.content as NotifyChannels;
|
||||
if (!chanels.dingtalk) {
|
||||
return df;
|
||||
}
|
||||
|
||||
return chanels.dingtalk as NotifyChannelDingTalk;
|
||||
};
|
||||
const data = getDetailDingTalk();
|
||||
setDingtalk({
|
||||
id: config.id ?? "",
|
||||
name: "dingtalk",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
dingtalk: {
|
||||
...dingtalk.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "配置保存成功",
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "配置保存失败:" + msg,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="AccessToken"
|
||||
value={dingtalk.data.accessToken}
|
||||
onChange={(e) => {
|
||||
setDingtalk({
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
accessToken: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
placeholder="加签的签名"
|
||||
className="mt-2"
|
||||
value={dingtalk.data.secret}
|
||||
onChange={(e) => {
|
||||
setDingtalk({
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
secret: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
checked={dingtalk.data.enabled}
|
||||
onCheckedChange={() => {
|
||||
setDingtalk({
|
||||
...dingtalk,
|
||||
data: {
|
||||
...dingtalk.data,
|
||||
enabled: !dingtalk.data.enabled,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DingTalk;
|
104
ui/src/components/notify/NotifyTemplate.tsx
Normal file
104
ui/src/components/notify/NotifyTemplate.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { Input } from "../ui/input";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import { Button } from "../ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
defaultNotifyTemplate,
|
||||
NotifyTemplates,
|
||||
NotifyTemplate as NotifyTemplateT,
|
||||
} from "@/domain/settings";
|
||||
import { getSetting, update } from "@/repository/settings";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
const NotifyTemplate = () => {
|
||||
const [id, setId] = useState("");
|
||||
const [templates, setTemplates] = useState<NotifyTemplateT[]>([
|
||||
defaultNotifyTemplate,
|
||||
]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
const featchData = async () => {
|
||||
const resp = await getSetting("templates");
|
||||
|
||||
if (resp.content) {
|
||||
setTemplates((resp.content as NotifyTemplates).notifyTemplates);
|
||||
setId(resp.id ? resp.id : "");
|
||||
}
|
||||
};
|
||||
featchData();
|
||||
}, []);
|
||||
|
||||
const handleTitleChange = (val: string) => {
|
||||
const template = templates[0];
|
||||
|
||||
setTemplates([
|
||||
{
|
||||
...template,
|
||||
title: val,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const handleContentChange = (val: string) => {
|
||||
const template = templates[0];
|
||||
|
||||
setTemplates([
|
||||
{
|
||||
...template,
|
||||
content: val,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
const resp = await update({
|
||||
id: id,
|
||||
content: {
|
||||
notifyTemplates: templates,
|
||||
},
|
||||
name: "templates",
|
||||
});
|
||||
|
||||
if (resp.id) {
|
||||
setId(resp.id);
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "通知模板保存成功",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
value={templates[0].title}
|
||||
onChange={(e) => {
|
||||
handleTitleChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="text-muted-foreground text-sm mt-1">
|
||||
可选的变量, COUNT:即将过期张数
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
className="mt-2"
|
||||
value={templates[0].content}
|
||||
onChange={(e) => {
|
||||
handleContentChange(e.target.value);
|
||||
}}
|
||||
></Textarea>
|
||||
<div className="text-muted-foreground text-sm mt-1">
|
||||
可选的变量, COUNT:即将过期张数,DOMAINS:域名列表
|
||||
</div>
|
||||
<div className="flex justify-end mt-2">
|
||||
<Button onClick={handleSaveClick}>保存</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotifyTemplate;
|
131
ui/src/components/notify/Telegram.tsx
Normal file
131
ui/src/components/notify/Telegram.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { Label } from "../ui/label";
|
||||
import { useNotify } from "@/providers/notify";
|
||||
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
|
||||
import { useEffect, useState } from "react";
|
||||
import { update } from "@/repository/settings";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
type TelegramSetting = {
|
||||
id: string;
|
||||
name: string;
|
||||
data: NotifyChannelTelegram;
|
||||
};
|
||||
|
||||
const Telegram = () => {
|
||||
const { config, setChannels } = useNotify();
|
||||
|
||||
const [telegram, setTelegram] = useState<TelegramSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
apiToken: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getDetailTelegram = () => {
|
||||
const df: NotifyChannelTelegram = {
|
||||
apiToken: "",
|
||||
enabled: false,
|
||||
};
|
||||
if (!config.content) {
|
||||
return df;
|
||||
}
|
||||
const chanels = config.content as NotifyChannels;
|
||||
if (!chanels.telegram) {
|
||||
return df;
|
||||
}
|
||||
|
||||
return chanels.telegram as NotifyChannelTelegram;
|
||||
};
|
||||
const data = getDetailTelegram();
|
||||
setTelegram({
|
||||
id: config.id ?? "",
|
||||
name: "telegram",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
telegram: {
|
||||
...telegram.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "配置保存成功",
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "配置保存失败:" + msg,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="ApiToken"
|
||||
value={telegram.data.apiToken}
|
||||
onChange={(e) => {
|
||||
setTelegram({
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
apiToken: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
checked={telegram.data.enabled}
|
||||
onCheckedChange={() => {
|
||||
setTelegram({
|
||||
...telegram,
|
||||
data: {
|
||||
...telegram.data,
|
||||
enabled: !telegram.data.enabled,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Telegram;
|
131
ui/src/components/notify/Webhook.tsx
Normal file
131
ui/src/components/notify/Webhook.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import { Input } from "../ui/input";
|
||||
import { Button } from "../ui/button";
|
||||
import { Switch } from "../ui/switch";
|
||||
import { Label } from "../ui/label";
|
||||
import { useNotify } from "@/providers/notify";
|
||||
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
|
||||
import { useEffect, useState } from "react";
|
||||
import { update } from "@/repository/settings";
|
||||
import { getErrMessage } from "@/lib/error";
|
||||
import { useToast } from "../ui/use-toast";
|
||||
|
||||
type WebhookSetting = {
|
||||
id: string;
|
||||
name: string;
|
||||
data: NotifyChannelWebhook;
|
||||
};
|
||||
|
||||
const Webhook = () => {
|
||||
const { config, setChannels } = useNotify();
|
||||
|
||||
const [webhook, setWebhook] = useState<WebhookSetting>({
|
||||
id: config.id ?? "",
|
||||
name: "notifyChannels",
|
||||
data: {
|
||||
url: "",
|
||||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const getDetailWebhook = () => {
|
||||
const df: NotifyChannelWebhook = {
|
||||
url: "",
|
||||
enabled: false,
|
||||
};
|
||||
if (!config.content) {
|
||||
return df;
|
||||
}
|
||||
const chanels = config.content as NotifyChannels;
|
||||
if (!chanels.webhook) {
|
||||
return df;
|
||||
}
|
||||
|
||||
return chanels.webhook as NotifyChannelWebhook;
|
||||
};
|
||||
const data = getDetailWebhook();
|
||||
setWebhook({
|
||||
id: config.id ?? "",
|
||||
name: "webhook",
|
||||
data,
|
||||
});
|
||||
}, [config]);
|
||||
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSaveClick = async () => {
|
||||
try {
|
||||
const resp = await update({
|
||||
...config,
|
||||
name: "notifyChannels",
|
||||
content: {
|
||||
...config.content,
|
||||
webhook: {
|
||||
...webhook.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setChannels(resp);
|
||||
toast({
|
||||
title: "保存成功",
|
||||
description: "配置保存成功",
|
||||
});
|
||||
} catch (e) {
|
||||
const msg = getErrMessage(e);
|
||||
|
||||
toast({
|
||||
title: "保存失败",
|
||||
description: "配置保存失败:" + msg,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Url"
|
||||
value={webhook.data.url}
|
||||
onChange={(e) => {
|
||||
setWebhook({
|
||||
...webhook,
|
||||
data: {
|
||||
...webhook.data,
|
||||
url: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center space-x-1 mt-2">
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
checked={webhook.data.enabled}
|
||||
onCheckedChange={() => {
|
||||
setWebhook({
|
||||
...webhook,
|
||||
data: {
|
||||
...webhook.data,
|
||||
enabled: !webhook.data.enabled,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleSaveClick();
|
||||
}}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Webhook;
|
@ -1,9 +1,50 @@
|
||||
export type Setting = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
content: EmailsSetting;
|
||||
content?: EmailsSetting | NotifyTemplates | NotifyChannels;
|
||||
};
|
||||
|
||||
type EmailsSetting = {
|
||||
export type EmailsSetting = {
|
||||
emails: string[];
|
||||
};
|
||||
|
||||
export type NotifyTemplates = {
|
||||
notifyTemplates: NotifyTemplate[];
|
||||
};
|
||||
|
||||
export type NotifyTemplate = {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type NotifyChannels = {
|
||||
dingtalk?: NotifyChannel;
|
||||
telegram?: NotifyChannel;
|
||||
webhook?: NotifyChannel;
|
||||
};
|
||||
|
||||
export type NotifyChannel =
|
||||
| NotifyChannelDingTalk
|
||||
| NotifyChannelTelegram
|
||||
| NotifyChannelWebhook;
|
||||
|
||||
export type NotifyChannelDingTalk = {
|
||||
accessToken: string;
|
||||
secret: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelTelegram = {
|
||||
apiToken: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type NotifyChannelWebhook = {
|
||||
url: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export const defaultNotifyTemplate: NotifyTemplate = {
|
||||
title: "您有{COUNT}张证书即将过期",
|
||||
content: "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!",
|
||||
};
|
||||
|
@ -1,11 +1,3 @@
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
NavigationMenuTrigger,
|
||||
} from "@/components/ui/navigation-menu";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { KeyRound, Megaphone, UserRound } from "lucide-react";
|
||||
|
@ -37,6 +37,7 @@ import { accessTypeMap } from "@/domain/access";
|
||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { EmailsSetting } from "@/domain/settings";
|
||||
|
||||
const Edit = () => {
|
||||
const {
|
||||
@ -270,11 +271,13 @@ const Edit = () => {
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>邮箱列表</SelectLabel>
|
||||
{emails.content.emails.map((item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
<div>{item}</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
{(emails.content as EmailsSetting).emails.map(
|
||||
(item) => (
|
||||
<SelectItem key={item} value={item}>
|
||||
<div>{item}</div>
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
@ -1,56 +1,54 @@
|
||||
import DingTalk from "@/components/notify/DingTalk";
|
||||
import NotifyTemplate from "@/components/notify/NotifyTemplate";
|
||||
import Telegram from "@/components/notify/Telegram";
|
||||
import Webhook from "@/components/notify/Webhook";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import React from "react";
|
||||
import { NotifyProvider } from "@/providers/notify";
|
||||
|
||||
const Notify = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="border rounded-sm p-5">
|
||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||
<AccordionTrigger>模板</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Input value="您有证书即将过期" />
|
||||
<Textarea
|
||||
className="mt-2"
|
||||
value={
|
||||
"有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
|
||||
}
|
||||
></Textarea>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
<div className="border rounded-md p-5 mt-7">
|
||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||
<AccordionTrigger>钉钉</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<NotifyProvider>
|
||||
<div className="border rounded-sm p-5 shadow-lg">
|
||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||
<AccordionTrigger>模板</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<NotifyTemplate />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||
<AccordionTrigger>钉钉</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<DingTalk />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||
<AccordionTrigger>Telegram</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||
<AccordionTrigger>Telegram</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Telegram />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||
<AccordionTrigger>Webhook</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
Yes. It adheres to the WAI-ARIA design pattern.
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||
<AccordionTrigger>Webhook</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Webhook />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
</NotifyProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Access } from "@/domain/access";
|
||||
import { ConfigData } from ".";
|
||||
import { Setting } from "@/domain/settings";
|
||||
import { EmailsSetting, Setting } from "@/domain/settings";
|
||||
import { AccessGroup } from "@/domain/access_groups";
|
||||
|
||||
type Action =
|
||||
@ -57,7 +57,10 @@ export const configReducer = (
|
||||
emails: {
|
||||
...state.emails,
|
||||
content: {
|
||||
emails: [...state.emails.content.emails, action.payload],
|
||||
emails: [
|
||||
...(state.emails.content as EmailsSetting).emails,
|
||||
action.payload,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
68
ui/src/providers/notify/index.tsx
Normal file
68
ui/src/providers/notify/index.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { NotifyChannel, Setting } from "@/domain/settings";
|
||||
import { getSetting } from "@/repository/settings";
|
||||
import {
|
||||
ReactNode,
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useReducer,
|
||||
useCallback,
|
||||
} from "react";
|
||||
import { notifyReducer } from "./reducer";
|
||||
|
||||
export type NotifyContext = {
|
||||
config: Setting;
|
||||
setChannel: (data: { channel: string; data: NotifyChannel }) => void;
|
||||
setChannels: (data: Setting) => void;
|
||||
};
|
||||
|
||||
const Context = createContext({} as NotifyContext);
|
||||
|
||||
export const useNotify = () => useContext(Context);
|
||||
interface ContainerProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const NotifyProvider = ({ children }: ContainerProps) => {
|
||||
const [notify, dispatchNotify] = useReducer(notifyReducer, {});
|
||||
|
||||
useEffect(() => {
|
||||
const featchData = async () => {
|
||||
const chanels = await getSetting("notifyChannels");
|
||||
dispatchNotify({
|
||||
type: "SET_CHANNELS",
|
||||
payload: chanels,
|
||||
});
|
||||
};
|
||||
featchData();
|
||||
}, []);
|
||||
|
||||
const setChannel = useCallback(
|
||||
(data: { channel: string; data: NotifyChannel }) => {
|
||||
dispatchNotify({
|
||||
type: "SET_CHANNEL",
|
||||
payload: data,
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const setChannels = useCallback((setting: Setting) => {
|
||||
dispatchNotify({
|
||||
type: "SET_CHANNELS",
|
||||
payload: setting,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
config: notify,
|
||||
setChannel,
|
||||
setChannels,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
35
ui/src/providers/notify/reducer.tsx
Normal file
35
ui/src/providers/notify/reducer.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import { NotifyChannel, Setting } from "@/domain/settings";
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: "SET_CHANNEL";
|
||||
payload: {
|
||||
channel: string;
|
||||
data: NotifyChannel;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: "SET_CHANNELS";
|
||||
payload: Setting;
|
||||
};
|
||||
|
||||
export const notifyReducer = (state: Setting, action: Action) => {
|
||||
switch (action.type) {
|
||||
case "SET_CHANNEL": {
|
||||
const channel = action.payload.channel;
|
||||
return {
|
||||
...state,
|
||||
content: {
|
||||
...state.content,
|
||||
[channel]: action.payload.data,
|
||||
},
|
||||
};
|
||||
}
|
||||
case "SET_CHANNELS": {
|
||||
return { ...action.payload };
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
@ -14,6 +14,20 @@ export const getEmails = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getSetting = async (name: string) => {
|
||||
try {
|
||||
const resp = await getPb()
|
||||
.collection("settings")
|
||||
.getFirstListItem<Setting>(`name='${name}'`);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
const rs: Setting = {
|
||||
name: name,
|
||||
};
|
||||
return rs;
|
||||
}
|
||||
};
|
||||
|
||||
export const update = async (setting: Setting) => {
|
||||
const pb = getPb();
|
||||
let resp: Setting;
|
||||
|
Loading…
x
Reference in New Issue
Block a user