From eabd16dd715e861f0f468144fa005f79f433fa87 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 16 May 2025 22:18:03 +0800 Subject: [PATCH] feat: new deployment provider: flexcdn --- internal/deployer/providers.go | 20 +++ internal/domain/access.go | 8 + internal/domain/provider.go | 6 +- .../core/deployer/providers/flexcdn/consts.go | 8 + .../deployer/providers/flexcdn/flexcdn.go | 144 ++++++++++++++++++ .../providers/flexcdn/flexcdn_test.go | 83 ++++++++++ .../deployer/providers/goedge/goedge_test.go | 1 + internal/pkg/sdk3rd/flexcdn/api.go | 46 ++++++ internal/pkg/sdk3rd/flexcdn/client.go | 103 +++++++++++++ internal/pkg/sdk3rd/flexcdn/models.go | 52 +++++++ internal/pkg/sdk3rd/goedge/client.go | 4 +- internal/pkg/sdk3rd/upyun/console/client.go | 4 +- ui/public/imgs/providers/flexcdn.png | Bin 0 -> 13243 bytes ui/src/components/access/AccessForm.tsx | 11 +- .../access/AccessFormFlexCDNConfig.tsx | 90 +++++++++++ .../access/AccessFormGoEdgeConfig.tsx | 12 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../node/DeployNodeConfigFormCdnflyConfig.tsx | 14 +- .../DeployNodeConfigFormFlexCDNConfig.tsx | 84 ++++++++++ .../node/DeployNodeConfigFormGoEdgeConfig.tsx | 7 +- ui/src/domain/access.ts | 9 ++ ui/src/domain/provider.ts | 8 +- ui/src/i18n/locales/en/nls.access.json | 15 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 9 ++ ui/src/i18n/locales/zh/nls.access.json | 15 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 9 ++ 28 files changed, 741 insertions(+), 26 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/flexcdn/consts.go create mode 100644 internal/pkg/core/deployer/providers/flexcdn/flexcdn.go create mode 100644 internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go create mode 100644 internal/pkg/sdk3rd/flexcdn/api.go create mode 100644 internal/pkg/sdk3rd/flexcdn/client.go create mode 100644 internal/pkg/sdk3rd/flexcdn/models.go create mode 100644 ui/public/imgs/providers/flexcdn.png create mode 100644 ui/src/components/access/AccessFormFlexCDNConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 9db3a0fb..bf7483d9 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -40,6 +40,7 @@ import ( pCdnfly "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/cdnfly" pDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" pEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications" + pFlexCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" pGcoreCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/gcore-cdn" pGoEdge "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/goedge" pHuaweiCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/huaweicloud-cdn" @@ -557,6 +558,25 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer return deployer, err } + case domain.DeploymentProviderTypeFlexCDN: + { + access := domain.AccessConfigForFlexCDN{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pFlexCDN.NewDeployer(&pFlexCDN.DeployerConfig{ + ApiUrl: access.ApiUrl, + ApiRole: access.ApiRole, + AccessKeyId: access.AccessKeyId, + AccessKey: access.AccessKey, + AllowInsecureConnections: access.AllowInsecureConnections, + ResourceType: pFlexCDN.ResourceType(maputil.GetString(options.ProviderExtendedConfig, "resourceType")), + CertificateId: maputil.GetInt64(options.ProviderExtendedConfig, "certificateId"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeGcoreCDN: { access := domain.AccessConfigForGcore{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 0321cb41..30e52412 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -140,6 +140,14 @@ type AccessConfigForEmail struct { DefaultReceiverAddress string `json:"defaultReceiverAddress,omitempty"` } +type AccessConfigForFlexCDN struct { + ApiUrl string `json:"apiUrl"` + ApiRole string `json:"apiRole"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` +} + type AccessConfigForGcore struct { ApiToken string `json:"apiToken"` } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index d4d9af2e..45c72c05 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -36,8 +36,8 @@ const ( AccessProviderTypeDynv6 = AccessProviderType("dynv6") AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeEmail = AccessProviderType("email") - AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) - AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") // FlexCDN(预留) + AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) + AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") AccessProviderTypeGname = AccessProviderType("gname") AccessProviderTypeGcore = AccessProviderType("gcore") AccessProviderTypeGoDaddy = AccessProviderType("godaddy") @@ -196,7 +196,7 @@ const ( DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly) DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn") DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications") - DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) // FlexCDN(预留) + DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn") DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge) DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn") diff --git a/internal/pkg/core/deployer/providers/flexcdn/consts.go b/internal/pkg/core/deployer/providers/flexcdn/consts.go new file mode 100644 index 00000000..be55a475 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/consts.go @@ -0,0 +1,8 @@ +package flexcdn + +type ResourceType string + +const ( + // 资源类型:替换指定证书。 + RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate") +) diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go new file mode 100644 index 00000000..3d0f05e9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn.go @@ -0,0 +1,144 @@ +package flexcdn + +import ( + "context" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "log/slog" + "net/url" + "time" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + flexcdnsdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/flexcdn" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" +) + +type DeployerConfig struct { + // FlexCDN URL。 + ApiUrl string `json:"apiUrl"` + // FlexCDN 用户角色。 + ApiRole string `json:"apiRole"` + // FlexCDN AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // FlexCDN AccessKey。 + AccessKey string `json:"accessKey"` + // 是否允许不安全的连接。 + AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 部署资源类型。 + ResourceType ResourceType `json:"resourceType"` + // 证书 ID。 + // 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。 + CertificateId int64 `json:"certificateId,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *flexcdnsdk.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.ApiRole, config.AccessKeyId, config.AccessKey, 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) { + // 根据部署资源类型决定部署方式 + switch d.config.ResourceType { + case RESOURCE_TYPE_CERTIFICATE: + if err := d.deployToCertificate(ctx, certPEM, privkeyPEM); err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unsupported resource type '%s'", d.config.ResourceType) + } + + return &deployer.DeployResult{}, nil +} + +func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM string, privkeyPEM string) error { + if d.config.CertificateId == 0 { + return errors.New("config `certificateId` is required") + } + + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return err + } + + // 修改证书 + // REF: https://flexcdn.cloud/dev/api/service/SSLCertService?role=user#updateSSLCert + updateSSLCertReq := &flexcdnsdk.UpdateSSLCertRequest{ + SSLCertId: d.config.CertificateId, + IsOn: true, + Name: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), + Description: "upload from certimate", + ServerName: certX509.Subject.CommonName, + IsCA: false, + CertData: base64.StdEncoding.EncodeToString([]byte(certPEM)), + KeyData: base64.StdEncoding.EncodeToString([]byte(privkeyPEM)), + TimeBeginAt: certX509.NotBefore.Unix(), + TimeEndAt: certX509.NotAfter.Unix(), + DNSNames: certX509.DNSNames, + CommonNames: []string{certX509.Subject.CommonName}, + } + updateSSLCertResp, err := d.sdkClient.UpdateSSLCert(updateSSLCertReq) + d.logger.Debug("sdk request 'flexcdn.UpdateSSLCert'", slog.Any("request", updateSSLCertReq), slog.Any("response", updateSSLCertResp)) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'flexcdn.UpdateSSLCert': %w", err) + } + + return nil +} + +func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*flexcdnsdk.Client, error) { + if _, err := url.Parse(apiUrl); err != nil { + return nil, errors.New("invalid flexcdn api url") + } + + if apiRole != "user" && apiRole != "admin" { + return nil, errors.New("invalid flexcdn api role") + } + + if accessKeyId == "" { + return nil, errors.New("invalid flexcdn access key id") + } + + if accessKey == "" { + return nil, errors.New("invalid flexcdn access key") + } + + client := flexcdnsdk.NewClient(apiUrl, apiRole, accessKeyId, accessKey) + if skipTlsVerify { + client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}) + } + + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go new file mode 100644 index 00000000..4a693dc9 --- /dev/null +++ b/internal/pkg/core/deployer/providers/flexcdn/flexcdn_test.go @@ -0,0 +1,83 @@ +package flexcdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/flexcdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fAccessKeyId string + fAccessKey string + fCertificateId int +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_FLEXCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKey, argsPrefix+"ACCESSKEY", "", "") + flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./flexcdn_test.go -args \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_FLEXCDN_APIURL="http://127.0.0.1:7788" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_FLEXCDN_ACCESSKEY="your-access-key" \ + --CERTIMATE_DEPLOYER_FLEXCDN_CERTIFICATEID="your-cerficiate-id" +*/ +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("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEY: %v", fAccessKey), + fmt.Sprintf("CERTIFICATEID: %v", fCertificateId), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + ApiUrl: fApiUrl, + ApiRole: "user", + AccessKeyId: fAccessKeyId, + AccessKey: fAccessKey, + AllowInsecureConnections: true, + ResourceType: provider.RESOURCE_TYPE_CERTIFICATE, + CertificateId: int64(fCertificateId), + }) + 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/core/deployer/providers/goedge/goedge_test.go b/internal/pkg/core/deployer/providers/goedge/goedge_test.go index c8c32b37..928fb420 100644 --- a/internal/pkg/core/deployer/providers/goedge/goedge_test.go +++ b/internal/pkg/core/deployer/providers/goedge/goedge_test.go @@ -58,6 +58,7 @@ func TestDeploy(t *testing.T) { deployer, err := provider.NewDeployer(&provider.DeployerConfig{ ApiUrl: fApiUrl, + ApiRole: "user", AccessKeyId: fAccessKeyId, AccessKey: fAccessKey, AllowInsecureConnections: true, diff --git a/internal/pkg/sdk3rd/flexcdn/api.go b/internal/pkg/sdk3rd/flexcdn/api.go new file mode 100644 index 00000000..07fb2b34 --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/api.go @@ -0,0 +1,46 @@ +package flexcdn + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +func (c *Client) getAccessToken() error { + req := &getAPIAccessTokenRequest{ + Type: c.apiRole, + AccessKeyId: c.accessKeyId, + AccessKey: c.accessKey, + } + res, err := c.sendRequest(http.MethodPost, "/APIAccessTokenService/getAPIAccessToken", req) + if err != nil { + return err + } + + resp := &getAPIAccessTokenResponse{} + if err := json.Unmarshal(res.Body(), &resp); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if resp.GetCode() != 200 { + return fmt.Errorf("flexcdn get access token failed: code: %d, message: %s", resp.GetCode(), resp.GetMessage()) + } + + c.accessTokenMtx.Lock() + c.accessToken = resp.Data.Token + c.accessTokenExp = time.Unix(resp.Data.ExpiresAt, 0) + c.accessTokenMtx.Unlock() + + return nil +} + +func (c *Client) UpdateSSLCert(req *UpdateSSLCertRequest) (*UpdateSSLCertResponse, error) { + if c.accessToken == "" || c.accessTokenExp.Before(time.Now()) { + if err := c.getAccessToken(); err != nil { + return nil, err + } + } + + resp := &UpdateSSLCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/SSLCertService/updateSSLCert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/flexcdn/client.go b/internal/pkg/sdk3rd/flexcdn/client.go new file mode 100644 index 00000000..6b5626ee --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/client.go @@ -0,0 +1,103 @@ +package flexcdn + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + apiHost string + apiRole string + accessKeyId string + accessKey string + + accessToken string + accessTokenExp time.Time + accessTokenMtx sync.Mutex + + client *resty.Client +} + +func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client { + client := resty.New() + + return &Client{ + apiHost: strings.TrimRight(apiHost, "/"), + apiRole: apiRole, + accessKeyId: accessKeyId, + accessKey: accessKey, + 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(method string, path string, params interface{}) (*resty.Response, error) { + req := c.client.R().SetBasicAuth(c.accessKeyId, c.accessKey) + req.Method = method + req.URL = c.apiHost + path + if strings.EqualFold(method, http.MethodGet) { + qs := make(map[string]string) + if params != nil { + temp := make(map[string]any) + jsonb, _ := json.Marshal(params) + json.Unmarshal(jsonb, &temp) + for k, v := range temp { + if v != nil { + qs[k] = fmt.Sprintf("%v", v) + } + } + } + + req = req. + SetHeader("X-Cloud-Access-Token", c.accessToken). + SetQueryParams(qs) + } else { + req = req. + SetHeader("Content-Type", "application/json"). + SetHeader("X-Cloud-Access-Token", c.accessToken). + SetBody(params) + } + + resp, err := req.Send() + if err != nil { + return resp, fmt.Errorf("flexcdn api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("flexcdn api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error { + resp, err := c.sendRequest(method, path, params) + if err != nil { + if resp != nil { + json.Unmarshal(resp.Body(), &result) + } + return err + } + + if err := json.Unmarshal(resp.Body(), &result); err != nil { + return fmt.Errorf("flexcdn api error: failed to unmarshal response: %w", err) + } else if errcode := result.GetCode(); errcode != 200 { + return fmt.Errorf("flexcdn api error: code='%d', message='%s'", errcode, result.GetMessage()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/flexcdn/models.go b/internal/pkg/sdk3rd/flexcdn/models.go new file mode 100644 index 00000000..c976eccc --- /dev/null +++ b/internal/pkg/sdk3rd/flexcdn/models.go @@ -0,0 +1,52 @@ +package flexcdn + +type BaseResponse interface { + GetCode() int32 + GetMessage() string +} + +type baseResponse struct { + Code int32 `json:"code"` + Message string `json:"message"` +} + +func (r *baseResponse) GetCode() int32 { + return r.Code +} + +func (r *baseResponse) GetMessage() string { + return r.Message +} + +type getAPIAccessTokenRequest struct { + Type string `json:"type"` + AccessKeyId string `json:"accessKeyId"` + AccessKey string `json:"accessKey"` +} + +type getAPIAccessTokenResponse struct { + baseResponse + Data *struct { + Token string `json:"token"` + ExpiresAt int64 `json:"expiresAt"` + } `json:"data,omitempty"` +} + +type UpdateSSLCertRequest struct { + SSLCertId int64 `json:"sslCertId"` + IsOn bool `json:"isOn"` + Name string `json:"name"` + Description string `json:"description"` + ServerName string `json:"serverName"` + IsCA bool `json:"isCA"` + CertData string `json:"certData"` + KeyData string `json:"keyData"` + TimeBeginAt int64 `json:"timeBeginAt"` + TimeEndAt int64 `json:"timeEndAt"` + DNSNames []string `json:"dnsNames"` + CommonNames []string `json:"commonNames"` +} + +type UpdateSSLCertResponse struct { + baseResponse +} diff --git a/internal/pkg/sdk3rd/goedge/client.go b/internal/pkg/sdk3rd/goedge/client.go index e0a2ce49..39ad8900 100644 --- a/internal/pkg/sdk3rd/goedge/client.go +++ b/internal/pkg/sdk3rd/goedge/client.go @@ -65,8 +65,8 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } req = req. - SetQueryParams(qs). - SetHeader("X-Edge-Access-Token", c.accessToken) + SetHeader("X-Edge-Access-Token", c.accessToken). + SetQueryParams(qs) } else { req = req. SetHeader("Content-Type", "application/json"). diff --git a/internal/pkg/sdk3rd/upyun/console/client.go b/internal/pkg/sdk3rd/upyun/console/client.go index 7a7ea7de..6b560adc 100644 --- a/internal/pkg/sdk3rd/upyun/console/client.go +++ b/internal/pkg/sdk3rd/upyun/console/client.go @@ -52,8 +52,8 @@ func (c *Client) sendRequest(method string, path string, params interface{}) (*r } req = req. - SetQueryParams(qs). - SetHeader("Cookie", c.loginCookie) + SetHeader("Cookie", c.loginCookie). + SetQueryParams(qs) } else { req = req. SetHeader("Content-Type", "application/json"). diff --git a/ui/public/imgs/providers/flexcdn.png b/ui/public/imgs/providers/flexcdn.png new file mode 100644 index 0000000000000000000000000000000000000000..00805598cec426ed3c2d1809aeea328c7aea50fb GIT binary patch literal 13243 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 className, return ; case ACCESS_PROVIDERS.DYNV6: return ; + case ACCESS_PROVIDERS.EDGIO: + return ; + case ACCESS_PROVIDERS.EMAIL: + return ; + case ACCESS_PROVIDERS.FLEXCDN: + return ; case ACCESS_PROVIDERS.GCORE: return ; case ACCESS_PROVIDERS.GNAME: @@ -228,10 +235,6 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.GOOGLETRUSTSERVICES: return ; - case ACCESS_PROVIDERS.EDGIO: - return ; - case ACCESS_PROVIDERS.EMAIL: - return ; case ACCESS_PROVIDERS.HUAWEICLOUD: return ; case ACCESS_PROVIDERS.JDCLOUD: diff --git a/ui/src/components/access/AccessFormFlexCDNConfig.tsx b/ui/src/components/access/AccessFormFlexCDNConfig.tsx new file mode 100644 index 00000000..6ca020bf --- /dev/null +++ b/ui/src/components/access/AccessFormFlexCDNConfig.tsx @@ -0,0 +1,90 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Radio, Switch } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForFlexCDN } from "@/domain/access"; + +type AccessFormFlexCDNConfigFieldValues = Nullish; + +export type AccessFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormFlexCDNConfigFieldValues; + onValuesChange?: (values: AccessFormFlexCDNConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormFlexCDNConfigFieldValues => { + return { + apiUrl: "http://:8000/", + apiRole: "user", + accessKeyId: "", + accessKey: "", + }; +}; + +const AccessFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + apiUrl: z.string().url(t("common.errmsg.url_invalid")), + role: z.union([z.literal("user"), z.literal("admin")], { + message: t("access.form.flexcdn_api_role.placeholder"), + }), + accessKeyId: z.string().nonempty(t("access.form.flexcdn_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.flexcdn_access_key.placeholder")).trim(), + allowInsecureConnections: z.boolean().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + ({ label: t(`access.form.flexcdn_api_role.option.${s}.label`), value: s }))} /> + + + } + > + + + + } + > + + + + + + +
+ ); +}; + +export default AccessFormFlexCDNConfig; diff --git a/ui/src/components/access/AccessFormGoEdgeConfig.tsx b/ui/src/components/access/AccessFormGoEdgeConfig.tsx index ced9b09a..9c03f2be 100644 --- a/ui/src/components/access/AccessFormGoEdgeConfig.tsx +++ b/ui/src/components/access/AccessFormGoEdgeConfig.tsx @@ -32,16 +32,8 @@ const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialVal role: z.union([z.literal("user"), z.literal("admin")], { message: t("access.form.goedge_api_role.placeholder"), }), - accessKeyId: z - .string() - .min(1, t("access.form.goedge_access_key_id.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), - accessKey: z - .string() - .min(1, t("access.form.goedge_access_key.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + accessKeyId: z.string().nonempty(t("access.form.goedge_access_key_id.placeholder")).trim(), + accessKey: z.string().nonempty(t("access.form.goedge_access_key.placeholder")).trim(), allowInsecureConnections: z.boolean().nullish(), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index b77ff9cf..981d7183 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -46,6 +46,7 @@ import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlu import DeployNodeConfigFormCdnflyConfig from "./DeployNodeConfigFormCdnflyConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig"; +import DeployNodeConfigFormFlexCDNConfig from "./DeployNodeConfigFormFlexCDNConfig"; import DeployNodeConfigFormGcoreCDNConfig from "./DeployNodeConfigFormGcoreCDNConfig"; import DeployNodeConfigFormGoEdgeConfig from "./DeployNodeConfigFormGoEdgeConfig"; import DeployNodeConfigFormHuaweiCloudCDNConfig from "./DeployNodeConfigFormHuaweiCloudCDNConfig"; @@ -240,6 +241,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS: return ; + case DEPLOYMENT_PROVIDERS.FLEXCDN: + return ; case DEPLOYMENT_PROVIDERS.GCORE_CDN: return ; case DEPLOYMENT_PROVIDERS.GOEDGE: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx index 45662e75..4d6ae25c 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormCdnflyConfig.tsx @@ -79,13 +79,23 @@ const DeployNodeConfigFormCdnflyConfig = ({ form: formInst, formName, disabled, - + } + > - + } + > diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx new file mode 100644 index 00000000..e24652be --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormFlexCDNConfig.tsx @@ -0,0 +1,84 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import Show from "@/components/Show"; + +type DeployNodeConfigFormFlexCDNConfigFieldValues = Nullish<{ + resourceType: string; + certificateId?: string | number; +}>; + +export type DeployNodeConfigFormFlexCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormFlexCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormFlexCDNConfigFieldValues) => void; +}; + +const RESOURCE_TYPE_CERTIFICATE = "certificate" as const; + +const initFormModel = (): DeployNodeConfigFormFlexCDNConfigFieldValues => { + return { + resourceType: RESOURCE_TYPE_CERTIFICATE, + certificateId: "", + }; +}; + +const DeployNodeConfigFormFlexCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormFlexCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, { + message: t("workflow_node.deploy.form.flexcdn_resource_type.placeholder"), + }), + certificateId: z + .union([z.string(), z.number().int()]) + .nullish() + .refine((v) => { + if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true; + return /^\d+$/.test(v + "") && +v! > 0; + }, t("workflow_node.deploy.form.flexcdn_certificate_id.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceType = Form.useWatch("resourceType", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + } + > + + + +
+ ); +}; + +export default DeployNodeConfigFormFlexCDNConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx index 89dffb5f..5e0f7f85 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormGoEdgeConfig.tsx @@ -68,7 +68,12 @@ const DeployNodeConfigFormGoEdgeConfig = ({ form: formInst, formName, disabled, - + } + > diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index c676b5f7..48882eb7 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -29,6 +29,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForDynv6 | AccessConfigForEdgio | AccessConfigForEmail + | AccessConfigForFlexCDN | AccessConfigForGcore | AccessConfigForGname | AccessConfigForGoDaddy @@ -194,6 +195,14 @@ export type AccessConfigForEmail = { defaultReceiverAddress?: string; }; +export type AccessConfigForFlexCDN = { + apiUrl: string; + apiRole: string; + accessKeyId: string; + accessKey: string; + allowInsecureConnections?: boolean; +}; + export type AccessConfigForGcore = { apiToken: string; }; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 2c6f0f0f..4cad8606 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -28,6 +28,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ DYNV6: "dynv6", EDGIO: "edgio", EMAIL: "email", + FLEXCDN: "flexcdn", GCORE: "gcore", GNAME: "gname", GODADDY: "godaddy", @@ -126,6 +127,7 @@ export const accessProvidersMap: Maphttps://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "Please enter FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "For more information, see https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "Insecure SSL/TLS connections", + "access.form.flexcdn_allow_insecure_conns.switch.on": "Allow", + "access.form.flexcdn_allow_insecure_conns.switch.off": "Disallow", "access.form.gcore_api_token.label": "Gcore API token", "access.form.gcore_api_token.placeholder": "Please enter Gcore API token", "access.form.gcore_api_token.tooltip": "For more information, see https://api.gcore.com/docs/iam#section/Authentication", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index dad0ae6f..337820a3 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -63,6 +63,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "Email", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - CDN (Content Delivery Network)", "provider.gname": "GNAME", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 006979f1..a998c26c 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -347,14 +347,22 @@ "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly site ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "Please enter Cdnfly site ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly certificate ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "Please enter Cdnfly certificate ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "You can find it on Cdnfly WebUI.", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "Doge Cloud CDN domain", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "Please enter Doge Cloud CDN domain name", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "For more information, see https://console.dogecloud.com/", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "Please enter Edgio Applications environment ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "For more information, see https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "Resource type", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "Please select resource type", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "Certificate", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "Please enter FlexCDN certificate ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "You can find it on FlexCDN WebUI.", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "Please enter Gcore CDN resource ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "For more information, see https://cdn.gcore.com/resources/list", @@ -366,6 +374,7 @@ "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "Certificate", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge certificate ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "Please enter GoEdge certificate ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "You can find it on GoEdge WebUI.", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "Huawei Cloud CDN region", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "Please enter Huawei Cloud CDN region (e.g. cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 1ca78d07..649d44f5 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -187,6 +187,21 @@ "access.form.email_default_sender_address.placeholder": "请输入默认的发送邮箱地址", "access.form.email_default_receiver_address.label": "默认的接收邮箱地址(可选)", "access.form.email_default_receiver_address.placeholder": "请输入默认的接收邮箱地址", + "access.form.flexcdn_api_url.label": "FlexCDN API URL", + "access.form.flexcdn_api_url.placeholder": "请输入 FlexCDN API URL", + "access.form.flexcdn_api_role.label": "FlexCDN 用户角色", + "access.form.flexcdn_api_role.placeholder": "请选择 FlexCDN 用户角色", + "access.form.flexcdn_api_role.option.user.label": "平台用户", + "access.form.flexcdn_api_role.option.admin.label": "系统管理员", + "access.form.flexcdn_access_key_id.label": "FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.placeholder": "请输入 FlexCDN AccessKeyId", + "access.form.flexcdn_access_key_id.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_access_key.label": "FlexCDN AccessKey", + "access.form.flexcdn_access_key.placeholder": "请输入 FlexCDN AccessKey", + "access.form.flexcdn_access_key.tooltip": "这是什么?请参阅 https://flexcdn.cn/docs/api/auth", + "access.form.flexcdn_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误", + "access.form.flexcdn_allow_insecure_conns.switch.on": "允许", + "access.form.flexcdn_allow_insecure_conns.switch.off": "不允许", "access.form.gcore_api_token.label": "Gcore API Token", "access.form.gcore_api_token.placeholder": "请输入 Gcore API Token", "access.form.gcore_api_token.tooltip": "这是什么?请参阅 https://api.gcore.com/docs/iam#section/Authentication", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e7c6ea31..27abe56c 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -63,6 +63,7 @@ "provider.edgio.applications": "Edgio - Applications", "provider.email": "邮件", "provider.fastly": "Fastly", + "provider.flexcdn": "FlexCDN", "provider.gcore": "Gcore", "provider.gcore.cdn": "Gcore - 内容分发网络 CDN", "provider.gname": "GNAME", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index acc5657f..e462f34a 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -346,14 +346,22 @@ "workflow_node.deploy.form.cdnfly_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.cdnfly_site_id.label": "Cdnfly 网站 ID", "workflow_node.deploy.form.cdnfly_site_id.placeholder": "请输入 Cdnfly 网站 ID", + "workflow_node.deploy.form.cdnfly_site_id.tooltip": "请登录 Cdnfly 管理平台查看。", "workflow_node.deploy.form.cdnfly_certificate_id.label": "Cdnfly 证书 ID", "workflow_node.deploy.form.cdnfly_certificate_id.placeholder": "请输入 Cdnfly 证书 ID", + "workflow_node.deploy.form.cdnfly_certificate_id.tooltip": "请登录 Cdnfly 管理平台查看。", "workflow_node.deploy.form.dogecloud_cdn_domain.label": "多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.placeholder": "请输入多吉云 CDN 加速域名", "workflow_node.deploy.form.dogecloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.dogecloud.com", "workflow_node.deploy.form.edgio_applications_environment_id.label": "Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.placeholder": "请输入 Edgio Applications 环境 ID", "workflow_node.deploy.form.edgio_applications_environment_id.tooltip": "这是什么?请参阅 https://edgio.app/", + "workflow_node.deploy.form.flexcdn_resource_type.label": "证书替换方式", + "workflow_node.deploy.form.flexcdn_resource_type.placeholder": "请选择证书替换方式", + "workflow_node.deploy.form.flexcdn_resource_type.option.certificate.label": "替换指定证书", + "workflow_node.deploy.form.flexcdn_certificate_id.label": "FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.placeholder": "请输入 FlexCDN 证书 ID", + "workflow_node.deploy.form.flexcdn_certificate_id.tooltip": "请登录 FlexCDN 管理平台查看。", "workflow_node.deploy.form.gcore_cdn_resource_id.label": "Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.placeholder": "请输入 Gcore CDN 资源 ID", "workflow_node.deploy.form.gcore_cdn_resource_id.tooltip": "这是什么?请参阅 https://cdn.gcore.com/resources/list", @@ -365,6 +373,7 @@ "workflow_node.deploy.form.goedge_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.goedge_certificate_id.label": "GoEdge 证书 ID", "workflow_node.deploy.form.goedge_certificate_id.placeholder": "请输入 GoEdge 证书 ID", + "workflow_node.deploy.form.goedge_certificate_id.tooltip": "请登录 GoEdge 管理平台查看。", "workflow_node.deploy.form.huaweicloud_cdn_region.label": "华为云 CDN 服务区域", "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云 CDN 服务区域(例如:cn-north-1)", "workflow_node.deploy.form.huaweicloud_cdn_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint",