mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-19 09:51:55 +08:00
feat: adapt email, mattermost, telegram, and webhook to new notifier module
This commit is contained in:
parent
a117fd7d93
commit
11b413d0dc
@ -5,6 +5,9 @@ import (
|
|||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
"github.com/usual2970/certimate/internal/pkg/core/notifier"
|
||||||
|
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
|
||||||
|
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
|
||||||
|
pTelegram "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegram"
|
||||||
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
|
||||||
maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
|
maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
|
||||||
)
|
)
|
||||||
@ -21,12 +24,65 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
|
|||||||
NOTICE: If you add new constant, please keep ASCII order.
|
NOTICE: If you add new constant, please keep ASCII order.
|
||||||
*/
|
*/
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case domain.NotificationProviderTypeWebhook:
|
case domain.NotificationProviderTypeEmail:
|
||||||
return pWebhook.NewNotifier(&pWebhook.NotifierConfig{
|
{
|
||||||
Url: maputil.GetString(options.ProviderAccessConfig, "url"),
|
access := domain.AccessConfigForEmail{}
|
||||||
AllowInsecureConnections: maputil.GetBool(options.ProviderAccessConfig, "allowInsecureConnections"),
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pEmail.NewNotifier(&pEmail.NotifierConfig{
|
||||||
|
SmtpHost: access.SmtpHost,
|
||||||
|
SmtpPort: access.SmtpPort,
|
||||||
|
SmtpTls: access.SmtpTls,
|
||||||
|
Username: access.Username,
|
||||||
|
Password: access.Password,
|
||||||
|
SenderAddress: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "senderAddress", access.DefaultSenderAddress),
|
||||||
|
ReceiverAddress: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "receiverAddress", access.DefaultReceiverAddress),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case domain.NotificationProviderTypeMattermost:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForMattermost{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pMattermost.NewNotifier(&pMattermost.NotifierConfig{
|
||||||
|
ServerUrl: access.ServerUrl,
|
||||||
|
Username: access.Username,
|
||||||
|
Password: access.Password,
|
||||||
|
ChannelId: maputil.GetOrDefaultString(options.ProviderNotifyConfig, "channelId", access.DefaultChannelId),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case domain.NotificationProviderTypeTelegram:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForTelegram{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pTelegram.NewNotifier(&pTelegram.NotifierConfig{
|
||||||
|
BotToken: access.BotToken,
|
||||||
|
ChatId: maputil.GetOrDefaultInt64(options.ProviderNotifyConfig, "chatId", access.DefaultChatId),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case domain.NotificationProviderTypeWebhook:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForWebhook{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pWebhook.NewNotifier(&pWebhook.NotifierConfig{
|
||||||
|
Url: access.Url,
|
||||||
|
AllowInsecureConnections: access.AllowInsecureConnections,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider)
|
return nil, fmt.Errorf("unsupported notifier provider '%s'", options.Provider)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import AccessFormDNSLAConfig from "./AccessFormDNSLAConfig";
|
|||||||
import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig";
|
import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig";
|
||||||
import AccessFormDynv6Config from "./AccessFormDynv6Config";
|
import AccessFormDynv6Config from "./AccessFormDynv6Config";
|
||||||
import AccessFormEdgioConfig from "./AccessFormEdgioConfig";
|
import AccessFormEdgioConfig from "./AccessFormEdgioConfig";
|
||||||
|
import AccessFormEmailConfig from "./AccessFormEmailConfig";
|
||||||
import AccessFormGcoreConfig from "./AccessFormGcoreConfig";
|
import AccessFormGcoreConfig from "./AccessFormGcoreConfig";
|
||||||
import AccessFormGnameConfig from "./AccessFormGnameConfig";
|
import AccessFormGnameConfig from "./AccessFormGnameConfig";
|
||||||
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
|
import AccessFormGoDaddyConfig from "./AccessFormGoDaddyConfig";
|
||||||
@ -36,6 +37,7 @@ import AccessFormGoogleTrustServicesConfig from "./AccessFormGoogleTrustServices
|
|||||||
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
|
import AccessFormHuaweiCloudConfig from "./AccessFormHuaweiCloudConfig";
|
||||||
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
|
import AccessFormJDCloudConfig from "./AccessFormJDCloudConfig";
|
||||||
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
|
import AccessFormKubernetesConfig from "./AccessFormKubernetesConfig";
|
||||||
|
import AccessFormMattermostConfig from "./AccessFormMattermostConfig";
|
||||||
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
|
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
|
||||||
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
|
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
|
||||||
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
|
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
|
||||||
@ -47,6 +49,7 @@ import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
|
|||||||
import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig";
|
import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig";
|
||||||
import AccessFormSSHConfig from "./AccessFormSSHConfig";
|
import AccessFormSSHConfig from "./AccessFormSSHConfig";
|
||||||
import AccessFormSSLComConfig from "./AccessFormSSLComConfig";
|
import AccessFormSSLComConfig from "./AccessFormSSLComConfig";
|
||||||
|
import AccessFormTelegramConfig from "./AccessFormTelegramConfig";
|
||||||
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
|
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
|
||||||
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
|
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
|
||||||
import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
|
import AccessFormUpyunConfig from "./AccessFormUpyunConfig";
|
||||||
@ -195,12 +198,16 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
return <AccessFormGoogleTrustServicesConfig {...nestedFormProps} />;
|
return <AccessFormGoogleTrustServicesConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.EDGIO:
|
case ACCESS_PROVIDERS.EDGIO:
|
||||||
return <AccessFormEdgioConfig {...nestedFormProps} />;
|
return <AccessFormEdgioConfig {...nestedFormProps} />;
|
||||||
|
case ACCESS_PROVIDERS.EMAIL:
|
||||||
|
return <AccessFormEmailConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.HUAWEICLOUD:
|
case ACCESS_PROVIDERS.HUAWEICLOUD:
|
||||||
return <AccessFormHuaweiCloudConfig {...nestedFormProps} />;
|
return <AccessFormHuaweiCloudConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.JDCLOUD:
|
case ACCESS_PROVIDERS.JDCLOUD:
|
||||||
return <AccessFormJDCloudConfig {...nestedFormProps} />;
|
return <AccessFormJDCloudConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.KUBERNETES:
|
case ACCESS_PROVIDERS.KUBERNETES:
|
||||||
return <AccessFormKubernetesConfig {...nestedFormProps} />;
|
return <AccessFormKubernetesConfig {...nestedFormProps} />;
|
||||||
|
case ACCESS_PROVIDERS.MATTERMOST:
|
||||||
|
return <AccessFormMattermostConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.NAMECHEAP:
|
case ACCESS_PROVIDERS.NAMECHEAP:
|
||||||
return <AccessFormNamecheapConfig {...nestedFormProps} />;
|
return <AccessFormNamecheapConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.NAMEDOTCOM:
|
case ACCESS_PROVIDERS.NAMEDOTCOM:
|
||||||
@ -221,6 +228,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
return <AccessFormSafeLineConfig {...nestedFormProps} />;
|
return <AccessFormSafeLineConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.SSH:
|
case ACCESS_PROVIDERS.SSH:
|
||||||
return <AccessFormSSHConfig {...nestedFormProps} />;
|
return <AccessFormSSHConfig {...nestedFormProps} />;
|
||||||
|
case ACCESS_PROVIDERS.TELEGRAM:
|
||||||
|
return <AccessFormTelegramConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.SSLCOM:
|
case ACCESS_PROVIDERS.SSLCOM:
|
||||||
return <AccessFormSSLComConfig {...nestedFormProps} />;
|
return <AccessFormSSLComConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.TENCENTCLOUD:
|
case ACCESS_PROVIDERS.TENCENTCLOUD:
|
||||||
|
127
ui/src/components/access/AccessFormEmailConfig.tsx
Normal file
127
ui/src/components/access/AccessFormEmailConfig.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input, InputNumber, Switch } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { type AccessConfigForEmail } from "@/domain/access";
|
||||||
|
import { validEmailAddress, validPortNumber } from "@/utils/validators";
|
||||||
|
|
||||||
|
type AccessFormEmailConfigFieldValues = Nullish<AccessConfigForEmail>;
|
||||||
|
|
||||||
|
export type AccessFormEmailConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: AccessFormEmailConfigFieldValues;
|
||||||
|
onValuesChange?: (values: AccessFormEmailConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): AccessFormEmailConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
smtpHost: "",
|
||||||
|
smtpPort: 465,
|
||||||
|
smtpTls: true,
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessFormEmailConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormEmailConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
smtpHost: z
|
||||||
|
.string()
|
||||||
|
.min(1, t("access.form.email_smtp_host.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
smtpPort: z.preprocess(
|
||||||
|
(v) => Number(v),
|
||||||
|
z.number().refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
|
||||||
|
),
|
||||||
|
smtpTls: z.boolean().nullish(),
|
||||||
|
username: z
|
||||||
|
.string()
|
||||||
|
.min(1, t("access.form.email_username.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(1, t("access.form.email_password.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
defaultSenderAddress: z
|
||||||
|
.string()
|
||||||
|
.nullish()
|
||||||
|
.refine((v) => {
|
||||||
|
if (!v) return true;
|
||||||
|
return validEmailAddress(v);
|
||||||
|
}, t("common.errmsg.email_invalid")),
|
||||||
|
defaultReceiverAddress: z
|
||||||
|
.string()
|
||||||
|
.nullish()
|
||||||
|
.refine((v) => {
|
||||||
|
if (!v) return true;
|
||||||
|
return validEmailAddress(v);
|
||||||
|
}, t("common.errmsg.email_invalid")),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleTlsSwitchChange = (checked: boolean) => {
|
||||||
|
const oldPort = formInst.getFieldValue("smtpPort");
|
||||||
|
const newPort = checked && (oldPort == null || oldPort === 25) ? 465 : !checked && (oldPort == null || oldPort === 465) ? 25 : oldPort;
|
||||||
|
if (newPort !== oldPort) {
|
||||||
|
formInst.setFieldValue("smtpPort", newPort);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<div className="w-2/5">
|
||||||
|
<Form.Item name="smtpHost" label={t("access.form.email_smtp_host.label")} rules={[formRule]}>
|
||||||
|
<Input placeholder={t("access.form.email_smtp_host.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-2/5">
|
||||||
|
<Form.Item name="smtpPort" label={t("access.form.email_smtp_port.label")} rules={[formRule]}>
|
||||||
|
<InputNumber className="w-full" placeholder={t("access.form.email_smtp_port.placeholder")} min={1} max={65535} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-1/5">
|
||||||
|
<Form.Item name="smtpTls" label={t("access.form.email_smtp_tls.label")} rules={[formRule]}>
|
||||||
|
<Switch onChange={handleTlsSwitchChange} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.Item name="username" label={t("access.form.email_username.label")} rules={[formRule]}>
|
||||||
|
<Input autoComplete="new-password" placeholder={t("access.form.email_username.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="password" label={t("access.form.email_password.label")} rules={[formRule]}>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("access.form.email_password.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="defaultSenderAddress" label={t("access.form.email_default_sender_address.label")} rules={[formRule]}>
|
||||||
|
<Input type="email" placeholder={t("access.form.email_default_sender_address.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="defaultReceiverAddress" label={t("access.form.email_default_receiver_address.label")} rules={[formRule]}>
|
||||||
|
<Input type="email" placeholder={t("access.form.email_default_receiver_address.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessFormEmailConfig;
|
79
ui/src/components/access/AccessFormMattermostConfig.tsx
Normal file
79
ui/src/components/access/AccessFormMattermostConfig.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { type AccessConfigForMattermost } from "@/domain/access";
|
||||||
|
|
||||||
|
type AccessFormMattermostConfigFieldValues = Nullish<AccessConfigForMattermost>;
|
||||||
|
|
||||||
|
export type AccessFormMattermostConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: AccessFormMattermostConfigFieldValues;
|
||||||
|
onValuesChange?: (values: AccessFormMattermostConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): AccessFormMattermostConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
serverUrl: "http://<your-host-addr>:8065/",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessFormMattermostConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormMattermostConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
serverUrl: z.string().url(t("common.errmsg.url_invalid")),
|
||||||
|
username: z.string().nonempty(t("access.form.mattermost_username.placeholder")),
|
||||||
|
password: z.string().nonempty(t("access.form.mattermost_password.placeholder")),
|
||||||
|
defaultChannelId: z.string().nullish(),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="serverUrl"
|
||||||
|
label={t("access.form.mattermost_server_url.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.mattermost_server_url.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("access.form.mattermost_server_url.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="username" label={t("access.form.mattermost_username.label")} rules={[formRule]}>
|
||||||
|
<Input placeholder={t("access.form.mattermost_username.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="password" label={t("access.form.mattermost_password.label")} rules={[formRule]}>
|
||||||
|
<Input.Password placeholder={t("access.form.mattermost_password.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="defaultChannelId"
|
||||||
|
label={t("access.form.mattermost_default_channel_id.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.mattermost_default_channel_id.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("access.form.mattermost_default_channel_id.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessFormMattermostConfig;
|
@ -31,13 +31,11 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
host: z
|
host: z.string().refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")),
|
||||||
.string({ message: t("access.form.ssh_host.placeholder") })
|
|
||||||
.refine((v) => validDomainName(v) || validIPv4Address(v) || validIPv6Address(v), t("common.errmsg.host_invalid")),
|
|
||||||
port: z.preprocess(
|
port: z.preprocess(
|
||||||
(v) => Number(v),
|
(v) => Number(v),
|
||||||
z
|
z
|
||||||
.number({ message: t("access.form.ssh_port.placeholder") })
|
.number()
|
||||||
.int(t("access.form.ssh_port.placeholder"))
|
.int(t("access.form.ssh_port.placeholder"))
|
||||||
.refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
|
.refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
|
||||||
),
|
),
|
||||||
|
81
ui/src/components/access/AccessFormTelegramConfig.tsx
Normal file
81
ui/src/components/access/AccessFormTelegramConfig.tsx
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { type AccessConfigForTelegram } from "@/domain/access";
|
||||||
|
|
||||||
|
type AccessFormTelegramConfigFieldValues = Nullish<AccessConfigForTelegram>;
|
||||||
|
|
||||||
|
export type AccessFormTelegramConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: AccessFormTelegramConfigFieldValues;
|
||||||
|
onValuesChange?: (values: AccessFormTelegramConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): AccessFormTelegramConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
botToken: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessFormTelegramConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormTelegramConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
botToken: z
|
||||||
|
.string({ message: t("access.form.telegram_bot_token.placeholder") })
|
||||||
|
.min(1, t("access.form.telegram_bot_token.placeholder"))
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 })),
|
||||||
|
defaultChatId: z
|
||||||
|
.preprocess(
|
||||||
|
(v) => Number(v),
|
||||||
|
z
|
||||||
|
.number()
|
||||||
|
.nullish()
|
||||||
|
.refine((v) => {
|
||||||
|
if (v == null || v + "" === "") return true;
|
||||||
|
return /^\d+$/.test(v + "") && +v! > 0;
|
||||||
|
}, t("access.form.telegram_default_chat_id.placeholder"))
|
||||||
|
)
|
||||||
|
.nullish(),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="botToken"
|
||||||
|
label={t("access.form.telegram_bot_token.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_bot_token.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("access.form.telegram_bot_token.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="defaultChatId"
|
||||||
|
label={t("access.form.telegram_default_chat_id.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.telegram_default_chat_id.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input type="number" placeholder={t("access.form.telegram_default_chat_id.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessFormTelegramConfig;
|
@ -25,7 +25,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
url: z.string({ message: t("access.form.webhook_url.placeholder") }).url(t("common.errmsg.url_invalid")),
|
url: z.string().url(t("common.errmsg.url_invalid")),
|
||||||
allowInsecureConnections: z.boolean().nullish(),
|
allowInsecureConnections: z.boolean().nullish(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
@ -7,9 +7,7 @@ const NotifyChannelEditFormMattermostFields = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
serverUrl: z
|
serverUrl: z.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") }).url(t("common.errmsg.url_invalid")),
|
||||||
.string({ message: t("settings.notification.channel.form.mattermost_server_url.placeholder") })
|
|
||||||
.url(t("common.errmsg.url_invalid")),
|
|
||||||
channelId: z
|
channelId: z
|
||||||
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
|
.string({ message: t("settings.notification.channel.form.mattermost_channel_id.placeholder") })
|
||||||
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
|
.nonempty(t("settings.notification.channel.form.mattermost_channel_id.placeholder")),
|
||||||
@ -42,19 +40,11 @@ const NotifyChannelEditFormMattermostFields = () => {
|
|||||||
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
|
<Input placeholder={t("settings.notification.channel.form.mattermost_channel_id.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item name="username" label={t("settings.notification.channel.form.mattermost_username.label")} rules={[formRule]}>
|
||||||
name="username"
|
|
||||||
label={t("settings.notification.channel.form.mattermost_username.label")}
|
|
||||||
rules={[formRule]}
|
|
||||||
>
|
|
||||||
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
|
<Input placeholder={t("settings.notification.channel.form.mattermost_username.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item name="password" label={t("settings.notification.channel.form.mattermost_password.label")} rules={[formRule]}>
|
||||||
name="password"
|
|
||||||
label={t("settings.notification.channel.form.mattermost_password.label")}
|
|
||||||
rules={[formRule]}
|
|
||||||
>
|
|
||||||
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
|
<Input.Password placeholder={t("settings.notification.channel.form.mattermost_password.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
|
@ -166,6 +166,19 @@
|
|||||||
"access.form.edgio_client_secret.label": "Edgio ClientSecret",
|
"access.form.edgio_client_secret.label": "Edgio ClientSecret",
|
||||||
"access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret",
|
"access.form.edgio_client_secret.placeholder": "Please enter Edgio ClientSecret",
|
||||||
"access.form.edgio_client_secret.tooltip": "For more information, see <a href=\"https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients\" target=\"_blank\">https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients</a>",
|
"access.form.edgio_client_secret.tooltip": "For more information, see <a href=\"https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients\" target=\"_blank\">https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients</a>",
|
||||||
|
"access.form.email_smtp_host.label": "SMTP host",
|
||||||
|
"access.form.email_smtp_host.placeholder": "Please enter SMTP host",
|
||||||
|
"access.form.email_smtp_port.label": "SMTP port",
|
||||||
|
"access.form.email_smtp_port.placeholder": "Please enter SMTP port",
|
||||||
|
"access.form.email_smtp_tls.label": "Use SSL/TLS",
|
||||||
|
"access.form.email_username.label": "Username",
|
||||||
|
"access.form.email_username.placeholder": "please enter username",
|
||||||
|
"access.form.email_password.label": "Password",
|
||||||
|
"access.form.email_password.placeholder": "please enter password",
|
||||||
|
"access.form.email_default_sender_address.label": "Default sender email address (Optional)",
|
||||||
|
"access.form.email_default_sender_address.placeholder": "Please enter default sender email address",
|
||||||
|
"access.form.email_default_receiver_address.label": "Default receiver email address (Optional)",
|
||||||
|
"access.form.email_default_receiver_address.placeholder": "Please enter default receiver email address",
|
||||||
"access.form.gcore_api_token.label": "Gcore API token",
|
"access.form.gcore_api_token.label": "Gcore API token",
|
||||||
"access.form.gcore_api_token.placeholder": "Please enter Gcore API token",
|
"access.form.gcore_api_token.placeholder": "Please enter Gcore API token",
|
||||||
"access.form.gcore_api_token.tooltip": "For more information, see <a href=\"https://api.gcore.com/docs/iam#section/Authentication\" target=\"_blank\">https://api.gcore.com/docs/iam#section/Authentication</a>",
|
"access.form.gcore_api_token.tooltip": "For more information, see <a href=\"https://api.gcore.com/docs/iam#section/Authentication\" target=\"_blank\">https://api.gcore.com/docs/iam#section/Authentication</a>",
|
||||||
@ -203,6 +216,15 @@
|
|||||||
"access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file",
|
"access.form.k8s_kubeconfig.placeholder": "Please enter KubeConfig file",
|
||||||
"access.form.k8s_kubeconfig.upload": "Choose File ...",
|
"access.form.k8s_kubeconfig.upload": "Choose File ...",
|
||||||
"access.form.k8s_kubeconfig.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>Leave it blank to use the Pod's ServiceAccount.",
|
"access.form.k8s_kubeconfig.tooltip": "For more information, see <a href=\"https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>Leave it blank to use the Pod's ServiceAccount.",
|
||||||
|
"access.form.mattermost_server_url.label": "Mattermost server URL",
|
||||||
|
"access.form.mattermost_server_url.placeholder": "Please enter Mattermost server URL",
|
||||||
|
"access.form.mattermost_username.label": "Mattermost username",
|
||||||
|
"access.form.mattermost_username.placeholder": "Please enter Mattermost username",
|
||||||
|
"access.form.mattermost_password.label": "Mattermost password",
|
||||||
|
"access.form.mattermost_password.placeholder": "Please enter Mattermost password",
|
||||||
|
"access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID",
|
||||||
|
"access.form.mattermost_default_channel_id.placeholder": "Please enter default Mattermost channel ID",
|
||||||
|
"access.form.mattermost_default_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.",
|
||||||
"access.form.namecheap_username.label": "Namecheap username",
|
"access.form.namecheap_username.label": "Namecheap username",
|
||||||
"access.form.namecheap_username.placeholder": "Please enter Namecheap username",
|
"access.form.namecheap_username.placeholder": "Please enter Namecheap username",
|
||||||
"access.form.namecheap_username.tooltip": "For more information, see <a href=\"https://www.namecheap.com/support/api/intro/\" target=\"_blank\">https://www.namecheap.com/support/api/intro/</a>",
|
"access.form.namecheap_username.tooltip": "For more information, see <a href=\"https://www.namecheap.com/support/api/intro/\" target=\"_blank\">https://www.namecheap.com/support/api/intro/</a>",
|
||||||
@ -274,6 +296,12 @@
|
|||||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||||
"access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
"access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key",
|
||||||
"access.form.sslcom_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
"access.form.sslcom_eab_hmac_key.tooltip": "For more information, see <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||||
|
"access.form.telegram_bot_token.label": "Telegram bot token",
|
||||||
|
"access.form.telegram_bot_token.placeholder": "Please enter Telegram bot token",
|
||||||
|
"access.form.telegram_bot_token.tooltip": "How to get the bot token? Please refer to <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||||
|
"access.form.telegram_default_chat_id.label": "Default Telegram chat ID (Optional)",
|
||||||
|
"access.form.telegram_default_chat_id.placeholder": "Please enter default Telegram chat ID",
|
||||||
|
"access.form.telegram_default_chat_id.tooltip": "How to get the chat ID? Please refer to <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||||
"access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId",
|
"access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId",
|
||||||
"access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId",
|
"access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId",
|
||||||
"access.form.tencentcloud_secret_id.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/598/40488?lang=en\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488?lang=en</a>",
|
"access.form.tencentcloud_secret_id.tooltip": "For more information, see <a href=\"https://cloud.tencent.com/document/product/598/40488?lang=en\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488?lang=en</a>",
|
||||||
|
@ -160,6 +160,19 @@
|
|||||||
"access.form.edgio_client_secret.label": "Edgio 客户端密码",
|
"access.form.edgio_client_secret.label": "Edgio 客户端密码",
|
||||||
"access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码",
|
"access.form.edgio_client_secret.placeholder": "请输入 Edgio 客户端密码",
|
||||||
"access.form.edgio_client_secret.tooltip": "这是什么?请参阅 <a href=\"https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients\" target=\"_blank\">https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients</a>",
|
"access.form.edgio_client_secret.tooltip": "这是什么?请参阅 <a href=\"https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients\" target=\"_blank\">https://docs.edg.io/applications/v7/rest_api/authentication#administering-api-clients</a>",
|
||||||
|
"access.form.email_smtp_host.label": "SMTP 服务器地址",
|
||||||
|
"access.form.email_smtp_host.placeholder": "请输入 SMTP 服务器地址",
|
||||||
|
"access.form.email_smtp_port.label": "SMTP 服务器端口",
|
||||||
|
"access.form.email_smtp_port.placeholder": "请输入 SMTP 服务器端口",
|
||||||
|
"access.form.email_smtp_tls.label": "SSL/TLS 连接",
|
||||||
|
"access.form.email_username.label": "用户名",
|
||||||
|
"access.form.email_username.placeholder": "请输入用户名",
|
||||||
|
"access.form.email_password.label": "密码",
|
||||||
|
"access.form.email_password.placeholder": "请输入密码",
|
||||||
|
"access.form.email_default_sender_address.label": "默认的发送邮箱地址(可选)",
|
||||||
|
"access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址",
|
||||||
|
"access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)",
|
||||||
|
"access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址",
|
||||||
"access.form.gcore_api_token.label": "Gcore API Token",
|
"access.form.gcore_api_token.label": "Gcore API Token",
|
||||||
"access.form.gcore_api_token.placeholder": "请输入 Gcore API Token",
|
"access.form.gcore_api_token.placeholder": "请输入 Gcore API Token",
|
||||||
"access.form.gcore_api_token.tooltip": "这是什么?请参阅 <a href=\"https://api.gcore.com/docs/iam#section/Authentication\" target=\"_blank\">https://api.gcore.com/docs/iam#section/Authentication</a>",
|
"access.form.gcore_api_token.tooltip": "这是什么?请参阅 <a href=\"https://api.gcore.com/docs/iam#section/Authentication\" target=\"_blank\">https://api.gcore.com/docs/iam#section/Authentication</a>",
|
||||||
@ -197,6 +210,15 @@
|
|||||||
"access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件",
|
"access.form.k8s_kubeconfig.placeholder": "请选择 KubeConfig 文件",
|
||||||
"access.form.k8s_kubeconfig.upload": "选择文件",
|
"access.form.k8s_kubeconfig.upload": "选择文件",
|
||||||
"access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>为空时,将使用 Pod 的 ServiceAccount 作为凭证。",
|
"access.form.k8s_kubeconfig.tooltip": "这是什么?请参阅 <a href=\"https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/\" target=\"_blank\">https://kubernetes.io/zh-cn/docs/concepts/configuration/organize-cluster-access-kubeconfig/</a><br><br>为空时,将使用 Pod 的 ServiceAccount 作为凭证。",
|
||||||
|
"access.form.mattermost_server_url.label": "Mattermost 服务地址",
|
||||||
|
"access.form.mattermost_server_url.placeholder": "请输入 Mattermost 服务地址",
|
||||||
|
"access.form.mattermost_username.label": "Mattermost 用户名",
|
||||||
|
"access.form.mattermost_username.placeholder": "请输入 Mattermost 用户名",
|
||||||
|
"access.form.mattermost_password.label": "Mattermost 密码",
|
||||||
|
"access.form.mattermost_password.placeholder": "请输入 Mattermost 密码",
|
||||||
|
"access.form.mattermost_default_channel_id.label": "默认的 Mattermost 频道 ID(可选)",
|
||||||
|
"access.form.mattermost_default_channel_id.placeholder": "请输入默认的 Mattermost 频道 ID",
|
||||||
|
"access.form.mattermost_default_channel_id.tooltip": "如何获取频道 ID?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道 ID。",
|
||||||
"access.form.namecheap_username.label": "Namecheap 用户名",
|
"access.form.namecheap_username.label": "Namecheap 用户名",
|
||||||
"access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名",
|
"access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名",
|
||||||
"access.form.namecheap_username.tooltip": "这是什么?请参阅 <a href=\"https://www.namecheap.com/support/api/intro/\" target=\"_blank\">https://www.namecheap.com/support/api/intro/</a>",
|
"access.form.namecheap_username.tooltip": "这是什么?请参阅 <a href=\"https://www.namecheap.com/support/api/intro/\" target=\"_blank\">https://www.namecheap.com/support/api/intro/</a>",
|
||||||
@ -268,6 +290,12 @@
|
|||||||
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
"access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key",
|
||||||
"access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key",
|
"access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key",
|
||||||
"access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
"access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 <a href=\"https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/#ftoc-heading-6\" target=\"_blank\">https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/</a>",
|
||||||
|
"access.form.telegram_bot_token.label": "Telegram 机器人 API Token",
|
||||||
|
"access.form.telegram_bot_token.placeholder": "请输入 Telegram 机器人 API Token",
|
||||||
|
"access.form.telegram_bot_token.tooltip": "如何获取机器人 API Token?请参阅 <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||||
|
"access.form.telegram_default_chat_id.label": "默认的 Telegram 会话 ID(可选)",
|
||||||
|
"access.form.telegram_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID",
|
||||||
|
"access.form.telegram_default_chat_id.tooltip": "如何获取会话 ID?请参阅 <a href=\"https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a\" target=\"_blank\">https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a</a>",
|
||||||
"access.form.tencentcloud_secret_id.label": "腾讯云 SecretId",
|
"access.form.tencentcloud_secret_id.label": "腾讯云 SecretId",
|
||||||
"access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId",
|
"access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId",
|
||||||
"access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/598/40488\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488</a>",
|
"access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/598/40488\" target=\"_blank\">https://cloud.tencent.com/document/product/598/40488</a>",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user