From 4c9095400e06224c5fd56dd074ab0aba94b97d8d Mon Sep 17 00:00:00 2001 From: yoan <536464346@qq.com> Date: Mon, 23 Sep 2024 23:13:34 +0800 Subject: [PATCH] message push config --- ui/src/components/certimate/EmailsEdit.tsx | 5 +- ui/src/components/notify/DingTalk.tsx | 146 ++++++++++++++++++++ ui/src/components/notify/NotifyTemplate.tsx | 104 ++++++++++++++ ui/src/components/notify/Telegram.tsx | 131 ++++++++++++++++++ ui/src/components/notify/Webhook.tsx | 131 ++++++++++++++++++ ui/src/domain/settings.ts | 45 +++++- ui/src/pages/SettingLayout.tsx | 8 -- ui/src/pages/domains/Edit.tsx | 13 +- ui/src/pages/setting/Notify.tsx | 80 ++++++----- ui/src/providers/config/reducer.ts | 7 +- ui/src/providers/notify/index.tsx | 68 +++++++++ ui/src/providers/notify/reducer.tsx | 35 +++++ ui/src/repository/settings.ts | 14 ++ 13 files changed, 727 insertions(+), 60 deletions(-) create mode 100644 ui/src/components/notify/DingTalk.tsx create mode 100644 ui/src/components/notify/NotifyTemplate.tsx create mode 100644 ui/src/components/notify/Telegram.tsx create mode 100644 ui/src/components/notify/Webhook.tsx create mode 100644 ui/src/providers/notify/index.tsx create mode 100644 ui/src/providers/notify/reducer.tsx diff --git a/ui/src/components/certimate/EmailsEdit.tsx b/ui/src/components/certimate/EmailsEdit.tsx index a36acfdd..d1d2fc97 100644 --- a/ui/src/components/certimate/EmailsEdit.tsx +++ b/ui/src/components/certimate/EmailsEdit.tsx @@ -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) => { - 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({ diff --git a/ui/src/components/notify/DingTalk.tsx b/ui/src/components/notify/DingTalk.tsx new file mode 100644 index 00000000..2e2a58f6 --- /dev/null +++ b/ui/src/components/notify/DingTalk.tsx @@ -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({ + 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 ( +
+ { + setDingtalk({ + ...dingtalk, + data: { + ...dingtalk.data, + accessToken: e.target.value, + }, + }); + }} + /> + { + setDingtalk({ + ...dingtalk, + data: { + ...dingtalk.data, + secret: e.target.value, + }, + }); + }} + /> +
+ { + setDingtalk({ + ...dingtalk, + data: { + ...dingtalk.data, + enabled: !dingtalk.data.enabled, + }, + }); + }} + /> + +
+ +
+ +
+
+ ); +}; + +export default DingTalk; diff --git a/ui/src/components/notify/NotifyTemplate.tsx b/ui/src/components/notify/NotifyTemplate.tsx new file mode 100644 index 00000000..2a761b08 --- /dev/null +++ b/ui/src/components/notify/NotifyTemplate.tsx @@ -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([ + 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 ( +
+ { + handleTitleChange(e.target.value); + }} + /> + +
+ 可选的变量, COUNT:即将过期张数 +
+ + +
+ 可选的变量, COUNT:即将过期张数,DOMAINS:域名列表 +
+
+ +
+
+ ); +}; + +export default NotifyTemplate; diff --git a/ui/src/components/notify/Telegram.tsx b/ui/src/components/notify/Telegram.tsx new file mode 100644 index 00000000..f6fe263e --- /dev/null +++ b/ui/src/components/notify/Telegram.tsx @@ -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({ + 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 ( +
+ { + setTelegram({ + ...telegram, + data: { + ...telegram.data, + apiToken: e.target.value, + }, + }); + }} + /> + +
+ { + setTelegram({ + ...telegram, + data: { + ...telegram.data, + enabled: !telegram.data.enabled, + }, + }); + }} + /> + +
+ +
+ +
+
+ ); +}; + +export default Telegram; diff --git a/ui/src/components/notify/Webhook.tsx b/ui/src/components/notify/Webhook.tsx new file mode 100644 index 00000000..813af473 --- /dev/null +++ b/ui/src/components/notify/Webhook.tsx @@ -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({ + 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 ( +
+ { + setWebhook({ + ...webhook, + data: { + ...webhook.data, + url: e.target.value, + }, + }); + }} + /> + +
+ { + setWebhook({ + ...webhook, + data: { + ...webhook.data, + enabled: !webhook.data.enabled, + }, + }); + }} + /> + +
+ +
+ +
+
+ ); +}; + +export default Webhook; diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index 45e7ad4c..556beae0 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -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},请保持关注!", +}; diff --git a/ui/src/pages/SettingLayout.tsx b/ui/src/pages/SettingLayout.tsx index e170e7e0..6ddacc24 100644 --- a/ui/src/pages/SettingLayout.tsx +++ b/ui/src/pages/SettingLayout.tsx @@ -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"; diff --git a/ui/src/pages/domains/Edit.tsx b/ui/src/pages/domains/Edit.tsx index 5387e98a..af03eccb 100644 --- a/ui/src/pages/domains/Edit.tsx +++ b/ui/src/pages/domains/Edit.tsx @@ -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 = () => { 邮箱列表 - {emails.content.emails.map((item) => ( - -
{item}
-
- ))} + {(emails.content as EmailsSetting).emails.map( + (item) => ( + +
{item}
+
+ ) + )}
diff --git a/ui/src/pages/setting/Notify.tsx b/ui/src/pages/setting/Notify.tsx index 0fb66820..fd59d052 100644 --- a/ui/src/pages/setting/Notify.tsx +++ b/ui/src/pages/setting/Notify.tsx @@ -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 ( <> -
- - - 模板 - - - - - - -
-
- - - 钉钉 - - Yes. It adheres to the WAI-ARIA design pattern. - - + +
+ + + 模板 + + + + + +
+
+ + + 钉钉 + + + + - - Telegram - - Yes. It adheres to the WAI-ARIA design pattern. - - + + Telegram + + + + - - Webhook - - Yes. It adheres to the WAI-ARIA design pattern. - - - -
+ + Webhook + + + + +
+
+ ); }; diff --git a/ui/src/providers/config/reducer.ts b/ui/src/providers/config/reducer.ts index 35181c5a..bb98c764 100644 --- a/ui/src/providers/config/reducer.ts +++ b/ui/src/providers/config/reducer.ts @@ -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, + ], }, }, }; diff --git a/ui/src/providers/notify/index.tsx b/ui/src/providers/notify/index.tsx new file mode 100644 index 00000000..e2659ee2 --- /dev/null +++ b/ui/src/providers/notify/index.tsx @@ -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 ( + + {children} + + ); +}; diff --git a/ui/src/providers/notify/reducer.tsx b/ui/src/providers/notify/reducer.tsx new file mode 100644 index 00000000..d5a36efc --- /dev/null +++ b/ui/src/providers/notify/reducer.tsx @@ -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; + } +}; diff --git a/ui/src/repository/settings.ts b/ui/src/repository/settings.ts index 150da307..1ccef99c 100644 --- a/ui/src/repository/settings.ts +++ b/ui/src/repository/settings.ts @@ -14,6 +14,20 @@ export const getEmails = async () => { } }; +export const getSetting = async (name: string) => { + try { + const resp = await getPb() + .collection("settings") + .getFirstListItem(`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;