Merge branch 'main' into feat/huaweicloud-cdn

This commit is contained in:
RHQYZ 2024-10-19 18:49:48 +08:00 committed by GitHub
commit 94ca0f27bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1196 additions and 191 deletions

23
internal/domain/err.go Normal file
View File

@ -0,0 +1,23 @@
package domain
var ErrAuthFailed = NewXError(4999, "auth failed")
type XError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func NewXError(code int, msg string) *XError {
return &XError{code, msg}
}
func (e *XError) Error() string {
return e.Msg
}
func (e *XError) GetCode() int {
if e.Code == 0 {
return 100
}
return e.Code
}

12
internal/domain/notify.go Normal file
View File

@ -0,0 +1,12 @@
package domain
const (
NotifyChannelDingtalk = "dingtalk"
NotifyChannelWebhook = "webhook"
NotifyChannelTelegram = "telegram"
NotifyChannelLark = "lark"
)
type NotifyTestPushReq struct {
Channel string `json:"channel"`
}

View File

@ -0,0 +1,31 @@
package domain
import (
"encoding/json"
"fmt"
"time"
)
type Setting struct {
ID string `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type ChannelsConfig map[string]map[string]any
func (s *Setting) GetChannelContent(channel string) (map[string]any, error) {
conf := &ChannelsConfig{}
if err := json.Unmarshal([]byte(s.Content), conf); err != nil {
return nil, err
}
v, ok := (*conf)[channel]
if !ok {
return nil, fmt.Errorf("channel %s not found", channel)
}
return v, nil
}

View File

@ -1,11 +1,13 @@
package notify
import (
"certimate/internal/utils/app"
"context"
"fmt"
"strconv"
"certimate/internal/domain"
"certimate/internal/utils/app"
notifyPackage "github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/dingding"
"github.com/nikoksr/notify/service/http"
@ -13,13 +15,6 @@ import (
"github.com/nikoksr/notify/service/telegram"
)
const (
notifyChannelDingtalk = "dingtalk"
notifyChannelWebhook = "webhook"
notifyChannelTelegram = "telegram"
notifyChannelLark = "lark"
)
func Send(title, content string) error {
// 获取所有的推送渠道
notifiers, err := getNotifiers()
@ -38,6 +33,28 @@ func Send(title, content string) error {
return n.Send(context.Background(), title, content)
}
type sendTestParam struct {
Title string `json:"title"`
Content string `json:"content"`
Channel string `json:"channel"`
Conf map[string]any `json:"conf"`
}
func SendTest(param *sendTestParam) error {
notifier, err := getNotifier(param.Channel, param.Conf)
if err != nil {
return err
}
n := notifyPackage.New()
// 添加推送渠道
n.UseServices(notifier)
// 发送消息
return n.Send(context.Background(), param.Title, param.Content)
}
func getNotifiers() ([]notifyPackage.Notifier, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='notifyChannels'")
if err != nil {
@ -58,27 +75,38 @@ func getNotifiers() ([]notifyPackage.Notifier, error) {
continue
}
switch k {
case notifyChannelTelegram:
temp := getTelegramNotifier(v)
if temp == nil {
continue
}
notifiers = append(notifiers, temp)
case notifyChannelDingtalk:
notifiers = append(notifiers, getDingTalkNotifier(v))
case notifyChannelLark:
notifiers = append(notifiers, getLarkNotifier(v))
case notifyChannelWebhook:
notifiers = append(notifiers, getWebhookNotifier(v))
notifier, err := getNotifier(k, v)
if err != nil {
continue
}
notifiers = append(notifiers, notifier)
}
return notifiers, nil
}
func getNotifier(channel string, conf map[string]any) (notifyPackage.Notifier, error) {
switch channel {
case domain.NotifyChannelTelegram:
temp := getTelegramNotifier(conf)
if temp == nil {
return nil, fmt.Errorf("telegram notifier config error")
}
return temp, nil
case domain.NotifyChannelDingtalk:
return getDingTalkNotifier(conf), nil
case domain.NotifyChannelLark:
return getLarkNotifier(conf), nil
case domain.NotifyChannelWebhook:
return getWebhookNotifier(conf), nil
}
return nil, fmt.Errorf("notifier not found")
}
func getWebhookNotifier(conf map[string]any) notifyPackage.Notifier {
rs := http.New()

View File

@ -0,0 +1,46 @@
package notify
import (
"context"
"fmt"
"certimate/internal/domain"
)
const (
notifyTestTitle = "测试通知"
notifyTestBody = "欢迎使用 Certimate ,这是一条测试通知。"
)
type SettingRepository interface {
GetByName(ctx context.Context, name string) (*domain.Setting, error)
}
type NotifyService struct {
settingRepo SettingRepository
}
func NewNotifyService(settingRepo SettingRepository) *NotifyService {
return &NotifyService{
settingRepo: settingRepo,
}
}
func (n *NotifyService) Test(ctx context.Context, req *domain.NotifyTestPushReq) error {
setting, err := n.settingRepo.GetByName(ctx, "notifyChannels")
if err != nil {
return fmt.Errorf("get notify channels setting failed: %w", err)
}
conf, err := setting.GetChannelContent(req.Channel)
if err != nil {
return fmt.Errorf("get notify channel %s config failed: %w", req.Channel, err)
}
return SendTest(&sendTestParam{
Title: notifyTestTitle,
Content: notifyTestBody,
Channel: req.Channel,
Conf: conf,
})
}

View File

@ -0,0 +1,31 @@
package repository
import (
"context"
"certimate/internal/domain"
"certimate/internal/utils/app"
)
type SettingRepository struct{}
func NewSettingRepository() *SettingRepository {
return &SettingRepository{}
}
func (s *SettingRepository) GetByName(ctx context.Context, name string) (*domain.Setting, error) {
resp, err := app.GetApp().Dao().FindFirstRecordByFilter("settings", "name='"+name+"'")
if err != nil {
return nil, err
}
rs := &domain.Setting{
ID: resp.GetString("id"),
Name: resp.GetString("name"),
Content: resp.GetString("content"),
Created: resp.GetTime("created"),
Updated: resp.GetTime("updated"),
}
return rs, nil
}

41
internal/rest/notify.go Normal file
View File

@ -0,0 +1,41 @@
package rest
import (
"context"
"certimate/internal/domain"
"certimate/internal/utils/resp"
"github.com/labstack/echo/v5"
)
type NotifyService interface {
Test(ctx context.Context, req *domain.NotifyTestPushReq) error
}
type notifyHandler struct {
service NotifyService
}
func NewNotifyHandler(route *echo.Group, service NotifyService) {
handler := &notifyHandler{
service: service,
}
group := route.Group("/notify")
group.POST("/test", handler.test)
}
func (handler *notifyHandler) test(c echo.Context) error {
req := &domain.NotifyTestPushReq{}
if err := c.Bind(req); err != nil {
return err
}
if err := handler.service.Test(c.Request().Context(), req); err != nil {
return resp.Err(c, err)
}
return resp.Succ(c, nil)
}

19
internal/routes/routes.go Normal file
View File

@ -0,0 +1,19 @@
package routes
import (
"certimate/internal/notify"
"certimate/internal/repository"
"certimate/internal/rest"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase/apis"
)
func Register(e *echo.Echo) {
notifyRepo := repository.NewSettingRepository()
notifySvc := notify.NewNotifyService(notifyRepo)
group := e.Group("/api", apis.RequireAdminAuth())
rest.NewNotifyHandler(group, notifySvc)
}

View File

@ -0,0 +1,39 @@
package resp
import (
"net/http"
"certimate/internal/domain"
"github.com/labstack/echo/v5"
)
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func Succ(e echo.Context, data interface{}) error {
rs := &Response{
Code: 0,
Msg: "success",
Data: data,
}
return e.JSON(http.StatusOK, rs)
}
func Err(e echo.Context, err error) error {
xerr, ok := err.(*domain.XError)
code := 100
if ok {
code = xerr.GetCode()
}
rs := &Response{
Code: code,
Msg: err.Error(),
Data: nil,
}
return e.JSON(http.StatusOK, rs)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/pocketbase/pocketbase/plugins/migratecmd"
"certimate/internal/domains"
"certimate/internal/routes"
"certimate/internal/utils/app"
_ "certimate/migrations"
"certimate/ui"
@ -32,9 +33,10 @@ func main() {
domains.AddEvent()
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
domains.InitSchedule()
routes.Register(e.Router)
e.Router.GET(
"/*",
echo.StaticDirectoryHandler(ui.DistDirFS, false),

329
ui/dist/assets/index-BYO3zdEX.js vendored Normal file

File diff suppressed because one or more lines are too long

7
ui/dist/index.html vendored
View File

@ -5,10 +5,11 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-tBXwi-8W.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-YqBWA4KK.css">
<script type="module" crossorigin src="/assets/index-BYO3zdEX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-YqBWA4KK.css">
</head>
<body class="bg-background">
<div id="root"></div>
<div id="root"></div>
</body>
</html>

22
ui/src/api/notify.ts Normal file
View File

@ -0,0 +1,22 @@
import { getPb } from "@/repository/api";
export const notifyTest = async (channel: string) => {
const pb = getPb();
const resp = await pb.send("/api/notify/test", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: {
channel,
},
});
if (resp.code != 0) {
throw new Error(resp.msg);
}
return resp;
};

View File

@ -10,6 +10,8 @@ import { getErrMessage } from "@/lib/error";
import { NotifyChannelDingTalk, NotifyChannels } from "@/domain/settings";
import { useNotify } from "@/providers/notify";
import { update } from "@/repository/settings";
import Show from "@/components/Show";
import { notifyTest } from "@/api/notify";
type DingTalkSetting = {
id: string;
@ -21,6 +23,8 @@ const DingTalk = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [changed, setChanged] = useState<boolean>(false);
const [dingtalk, setDingtalk] = useState<DingTalkSetting>({
id: config.id ?? "",
name: "notifyChannels",
@ -31,23 +35,30 @@ const DingTalk = () => {
},
});
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;
}
const [originDingtalk, setOriginDingtalk] = useState<DingTalkSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
accessToken: "",
secret: "",
enabled: false,
},
});
return chanels.dingtalk as NotifyChannelDingTalk;
};
useEffect(() => {
setChanged(false);
}, [config]);
useEffect(() => {
const data = getDetailDingTalk();
setOriginDingtalk({
id: config.id ?? "",
name: "dingtalk",
data,
});
}, [config]);
useEffect(() => {
const data = getDetailDingTalk();
setDingtalk({
id: config.id ?? "",
@ -58,6 +69,31 @@ const DingTalk = () => {
const { toast } = useToast();
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 checkChanged = (data: NotifyChannelDingTalk) => {
if (data.accessToken !== originDingtalk.data.accessToken || data.secret !== originDingtalk.data.secret) {
setChanged(true);
} else {
setChanged(false);
}
};
const handleSaveClick = async () => {
try {
const resp = await update({
@ -87,19 +123,74 @@ const DingTalk = () => {
}
};
const handlePushTestClick = async () => {
try {
await notifyTest("dingtalk");
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 = {
...dingtalk,
data: {
...dingtalk.data,
enabled: !dingtalk.data.enabled,
},
};
setDingtalk(newData);
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
dingtalk: {
...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="AccessToken"
value={dingtalk.data.accessToken}
onChange={(e) => {
setDingtalk({
const newData = {
...dingtalk,
data: {
...dingtalk.data,
accessToken: e.target.value,
},
});
};
checkChanged(newData.data);
setDingtalk(newData);
}}
/>
<Input
@ -107,43 +198,47 @@ const DingTalk = () => {
className="mt-2"
value={dingtalk.data.secret}
onChange={(e) => {
setDingtalk({
const newData = {
...dingtalk,
data: {
...dingtalk.data,
secret: e.target.value,
},
});
};
checkChanged(newData.data);
setDingtalk(newData);
}}
/>
<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,
},
});
}}
/>
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && dingtalk.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
</div>
</div>
);
};
export default DingTalk;

View File

@ -1,14 +1,16 @@
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Switch } from "../ui/switch";
import { Label } from "../ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { useNotify } from "@/providers/notify";
import { NotifyChannelLark, 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";
import { useToast } from "@/components/ui/use-toast";
import { useTranslation } from "react-i18next";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
type LarkSetting = {
id: string;
@ -20,6 +22,8 @@ const Lark = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [changed, setChanged] = useState<boolean>(false);
const [lark, setLark] = useState<LarkSetting>({
id: config.id ?? "",
name: "notifyChannels",
@ -29,22 +33,29 @@ const Lark = () => {
},
});
useEffect(() => {
const getDetailLark = () => {
const df: NotifyChannelLark = {
webhookUrl: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.lark) {
return df;
}
const [originLark, setOriginLark] = useState<LarkSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
webhookUrl: "",
enabled: false,
},
});
return chanels.lark as NotifyChannelLark;
};
useEffect(() => {
setChanged(false);
}, [config]);
useEffect(() => {
const data = getDetailLark();
setOriginLark({
id: config.id ?? "",
name: "lark",
data,
});
}, [config]);
useEffect(() => {
const data = getDetailLark();
setLark({
id: config.id ?? "",
@ -55,6 +66,30 @@ const Lark = () => {
const { toast } = useToast();
const checkChanged = (data: NotifyChannelLark) => {
if (data.webhookUrl !== originLark.data.webhookUrl) {
setChanged(true);
} else {
setChanged(false);
}
};
const getDetailLark = () => {
const df: NotifyChannelLark = {
webhookUrl: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.lark) {
return df;
}
return chanels.lark as NotifyChannelLark;
};
const handleSaveClick = async () => {
try {
const resp = await update({
@ -78,9 +113,60 @@ const Lark = () => {
toast({
title: t("common.save.failed.message"),
description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
}
};
const handlePushTestClick = async () => {
try {
await notifyTest("lark");
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 = {
...lark,
data: {
...lark.data,
enabled: !lark.data.enabled,
},
};
setLark(newData);
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
lark: {
...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",
});
}
@ -92,45 +178,48 @@ const Lark = () => {
placeholder="Webhook Url"
value={lark.data.webhookUrl}
onChange={(e) => {
setLark({
const newData = {
...lark,
data: {
...lark.data,
webhookUrl: e.target.value,
},
});
};
checkChanged(newData.data);
setLark(newData);
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch
id="airplane-mode"
checked={lark.data.enabled}
onCheckedChange={() => {
setLark({
...lark,
data: {
...lark.data,
enabled: !lark.data.enabled,
},
});
}}
/>
<Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && lark.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
</div>
</div>
);
};
export default Lark;

View File

@ -10,6 +10,8 @@ import { getErrMessage } from "@/lib/error";
import { NotifyChannels, NotifyChannelTelegram } from "@/domain/settings";
import { update } from "@/repository/settings";
import { useNotify } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
type TelegramSetting = {
id: string;
@ -21,6 +23,8 @@ const Telegram = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [changed, setChanged] = useState<boolean>(false);
const [telegram, setTelegram] = useState<TelegramSetting>({
id: config.id ?? "",
name: "notifyChannels",
@ -31,23 +35,30 @@ const Telegram = () => {
},
});
useEffect(() => {
const getDetailTelegram = () => {
const df: NotifyChannelTelegram = {
apiToken: "",
chatId: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.telegram) {
return df;
}
const [originTelegram, setOriginTelegram] = useState<TelegramSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
apiToken: "",
chatId: "",
enabled: false,
},
});
return chanels.telegram as NotifyChannelTelegram;
};
useEffect(() => {
setChanged(false);
}, [config]);
useEffect(() => {
const data = getDetailTelegram();
setOriginTelegram({
id: config.id ?? "",
name: "common.provider.telegram",
data,
});
}, [config]);
useEffect(() => {
const data = getDetailTelegram();
setTelegram({
id: config.id ?? "",
@ -58,6 +69,31 @@ const Telegram = () => {
const { toast } = useToast();
const checkChanged = (data: NotifyChannelTelegram) => {
if (data.apiToken !== originTelegram.data.apiToken || data.chatId !== originTelegram.data.chatId) {
setChanged(true);
} else {
setChanged(false);
}
};
const getDetailTelegram = () => {
const df: NotifyChannelTelegram = {
apiToken: "",
chatId: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.telegram) {
return df;
}
return chanels.telegram as NotifyChannelTelegram;
};
const handleSaveClick = async () => {
try {
const resp = await update({
@ -87,19 +123,75 @@ const Telegram = () => {
}
};
const handlePushTestClick = async () => {
try {
await notifyTest("telegram");
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 = {
...telegram,
data: {
...telegram.data,
enabled: !telegram.data.enabled,
},
};
setTelegram(newData);
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
telegram: {
...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="ApiToken"
value={telegram.data.apiToken}
onChange={(e) => {
setTelegram({
const newData = {
...telegram,
data: {
...telegram.data,
apiToken: e.target.value,
},
});
};
checkChanged(newData.data);
setTelegram(newData);
}}
/>
@ -107,44 +199,49 @@ const Telegram = () => {
placeholder="ChatId"
value={telegram.data.chatId}
onChange={(e) => {
setTelegram({
const newData = {
...telegram,
data: {
...telegram.data,
chatId: e.target.value,
},
});
};
checkChanged(newData.data);
setTelegram(newData);
}}
/>
<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,
},
});
}}
/>
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && telegram.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
</div>
</div>
);
};
export default Telegram;

View File

@ -11,6 +11,8 @@ import { isValidURL } from "@/lib/url";
import { NotifyChannels, NotifyChannelWebhook } from "@/domain/settings";
import { update } from "@/repository/settings";
import { useNotify } from "@/providers/notify";
import { notifyTest } from "@/api/notify";
import Show from "@/components/Show";
type WebhookSetting = {
id: string;
@ -21,6 +23,7 @@ type WebhookSetting = {
const Webhook = () => {
const { config, setChannels } = useNotify();
const { t } = useTranslation();
const [changed, setChanged] = useState<boolean>(false);
const [webhook, setWebhook] = useState<WebhookSetting>({
id: config.id ?? "",
@ -31,22 +34,29 @@ const Webhook = () => {
},
});
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;
}
const [originWebhook, setOriginWebhook] = useState<WebhookSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
url: "",
enabled: false,
},
});
return chanels.webhook as NotifyChannelWebhook;
};
useEffect(() => {
setChanged(false);
}, [config]);
useEffect(() => {
const data = getDetailWebhook();
setOriginWebhook({
id: config.id ?? "",
name: "webhook",
data,
});
}, [config]);
useEffect(() => {
const data = getDetailWebhook();
setWebhook({
id: config.id ?? "",
@ -57,6 +67,30 @@ const Webhook = () => {
const { toast } = useToast();
const checkChanged = (data: NotifyChannelWebhook) => {
if (data.url !== originWebhook.data.url) {
setChanged(true);
} else {
setChanged(false);
}
};
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 handleSaveClick = async () => {
try {
webhook.data.url = webhook.data.url.trim();
@ -96,50 +130,108 @@ const Webhook = () => {
}
};
const handlePushTestClick = async () => {
try {
await notifyTest("webhook");
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 = {
...webhook,
data: {
...webhook.data,
enabled: !webhook.data.enabled,
},
};
setWebhook(newData);
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
webhook: {
...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="Url"
value={webhook.data.url}
onChange={(e) => {
setWebhook({
const newData = {
...webhook,
data: {
...webhook.data,
url: e.target.value,
},
});
};
checkChanged(newData.data);
setWebhook(newData);
}}
/>
<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,
},
});
}}
/>
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex justify-end mt-2">
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && webhook.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
</div>
</div>
);
};
export default Webhook;

View File

@ -30,6 +30,9 @@
"settings.notification.config.enable": "Enable",
"settings.notification.config.saved.message": "Configuration saved successfully",
"settings.notification.config.failed.message": "Configuration save failed",
"settings.notification.config.push.test.message": "Send test notification",
"settings.notification.config.push.test.message.failed.message": "Send test notification failed",
"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",
@ -39,3 +42,4 @@
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
}

View File

@ -30,6 +30,9 @@
"settings.notification.config.enable": "是否启用",
"settings.notification.config.saved.message": "配置保存成功",
"settings.notification.config.failed.message": "配置保存失败",
"settings.notification.config.push.test.message": "推送测试消息",
"settings.notification.config.push.test.message.failed.message": "推送测试消息失败",
"settings.notification.config.push.test.message.success.message": "推送测试消息成功",
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
@ -39,3 +42,4 @@
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
}