From 591df58992c737ef6a41f919d2403e67a0f54b38 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 20 May 2025 21:16:28 +0800 Subject: [PATCH] feat: new deployment provider: baotawaf site --- internal/deployer/providers.go | 24 +++ internal/domain/access.go | 6 + internal/domain/provider.go | 2 + .../providers/baotawaf-site/baotawaf_site.go | 151 ++++++++++++++++++ .../baotawaf-site/baotawaf_site_test.go | 81 ++++++++++ internal/pkg/sdk3rd/btwaf/api.go | 19 +++ internal/pkg/sdk3rd/btwaf/client.go | 77 +++++++++ internal/pkg/sdk3rd/btwaf/models.go | 67 ++++++++ ui/public/imgs/providers/baotawaf.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormBaotaPanelConfig.tsx | 6 +- .../access/AccessFormBaotaWAFConfig.tsx | 71 ++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...DeployNodeConfigFormBaotaWAFSiteConfig.tsx | 78 +++++++++ ui/src/domain/access.ts | 7 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 8 + ui/src/i18n/locales/en/nls.provider.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 7 +- ui/src/i18n/locales/zh/nls.access.json | 8 + ui/src/i18n/locales/zh/nls.provider.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 +- 22 files changed, 628 insertions(+), 8 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site.go create mode 100644 internal/pkg/core/deployer/providers/baotawaf-site/baotawaf_site_test.go create mode 100644 internal/pkg/sdk3rd/btwaf/api.go create mode 100644 internal/pkg/sdk3rd/btwaf/client.go create mode 100644 internal/pkg/sdk3rd/btwaf/models.go create mode 100644 ui/public/imgs/providers/baotawaf.svg create mode 100644 ui/src/components/access/AccessFormBaotaWAFConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormBaotaWAFSiteConfig.tsx 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",