diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go
index 3f0b2600..0cf989a9 100644
--- a/internal/deployer/providers.go
+++ b/internal/deployer/providers.go
@@ -35,6 +35,7 @@ import (
pBaishanCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baishan-cdn"
pBaotaPanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-console"
pBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site"
+ pBaotaWAFSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site"
pBunnyCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/bunny-cdn"
pBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn"
pCacheFly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cachefly"
@@ -475,6 +476,29 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
}
}
+ case domain.DeploymentProviderTypeBaotaWAFSite:
+ {
+ access := domain.AccessConfigForBaotaWAF{}
+ if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ switch options.Provider {
+ case domain.DeploymentProviderTypeBaotaWAFSite:
+ deployer, err := pBaotaWAFSite.NewDeployer(&pBaotaWAFSite.DeployerConfig{
+ ApiUrl: access.ApiUrl,
+ ApiKey: access.ApiKey,
+ AllowInsecureConnections: access.AllowInsecureConnections,
+ SiteName: maputil.GetString(options.ProviderServiceConfig, "siteName"),
+ SitePort: maputil.GetOrDefaultInt32(options.ProviderServiceConfig, "sitePort", 443),
+ })
+ return deployer, err
+
+ default:
+ break
+ }
+ }
+
case domain.DeploymentProviderTypeBunnyCDN:
{
access := domain.AccessConfigForBunny{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index a2fd8a4a..cb5b7708 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -67,6 +67,12 @@ type AccessConfigForBaotaPanel struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
+type AccessConfigForBaotaWAF struct {
+ ApiUrl string `json:"apiUrl"`
+ ApiKey string `json:"apiKey"`
+ AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
+}
+
type AccessConfigForBytePlus struct {
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index fc2d4124..b825b823 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -19,6 +19,7 @@ const (
AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud")
AccessProviderTypeBaishan = AccessProviderType("baishan")
AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel")
+ AccessProviderTypeBaotaWAF = AccessProviderType("baotawaf")
AccessProviderTypeBytePlus = AccessProviderType("byteplus")
AccessProviderTypeBunny = AccessProviderType("bunny")
AccessProviderTypeBuypass = AccessProviderType("buypass")
@@ -190,6 +191,7 @@ const (
DeploymentProviderTypeBaishanCDN = DeploymentProviderType(AccessProviderTypeBaishan + "-cdn")
DeploymentProviderTypeBaotaPanelConsole = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-console")
DeploymentProviderTypeBaotaPanelSite = DeploymentProviderType(AccessProviderTypeBaotaPanel + "-site")
+ DeploymentProviderTypeBaotaWAFSite = DeploymentProviderType(AccessProviderTypeBaotaWAF + "-site")
DeploymentProviderTypeBunnyCDN = DeploymentProviderType(AccessProviderTypeBunny + "-cdn")
DeploymentProviderTypeBytePlusCDN = DeploymentProviderType(AccessProviderTypeBytePlus + "-cdn")
DeploymentProviderTypeCacheFly = DeploymentProviderType(AccessProviderTypeCacheFly)
diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go
new file mode 100644
index 00000000..ed05937a
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go
@@ -0,0 +1,151 @@
+package baotapanelwaf
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/url"
+
+ "github.com/usual2970/certimate/internal/pkg/core/deployer"
+ btsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/btwaf"
+ typeutil "github.com/usual2970/certimate/internal/pkg/utils/type"
+)
+
+type DeployerConfig struct {
+ // 堡塔云 WAF 地址。
+ ApiUrl string `json:"apiUrl"`
+ // 堡塔云 WAF 接口密钥。
+ ApiKey string `json:"apiKey"`
+ // 是否允许不安全的连接。
+ AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
+ // 网站名称。
+ SiteName string `json:"siteName"`
+ // 网站 SSL 端口。
+ // 零值时默认为 443。
+ SitePort int32 `json:"sitePort,omitempty"`
+}
+
+type DeployerProvider struct {
+ config *DeployerConfig
+ logger *slog.Logger
+ sdkClient *btsdk.Client
+}
+
+var _ deployer.Deployer = (*DeployerProvider)(nil)
+
+func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
+ if config == nil {
+ panic("config is nil")
+ }
+
+ client, err := createSdkClient(config.ApiUrl, config.ApiKey, config.AllowInsecureConnections)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create sdk client: %w", err)
+ }
+
+ return &DeployerProvider{
+ config: config,
+ logger: slog.Default(),
+ sdkClient: client,
+ }, nil
+}
+
+func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
+ if logger == nil {
+ d.logger = slog.Default()
+ } else {
+ d.logger = logger
+ }
+ return d
+}
+
+func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
+ if d.config.SiteName == "" {
+ return nil, errors.New("config `siteName` is required")
+ }
+ if d.config.SitePort == 0 {
+ d.config.SitePort = 443
+ }
+
+ // 遍历获取网站列表,获取网站 ID
+ // REF: https://support.huaweicloud.com/api-waf/ListHost.html
+ siteId := ""
+ getSitListPage := int32(1)
+ getSitListPageSize := int32(100)
+ for {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ getSiteListReq := &btsdk.GetSiteListRequest{
+ SiteName: typeutil.ToPtr(d.config.SiteName),
+ Page: typeutil.ToPtr(getSitListPage),
+ PageSize: typeutil.ToPtr(getSitListPageSize),
+ }
+ getSiteListResp, err := d.sdkClient.GetSiteList(getSiteListReq)
+ d.logger.Debug("sdk request 'bt.GetSiteList'", slog.Any("request", getSiteListReq), slog.Any("response", getSiteListResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request 'bt.GetSiteList': %w", err)
+ }
+
+ if getSiteListResp.Result != nil && getSiteListResp.Result.List != nil {
+ for _, siteItem := range getSiteListResp.Result.List {
+ if siteItem.SiteName == d.config.SiteName {
+ siteId = siteItem.SiteId
+ break
+ }
+ }
+ }
+
+ if getSiteListResp.Result == nil || len(getSiteListResp.Result.List) < int(getSitListPageSize) {
+ break
+ } else {
+ getSitListPage++
+ }
+ }
+ if siteId == "" {
+ return nil, errors.New("site not found")
+ }
+
+ // 修改站点配置
+ modifySiteReq := &btsdk.ModifySiteRequest{
+ SiteId: siteId,
+ Type: typeutil.ToPtr("openCert"),
+ Server: &btsdk.SiteServerInfo{
+ ListenSSLPort: typeutil.ToPtr(d.config.SitePort),
+ SSL: &btsdk.SiteServerSSLInfo{
+ IsSSL: typeutil.ToPtr(int32(1)),
+ FullChain: typeutil.ToPtr(certPEM),
+ PrivateKey: typeutil.ToPtr(privkeyPEM),
+ },
+ },
+ }
+ modifySiteResp, err := d.sdkClient.ModifySite(modifySiteReq)
+ d.logger.Debug("sdk request 'bt.ModifySite'", slog.Any("request", modifySiteReq), slog.Any("response", modifySiteResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request 'bt.ModifySite': %w", err)
+ }
+
+ return &deployer.DeployResult{}, nil
+}
+
+func createSdkClient(apiUrl, apiKey string, skipTlsVerify bool) (*btsdk.Client, error) {
+ if _, err := url.Parse(apiUrl); err != nil {
+ return nil, errors.New("invalid baota api url")
+ }
+
+ if apiKey == "" {
+ return nil, errors.New("invalid baota api key")
+ }
+
+ client := btsdk.NewClient(apiUrl, apiKey)
+ if skipTlsVerify {
+ client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
+ }
+
+ return client, nil
+}
diff --git a/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go
new file mode 100644
index 00000000..4e1ffe34
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go
@@ -0,0 +1,81 @@
+package baotapanelwaf_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotawaf-site"
+)
+
+var (
+ fInputCertPath string
+ fInputKeyPath string
+ fApiUrl string
+ fApiKey string
+ fSiteName string
+ fSitePort int64
+)
+
+func init() {
+ argsPrefix := "CERTIMATE_DEPLOYER_BAOTAWAFSITE_"
+
+ flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
+ flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
+ flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
+ flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
+ flag.StringVar(&fSiteName, argsPrefix+"SITENAME", "", "")
+ flag.Int64Var(&fSitePort, argsPrefix+"SITEPORT", 0, "")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./baotawaf_site_test.go -args \
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIURL="http://127.0.0.1:8888" \
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_APIKEY="your-api-key" \
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITENAME="your-site-name"\
+ --CERTIMATE_DEPLOYER_BAOTAWAFSITE_SITEPORT=443
+*/
+func TestDeploy(t *testing.T) {
+ flag.Parse()
+
+ t.Run("Deploy", func(t *testing.T) {
+ t.Log(strings.Join([]string{
+ "args:",
+ fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
+ fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
+ fmt.Sprintf("APIURL: %v", fApiUrl),
+ fmt.Sprintf("APIKEY: %v", fApiKey),
+ fmt.Sprintf("SITENAME: %v", fSiteName),
+ fmt.Sprintf("SITEPORT: %v", fSitePort),
+ }, "\n"))
+
+ deployer, err := provider.NewDeployer(&provider.DeployerConfig{
+ ApiUrl: fApiUrl,
+ ApiKey: fApiKey,
+ AllowInsecureConnections: true,
+ SiteName: fSiteName,
+ SitePort: int32(fSitePort),
+ })
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ fInputCertData, _ := os.ReadFile(fInputCertPath)
+ fInputKeyData, _ := os.ReadFile(fInputKeyPath)
+ res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
+ if err != nil {
+ t.Errorf("err: %+v", err)
+ return
+ }
+
+ t.Logf("ok: %v", res)
+ })
+}
diff --git a/internal/pkg/sdk3rd/btwaf/api.go b/internal/pkg/sdk3rd/btwaf/api.go
new file mode 100644
index 00000000..bc35dee5
--- /dev/null
+++ b/internal/pkg/sdk3rd/btwaf/api.go
@@ -0,0 +1,19 @@
+package btwaf
+
+func (c *Client) GetSiteList(req *GetSiteListRequest) (*GetSiteListResponse, error) {
+ resp := &GetSiteListResponse{}
+ err := c.sendRequestWithResult("/wafmastersite/get_site_list", req, resp)
+ return resp, err
+}
+
+func (c *Client) ModifySite(req *ModifySiteRequest) (*ModifySiteResponse, error) {
+ resp := &ModifySiteResponse{}
+ err := c.sendRequestWithResult("/wafmastersite/modify_site", req, resp)
+ return resp, err
+}
+
+func (c *Client) ConfigSetSSL(req *ConfigSetSSLRequest) (*ConfigSetSSLResponse, error) {
+ resp := &ConfigSetSSLResponse{}
+ err := c.sendRequestWithResult("/config/set_cert", req, resp)
+ return resp, err
+}
diff --git a/internal/pkg/sdk3rd/btwaf/client.go b/internal/pkg/sdk3rd/btwaf/client.go
new file mode 100644
index 00000000..5ae545cc
--- /dev/null
+++ b/internal/pkg/sdk3rd/btwaf/client.go
@@ -0,0 +1,77 @@
+package btwaf
+
+import (
+ "crypto/md5"
+ "crypto/tls"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+)
+
+type Client struct {
+ client *resty.Client
+}
+
+func NewClient(apiHost, apiKey string) *Client {
+ client := resty.New().
+ SetBaseURL(strings.TrimRight(apiHost, "/") + "/api").
+ SetPreRequestHook(func(c *resty.Client, req *http.Request) error {
+ timestamp := fmt.Sprintf("%d", time.Now().Unix())
+ keyMd5 := md5.Sum([]byte(apiKey))
+ keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:]))
+ signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex))
+ signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:]))
+ req.Header.Set("waf_request_time", timestamp)
+ req.Header.Set("waf_request_token", signMd5Hex)
+
+ return nil
+ })
+
+ return &Client{
+ client: client,
+ }
+}
+
+func (c *Client) WithTimeout(timeout time.Duration) *Client {
+ c.client.SetTimeout(timeout)
+ return c
+}
+
+func (c *Client) WithTLSConfig(config *tls.Config) *Client {
+ c.client.SetTLSClientConfig(config)
+ return c
+}
+
+func (c *Client) sendRequest(path string, params interface{}) (*resty.Response, error) {
+ req := c.client.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(params)
+ resp, err := req.Post(path)
+ if err != nil {
+ return resp, fmt.Errorf("baota api error: failed to send request: %w", err)
+ } else if resp.IsError() {
+ return resp, fmt.Errorf("baota api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String())
+ }
+
+ return resp, nil
+}
+
+func (c *Client) sendRequestWithResult(path string, params interface{}, result BaseResponse) error {
+ resp, err := c.sendRequest(path, params)
+ if err != nil {
+ return err
+ }
+
+ if err := json.Unmarshal(resp.Body(), &result); err != nil {
+ return fmt.Errorf("baota api error: failed to unmarshal response: %w", err)
+ } else if errcode := result.GetCode(); errcode != 0 {
+ return fmt.Errorf("baota api error: code='%d'", errcode)
+ }
+
+ return nil
+}
diff --git a/internal/pkg/sdk3rd/btwaf/models.go b/internal/pkg/sdk3rd/btwaf/models.go
new file mode 100644
index 00000000..16290e88
--- /dev/null
+++ b/internal/pkg/sdk3rd/btwaf/models.go
@@ -0,0 +1,67 @@
+package btwaf
+
+type BaseResponse interface {
+ GetCode() int32
+}
+
+type baseResponse struct {
+ Code *int32 `json:"code,omitempty"`
+}
+
+func (r *baseResponse) GetCode() int32 {
+ if r.Code != nil {
+ return *r.Code
+ }
+ return 0
+}
+
+type GetSiteListRequest struct {
+ Page *int32 `json:"p,omitempty"`
+ PageSize *int32 `json:"p_size,omitempty"`
+ SiteName *string `json:"site_name,omitempty"`
+}
+
+type GetSiteListResponse struct {
+ baseResponse
+ Result *struct {
+ List []*struct {
+ SiteId string `json:"site_id"`
+ SiteName string `json:"site_name"`
+ Type string `json:"types"`
+ Status int32 `json:"status"`
+ CreateTime int64 `json:"create_time"`
+ UpdateTime int64 `json:"update_time"`
+ } `json:"list"`
+ Total int32 `json:"total"`
+ } `json:"res,omitempty"`
+}
+
+type SiteServerInfo struct {
+ ListenSSLPort *int32 `json:"listen_ssl_port,omitempty"`
+ SSL *SiteServerSSLInfo `json:"ssl,omitempty"`
+}
+
+type SiteServerSSLInfo struct {
+ IsSSL *int32 `json:"is_ssl,omitempty"`
+ FullChain *string `json:"full_chain,omitempty"`
+ PrivateKey *string `json:"private_key,omitempty"`
+}
+
+type ModifySiteRequest struct {
+ SiteId string `json:"site_id"`
+ Type *string `json:"types,omitempty"`
+ Server *SiteServerInfo `json:"server,omitempty"`
+}
+
+type ModifySiteResponse struct {
+ baseResponse
+}
+
+type ConfigSetSSLRequest struct {
+ CertContent string `json:"certContent"`
+ KeyContent string `json:"keyContent"`
+}
+
+type ConfigSetSSLResponse struct {
+ baseResponse
+}
diff --git a/ui/public/imgs/providers/baotawaf.svg b/ui/public/imgs/providers/baotawaf.svg
new file mode 100644
index 00000000..34ab8ec8
--- /dev/null
+++ b/ui/public/imgs/providers/baotawaf.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 5bc6cab0..fdf4f93f 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -20,6 +20,7 @@ import AccessFormAzureConfig from "./AccessFormAzureConfig";
import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig";
import AccessFormBaishanConfig from "./AccessFormBaishanConfig";
import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig";
+import AccessFormBaotaWAFConfig from "./AccessFormBaotaWAFConfig";
import AccessFormBunnyConfig from "./AccessFormBunnyConfig";
import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig";
import AccessFormCacheFlyConfig from "./AccessFormCacheFlyConfig";
@@ -196,6 +197,8 @@ const AccessForm = forwardRef(({ className,
return ;
case ACCESS_PROVIDERS.BAOTAPANEL:
return ;
+ case ACCESS_PROVIDERS.BAOTAWAF:
+ return ;
case ACCESS_PROVIDERS.BUNNY:
return ;
case ACCESS_PROVIDERS.BYTEPLUS:
diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
index d03c0f1b..dd355b5e 100644
--- a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
+++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
@@ -27,11 +27,7 @@ const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initia
const formSchema = z.object({
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
- apiKey: z
- .string()
- .min(1, t("access.form.baotapanel_api_key.placeholder"))
- .max(64, t("common.errmsg.string_max", { max: 64 }))
- .trim(),
+ apiKey: z.string().nonempty(t("access.form.baotapanel_api_key.placeholder")).trim(),
allowInsecureConnections: z.boolean().nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
diff --git a/ui/src/components/access/AccessFormBaotaWAFConfig.tsx b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx
new file mode 100644
index 00000000..e87ed596
--- /dev/null
+++ b/ui/src/components/access/AccessFormBaotaWAFConfig.tsx
@@ -0,0 +1,71 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input, Switch } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { type AccessConfigForBaotaWAF } from "@/domain/access";
+
+type AccessFormBaotaWAFConfigFieldValues = Nullish;
+
+export type AccessFormBaotaWAFConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessFormBaotaWAFConfigFieldValues;
+ onValuesChange?: (values: AccessFormBaotaWAFConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessFormBaotaWAFConfigFieldValues => {
+ return {
+ apiUrl: "http://:8379/",
+ apiKey: "",
+ };
+};
+
+const AccessFormBaotaWAFConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormBaotaWAFConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ apiUrl: z.string().url(t("common.errmsg.url_invalid")),
+ apiKey: z.string().nonempty(t("access.form.baotawaf_api_key.placeholder")).trim(),
+ allowInsecureConnections: z.boolean().nullish(),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+
+
+
+
+ }
+ >
+
+
+
+
+
+
+
+ );
+};
+
+export default AccessFormBaotaWAFConfig;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index 67b43e77..49ed12ec 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -42,6 +42,7 @@ import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaidu
import DeployNodeConfigFormBaishanCDNConfig from "./DeployNodeConfigFormBaishanCDNConfig";
import DeployNodeConfigFormBaotaPanelConsoleConfig from "./DeployNodeConfigFormBaotaPanelConsoleConfig";
import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig";
+import DeployNodeConfigFormBaotaWAFSiteConfig from "./DeployNodeConfigFormBaotaWAFSiteConfig";
import DeployNodeConfigFormBunnyCDNConfig from "./DeployNodeConfigFormBunnyCDNConfig.tsx";
import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig";
import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig";
@@ -237,6 +238,8 @@ const DeployNodeConfigForm = forwardRef;
case DEPLOYMENT_PROVIDERS.BAOTAPANEL_SITE:
return ;
+ case DEPLOYMENT_PROVIDERS.BAOTAWAF_SITE:
+ return ;
case DEPLOYMENT_PROVIDERS.BUNNY_CDN:
return ;
case DEPLOYMENT_PROVIDERS.BYTEPLUS_CDN:
diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx
new file mode 100644
index 00000000..6f992fb8
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx
@@ -0,0 +1,78 @@
+import { useTranslation } from "react-i18next";
+import { Form, type FormInstance, Input, InputNumber } from "antd";
+import { createSchemaFieldRule } from "antd-zod";
+import { z } from "zod";
+
+import { validPortNumber } from "@/utils/validators";
+
+type DeployNodeConfigFormBaotaWAFSiteConfigFieldValues = Nullish<{
+ siteName: string;
+ sitePort: number;
+}>;
+
+export type DeployNodeConfigFormBaotaWAFSiteConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues;
+ onValuesChange?: (values: DeployNodeConfigFormBaotaWAFSiteConfigFieldValues) => void;
+};
+
+const initFormModel = (): DeployNodeConfigFormBaotaWAFSiteConfigFieldValues => {
+ return {
+ siteName: "",
+ sitePort: 443,
+ };
+};
+
+const DeployNodeConfigFormBaotaWAFSiteConfig = ({
+ form: formInst,
+ formName,
+ disabled,
+ initialValues,
+ onValuesChange,
+}: DeployNodeConfigFormBaotaWAFSiteConfigProps) => {
+ const { t } = useTranslation();
+
+ const formSchema = z.object({
+ siteName: z.string().nonempty(t("workflow_node.deploy.form.baotawaf_site_name.placeholder")).trim(),
+ sitePort: z.preprocess(
+ (v) => Number(v),
+ z
+ .number()
+ .int(t("workflow_node.deploy.form.baotawaf_site_port.placeholder"))
+ .refine((v) => validPortNumber(v), t("common.errmsg.port_invalid"))
+ ),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+
+ const handleFormChange = (_: unknown, values: z.infer) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+
+
+
+
+ );
+};
+
+export default DeployNodeConfigFormBaotaWAFSiteConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index 4326aa75..f60f39c8 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -15,6 +15,7 @@ export interface AccessModel extends BaseModel {
| AccessConfigForBaiduCloud
| AccessConfigForBaishan
| AccessConfigForBaotaPanel
+ | AccessConfigForBaotaWAF
| AccessConfigForBunny
| AccessConfigForBytePlus
| AccessConfigForCacheFly
@@ -123,6 +124,12 @@ export type AccessConfigForBaotaPanel = {
allowInsecureConnections?: boolean;
};
+export type AccessConfigForBaotaWAF = {
+ apiUrl: string;
+ apiKey: string;
+ allowInsecureConnections?: boolean;
+};
+
export type AccessConfigForBunny = {
apiKey: string;
};
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 1c80910e..3cb52e9f 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -13,6 +13,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
BAIDUCLOUD: "baiducloud",
BAISHAN: "baishan",
BAOTAPANEL: "baotapanel",
+ BAOTAWAF: "baotawaf",
BUNNY: "bunny",
BYTEPLUS: "byteplus",
BUYPASS: "buypass",
@@ -124,6 +125,7 @@ export const accessProvidersMap: Map [
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json
index 4bf67617..589b2572 100644
--- a/ui/src/i18n/locales/en/nls.access.json
+++ b/ui/src/i18n/locales/en/nls.access.json
@@ -112,6 +112,14 @@
"access.form.baotapanel_allow_insecure_conns.label": "Insecure SSL/TLS connections",
"access.form.baotapanel_allow_insecure_conns.switch.on": "Allow",
"access.form.baotapanel_allow_insecure_conns.switch.off": "Disallow",
+ "access.form.baotawaf_api_url.label": "aaWAF URL",
+ "access.form.baotawaf_api_url.placeholder": "Please enter aaWAF URL",
+ "access.form.baotawaf_api_key.label": "aaWAF API key",
+ "access.form.baotawaf_api_key.placeholder": "Please enter aaWAF API key",
+ "access.form.baotawaf_api_key.tooltip": "For more information, see https://github.com/aaPanel/aaWAF/blob/main/API.md",
+ "access.form.baotawaf_allow_insecure_conns.label": "Insecure SSL/TLS connections",
+ "access.form.baotawaf_allow_insecure_conns.switch.on": "Allow",
+ "access.form.baotawaf_allow_insecure_conns.switch.off": "Disallow",
"access.form.byteplus_access_key.label": "BytePlus AccessKey",
"access.form.byteplus_access_key.placeholder": "Please enter BytePlus AccessKey",
"access.form.byteplus_access_key.tooltip": "For more information, see https://docs.byteplus.com/en/docs/byteplus-platform/docs-managing-keys",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index f87a5c2f..2493af42 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -42,6 +42,8 @@
"provider.baotapanel": "aaPanel (aka BaoTaPanel)",
"provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console",
"provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website",
+ "provider.baotawaf": "aaWAF (aka BaotaWAF)",
+ "provider.baotawaf.site": "aaWAF (aka BaotaWAF) - Website",
"provider.bunny": "Bunny",
"provider.bunny.cdn": "Bunny - CDN (Content Delivery Network)",
"provider.byteplus": "BytePlus",
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index ba5ebe49..80237287 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -342,9 +342,14 @@
"workflow_node.deploy.form.baotapanel_site_names.label": "aaPanel site names",
"workflow_node.deploy.form.baotapanel_site_names.placeholder": "Please enter aaPanel site names (separated by semicolons)",
"workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "Please enter a valid aaPanel site name",
- "workflow_node.deploy.form.baotapanel_site_names.tooltip": "Usually equal to the websites domain name.",
+ "workflow_node.deploy.form.baotapanel_site_names.tooltip": "You can find it on aaPanel WebUI.",
"workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "Change aaPanel site names",
"workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "Please enter aaPanel site name",
+ "workflow_node.deploy.form.baotawaf_site_name.label": "aaWAF site name",
+ "workflow_node.deploy.form.baotawaf_site_name.placeholder": "Please enter aaWAF site name",
+ "workflow_node.deploy.form.baotawaf_site_name.tooltip": "You can find it on aaWAF WebUI.",
+ "workflow_node.deploy.form.baotawaf_site_port.label": "aaWAF site SSL port",
+ "workflow_node.deploy.form.baotawaf_site_port.placeholder": "Please enter aaWAF SSL port",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN pull zone ID",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "Please enter Bunny CDN pull zone ID",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "What is this? See https://dash.bunny.net/cdn",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index a3d059e1..f4ee2c2a 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -103,6 +103,14 @@
"access.form.baotapanel_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
"access.form.baotapanel_allow_insecure_conns.switch.on": "允许",
"access.form.baotapanel_allow_insecure_conns.switch.off": "不允许",
+ "access.form.baotawaf_api_url.label": "堡塔云 WAF URL",
+ "access.form.baotawaf_api_url.placeholder": "请输入堡塔云 WAF URL",
+ "access.form.baotawaf_api_key.label": "堡塔云 WAF 接口密钥",
+ "access.form.baotawaf_api_key.placeholder": "请输入 堡塔云 WAF 接口密钥",
+ "access.form.baotawaf_api_key.tooltip": "这是什么?请参阅 https://github.com/aaPanel/aaWAF/blob/main/API.md",
+ "access.form.baotawaf_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
+ "access.form.baotawaf_allow_insecure_conns.switch.on": "允许",
+ "access.form.baotawaf_allow_insecure_conns.switch.off": "不允许",
"access.form.bunny_api_key.label": "Bunny API Key",
"access.form.bunny_api_key.placeholder": "请输入 Bunny API Key",
"access.form.bunny_api_key.tooltip": "这是什么?请参阅 https://docs.bunny.net/reference/bunnynet-api-overview",
diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json
index d69b1e38..7f57b7e1 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -42,6 +42,8 @@
"provider.baotapanel": "宝塔面板",
"provider.baotapanel.console": "宝塔面板 - 面板",
"provider.baotapanel.site": "宝塔面板 - 网站",
+ "provider.baotawaf": "堡塔云 WAF",
+ "provider.baotawaf.site": "堡塔云 WAF - 网站",
"provider.bunny": "Bunny",
"provider.bunny.cdn": "Bunny - 内容分发网络 CDN",
"provider.byteplus": "BytePlus",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 13d21eed..faf40816 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -337,13 +337,18 @@
"workflow_node.deploy.form.baotapanel_site_type.option.other.label": "其他",
"workflow_node.deploy.form.baotapanel_site_name.label": "宝塔面板网站名称",
"workflow_node.deploy.form.baotapanel_site_name.placeholder": "请输入宝塔面板网站名称",
- "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看",
+ "workflow_node.deploy.form.baotapanel_site_name.tooltip": "请登录宝塔面板查看。",
"workflow_node.deploy.form.baotapanel_site_names.label": "宝塔面板网站名称",
"workflow_node.deploy.form.baotapanel_site_names.placeholder": "请输入宝塔面板网站名称(多个值请用半角分号隔开)",
"workflow_node.deploy.form.baotapanel_site_names.errmsg.invalid": "请输入正确的宝塔面板网站名称",
- "workflow_node.deploy.form.baotapanel_site_names.tooltip": "通常为网站域名。",
+ "workflow_node.deploy.form.baotapanel_site_names.tooltip": "请登录宝塔面板查看。",
"workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.title": "修改宝塔面板网站名称",
"workflow_node.deploy.form.baotapanel_site_names.multiple_input_modal.placeholder": "请输入宝塔面板网站名称",
+ "workflow_node.deploy.form.baotawaf_site_name.label": "堡塔云 WAF 网站名称",
+ "workflow_node.deploy.form.baotawaf_site_name.placeholder": "请输入堡塔云 WAF 网站名称",
+ "workflow_node.deploy.form.baotawaf_site_name.tooltip": "请登录堡塔云 WAF 面板查看。",
+ "workflow_node.deploy.form.baotawaf_site_port.label": "堡塔云 WAF 网站 SSL 端口",
+ "workflow_node.deploy.form.baotawaf_site_port.placeholder": "请输入堡塔云 WAF 网站 SSL 端口",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.label": "Bunny CDN 拉取区域 ID",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.placeholder": "请输入 Bunny CDN 拉取区域 ID",
"workflow_node.deploy.form.bunny_cdn_pull_zone_id.tooltip": "这是什么?请参阅 https://dash.bunny.net/cdn",