mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-23 04:39:57 +00:00
message push config
This commit is contained in:
parent
5422f17fab
commit
4c9095400e
@ -25,6 +25,7 @@ import { update } from "@/repository/settings";
|
|||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
import { PbErrorData } from "@/domain/base";
|
import { PbErrorData } from "@/domain/base";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { EmailsSetting } from "@/domain/settings";
|
||||||
|
|
||||||
type EmailsEditProps = {
|
type EmailsEditProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -51,7 +52,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
const onSubmit = async (data: z.infer<typeof formSchema>) => {
|
||||||
if (emails.content.emails.includes(data.email)) {
|
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
|
||||||
form.setError("email", {
|
form.setError("email", {
|
||||||
message: "邮箱已存在",
|
message: "邮箱已存在",
|
||||||
});
|
});
|
||||||
@ -59,7 +60,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存到 config
|
// 保存到 config
|
||||||
const newEmails = [...emails.content.emails, data.email];
|
const newEmails = [...(emails.content as EmailsSetting).emails, data.email];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await update({
|
const resp = await update({
|
||||||
|
146
ui/src/components/notify/DingTalk.tsx
Normal file
146
ui/src/components/notify/DingTalk.tsx
Normal file
@ -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<DingTalkSetting>({
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="AccessToken"
|
||||||
|
value={dingtalk.data.accessToken}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDingtalk({
|
||||||
|
...dingtalk,
|
||||||
|
data: {
|
||||||
|
...dingtalk.data,
|
||||||
|
accessToken: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="加签的签名"
|
||||||
|
className="mt-2"
|
||||||
|
value={dingtalk.data.secret}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDingtalk({
|
||||||
|
...dingtalk,
|
||||||
|
data: {
|
||||||
|
...dingtalk.data,
|
||||||
|
secret: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DingTalk;
|
104
ui/src/components/notify/NotifyTemplate.tsx
Normal file
104
ui/src/components/notify/NotifyTemplate.tsx
Normal file
@ -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<NotifyTemplateT[]>([
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
value={templates[0].title}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleTitleChange(e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
|
可选的变量, COUNT:即将过期张数
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Textarea
|
||||||
|
className="mt-2"
|
||||||
|
value={templates[0].content}
|
||||||
|
onChange={(e) => {
|
||||||
|
handleContentChange(e.target.value);
|
||||||
|
}}
|
||||||
|
></Textarea>
|
||||||
|
<div className="text-muted-foreground text-sm mt-1">
|
||||||
|
可选的变量, COUNT:即将过期张数,DOMAINS:域名列表
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<Button onClick={handleSaveClick}>保存</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotifyTemplate;
|
131
ui/src/components/notify/Telegram.tsx
Normal file
131
ui/src/components/notify/Telegram.tsx
Normal file
@ -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<TelegramSetting>({
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="ApiToken"
|
||||||
|
value={telegram.data.apiToken}
|
||||||
|
onChange={(e) => {
|
||||||
|
setTelegram({
|
||||||
|
...telegram,
|
||||||
|
data: {
|
||||||
|
...telegram.data,
|
||||||
|
apiToken: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Telegram;
|
131
ui/src/components/notify/Webhook.tsx
Normal file
131
ui/src/components/notify/Webhook.tsx
Normal file
@ -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<WebhookSetting>({
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="Url"
|
||||||
|
value={webhook.data.url}
|
||||||
|
onChange={(e) => {
|
||||||
|
setWebhook({
|
||||||
|
...webhook,
|
||||||
|
data: {
|
||||||
|
...webhook.data,
|
||||||
|
url: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="airplane-mode">是否启用</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end mt-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleSaveClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Webhook;
|
@ -1,9 +1,50 @@
|
|||||||
export type Setting = {
|
export type Setting = {
|
||||||
id?: string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
content: EmailsSetting;
|
content?: EmailsSetting | NotifyTemplates | NotifyChannels;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmailsSetting = {
|
export type EmailsSetting = {
|
||||||
emails: string[];
|
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},请保持关注!",
|
||||||
|
};
|
||||||
|
@ -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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { KeyRound, Megaphone, UserRound } from "lucide-react";
|
import { KeyRound, Megaphone, UserRound } from "lucide-react";
|
||||||
|
@ -37,6 +37,7 @@ import { accessTypeMap } from "@/domain/access";
|
|||||||
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
import EmailsEdit from "@/components/certimate/EmailsEdit";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { EmailsSetting } from "@/domain/settings";
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const {
|
const {
|
||||||
@ -270,11 +271,13 @@ const Edit = () => {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
<SelectLabel>邮箱列表</SelectLabel>
|
<SelectLabel>邮箱列表</SelectLabel>
|
||||||
{emails.content.emails.map((item) => (
|
{(emails.content as EmailsSetting).emails.map(
|
||||||
|
(item) => (
|
||||||
<SelectItem key={item} value={item}>
|
<SelectItem key={item} value={item}>
|
||||||
<div>{item}</div>
|
<div>{item}</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -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 {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { Input } from "@/components/ui/input";
|
import { NotifyProvider } from "@/providers/notify";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
const Notify = () => {
|
const Notify = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="border rounded-sm p-5">
|
<NotifyProvider>
|
||||||
|
<div className="border rounded-sm p-5 shadow-lg">
|
||||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-1" className="dark:border-stone-200">
|
<AccordionItem value="item-1" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>模板</AccordionTrigger>
|
<AccordionTrigger>模板</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<Input value="您有证书即将过期" />
|
<NotifyTemplate />
|
||||||
<Textarea
|
|
||||||
className="mt-2"
|
|
||||||
value={
|
|
||||||
"有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!"
|
|
||||||
}
|
|
||||||
></Textarea>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
<div className="border rounded-md p-5 mt-7">
|
<div className="border rounded-md p-5 mt-7 shadow-lg">
|
||||||
<Accordion type={"multiple"} className="dark:text-stone-200">
|
<Accordion type={"multiple"} className="dark:text-stone-200">
|
||||||
<AccordionItem value="item-2" className="dark:border-stone-200">
|
<AccordionItem value="item-2" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>钉钉</AccordionTrigger>
|
<AccordionTrigger>钉钉</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
Yes. It adheres to the WAI-ARIA design pattern.
|
<DingTalk />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-4" className="dark:border-stone-200">
|
<AccordionItem value="item-4" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>Telegram</AccordionTrigger>
|
<AccordionTrigger>Telegram</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
Yes. It adheres to the WAI-ARIA design pattern.
|
<Telegram />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|
||||||
<AccordionItem value="item-5" className="dark:border-stone-200">
|
<AccordionItem value="item-5" className="dark:border-stone-200">
|
||||||
<AccordionTrigger>Webhook</AccordionTrigger>
|
<AccordionTrigger>Webhook</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
Yes. It adheres to the WAI-ARIA design pattern.
|
<Webhook />
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
|
</NotifyProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Access } from "@/domain/access";
|
import { Access } from "@/domain/access";
|
||||||
import { ConfigData } from ".";
|
import { ConfigData } from ".";
|
||||||
import { Setting } from "@/domain/settings";
|
import { EmailsSetting, Setting } from "@/domain/settings";
|
||||||
import { AccessGroup } from "@/domain/access_groups";
|
import { AccessGroup } from "@/domain/access_groups";
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
@ -57,7 +57,10 @@ export const configReducer = (
|
|||||||
emails: {
|
emails: {
|
||||||
...state.emails,
|
...state.emails,
|
||||||
content: {
|
content: {
|
||||||
emails: [...state.emails.content.emails, action.payload],
|
emails: [
|
||||||
|
...(state.emails.content as EmailsSetting).emails,
|
||||||
|
action.payload,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
68
ui/src/providers/notify/index.tsx
Normal file
68
ui/src/providers/notify/index.tsx
Normal file
@ -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 (
|
||||||
|
<Context.Provider
|
||||||
|
value={{
|
||||||
|
config: notify,
|
||||||
|
setChannel,
|
||||||
|
setChannels,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
);
|
||||||
|
};
|
35
ui/src/providers/notify/reducer.tsx
Normal file
35
ui/src/providers/notify/reducer.tsx
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
};
|
@ -14,6 +14,20 @@ export const getEmails = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getSetting = async (name: string) => {
|
||||||
|
try {
|
||||||
|
const resp = await getPb()
|
||||||
|
.collection("settings")
|
||||||
|
.getFirstListItem<Setting>(`name='${name}'`);
|
||||||
|
return resp;
|
||||||
|
} catch (e) {
|
||||||
|
const rs: Setting = {
|
||||||
|
name: name,
|
||||||
|
};
|
||||||
|
return rs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const update = async (setting: Setting) => {
|
export const update = async (setting: Setting) => {
|
||||||
const pb = getPb();
|
const pb = getPb();
|
||||||
let resp: Setting;
|
let resp: Setting;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user