feat: new notification provider: slack bot

This commit is contained in:
Fu Diwei 2025-05-26 16:41:16 +08:00
parent 8e23b14bf3
commit e82a59289b
30 changed files with 341 additions and 11 deletions

View File

@ -300,6 +300,11 @@ type AccessConfigForSafeLine struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
type AccessConfigForSlackBot struct {
BotToken string `json:"botToken"`
DefaultChannelId string `json:"defaultChannelId,omitempty"`
}
type AccessConfigForSSH struct {
Host string `json:"host"`
Port int32 `json:"port"`

View File

@ -71,6 +71,7 @@ const (
AccessProviderTypeRainYun = AccessProviderType("rainyun")
AccessProviderTypeRatPanel = AccessProviderType("ratpanel")
AccessProviderTypeSafeLine = AccessProviderType("safeline")
AccessProviderTypeSlackBot = AccessProviderType("slackbot")
AccessProviderTypeSSH = AccessProviderType("ssh")
AccessProviderTypeSSLCOM = AccessProviderType("sslcom")
AccessProviderTypeTelegramBot = AccessProviderType("telegrambot")
@ -274,6 +275,7 @@ const (
NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail)
NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot)
NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost)
NotificationProviderTypeSlackBot = NotificationProviderType(AccessProviderTypeSlackBot)
NotificationProviderTypeTelegramBot = NotificationProviderType(AccessProviderTypeTelegramBot)
NotificationProviderTypeWebhook = NotificationProviderType(AccessProviderTypeWebhook)
NotificationProviderTypeWeComBot = NotificationProviderType(AccessProviderTypeWeComBot)

View File

