From ffdd61b5ee025caddc6fe8351ad565efb2f56f4f Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Sun, 27 Oct 2024 04:01:42 +0800 Subject: [PATCH] feat: add ServerChan notifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增Server酱通知 --- internal/domain/notify.go | 1 + internal/notify/notify.go | 23 +++ ui/src/components/notify/ServerChan.tsx | 236 +++++++++++++++++++++++ ui/src/domain/settings.ts | 8 +- ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/en/nls.settings.json | 1 + ui/src/i18n/locales/zh/nls.common.json | 1 + ui/src/i18n/locales/zh/nls.settings.json | 1 + ui/src/pages/setting/Notify.tsx | 8 + 9 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 ui/src/components/notify/ServerChan.tsx diff --git a/internal/domain/notify.go b/internal/domain/notify.go index 11252432..de9cf6cf 100644 --- a/internal/domain/notify.go +++ b/internal/domain/notify.go @@ -5,6 +5,7 @@ const ( NotifyChannelWebhook = "webhook" NotifyChannelTelegram = "telegram" NotifyChannelLark = "lark" + NotifyChannelServerChan = "serverchan" ) type NotifyTestPushReq struct { diff --git a/internal/notify/notify.go b/internal/notify/notify.go index e5265b51..3dfa643b 100644 --- a/internal/notify/notify.go +++ b/internal/notify/notify.go @@ -5,6 +5,8 @@ import ( "fmt" "strconv" + stdhttp "net/http" + "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/utils/app" @@ -102,6 +104,8 @@ func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, e return getLarkNotifier(conf), nil case domain.NotifyChannelWebhook: return getWebhookNotifier(conf), nil + case domain.NotifyChannelServerChan: + return getServerChanNotifier(conf), nil } return nil, fmt.Errorf("notifier not found") @@ -132,6 +136,25 @@ func getTelegramNotifier(conf map[string]any) notifyPackage.Notifier { return rs } +func getServerChanNotifier(conf map[string]any) notifyPackage.Notifier { + rs := http.New() + + rs.AddReceivers(&http.Webhook{ + URL: getString(conf, "url"), + Header: stdhttp.Header{}, + ContentType: "application/json", + Method: stdhttp.MethodPost, + BuildPayload: func(subject, message string) (payload any) { + return map[string]string{ + "text": subject, + "desp": message, + } + }, + }) + + return rs +} + func getDingTalkNotifier(conf map[string]any) notifyPackage.Notifier { return dingding.New(&dingding.Config{ Token: getString(conf, "accessToken"), diff --git a/ui/src/components/notify/ServerChan.tsx b/ui/src/components/notify/ServerChan.tsx new file mode 100644 index 00000000..f31c7bdc --- /dev/null +++ b/ui/src/components/notify/ServerChan.tsx @@ -0,0 +1,236 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { useToast } from "@/components/ui/use-toast"; +import { getErrMessage } from "@/lib/error"; +import { isValidURL } from "@/lib/url"; +import { NotifyChannels, NotifyChannelServerChan } from "@/domain/settings"; +import { update } from "@/repository/settings"; +import { useNotifyContext } from "@/providers/notify"; +import { notifyTest } from "@/api/notify"; +import Show from "@/components/Show"; + +type ServerChanSetting = { + id: string; + name: string; + data: NotifyChannelServerChan; +}; + +const ServerChan = () => { + const { config, setChannels } = useNotifyContext(); + const { t } = useTranslation(); + const [changed, setChanged] = useState(false); + + const [serverchan, setServerChan] = useState({ + id: config.id ?? "", + name: "notifyChannels", + data: { + url: "", + enabled: false, + }, + }); + + const [originServerChan, setOriginServerChan] = useState({ + id: config.id ?? "", + name: "notifyChannels", + data: { + url: "", + enabled: false, + }, + }); + + useEffect(() => { + setChanged(false); + }, [config]); + + useEffect(() => { + const data = getDetailServerChan(); + setOriginServerChan({ + id: config.id ?? "", + name: "serverchan", + data, + }); + }, [config]); + + useEffect(() => { + const data = getDetailServerChan(); + setServerChan({ + id: config.id ?? "", + name: "serverchan", + data, + }); + }, [config]); + + const { toast } = useToast(); + + const checkChanged = (data: NotifyChannelServerChan) => { + if (data.url !== originServerChan.data.url) { + setChanged(true); + } else { + setChanged(false); + } + }; + + const getDetailServerChan = () => { + const df: NotifyChannelServerChan = { + url: "", + enabled: false, + }; + if (!config.content) { + return df; + } + const chanels = config.content as NotifyChannels; + if (!chanels.serverchan) { + return df; + } + + return chanels.serverchan as NotifyChannelServerChan; + }; + + const handleSaveClick = async () => { + try { + serverchan.data.url = serverchan.data.url.trim(); + if (!isValidURL(serverchan.data.url)) { + toast({ + title: t("common.save.failed.message"), + description: t("settings.notification.url.errmsg.invalid"), + variant: "destructive", + }); + return; + } + + const resp = await update({ + ...config, + name: "notifyChannels", + content: { + ...config.content, + serverchan: { + ...serverchan.data, + }, + }, + }); + + setChannels(resp); + toast({ + title: t("common.save.succeeded.message"), + description: t("settings.notification.config.saved.message"), + }); + } catch (e) { + const msg = getErrMessage(e); + + toast({ + title: t("common.save.failed.message"), + description: `${t("settings.notification.config.failed.message")}: ${msg}`, + variant: "destructive", + }); + } + }; + + const handlePushTestClick = async () => { + try { + await notifyTest("serverchan"); + + toast({ + title: t("settings.notification.config.push.test.message.success.message"), + description: t("settings.notification.config.push.test.message.success.message"), + }); + } catch (e) { + const msg = getErrMessage(e); + + toast({ + title: t("settings.notification.config.push.test.message.failed.message"), + description: `${t("settings.notification.config.push.test.message.failed.message")}: ${msg}`, + variant: "destructive", + }); + } + }; + + const handleSwitchChange = async () => { + const newData = { + ...serverchan, + data: { + ...serverchan.data, + enabled: !serverchan.data.enabled, + }, + }; + setServerChan(newData); + + try { + const resp = await update({ + ...config, + name: "notifyChannels", + content: { + ...config.content, + serverchan: { + ...newData.data, + }, + }, + }); + + setChannels(resp); + } catch (e) { + const msg = getErrMessage(e); + + toast({ + title: t("common.save.failed.message"), + description: `${t("settings.notification.config.failed.message")}: ${msg}`, + variant: "destructive", + }); + } + }; + + return ( +
+ { + const newData = { + ...serverchan, + data: { + ...serverchan.data, + url: e.target.value, + }, + }; + + checkChanged(newData.data); + setServerChan(newData); + }} + /> + +
+ + +
+ +
+ + + + + + + +
+
+ ); +}; + +export default ServerChan; diff --git a/ui/src/domain/settings.ts b/ui/src/domain/settings.ts index 62f00670..af77ea98 100644 --- a/ui/src/domain/settings.ts +++ b/ui/src/domain/settings.ts @@ -22,9 +22,10 @@ export type NotifyChannels = { lark?: NotifyChannel; telegram?: NotifyChannel; webhook?: NotifyChannel; + serverchan?: NotifyChannel; }; -export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook; +export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook | NotifyChannelServerChan; export type NotifyChannelDingTalk = { accessToken: string; @@ -48,6 +49,11 @@ export type NotifyChannelWebhook = { enabled: boolean; }; +export type NotifyChannelServerChan = { + url: string; + enabled: boolean; +}; + export const defaultNotifyTemplate: NotifyTemplate = { title: "您有 {COUNT} 张证书即将过期", content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 872ef19b..5ec5757e 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -74,6 +74,7 @@ "common.provider.local": "Local Deployment", "common.provider.ssh": "SSH Deployment", "common.provider.webhook": "Webhook", + "common.provider.serverchan": "ServerChan", "common.provider.kubernetes": "Kubernetes", "common.provider.kubernetes.secret": "Kubernetes - Secret", "common.provider.dingtalk": "DingTalk", diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index b2cac073..b23f6ee0 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -35,6 +35,7 @@ "settings.notification.config.push.test.message.success.message": "Send test notification successfully", "settings.notification.dingtalk.secret.placeholder": "Signature for signed addition", "settings.notification.url.errmsg.invalid": "Invalid Url format", + "settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send", "settings.ca.tab": "Certificate Authority", "settings.ca.provider.errmsg.empty": "Please select a Certificate Authority", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 818807ce..6f80af04 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -73,6 +73,7 @@ "common.provider.local": "本地部署", "common.provider.ssh": "SSH 部署", "common.provider.webhook": "Webhook", + "common.provider.serverchan": "Server酱", "common.provider.kubernetes": "Kubernetes", "common.provider.kubernetes.secret": "Kubernetes - Secret", "common.provider.dingtalk": "钉钉", diff --git a/ui/src/i18n/locales/zh/nls.settings.json b/ui/src/i18n/locales/zh/nls.settings.json index 25140746..f3dedaa9 100644 --- a/ui/src/i18n/locales/zh/nls.settings.json +++ b/ui/src/i18n/locales/zh/nls.settings.json @@ -35,6 +35,7 @@ "settings.notification.config.push.test.message.success.message": "推送测试消息成功", "settings.notification.dingtalk.secret.placeholder": "加签的签名", "settings.notification.url.errmsg.invalid": "URL 格式不正确", + "settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send", "settings.ca.tab": "证书颁发机构(CA)", "settings.ca.provider.errmsg.empty": "请选择证书分发机构", diff --git a/ui/src/pages/setting/Notify.tsx b/ui/src/pages/setting/Notify.tsx index fed8e7bc..ad06a082 100644 --- a/ui/src/pages/setting/Notify.tsx +++ b/ui/src/pages/setting/Notify.tsx @@ -6,6 +6,7 @@ import Lark from "@/components/notify/Lark"; import NotifyTemplate from "@/components/notify/NotifyTemplate"; import Telegram from "@/components/notify/Telegram"; import Webhook from "@/components/notify/Webhook"; +import ServerChan from "@/components/notify/ServerChan"; import { NotifyProvider } from "@/providers/notify"; const Notify = () => { @@ -53,6 +54,13 @@ const Notify = () => { + + + {t("common.provider.serverchan")} + + + +