Merge pull request #321 from fudiwei/feat/notifier

feat: notifiers
This commit is contained in:
usual2970
2024-11-11 18:16:30 +08:00
committed by GitHub
42 changed files with 1735 additions and 1011 deletions

View File

@@ -123,22 +123,29 @@ const Bark = () => {
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("bark");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
@@ -177,67 +184,76 @@ const Bark = () => {
};
return (
<div>
<Input
placeholder={t("settings.notification.bark.serverUrl.placeholder")}
value={bark.data.serverUrl}
onChange={(e) => {
const newData = {
...bark,
data: {
...bark.data,
serverUrl: e.target.value,
},
};
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.bark.server_url.label")}</Label>
<Input
placeholder={t("settings.notification.bark.server_url.placeholder")}
value={bark.data.serverUrl}
onChange={(e) => {
const newData = {
...bark,
data: {
...bark.data,
serverUrl: e.target.value,
},
};
checkChanged(newData.data);
setBark(newData);
}}
/>
<Input
placeholder={t("settings.notification.bark.deviceKey.placeholder")}
value={bark.data.deviceKey}
onChange={(e) => {
const newData = {
...bark,
data: {
...bark.data,
deviceKey: e.target.value,
},
};
checkChanged(newData.data);
setBark(newData);
}}
/>
<div className="flex items-center space-x-1 mt-2">
<Switch id="airplane-mode" checked={bark.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
checkChanged(newData.data);
setBark(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div>
<Label>{t("settings.notification.bark.device_key.label")}</Label>
<Input
placeholder={t("settings.notification.bark.device_key.placeholder")}
value={bark.data.deviceKey}
onChange={(e) => {
const newData = {
...bark,
data: {
...bark.data,
deviceKey: e.target.value,
},
};
<Show when={!changed && bark.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
checkChanged(newData.data);
setBark(newData);
}}
/>
</div>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={bark.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && bark.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -120,23 +120,30 @@ const DingTalk = () => {
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("dingtalk");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
}
@@ -177,64 +184,74 @@ const DingTalk = () => {
};
return (
<div>
<Input
placeholder="AccessToken"
value={dingtalk.data.accessToken}
onChange={(e) => {
const newData = {
...dingtalk,
data: {
...dingtalk.data,
accessToken: e.target.value,
},
};
checkChanged(newData.data);
setDingtalk(newData);
}}
/>
<Input
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
className="mt-2"
value={dingtalk.data.secret}
onChange={(e) => {
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={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.dingtalk.access_token.label")}</Label>
<Input
placeholder={t("settings.notification.dingtalk.access_token.placeholder")}
value={dingtalk.data.accessToken}
onChange={(e) => {
const newData = {
...dingtalk,
data: {
...dingtalk.data,
accessToken: e.target.value,
},
};
checkChanged(newData.data);
setDingtalk(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div>
<Label>{t("settings.notification.dingtalk.secret.label")}</Label>
<Input
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
value={dingtalk.data.secret}
onChange={(e) => {
const newData = {
...dingtalk,
data: {
...dingtalk.data,
secret: e.target.value,
},
};
checkChanged(newData.data);
setDingtalk(newData);
}}
/>
</div>
<Show when={!changed && dingtalk.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={dingtalk.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && dingtalk.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,384 @@
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 { NotifyChannelEmail, 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 EmailSetting = {
id: string;
name: string;
data: NotifyChannelEmail;
};
const Mail = () => {
const { config, setChannels } = useNotifyContext();
const { t } = useTranslation();
const [changed, setChanged] = useState<boolean>(false);
const [mail, setMail] = useState<EmailSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
smtpHost: "",
smtpPort: 465,
smtpTLS: true,
username: "",
password: "",
senderAddress: "",
receiverAddress: "",
enabled: false,
},
});
const [originMail, setOriginMail] = useState<EmailSetting>({
id: config.id ?? "",
name: "notifyChannels",
data: {
smtpHost: "",
smtpPort: 465,
smtpTLS: true,
username: "",
password: "",
senderAddress: "",
receiverAddress: "",
enabled: false,
},
});
useEffect(() => {
setChanged(false);
}, [config]);
useEffect(() => {
const data = getDetailMail();
setOriginMail({
id: config.id ?? "",
name: "email",
data,
});
}, [config]);
useEffect(() => {
const data = getDetailMail();
setMail({
id: config.id ?? "",
name: "email",
data,
});
}, [config]);
const { toast } = useToast();
const getDetailMail = () => {
const df: NotifyChannelEmail = {
smtpHost: "smtp.example.com",
smtpPort: 465,
smtpTLS: true,
username: "",
password: "",
senderAddress: "",
receiverAddress: "",
enabled: false,
};
if (!config.content) {
return df;
}
const chanels = config.content as NotifyChannels;
if (!chanels.email) {
return df;
}
return chanels.email as NotifyChannelEmail;
};
const checkChanged = (data: NotifyChannelEmail) => {
if (
data.smtpHost !== originMail.data.smtpHost ||
data.smtpPort !== originMail.data.smtpPort ||
data.smtpTLS !== originMail.data.smtpTLS ||
data.username !== originMail.data.username ||
data.password !== originMail.data.password ||
data.senderAddress !== originMail.data.senderAddress ||
data.receiverAddress !== originMail.data.receiverAddress
) {
setChanged(true);
} else {
setChanged(false);
}
};
const handleSaveClick = async () => {
try {
const resp = await update({
...config,
name: "notifyChannels",
content: {
...config.content,
email: {
...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 [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("email");
toast({
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.message"),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
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,
email: {
...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 className="flex flex-col space-y-4">
<div className="flex space-x-4">
<div className="w-2/5">
<Label>{t("settings.notification.email.smtp_host.label")}</Label>
<Input
placeholder={t("settings.notification.email.smtp_host.placeholder")}
value={mail.data.smtpHost}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
smtpHost: e.target.value,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
<div className="w-2/5">
<Label>{t("settings.notification.email.smtp_port.label")}</Label>
<Input
type="number"
placeholder={t("settings.notification.email.smtp_port.placeholder")}
value={mail.data.smtpPort}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
smtpPort: +e.target.value || 0,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
<div className="w-1/5">
<Label>{t("settings.notification.email.smtp_tls.label")}</Label>
<Switch
className="block mt-2"
checked={mail.data.smtpTLS}
onCheckedChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
smtpPort: e && mail.data.smtpPort === 25 ? 465 : !e && mail.data.smtpPort === 465 ? 25 : mail.data.smtpPort,
smtpTLS: e,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
</div>
<div className="flex space-x-4">
<div className="w-1/2">
<Label>{t("settings.notification.email.username.label")}</Label>
<Input
placeholder={t("settings.notification.email.username.placeholder")}
value={mail.data.username}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
username: e.target.value,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
<div className="w-1/2">
<Label>{t("settings.notification.email.password.label")}</Label>
<Input
placeholder={t("settings.notification.email.password.placeholder")}
value={mail.data.password}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
password: e.target.value,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
</div>
<div>
<Label>{t("settings.notification.email.sender_address.label")}</Label>
<Input
placeholder={t("settings.notification.email.sender_address.placeholder")}
value={mail.data.senderAddress}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
senderAddress: e.target.value,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
<div>
<Label>{t("settings.notification.email.receiver_address.label")}</Label>
<Input
placeholder={t("settings.notification.email.receiver_address.placeholder")}
value={mail.data.receiverAddress}
onChange={(e) => {
const newData = {
...mail,
data: {
...mail.data,
receiverAddress: e.target.value,
},
};
checkChanged(newData.data);
setMail(newData);
}}
/>
</div>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={mail.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && mail.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);
};
export default Mail;

View File

@@ -116,23 +116,30 @@ const Lark = () => {
description: `${t("settings.notification.config.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("lark");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
}
@@ -173,49 +180,56 @@ const Lark = () => {
};
return (
<div>
<Input
placeholder="Webhook Url"
value={lark.data.webhookUrl}
onChange={(e) => {
const newData = {
...lark,
data: {
...lark.data,
webhookUrl: e.target.value,
},
};
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.lark.webhook_url.label")}</Label>
<Input
placeholder={t("settings.notification.lark.webhook_url.placeholder")}
value={lark.data.webhookUrl}
onChange={(e) => {
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={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
checkChanged(newData.data);
setLark(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={lark.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<Show when={!changed && lark.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && lark.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -1,319 +0,0 @@
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,
receiverAddresses: 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,
smtpHostAddr: 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,
smtpHostPort: 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,
username: 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,
password: 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;

View File

@@ -97,7 +97,7 @@ const ServerChan = () => {
if (!isValidURL(serverchan.data.url)) {
toast({
title: t("common.save.failed.message"),
description: t("settings.notification.url.errmsg.invalid"),
description: t("common.errmsg.url_invalid"),
variant: "destructive",
});
return;
@@ -130,22 +130,29 @@ const ServerChan = () => {
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("serverchan");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
@@ -184,50 +191,56 @@ const ServerChan = () => {
};
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,
},
};
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.serverchan.url.label")}</Label>
<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>
checkChanged(newData.data);
setServerChan(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={serverchan.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<Show when={!changed && serverchan.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && serverchan.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -123,22 +123,29 @@ const Telegram = () => {
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("telegram");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
@@ -177,67 +184,76 @@ const Telegram = () => {
};
return (
<div>
<Input
placeholder="ApiToken"
value={telegram.data.apiToken}
onChange={(e) => {
const newData = {
...telegram,
data: {
...telegram.data,
apiToken: e.target.value,
},
};
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.telegram.api_token.label")}</Label>
<Input
placeholder={t("settings.notification.telegram.api_token.placeholder")}
value={telegram.data.apiToken}
onChange={(e) => {
const newData = {
...telegram,
data: {
...telegram.data,
apiToken: e.target.value,
},
};
checkChanged(newData.data);
setTelegram(newData);
}}
/>
<Input
placeholder="ChatId"
value={telegram.data.chatId}
onChange={(e) => {
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={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
checkChanged(newData.data);
setTelegram(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div>
<Label>{t("settings.notification.telegram.chat_id.label")}</Label>
<Input
placeholder={t("settings.notification.telegram.chat_id.placeholder")}
value={telegram.data.chatId}
onChange={(e) => {
const newData = {
...telegram,
data: {
...telegram.data,
chatId: e.target.value,
},
};
<Show when={!changed && telegram.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
checkChanged(newData.data);
setTelegram(newData);
}}
/>
</div>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={telegram.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && telegram.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -97,7 +97,7 @@ const Webhook = () => {
if (!isValidURL(webhook.data.url)) {
toast({
title: t("common.save.failed.message"),
description: t("settings.notification.url.errmsg.invalid"),
description: t("common.errmsg.url_invalid"),
variant: "destructive",
});
return;
@@ -130,22 +130,29 @@ const Webhook = () => {
}
};
const [testing, setTesting] = useState<boolean>(false);
const handlePushTestClick = async () => {
if (testing) return;
try {
setTesting(true);
await notifyTest("webhook");
toast({
title: t("settings.notification.config.push.test.message.success.message"),
description: t("settings.notification.config.push.test.message.success.message"),
title: t("settings.notification.push_test_message.succeeded.message"),
description: t("settings.notification.push_test_message.succeeded.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}`,
title: t("settings.notification.push_test_message.failed.message"),
description: `${t("settings.notification.push_test_message.failed.message")}: ${msg}`,
variant: "destructive",
});
} finally {
setTesting(false);
}
};
@@ -184,50 +191,56 @@ const Webhook = () => {
};
return (
<div>
<Input
placeholder="Url"
value={webhook.data.url}
onChange={(e) => {
const newData = {
...webhook,
data: {
...webhook.data,
url: e.target.value,
},
};
<div className="flex flex-col space-y-4">
<div>
<Label>{t("settings.notification.webhook.url.label")}</Label>
<Input
placeholder={t("settings.notification.webhook.url.placeholder")}
value={webhook.data.url}
onChange={(e) => {
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={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
checkChanged(newData.data);
setWebhook(newData);
}}
/>
</div>
<div className="flex justify-end mt-2">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<div className="flex justify-between gap-4">
<div className="flex items-center space-x-1">
<Switch id="airplane-mode" checked={webhook.data.enabled} onCheckedChange={handleSwitchChange} />
<Label htmlFor="airplane-mode">{t("settings.notification.config.enable")}</Label>
</div>
<Show when={!changed && webhook.id != ""}>
<Button
variant="secondary"
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.config.push.test.message")}
</Button>
</Show>
<div className="flex items-center space-x-1">
<Show when={changed}>
<Button
onClick={() => {
handleSaveClick();
}}
>
{t("common.save")}
</Button>
</Show>
<Show when={!changed && webhook.id != ""}>
<Button
variant="secondary"
loading={testing}
onClick={() => {
handlePushTestClick();
}}
>
{t("settings.notification.push_test_message")}
</Button>
</Show>
</div>
</div>
</div>
);

View File

@@ -1,6 +1,7 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
@@ -32,11 +33,38 @@ const buttonVariants = cva(
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, loading, asChild = false, children, ...props }, ref) => {
if (asChild) {
return (
<Slot ref={ref} {...props}>
<>
{React.Children.map(children as React.ReactElement, (child: React.ReactElement) => {
return React.cloneElement(child, {
className: cn(buttonVariants({ variant, size }), className),
children: (
<>
{loading && <Loader2 className={cn("h-4 w-4 animate-spin", children && "mr-2")} />}
{child.props.children}
</>
),
});
})}
</>
</Slot>
);
}
return (
<button className={cn(buttonVariants({ variant, size, className }))} disabled={loading} ref={ref} {...props}>
<>
{loading && <Loader2 className={cn("h-4 w-4 animate-spin", children && "mr-2")} />}
{children}
</>
</button>
);
});
Button.displayName = "Button";

View File

@@ -18,24 +18,40 @@ export type NotifyTemplate = {
};
export type NotifyChannels = {
email?: NotifyChannelEmail;
webhook?: NotifyChannel;
dingtalk?: NotifyChannel;
lark?: NotifyChannel;
telegram?: NotifyChannel;
webhook?: NotifyChannel;
serverchan?: NotifyChannel;
mail?: NotifyChannelMail;
bark?: NotifyChannelBark;
};
export type NotifyChannel =
| NotifyChannelEmail
| NotifyChannelWebhook
| NotifyChannelDingTalk
| NotifyChannelLark
| NotifyChannelTelegram
| NotifyChannelWebhook
| NotifyChannelServerChan
| NotifyChannelMail
| NotifyChannelBark;
export type NotifyChannelEmail = {
smtpHost: string;
smtpPort: number;
smtpTLS: boolean;
username: string;
password: string;
senderAddress: string;
receiverAddress: string;
enabled: boolean;
};
export type NotifyChannelWebhook = {
url: string;
enabled: boolean;
};
export type NotifyChannelDingTalk = {
accessToken: string;
secret: string;
@@ -53,26 +69,11 @@ export type NotifyChannelTelegram = {
enabled: boolean;
};
export type NotifyChannelWebhook = {
url: string;
enabled: boolean;
};
export type NotifyChannelServerChan = {
url: string;
enabled: boolean;
};
export type NotifyChannelMail = {
senderAddress: string;
receiverAddresses: string;
smtpHostAddr: string;
smtpHostPort: string;
username: string;
password: string;
enabled: boolean;
};
export type NotifyChannelBark = {
deviceKey: string;
serverUrl: string;

View File

@@ -83,12 +83,12 @@
"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.email": "Email",
"common.provider.dingtalk": "DingTalk",
"common.provider.telegram": "Telegram",
"common.provider.lark": "Lark",
"common.provider.mail": "Mail",
"common.provider.telegram": "Telegram",
"common.provider.serverchan": "ServerChan",
"common.provider.bark": "Bark"
}

View File

@@ -30,20 +30,40 @@
"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",
"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.notification.bark.serverUrl.placeholder": "Server URL, e.g. https://your-bark-server.com, leave it blank to use the bark default server",
"settings.notification.bark.deviceKey.placeholder": "Device Keye.g. XXXXXXXXXXXXXXXXXXXX",
"settings.notification.push_test_message": "Send test notification",
"settings.notification.push_test_message.succeeded.message": "Send test notification successfully",
"settings.notification.push_test_message.failed.message": "Send test notification failed",
"settings.notification.email.smtp_host.label": "SMTP Host",
"settings.notification.email.smtp_host.placeholder": "Please enter SMTP host",
"settings.notification.email.smtp_port.label": "SMTP Port",
"settings.notification.email.smtp_port.placeholder": "Please enter SMTP port",
"settings.notification.email.smtp_tls.label": "Use TLS/SSL",
"settings.notification.email.username.label": "Username",
"settings.notification.email.username.placeholder": "please enter username",
"settings.notification.email.password.label": "Password",
"settings.notification.email.password.placeholder": "please enter password",
"settings.notification.email.sender_address.label": "Sender Email Address",
"settings.notification.email.sender_address.placeholder": "Please enter sender email address",
"settings.notification.email.receiver_address.label": "Receiver Email Address",
"settings.notification.email.receiver_address.placeholder": "Please enter receiver email address",
"settings.notification.webhook.url.label": "Webhook URL",
"settings.notification.webhook.url.placeholder": "Please enter Webhook URL",
"settings.notification.dingtalk.access_token.label": "AccessToken",
"settings.notification.dingtalk.access_token.placeholder": "Please enter access token",
"settings.notification.dingtalk.secret.label": "Secret",
"settings.notification.dingtalk.secret.placeholder": "Please enter secret",
"settings.notification.lark.webhook_url.label": "Webhook URL",
"settings.notification.lark.webhook_url.placeholder": "Please enter Webhook URL",
"settings.notification.telegram.api_token.label": "API Token",
"settings.notification.telegram.api_token.placeholder": "Please enter API token",
"settings.notification.telegram.chat_id.label": "Chat ID",
"settings.notification.telegram.chat_id.placeholder": "Please enter Telegram chat ID",
"settings.notification.serverchan.url.label": "Server URL",
"settings.notification.serverchan.url.placeholder": "Please enter server URL (e.g. https://sctapi.ftqq.com/*****.send)",
"settings.notification.bark.server_url.label": "Server URL",
"settings.notification.bark.server_url.placeholder": "Please enter server URL (e.g. https://your-bark-server.com. Leave it blank to use the bark default server)",
"settings.notification.bark.device_key.label": "Device Key",
"settings.notification.bark.device_key.placeholder": "Please enter device key",
"settings.ca.tab": "Certificate Authority",
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",

View File

@@ -83,12 +83,12 @@
"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.email": "电子邮件",
"common.provider.dingtalk": "钉钉",
"common.provider.telegram": "Telegram",
"common.provider.lark": "飞书",
"common.provider.mail": "电子邮件",
"common.provider.telegram": "Telegram",
"common.provider.serverchan": "Server酱",
"common.provider.bark": "Bark"
}

View File

@@ -30,20 +30,40 @@
"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 格式不正确",
"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.notification.bark.serverUrl.placeholder": "服务器URL形如: https://your-bark-server.com 留空则使用 Bark 默认服务器",
"settings.notification.bark.deviceKey.placeholder": "设备密钥,形如: XXXXXXXXXXXXXXXXXXXX",
"settings.notification.push_test_message": "推送测试消息",
"settings.notification.push_test_message.failed.message": "推送测试消息失败",
"settings.notification.push_test_message.succeeded.message": "推送测试消息成功",
"settings.notification.email.smtp_host.label": "SMTP 服务器地址",
"settings.notification.email.smtp_host.placeholder": "请输入 SMTP 服务器地址",
"settings.notification.email.smtp_port.label": "SMTP 服务器端口",
"settings.notification.email.smtp_port.placeholder": "请输入 SMTP 服务器端口",
"settings.notification.email.smtp_tls.label": "TLS/SSL 连接",
"settings.notification.email.username.label": "用户名",
"settings.notification.email.username.placeholder": "请输入用户名",
"settings.notification.email.password.label": "密码",
"settings.notification.email.password.placeholder": "请输入密码",
"settings.notification.email.sender_address.label": "发送邮箱地址",
"settings.notification.email.sender_address.placeholder": "请输入发送邮箱地址",
"settings.notification.email.receiver_address.label": "接收邮箱地址",
"settings.notification.email.receiver_address.placeholder": "请输入接收邮箱地址",
"settings.notification.webhook.url.label": "Webhook 回调地址",
"settings.notification.webhook.url.placeholder": "请输入 Webhook 回调地址",
"settings.notification.dingtalk.access_token.label": "AccessToken",
"settings.notification.dingtalk.access_token.placeholder": "请输入 AccessToken",
"settings.notification.dingtalk.secret.label": "签名密钥",
"settings.notification.dingtalk.secret.placeholder": "请输入签名密钥",
"settings.notification.lark.webhook_url.label": "Webhook URL",
"settings.notification.lark.webhook_url.placeholder": "请输入 Webhook URL",
"settings.notification.telegram.api_token.label": "API Token",
"settings.notification.telegram.api_token.placeholder": "请输入 API token",
"settings.notification.telegram.chat_id.label": "会话 ID",
"settings.notification.telegram.chat_id.placeholder": "请输入 Telegram 会话 ID",
"settings.notification.serverchan.url.label": "服务器 URL",
"settings.notification.serverchan.url.placeholder": "请输入服务器 URL形如: https://sctapi.ftqq.com/*****.send",
"settings.notification.bark.server_url.label": "服务器 URL",
"settings.notification.bark.server_url.placeholder": "请输入服务器 URL形如: https://your-bark-server.com留空则使用 Bark 默认服务器)",
"settings.notification.bark.device_key.label": "设备密钥",
"settings.notification.bark.device_key.placeholder": "请输入设备密钥",
"settings.ca.tab": "证书颁发机构CA",
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",

View File

@@ -7,7 +7,7 @@ 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 Mail from "@/components/notify/Mail";
import Email from "@/components/notify/Email";
import Bark from "@/components/notify/Bark";
import { NotifyProvider } from "@/providers/notify";
@@ -27,51 +27,52 @@ const Notify = () => {
</AccordionItem>
</Accordion>
</div>
<div className="border rounded-md p-5 mt-7 shadow-lg">
<Accordion type={"single"} className="dark:text-stone-200">
<AccordionItem value="item-2" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
<AccordionItem value="item-email" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.email")}</AccordionTrigger>
<AccordionContent>
<DingTalk />
<Email />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.lark")}</AccordionTrigger>
<AccordionContent>
<Lark />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-4" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
<AccordionContent>
<Telegram />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-5" className="dark:border-stone-200">
<AccordionItem value="item-webhook" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.webhook")}</AccordionTrigger>
<AccordionContent>
<Webhook />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-6" className="dark:border-stone-200">
<AccordionItem value="item-dingtalk" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.dingtalk")}</AccordionTrigger>
<AccordionContent>
<DingTalk />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-lark" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.lark")}</AccordionTrigger>
<AccordionContent>
<Lark />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-telegram" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.telegram")}</AccordionTrigger>
<AccordionContent>
<Telegram />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-serverchan" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.serverchan")}</AccordionTrigger>
<AccordionContent>
<ServerChan />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-7" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.mail")}</AccordionTrigger>
<AccordionContent>
<Mail />
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-8" className="dark:border-stone-200">
<AccordionItem value="item-bark" className="dark:border-stone-200">
<AccordionTrigger>{t("common.provider.bark")}</AccordionTrigger>
<AccordionContent>
<Bark />