@ -11,6 +11,7 @@ import (
pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email"
pLarkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot"
pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost"
pSlackBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/slackbot"
pTelegramBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/telegrambot"
pWebhook "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/webhook"
pWeComBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/wecombot"
@ -101,6 +102,19 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier
})
}
case domain.NotificationProviderTypeSlackBot:
{
access := domain.AccessConfigForSlackBot{}
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
return pSlackBot.NewNotifier(&pSlackBot.NotifierConfig{
BotToken: access.BotToken,
ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId),
})
}
case domain.NotificationProviderTypeTelegramBot:
{
access := domain.AccessConfigForTelegramBot{}

View File

@ -58,6 +58,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
// REF: https://bark.day.app/#/tutorial
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,

View File

@ -51,6 +51,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://discord.com/developers/docs/resources/message#create-message
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bot "+n.config.BotToken).
SetBody(map[string]any{

View File

@ -56,6 +56,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
// REF: https://gotify.net/api-docs#/message/createMessage
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+n.config.Token).
SetBody(map[string]any{

View File

@ -58,6 +58,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
// REF: https://developers.mattermost.com/api-documentation/#/operations/Login
loginReq := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"login_id": n.config.Username,
@ -74,6 +75,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
// REF: https://developers.mattermost.com/api-documentation/#/operations/CreatePost
postReq := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+loginResp.Header().Get("Token")).
SetBody(map[string]any{

View File

@ -51,6 +51,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://pushover.net/api
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,

View File

@ -50,6 +50,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://pushplus.plus/doc/guide/api.html#%E4%B8%80%E3%80%81%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"title": subject,

View File

@ -49,6 +49,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://sct.ftqq.com/
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"text": subject,

View File

@ -0,0 +1,70 @@
package discordbot
import (
"context"
"fmt"
"log/slog"
"github.com/go-resty/resty/v2"
"github.com/usual2970/certimate/internal/pkg/core/notifier"
)
type NotifierConfig struct {
// Slack Bot API Token。
BotToken string `json:"botToken"`
// Slack Channel ID。
ChannelId string `json:"channelId"`
}
type NotifierProvider struct {
config *NotifierConfig
logger *slog.Logger
httpClient *resty.Client
}
var _ notifier.Notifier = (*NotifierProvider)(nil)
func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) {
if config == nil {
panic("config is nil")
}
client := resty.New()
return &NotifierProvider{
config: config,
logger: slog.Default(),
httpClient: client,
}, nil
}
func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
if logger == nil {
n.logger = slog.Default()
} else {
n.logger = logger
}
return n
}
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://docs.slack.dev/messaging/sending-and-scheduling-messages#publishing
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+n.config.BotToken).
SetBody(map[string]any{
"token": n.config.BotToken,
"channel": n.config.ChannelId,
"text": subject + "\n" + message,
})
resp, err := req.Post("https://slack.com/api/chat.postMessage")
if err != nil {
return nil, fmt.Errorf("slack api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("slack api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
}
return &notifier.NotifyResult{}, nil
}

View File

@ -0,0 +1,64 @@
package discordbot_test
import (
"context"
"flag"
"fmt"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/slackbot"
)
const (
mockSubject = "test_subject"
mockMessage = "test_message"
)
var (
fApiToken string
fChannelId string
)
func init() {
argsPrefix := "CERTIMATE_NOTIFIER_SLACKBOT_"
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", 0, "")
}
/*
Shell command to run this test:
go test -v ./slackbot_test.go -args \
--CERTIMATE_NOTIFIER_SLACKBOT_APITOKEN="your-bot-token" \
--CERTIMATE_NOTIFIER_SLACKBOT_CHANNELID="your-channel-id"
*/
func TestNotify(t *testing.T) {
flag.Parse()
t.Run("Notify", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("APITOKEN: %v", fApiToken),
fmt.Sprintf("CHANNELID: %v", fChannelId),
}, "\n"))
notifier, err := provider.NewNotifier(&provider.NotifierConfig{
BotToken: fApiToken,
ChannelId: fChannelId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
res, err := notifier.Notify(context.Background(), mockSubject, mockMessage)
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@ -51,6 +51,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://core.telegram.org/bots/api#sendmessage
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"chat_id": n.config.ChatId,

View File

@ -139,7 +139,7 @@ func (n *NotifierProvider) Notify(ctx context.Context, subject string, message s
// 生成请求
// 其中 GET 请求需转换为查询参数
req := n.httpClient.R().SetHeaderMultiValues(webhookHeaders)
req := n.httpClient.R().SetContext(ctx).SetHeaderMultiValues(webhookHeaders)
req.URL = webhookUrl.String()
req.Method = webhookMethod
if webhookMethod == http.MethodGet {

View File

@ -49,6 +49,7 @@ func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier {
func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) {
// REF: https://developer.work.weixin.qq.com/document/path/91770
req := n.httpClient.R().
SetContext(ctx).
SetHeader("Content-Type", "application/json").
SetBody(map[string]any{
"msgtype": "text",

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M244.224 643.84c0 59.221333-45.098667 107.264-100.778667 107.264C87.808 751.104 42.666667 703.061333 42.666667 643.84c0-59.221333 45.141333-107.264 100.778666-107.264h100.778667v107.264zM294.613333 643.84c0-59.306667 45.141333-107.306667 100.821334-107.306667 55.637333 0 100.778667 48.042667 100.778666 107.264v268.288c0 59.264-45.141333 107.306667-100.778666 107.306667-55.68 0-100.821333-48.042667-100.821334-107.306667v-268.288z" fill="#E01E5A"></path><path d="M395.392 214.613333c-55.637333 0-100.778667-48.042667-100.778667-107.306666C294.613333 48.042667 339.754667 0 395.392 0c55.68 0 100.821333 48.042667 100.821333 107.306667V214.613333H395.392zM395.392 268.245333c55.68 0 100.821333 48.085333 100.821333 107.306667 0 59.306667-45.141333 107.306667-100.821333 107.306667H143.445333C87.808 482.858667 42.666667 434.816 42.666667 375.552c0-59.221333 45.141333-107.306667 100.778666-107.306667h251.946667z" fill="#36C5F0"></path><path d="M798.549333 375.552c0-59.221333 45.098667-107.306667 100.778667-107.306667 55.637333 0 100.778667 48.085333 100.778667 107.306667 0 59.306667-45.141333 107.306667-100.778667 107.306667h-100.778667V375.552zM748.16 375.552c0 59.306667-45.141333 107.306667-100.821333 107.306667-55.637333 0-100.778667-48.042667-100.778667-107.306667V107.306667C546.56 48.042667 591.701333 0 647.338667 0c55.68 0 100.821333 48.042667 100.821333 107.306667v268.245333z" fill="#2EB67D"></path><path d="M647.381333 804.778667c55.637333 0 100.778667 48.042667 100.778667 107.306666 0 59.264-45.141333 107.306667-100.778667 107.306667-55.68 0-100.821333-48.042667-100.821333-107.306667v-107.306666h100.821333zM647.381333 751.104c-55.68 0-100.821333-48.042667-100.821333-107.306667 0-59.221333 45.141333-107.306667 100.821333-107.306666h251.904c55.68 0 100.778667 48.085333 100.778667 107.306666 0 59.306667-45.098667 107.306667-100.778667 107.306667h-251.904z" fill="#ECB22E"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -64,6 +64,7 @@ import AccessFormQiniuConfig from "./AccessFormQiniuConfig";
import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
import AccessFormRatPanelConfig from "./AccessFormRatPanelConfig";
import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig";
import AccessFormSlackBotConfig from "./AccessFormSlackBotConfig";
import AccessFormSSHConfig from "./AccessFormSSHConfig";
import AccessFormSSLComConfig from "./AccessFormSSLComConfig";
import AccessFormTelegramBotConfig from "./AccessFormTelegramBotConfig";
@ -289,6 +290,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
return <AccessFormRatPanelConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.SAFELINE:
return <AccessFormSafeLineConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.SLACKBOT:
return <AccessFormSlackBotConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.SSH:
return <AccessFormSSHConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.TELEGRAMBOT:

View File

@ -28,7 +28,8 @@ const AccessFormDiscordBotConfig = ({ form: formInst, formName, disabled, initia
botToken: z
.string({ message: t("access.form.discordbot_token.placeholder") })
.min(1, t("access.form.discordbot_token.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 })),
.max(256, t("common.errmsg.string_max", { max: 256 }))
.trim(),
defaultChannelId: z.string().nullish(),
});
const formRule = createSchemaFieldRule(formSchema);

View File

@ -0,0 +1,71 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { type AccessConfigForSlackBot } from "@/domain/access";
type AccessFormSlackBotConfigFieldValues = Nullish<AccessConfigForSlackBot>;
export type AccessFormSlackBotConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessFormSlackBotConfigFieldValues;
onValuesChange?: (values: AccessFormSlackBotConfigFieldValues) => void;
};
const initFormModel = (): AccessFormSlackBotConfigFieldValues => {
return {
botToken: "",
};
};
const AccessFormSlackBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSlackBotConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
botToken: z
.string({ message: t("access.form.slackbot_token.placeholder") })
.min(1, t("access.form.slackbot_token.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 }))
.trim(),
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="botToken"
label={t("access.form.slackbot_token.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.slackbot_token.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.slackbot_token.placeholder")} />
</Form.Item>
<Form.Item
name="defaultChannelId"
label={t("access.form.slackbot_default_channel_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.slackbot_default_channel_id.tooltip") }}></span>}
>
<Input allowClear placeholder={t("access.form.slackbot_default_channel_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessFormSlackBotConfig;

View File

@ -28,7 +28,8 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi
botToken: z
.string({ message: t("access.form.telegrambot_token.placeholder") })
.min(1, t("access.form.telegrambot_token.placeholder"))
.max(256, t("common.errmsg.string_max", { max: 256 })),
.max(256, t("common.errmsg.string_max", { max: 256 }))
.trim(),
defaultChatId: z
.preprocess(
(v) => (v == null || v === "" ? undefined : Number(v)),

View File

@ -20,6 +20,7 @@ import { useNotifyChannelsStore } from "@/stores/notify";
import NotifyNodeConfigFormDiscordBotConfig from "./NotifyNodeConfigFormDiscordBotConfig";
import NotifyNodeConfigFormEmailConfig from "./NotifyNodeConfigFormEmailConfig";
import NotifyNodeConfigFormMattermostConfig from "./NotifyNodeConfigFormMattermostConfig";
import NotifyNodeConfigFormSlackBotConfig from "./NotifyNodeConfigFormSlackBotConfig";
import NotifyNodeConfigFormTelegramBotConfig from "./NotifyNodeConfigFormTelegramBotConfig";
import NotifyNodeConfigFormWebhookConfig from "./NotifyNodeConfigFormWebhookConfig";
@ -117,6 +118,8 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
return <NotifyNodeConfigFormEmailConfig {...nestedFormProps} />;
case NOTIFICATION_PROVIDERS.MATTERMOST:
return <NotifyNodeConfigFormMattermostConfig {...nestedFormProps} />;
case NOTIFICATION_PROVIDERS.SLACKBOT:
return <NotifyNodeConfigFormSlackBotConfig {...nestedFormProps} />;
case NOTIFICATION_PROVIDERS.TELEGRAMBOT:
return <NotifyNodeConfigFormTelegramBotConfig {...nestedFormProps} />;
case NOTIFICATION_PROVIDERS.WEBHOOK:

View File

@ -0,0 +1,55 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
type NotifyNodeConfigFormSlackBotConfigFieldValues = Nullish<{
channelId?: string;
}>;
export type NotifyNodeConfigFormSlackBotConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: NotifyNodeConfigFormSlackBotConfigFieldValues;
onValuesChange?: (values: NotifyNodeConfigFormSlackBotConfigFieldValues) => void;
};
const initFormModel = (): NotifyNodeConfigFormSlackBotConfigFieldValues => {
return {};
};
const NotifyNodeConfigFormSlackBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: NotifyNodeConfigFormSlackBotConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
channelId: 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="channelId"
label={t("workflow_node.notify.form.slackbot_channel_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.slackbot_channel_id.tooltip") }}></span>}
>
<Input allowClear placeholder={t("workflow_node.notify.form.slackbot_channel_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default NotifyNodeConfigFormSlackBotConfig;

View File

@ -58,6 +58,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForRainYun
| AccessConfigForRatPanel
| AccessConfigForSafeLine
| AccessConfigForSlackBot
| AccessConfigForSSH
| AccessConfigForSSLCom
| AccessConfigForTelegramBot
@ -361,6 +362,11 @@ export type AccessConfigForSafeLine = {
allowInsecureConnections?: boolean;
};
export type AccessConfigForSlackBot = {
botToken: string;
defaultChannelId?: string;
};
export type AccessConfigForSSH = {
host: string;
port: number;

View File

@ -61,6 +61,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
RAINYUN: "rainyun",
RATPANEL: "ratpanel",
SAFELINE: "safeline",
SLACKBOT: "slackbot",
SSH: "ssh",
SSLCOM: "sslcom",
TELEGRAMBOT: "telegrambot",
@ -174,6 +175,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[ACCESS_PROVIDERS.LARKBOT, "provider.larkbot", "/imgs/providers/lark.svg", [ACCESS_USAGES.NOTIFICATION]],
[ACCESS_PROVIDERS.WECOMBOT, "provider.wecombot", "/imgs/providers/wecom.svg", [ACCESS_USAGES.NOTIFICATION]],
[ACCESS_PROVIDERS.DISCORDBOT, "provider.discordbot", "/imgs/providers/discord.svg", [ACCESS_USAGES.NOTIFICATION]],
[ACCESS_PROVIDERS.SLACKBOT, "provider.slackbot", "/imgs/providers/slack.svg", [ACCESS_USAGES.NOTIFICATION]],
[ACCESS_PROVIDERS.TELEGRAMBOT, "provider.telegrambot", "/imgs/providers/telegram.svg", [ACCESS_USAGES.NOTIFICATION]],
[ACCESS_PROVIDERS.MATTERMOST, "provider.mattermost", "/imgs/providers/mattermost.svg", [ACCESS_USAGES.NOTIFICATION]],
].map((e) => [
@ -594,6 +596,7 @@ export const NOTIFICATION_PROVIDERS = Object.freeze({
EMAIL: `${ACCESS_PROVIDERS.EMAIL}`,
LARKBOT: `${ACCESS_PROVIDERS.LARKBOT}`,
MATTERMOST: `${ACCESS_PROVIDERS.MATTERMOST}`,
SLACKBOT: `${ACCESS_PROVIDERS.SLACKBOT}`,
TELEGRAMBOT: `${ACCESS_PROVIDERS.TELEGRAMBOT}`,
WEBHOOK: `${ACCESS_PROVIDERS.WEBHOOK}`,
WECOMBOT: `${ACCESS_PROVIDERS.WECOMBOT}`,
@ -620,6 +623,7 @@ export const notificationProvidersMap: Map<NotificationProvider["type"] | string
[NOTIFICATION_PROVIDERS.LARKBOT],
[NOTIFICATION_PROVIDERS.WECOMBOT],
[NOTIFICATION_PROVIDERS.DISCORDBOT],
[NOTIFICATION_PROVIDERS.SLACKBOT],
[NOTIFICATION_PROVIDERS.TELEGRAMBOT],
[NOTIFICATION_PROVIDERS.MATTERMOST],
].map(([type]) => [

View File

@ -163,7 +163,7 @@
"access.form.dingtalkbot_secret.tooltip": "For more information, see <a href=\"https://open.dingtalk.com/document/orgapp/customize-robot-security-settings\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/customize-robot-security-settings</a>",
"access.form.discordbot_token.label": "Discord bot token",
"access.form.discordbot_token.placeholder": "Please enter Discord bot token",
"access.form.discordbot_token.tooltip": "How to get it? Please refer to <a href=\"https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token\" target=\"_blank\">https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token</a>",
"access.form.discordbot_token.tooltip": "For more information, see <a href=\"https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token\" target=\"_blank\">https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token</a>",
"access.form.discordbot_default_channel_id.label": "Default Discord channel ID (Optional)",
"access.form.discordbot_default_channel_id.placeholder": "Please enter default Discord channel ID",
"access.form.discordbot_default_channel_id.tooltip": "For more information, see <a href=\"https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID\" target=\"_blank\">https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID</a>",
@ -362,6 +362,12 @@
"access.form.safeline_api_token.label": "SafeLine API token",
"access.form.safeline_api_token.placeholder": "Please enter SafeLine API token",
"access.form.safeline_api_token.tooltip": "For more information, see <a href=\"https://docs.waf.chaitin.com/en/reference/articles/openapi\" target=\"_blank\">https://docs.waf.chaitin.com/en/reference/articles/openapi</a>",
"access.form.slackbot_token.label": "Slack bot token",
"access.form.slackbot_token.placeholder": "Please enter Slack bot token",
"access.form.slackbot_token.tooltip": "For more information, see <a href=\"https://docs.slack.dev/authentication/tokens#bot\" target=\"_blank\">https://docs.slack.dev/authentication/tokens#bot</a>",
"access.form.slackbot_default_channel_id.label": "Default Slack channel ID (Optional)",
"access.form.slackbot_default_channel_id.placeholder": "Please enter default Slack channel ID",
"access.form.slackbot_default_channel_id.tooltip": "How to get it? Please refer to <a href=\"https://www.youtube.com/watch?v=Uz5Yi5C2pwQ\" target=\"_blank\">https://www.youtube.com/watch?v=Uz5Yi5C2pwQ</a>",
"access.form.ssh_host.label": "Server host",
"access.form.ssh_host.placeholder": "Please enter server host",
"access.form.ssh_port.label": "Server port",

View File

@ -68,7 +68,7 @@
"provider.dynv6": "dynv6",
"provider.edgio": "Edgio",
"provider.edgio.applications": "Edgio - Applications",
"provider.email": "Email",
"provider.email": "Email (SMTP)",
"provider.fastly": "Fastly",
"provider.flexcdn": "FlexCDN",
"provider.gcore": "Gcore",
@ -96,7 +96,7 @@
"provider.lecdn": "LeCDN",
"provider.letsencrypt": "Let's Encrypt",
"provider.letsencryptstaging": "Let's Encrypt Staging Environment",
"provider.local": "Local deployment",
"provider.local": "Local host",
"provider.mattermost": "Mattermost",
"provider.namecheap": "Namecheap",
"provider.namedotcom": "Name.com",
@ -118,7 +118,8 @@
"provider.ratpanel.console": "RatPanel - Console",
"provider.ratpanel.site": "RatPanel - Website",
"provider.safeline": "SafeLine",
"provider.ssh": "SSH deployment",
"provider.slackbot": "Slack Bot",
"provider.ssh": "Remote host (SSH)",
"provider.sslcom": "SSL.com",
"provider.telegrambot": "Telegram Bot",
"provider.tencentcloud": "Tencent Cloud",

View File

@ -827,6 +827,9 @@
"workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)",
"workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value",
"workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.",
"workflow_node.notify.form.slackbot_channel_id.label": "Slack channel ID (Optional)",
"workflow_node.notify.form.slackbot_channel_id.placeholder": "Please enter Slack channel ID to override the default value",
"workflow_node.notify.form.slackbot_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.",
"workflow_node.notify.form.telegrambot_chat_id.label": "Telegram chat ID (Optional)",
"workflow_node.notify.form.telegrambot_chat_id.placeholder": "Please enter Telegram chat ID to override the default value",
"workflow_node.notify.form.telegrambot_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.",

View File

@ -157,7 +157,7 @@
"access.form.dingtalkbot_secret.tooltip": "这是什么?请参阅 <a href=\"https://open.dingtalk.com/document/orgapp/customize-robot-security-settings\" target=\"_blank\">https://open.dingtalk.com/document/orgapp/customize-robot-security-settings</a>",
"access.form.discordbot_token.label": "Discord 机器人 API Token",
"access.form.discordbot_token.placeholder": "请输入 Discord 机器人 API Token",
"access.form.discordbot_token.tooltip": "如何获取此参数?请参阅 <a href=\"https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token\" target=\"_blank\">https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token</a>",
"access.form.discordbot_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token\" target=\"_blank\">https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token</a>",
"access.form.discordbot_default_channel_id.label": "默认的 Discord 频道 ID可选",
"access.form.discordbot_default_channel_id.placeholder": "请输入默认的 Discord 频道 ID",
"access.form.discordbot_default_channel_id.tooltip": "这是什么?请参阅 <a href=\"https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID\" target=\"_blank\">https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID</a>",
@ -356,6 +356,12 @@
"access.form.safeline_api_token.label": "雷池 API Token",
"access.form.safeline_api_token.placeholder": "请输入雷池 API Token",
"access.form.safeline_api_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.waf-ce.chaitin.cn/zh/%E6%9B%B4%E5%A4%9A%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3/OPENAPI\" target=\"_blank\">https://docs.waf-ce.chaitin.cn/zh/更多技术文档/OPENAPI</a>",
"access.form.slackbot_token.label": "Slack 机器人 Token",
"access.form.slackbot_token.placeholder": "请输入 Slack 机器人 Token",
"access.form.slackbot_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.slack.dev/authentication/tokens#bot\" target=\"_blank\">https://docs.slack.dev/authentication/tokens#bot</a>",
"access.form.slackbot_default_channel_id.label": "默认的 Slack 频道 ID可选",
"access.form.slackbot_default_channel_id.placeholder": "请输入默认的 Slack 频道 ID",
"access.form.slackbot_default_channel_id.tooltip": "如何获取此参数?请参阅 <a href=\"https://www.youtube.com/watch?v=Uz5Yi5C2pwQ\" target=\"_blank\">https://www.youtube.com/watch?v=Uz5Yi5C2pwQ</a>",
"access.form.ssh_host.label": "服务器地址",
"access.form.ssh_host.placeholder": "请输入服务器地址",
"access.form.ssh_port.label": "服务器端口",

View File

@ -68,7 +68,7 @@
"provider.dynv6": "dynv6",
"provider.edgio": "Edgio",
"provider.edgio.applications": "Edgio - Applications",
"provider.email": "邮件",
"provider.email": "邮件SMTP",
"provider.fastly": "Fastly",
"provider.flexcdn": "FlexCDN",
"provider.gcore": "Gcore",
@ -96,7 +96,7 @@
"provider.lecdn": "LeCDN",
"provider.letsencrypt": "Let's Encrypt",
"provider.letsencryptstaging": "Let's Encrypt 测试环境",
"provider.local": "本地部署",
"provider.local": "本地主机",
"provider.mattermost": "Mattermost",
"provider.namecheap": "Namecheap",
"provider.namedotcom": "Name.com",
@ -118,7 +118,8 @@
"provider.ratpanel.console": "耗子面板 - 控制台",
"provider.ratpanel.site": "耗子面板 - 网站",
"provider.safeline": "雷池",
"provider.ssh": "SSH 部署",
"provider.slackbot": "Slack 机器人",
"provider.ssh": "远程主机SSH",
"provider.sslcom": "SSL.com",
"provider.telegrambot": "Telegram 机器人",
"provider.tencentcloud": "腾讯云",

View File

@ -826,6 +826,9 @@
"workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID可选",
"workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID 以覆盖默认值",
"workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。",
"workflow_node.notify.form.slackbot_channel_id.label": "Slack 频道 ID可选",
"workflow_node.notify.form.slackbot_channel_id.placeholder": "请输入 Slack 频道 ID 以覆盖默认值",
"workflow_node.notify.form.slackbot_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。",
"workflow_node.notify.form.telegrambot_chat_id.label": "Telegram 会话 ID可选",
"workflow_node.notify.form.telegrambot_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值",
"workflow_node.notify.form.telegrambot_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。",