mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 13:39:53 +00:00
feat: add mail push
新增电子邮箱推送
This commit is contained in:
parent
ffacfe0f42
commit
0396d8222e
@ -6,6 +6,7 @@ const (
|
|||||||
NotifyChannelTelegram = "telegram"
|
NotifyChannelTelegram = "telegram"
|
||||||
NotifyChannelLark = "lark"
|
NotifyChannelLark = "lark"
|
||||||
NotifyChannelServerChan = "serverchan"
|
NotifyChannelServerChan = "serverchan"
|
||||||
|
NotifyChannelMail = "mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotifyTestPushReq struct {
|
type NotifyTestPushReq struct {
|
||||||
|
58
internal/notify/mail.go
Normal file
58
internal/notify/mail.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mail struct {
|
||||||
|
senderAddress string
|
||||||
|
smtpHostAddr string
|
||||||
|
smtpHostPort string
|
||||||
|
smtpAuth smtp.Auth
|
||||||
|
receiverAddresses string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMail(senderAddress, receiverAddresses, smtpHostAddr, smtpHostPort string) *Mail {
|
||||||
|
if(smtpHostPort == "") {
|
||||||
|
smtpHostPort = "25"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Mail{
|
||||||
|
senderAddress: senderAddress,
|
||||||
|
smtpHostAddr: smtpHostAddr,
|
||||||
|
smtpHostPort: smtpHostPort,
|
||||||
|
receiverAddresses: receiverAddresses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mail) SetAuth(username, password string) {
|
||||||
|
m.smtpAuth = smtp.PlainAuth("", username, password, m.smtpHostAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mail) Send(ctx context.Context, subject, message string) error {
|
||||||
|
// 构建邮件
|
||||||
|
from := m.senderAddress
|
||||||
|
to := []string{m.receiverAddresses}
|
||||||
|
msg := []byte(
|
||||||
|
"From: " + from + "\r\n" +
|
||||||
|
"To: " + m.receiverAddresses + "\r\n" +
|
||||||
|
"Subject: " + subject + "\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
message + "\r\n")
|
||||||
|
|
||||||
|
var smtpAddress string
|
||||||
|
// 组装邮箱服务器地址
|
||||||
|
if(m.smtpHostPort == "25"){
|
||||||
|
smtpAddress = m.smtpHostAddr
|
||||||
|
}else{
|
||||||
|
smtpAddress = m.smtpHostAddr + ":" + m.smtpHostPort
|
||||||
|
}
|
||||||
|
|
||||||
|
err := smtp.SendMail(smtpAddress, m.smtpAuth, from, to, msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -106,6 +106,8 @@ func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, e
|
|||||||
return getWebhookNotifier(conf), nil
|
return getWebhookNotifier(conf), nil
|
||||||
case domain.NotifyChannelServerChan:
|
case domain.NotifyChannelServerChan:
|
||||||
return getServerChanNotifier(conf), nil
|
return getServerChanNotifier(conf), nil
|
||||||
|
case domain.NotifyChannelMail:
|
||||||
|
return getMailNotifier(conf), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("notifier not found")
|
return nil, fmt.Errorf("notifier not found")
|
||||||
@ -166,6 +168,14 @@ func getLarkNotifier(conf map[string]any) notifyPackage.Notifier {
|
|||||||
return lark.NewWebhookService(getString(conf, "webhookUrl"))
|
return lark.NewWebhookService(getString(conf, "webhookUrl"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMailNotifier(conf map[string]any) notifyPackage.Notifier {
|
||||||
|
rs := NewMail(getString(conf, "senderAddress"),getString(conf,"receiverAddress"), getString(conf, "smtpHostAddr"), getString(conf, "smtpHostPort"))
|
||||||
|
|
||||||
|
rs.SetAuth(getString(conf, "username"), getString(conf, "password"))
|
||||||
|
|
||||||
|
return rs
|
||||||
|
}
|
||||||
|
|
||||||
func getString(conf map[string]any, key string) string {
|
func getString(conf map[string]any, key string) string {
|
||||||
if _, ok := conf[key]; !ok {
|
if _, ok := conf[key]; !ok {
|
||||||
return ""
|
return ""
|
||||||
|
319
ui/src/components/notify/Mail.tsx
Normal file
319
ui/src/components/notify/Mail.tsx
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
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 { NotifyChannelMail, NotifyChannels } from "@/domain/settings";
|
||||||
|
import { useNotifyContext } from "@/providers/notify";
|
||||||
|
import { update } from "@/repository/settings";
|
||||||
|
import Show from "@/components/Show";
|
||||||
|
import { notifyTest } from "@/api/notify";
|
||||||
|
|
||||||
|
type MailSetting = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
data: NotifyChannelMail;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Mail = () => {
|
||||||
|
const { config, setChannels } = useNotifyContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [changed, setChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [mail, setmail] = useState<MailSetting>({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "notifyChannels",
|
||||||
|
data: {
|
||||||
|
senderAddress: "",
|
||||||
|
receiverAddresses: "",
|
||||||
|
smtpHostAddr: "",
|
||||||
|
smtpHostPort: "25",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [originMail, setoriginMail] = useState<MailSetting>({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "notifyChannels",
|
||||||
|
data: {
|
||||||
|
senderAddress: "",
|
||||||
|
receiverAddresses: "",
|
||||||
|
smtpHostAddr: "",
|
||||||
|
smtpHostPort: "25",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setChanged(false);
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailMail();
|
||||||
|
setoriginMail({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "mail",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = getDetailMail();
|
||||||
|
setmail({
|
||||||
|
id: config.id ?? "",
|
||||||
|
name: "mail",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const getDetailMail = () => {
|
||||||
|
const df: NotifyChannelMail = {
|
||||||
|
senderAddress: "",
|
||||||
|
receiverAddresses: "",
|
||||||
|
smtpHostAddr: "",
|
||||||
|
smtpHostPort: "25",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
enabled: false,
|
||||||
|
};
|
||||||
|
if (!config.content) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
const chanels = config.content as NotifyChannels;
|
||||||
|
if (!chanels.mail) {
|
||||||
|
return df;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chanels.mail as NotifyChannelMail;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkChanged = (data: NotifyChannelMail) => {
|
||||||
|
if (data.senderAddress !== originMail.data.senderAddress || data.receiverAddresses !== originMail.data.receiverAddresses || data.smtpHostAddr !== originMail.data.smtpHostAddr || data.smtpHostPort !== originMail.data.smtpHostPort || data.username !== originMail.data.username || data.password !== originMail.data.password) {
|
||||||
|
setChanged(true);
|
||||||
|
} else {
|
||||||
|
setChanged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveClick = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
mail: {
|
||||||
|
...mail.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("mail");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
enabled: !mail.data.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setmail(newData);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await update({
|
||||||
|
...config,
|
||||||
|
name: "notifyChannels",
|
||||||
|
content: {
|
||||||
|
...config.content,
|
||||||
|
mail: {
|
||||||
|
...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.mail.sender_address.placeholder")}
|
||||||
|
value={mail.data.senderAddress}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
senderAddress: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t("settings.notification.mail.receiver_address.placeholder")}
|
||||||
|
className="mt-2"
|
||||||
|
value={mail.data.receiverAddresses}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t("settings.notification.mail.smtp_host.placeholder")}
|
||||||
|
className="mt-2"
|
||||||
|
value={mail.data.smtpHostAddr}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t("settings.notification.mail.smtp_port.placeholder")}
|
||||||
|
className="mt-2"
|
||||||
|
value={mail.data.smtpHostPort}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t("settings.notification.mail.username.placeholder")}
|
||||||
|
className="mt-2"
|
||||||
|
value={mail.data.username}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder={t("settings.notification.mail.password.placeholder")}
|
||||||
|
className="mt-2"
|
||||||
|
value={mail.data.password}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newData = {
|
||||||
|
...mail,
|
||||||
|
data: {
|
||||||
|
...mail.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
checkChanged(newData.data);
|
||||||
|
setmail(newData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center space-x-1 mt-2">
|
||||||
|
<Switch id="airplane-mode" checked={mail.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 && mail.id != ""}>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
handlePushTestClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.notification.config.push.test.message")}
|
||||||
|
</Button>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Mail;
|
@ -23,9 +23,10 @@ export type NotifyChannels = {
|
|||||||
telegram?: NotifyChannel;
|
telegram?: NotifyChannel;
|
||||||
webhook?: NotifyChannel;
|
webhook?: NotifyChannel;
|
||||||
serverchan?: NotifyChannel;
|
serverchan?: NotifyChannel;
|
||||||
|
mail?: NotifyChannelMail;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook | NotifyChannelServerChan;
|
export type NotifyChannel = NotifyChannelDingTalk | NotifyChannelLark | NotifyChannelTelegram | NotifyChannelWebhook | NotifyChannelServerChan | NotifyChannelMail;
|
||||||
|
|
||||||
export type NotifyChannelDingTalk = {
|
export type NotifyChannelDingTalk = {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
@ -54,6 +55,16 @@ export type NotifyChannelServerChan = {
|
|||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NotifyChannelMail = {
|
||||||
|
senderAddress: string;
|
||||||
|
receiverAddresses: string;
|
||||||
|
smtpHostAddr: string;
|
||||||
|
smtpHostPort: string;
|
||||||
|
username:string;
|
||||||
|
password:string;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const defaultNotifyTemplate: NotifyTemplate = {
|
export const defaultNotifyTemplate: NotifyTemplate = {
|
||||||
title: "您有 {COUNT} 张证书即将过期",
|
title: "您有 {COUNT} 张证书即将过期",
|
||||||
content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!",
|
content: "有 {COUNT} 张证书即将过期,域名分别为 {DOMAINS},请保持关注!",
|
||||||
|
@ -85,5 +85,6 @@
|
|||||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||||
"common.provider.dingtalk": "DingTalk",
|
"common.provider.dingtalk": "DingTalk",
|
||||||
"common.provider.telegram": "Telegram",
|
"common.provider.telegram": "Telegram",
|
||||||
"common.provider.lark": "Lark"
|
"common.provider.lark": "Lark",
|
||||||
|
"common.provider.mail": "Mail"
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,12 @@
|
|||||||
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
|
||||||
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
"settings.notification.url.errmsg.invalid": "Invalid Url format",
|
||||||
"settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send",
|
"settings.notification.serverchan.url.placeholder": "Url, e.g. https://sctapi.ftqq.com/****************.send",
|
||||||
|
"settings.notification.mail.sender_address.placeholder": "Sender email address",
|
||||||
|
"settings.notification.mail.receiver_address.placeholder": "Receiver email address",
|
||||||
|
"settings.notification.mail.smtp_host.placeholder": "SMTP server address",
|
||||||
|
"settings.notification.mail.smtp_port.placeholder": "SMTP server port, if not set, default is 25",
|
||||||
|
"settings.notification.mail.username.placeholder": "username",
|
||||||
|
"settings.notification.mail.password.placeholder": "password",
|
||||||
|
|
||||||
"settings.ca.tab": "Certificate Authority",
|
"settings.ca.tab": "Certificate Authority",
|
||||||
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
|
||||||
|
@ -85,6 +85,7 @@
|
|||||||
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
"common.provider.kubernetes.secret": "Kubernetes - Secret",
|
||||||
"common.provider.dingtalk": "钉钉",
|
"common.provider.dingtalk": "钉钉",
|
||||||
"common.provider.telegram": "Telegram",
|
"common.provider.telegram": "Telegram",
|
||||||
"common.provider.lark": "飞书"
|
"common.provider.lark": "飞书",
|
||||||
|
"common.provider.mail": "电子邮件"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,12 @@
|
|||||||
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
|
||||||
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
|
||||||
"settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send",
|
"settings.notification.serverchan.url.placeholder": "Url, 形如: https://sctapi.ftqq.com/****************.send",
|
||||||
|
"settings.notification.mail.sender_address.placeholder": "发送邮箱地址",
|
||||||
|
"settings.notification.mail.receiver_address.placeholder": "接收邮箱地址",
|
||||||
|
"settings.notification.mail.smtp_host.placeholder": "SMTP服务器地址",
|
||||||
|
"settings.notification.mail.smtp_port.placeholder": "SMTP服务器端口, 如果未设置, 默认为25",
|
||||||
|
"settings.notification.mail.username.placeholder": "用于登录到邮件服务器的用户名",
|
||||||
|
"settings.notification.mail.password.placeholder": "用于登录到邮件服务器的密码",
|
||||||
|
|
||||||
"settings.ca.tab": "证书颁发机构(CA)",
|
"settings.ca.tab": "证书颁发机构(CA)",
|
||||||
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
|
||||||
|
@ -7,6 +7,7 @@ import NotifyTemplate from "@/components/notify/NotifyTemplate";
|
|||||||
import Telegram from "@/components/notify/Telegram";
|
import Telegram from "@/components/notify/Telegram";
|
||||||
import Webhook from "@/components/notify/Webhook";
|
import Webhook from "@/components/notify/Webhook";
|
||||||
import ServerChan from "@/components/notify/ServerChan";
|
import ServerChan from "@/components/notify/ServerChan";
|
||||||
|
import Mail from "@/components/notify/Mail";
|
||||||
import { NotifyProvider } from "@/providers/notify";
|
import { NotifyProvider } from "@/providers/notify";
|
||||||
|
|
||||||
const Notify = () => {
|
const Notify = () => {
|
||||||
@ -61,6 +62,12 @@ const Notify = () => {
|
|||||||
<ServerChan />
|
<ServerChan />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
<AccordionItem value="item-7" className="dark:border-stone-200">
|
||||||
|
<AccordionTrigger>{t("common.provider.mail")}</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<Mail />
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
</NotifyProvider>
|
</NotifyProvider>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user