feat: add ServerChan notifier

新增Server酱通知
This commit is contained in:
Leo Chen 2024-10-27 04:01:42 +08:00
parent 332c5c5127
commit ffdd61b5ee
9 changed files with 279 additions and 1 deletions

View File

@ -5,6 +5,7 @@ const (
NotifyChannelWebhook = "webhook"
NotifyChannelTelegram = "telegram"
NotifyChannelLark = "lark"
NotifyChannelServerChan = "serverchan"
)
type NotifyTestPushReq struct {

View File

@ -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"),

View File

@ -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<boolean>(false);
const [serverchan, setServerChan] = useState<ServerChanSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
url: "",
enabled: false,
},
});
const [originServerChan, setOriginServerChan] = useState<ServerChanSetting>({
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 (
<div>
<Input
placeholder={t("settings.notification.serverchan.url.placeholder")}
value={serverchan.data.url}
onChange={(e) => {
const newData = {
...serverchan,
data: {
...serverchan.data,
url: e.target.value,
},
};
checkChanged(newData.data);
setServerChan(newData);
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && serverchan.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
</div>
</div>
);
};
export default ServerChan;

View File

@ -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},请保持关注!",

View File

@ -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",

View File

@ -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",

View File

@ -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": "钉钉",

View File

@ -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": "请选择证书分发机构",

View File

@ -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 = () => {
<Webhook />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-6" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.serverchan")}</AccordionTrigger>
<AccordionContent>
<ServerChan />
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</NotifyProvider>