From 533996352452ad0559a43da23468c73c54c3b7af Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 26 May 2025 23:02:57 +0800 Subject: [PATCH 1/3] feat(ui): improve i18n --- .../workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx | 6 +++++- .../workflow/node/DeployNodeConfigFormUpyunFileConfig.tsx | 6 +++++- ui/src/i18n/locales/en/nls.workflow.nodes.json | 2 ++ ui/src/i18n/locales/zh/nls.workflow.nodes.json | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx index e09f5266..c18d570c 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Alert, Form, type FormInstance, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -44,6 +44,10 @@ const DeployNodeConfigFormUpyunCDNConfig = ({ form: formInst, formName, disabled name={formName} onValuesChange={handleFormChange} > + + } /> + + + + } /> + + https://console.ucloud-global.com/ufile", + "workflow_node.deploy.form.upyun_cdn.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "For more information, see https://console.upyun.com/services/cdn/", + "workflow_node.deploy.form.upyun_file.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", "workflow_node.deploy.form.upyun_file_domain.label": "UPYUN bucket domain", "workflow_node.deploy.form.upyun_file_domain.placeholder": "Please enter UPYUN bucket domain name", "workflow_node.deploy.form.upyun_file_domain.tooltip": "For more information, see https://console.upyun.com/services/file/", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 87f0076b..6f13984e 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -695,9 +695,11 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", + "workflow_node.deploy.form.upyun_cdn.guide": "小贴士:由于又拍云未提供相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/cdn/", + "workflow_node.deploy.form.upyun_file.guide": "小贴士:由于又拍云未提供相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.upyun_file_domain.label": "又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.placeholder": "请输入又拍云云存储加速域名", "workflow_node.deploy.form.upyun_file_domain.tooltip": "这是什么?请参阅 https://console.upyun.com/services/file/", From cfdd3c621fe87e35d1358e1b870f95d117c77e35 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 27 May 2025 04:29:51 +0800 Subject: [PATCH 2/3] feat: new deployment provider: unicloud webhost --- internal/deployer/providers.go | 18 ++ internal/domain/access.go | 5 + internal/domain/provider.go | 2 + .../unicloud-webhost/unicloud_webhost.go | 101 +++++++ .../unicloud-webhost/unicloud_webhost_test.go | 85 ++++++ internal/pkg/sdk3rd/dcloud/unicloud/api.go | 78 ++++++ internal/pkg/sdk3rd/dcloud/unicloud/client.go | 257 ++++++++++++++++++ internal/pkg/sdk3rd/dcloud/unicloud/models.go | 103 +++++++ ui/public/imgs/providers/unicloud.png | Bin 0 -> 17955 bytes ui/src/components/access/AccessForm.tsx | 7 +- .../access/AccessFormUniCloudConfig.tsx | 68 +++++ .../access/AccessFormUpyunConfig.tsx | 4 +- .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...loyNodeConfigFormUniCloudWebHostConfig.tsx | 85 ++++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 26 +- ui/src/i18n/locales/en/nls.access.json | 16 +- ui/src/i18n/locales/en/nls.provider.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 10 + ui/src/i18n/locales/zh/nls.access.json | 4 + ui/src/i18n/locales/zh/nls.provider.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 10 + 22 files changed, 871 insertions(+), 21 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost.go create mode 100644 internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost_test.go create mode 100644 internal/pkg/sdk3rd/dcloud/unicloud/api.go create mode 100644 internal/pkg/sdk3rd/dcloud/unicloud/client.go create mode 100644 internal/pkg/sdk3rd/dcloud/unicloud/models.go create mode 100644 ui/public/imgs/providers/unicloud.png create mode 100644 ui/src/components/access/AccessFormUniCloudConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormUniCloudWebHostConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index e8dbaf1a..7f9bae91 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -79,6 +79,7 @@ import ( pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf" pUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" pUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" + pUniCloudWebHost "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/unicloud-webhost" pUpyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/upyun-cdn" pVolcEngineALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-alb" pVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" @@ -1144,6 +1145,23 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } + case domain.DeploymentProviderTypeUniCloudWebHost: + { + access := domain.AccessConfigForUniCloud{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + deployer, err := pUniCloudWebHost.NewDeployer(&pUniCloudWebHost.DeployerConfig{ + Username: access.Username, + Password: access.Password, + SpaceProvider: maputil.GetString(options.ProviderServiceConfig, "spaceProvider"), + SpaceId: maputil.GetString(options.ProviderServiceConfig, "spaceId"), + Domain: maputil.GetString(options.ProviderServiceConfig, "domain"), + }) + return deployer, err + } + case domain.DeploymentProviderTypeUpyunCDN, domain.DeploymentProviderTypeUpyunFile: { access := domain.AccessConfigForUpyun{} diff --git a/internal/domain/access.go b/internal/domain/access.go index 482f753a..e31bb1a0 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -343,6 +343,11 @@ type AccessConfigForUCloud struct { ProjectId string `json:"projectId,omitempty"` } +type AccessConfigForUniCloud struct { + Username string `json:"username"` + Password string `json:"password"` +} + type AccessConfigForUpyun struct { Username string `json:"username"` Password string `json:"password"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index c8cb37d5..55f8b2af 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -77,6 +77,7 @@ const ( AccessProviderTypeTelegramBot = AccessProviderType("telegrambot") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") + AccessProviderTypeUniCloud = AccessProviderType("unicloud") AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeVercel = AccessProviderType("vercel") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") @@ -244,6 +245,7 @@ const ( DeploymentProviderTypeTencentCloudWAF = DeploymentProviderType(AccessProviderTypeTencentCloud + "-waf") DeploymentProviderTypeUCloudUCDN = DeploymentProviderType(AccessProviderTypeUCloud + "-ucdn") DeploymentProviderTypeUCloudUS3 = DeploymentProviderType(AccessProviderTypeUCloud + "-us3") + DeploymentProviderTypeUniCloudWebHost = DeploymentProviderType(AccessProviderTypeUniCloud + "-webhost") DeploymentProviderTypeUpyunCDN = DeploymentProviderType(AccessProviderTypeUpyun + "-cdn") DeploymentProviderTypeUpyunFile = DeploymentProviderType(AccessProviderTypeUpyun + "-file") DeploymentProviderTypeVolcEngineALB = DeploymentProviderType(AccessProviderTypeVolcEngine + "-alb") diff --git a/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost.go b/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost.go new file mode 100644 index 00000000..e24708bd --- /dev/null +++ b/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost.go @@ -0,0 +1,101 @@ +package unicloudwebhost + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net/url" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + unisdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/dcloud/unicloud" +) + +type DeployerConfig struct { + // uniCloud 控制台账号。 + Username string `json:"username"` + // uniCloud 控制台密码。 + Password string `json:"password"` + // 服务空间提供商。 + // 可取值 "aliyun"、"tencent"。 + SpaceProvider string `json:"spaceProvider"` + // 服务空间 ID。 + SpaceId string `json:"spaceId"` + // 托管网站域名(不支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *unisdk.Client +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + client, err := createSdkClient(config.Username, config.Password) + 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.SpaceProvider == "" { + return nil, errors.New("config `spaceProvider` is required") + } + if d.config.SpaceId == "" { + return nil, errors.New("config `spaceId` is required") + } + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // 变更网站证书 + createDomainWithCertReq := &unisdk.CreateDomainWithCertRequest{ + Provider: d.config.SpaceProvider, + SpaceId: d.config.SpaceId, + Domain: d.config.Domain, + Cert: url.QueryEscape(certPEM), + Key: url.QueryEscape(privkeyPEM), + } + createDomainWithCertResp, err := d.sdkClient.CreateDomainWithCert(createDomainWithCertReq) + d.logger.Debug("sdk request 'unicloud.host.CreateDomainWithCert'", slog.Any("request", createDomainWithCertReq), slog.Any("response", createDomainWithCertResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'unicloud.host.CreateDomainWithCert': %w", err) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(username, password string) (*unisdk.Client, error) { + if username == "" { + return nil, errors.New("invalid unicloud username") + } + + if password == "" { + return nil, errors.New("invalid unicloud password") + } + + client := unisdk.NewClient(username, password) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost_test.go b/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost_test.go new file mode 100644 index 00000000..1e47ba24 --- /dev/null +++ b/internal/pkg/core/deployer/providers/unicloud-webhost/unicloud_webhost_test.go @@ -0,0 +1,85 @@ +package unicloudwebhost_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/unicloud-webhost" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string + fSpaceProvider string + fSpaceId string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fSpaceProvider, argsPrefix+"SPACEPROVIDER", "", "") + flag.StringVar(&fSpaceId, argsPrefix+"SPACEID", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./unicloud_webhost_test.go -args \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_USERNAME="your-username" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_PASSWORD="your-password" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_SPACEPROVIDER="aliyun/tencent" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_SPACEID="your-space-id" \ + --CERTIMATE_DEPLOYER_UNICLOUDWEBHOST_DOMAIN="example.com" +*/ +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("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("SPACEPROVIDER: %v", fSpaceProvider), + fmt.Sprintf("SPACEID: %v", fSpaceId), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + Username: fUsername, + Password: fPassword, + SpaceProvider: fSpaceProvider, + SpaceId: fSpaceId, + Domain: fDomain, + }) + 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/dcloud/unicloud/api.go b/internal/pkg/sdk3rd/dcloud/unicloud/api.go new file mode 100644 index 00000000..1cd90b15 --- /dev/null +++ b/internal/pkg/sdk3rd/dcloud/unicloud/api.go @@ -0,0 +1,78 @@ +package unicloud + +import ( + "fmt" + "net/http" + "regexp" + "time" +) + +func (c *Client) ensureServerlessJwtTokenExists() error { + c.serverlessJwtTokenMtx.Lock() + defer c.serverlessJwtTokenMtx.Unlock() + if c.serverlessJwtToken != "" && c.serverlessJwtTokenExp.After(time.Now()) { + return nil + } + + params := &loginParams{ + Password: c.password, + } + if regexp.MustCompile("^1\\d{10}$").MatchString(c.username) { + params.Mobile = c.username + } else if regexp.MustCompile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$").MatchString(c.username) { + params.Email = c.username + } else { + params.Username = c.username + } + + resp := &loginResponse{} + if err := c.invokeServerlessWithResult( + uniIdentityEndpoint, uniIdentityClientSecret, uniIdentityAppId, uniIdentitySpaceId, + "uni-id-co", "login", "", params, nil, + resp); err != nil { + return err + } else if resp.Data == nil || resp.Data.NewToken == nil || resp.Data.NewToken.Token == "" { + return fmt.Errorf("unicloud api error: received empty token") + } + + c.serverlessJwtToken = resp.Data.NewToken.Token + c.serverlessJwtTokenExp = time.UnixMilli(resp.Data.NewToken.TokenExpired) + + return nil +} + +func (c *Client) ensureApiUserTokenExists() error { + if err := c.ensureServerlessJwtTokenExists(); err != nil { + return err + } + + c.apiUserTokenMtx.Lock() + defer c.apiUserTokenMtx.Unlock() + if c.apiUserToken != "" { + return nil + } + + resp := &getUserTokenResponse{} + if err := c.invokeServerlessWithResult( + uniConsoleEndpoint, uniConsoleClientSecret, uniConsoleAppId, uniConsoleSpaceId, + "uni-cloud-kernel", "", "user/getUserToken", nil, map[string]any{"isLogin": true}, + resp); err != nil { + return err + } else if resp.Data == nil || resp.Data.Data == nil || resp.Data.Data.Data == nil || resp.Data.Data.Data.Token == "" { + return fmt.Errorf("unicloud api error: received empty user token") + } + + c.apiUserToken = resp.Data.Data.Data.Token + + return nil +} + +func (c *Client) CreateDomainWithCert(req *CreateDomainWithCertRequest) (*CreateDomainWithCertResponse, error) { + if err := c.ensureApiUserTokenExists(); err != nil { + return nil, err + } + + resp := &CreateDomainWithCertResponse{} + err := c.sendRequestWithResult(http.MethodPost, "/host/create-domain-with-cert", req, resp) + return resp, err +} diff --git a/internal/pkg/sdk3rd/dcloud/unicloud/client.go b/internal/pkg/sdk3rd/dcloud/unicloud/client.go new file mode 100644 index 00000000..1e0f3728 --- /dev/null +++ b/internal/pkg/sdk3rd/dcloud/unicloud/client.go @@ -0,0 +1,257 @@ +package unicloud + +import ( + "crypto/hmac" + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "runtime" + "sort" + "strings" + "sync" + "time" + + "github.com/go-resty/resty/v2" +) + +type Client struct { + username string + password string + + serverlessJwtToken string + serverlessJwtTokenExp time.Time + serverlessJwtTokenMtx sync.Mutex + + serverlessClient *resty.Client + + apiUserToken string + apiUserTokenMtx sync.Mutex + + apiClient *resty.Client +} + +const ( + uniIdentityEndpoint = "https://account.dcloud.net.cn/client" + uniIdentityClientSecret = "ba461799-fde8-429f-8cc4-4b6d306e2339" + uniIdentityAppId = "__UNI__uniid_server" + uniIdentitySpaceId = "uni-id-server" + uniConsoleEndpoint = "https://unicloud.dcloud.net.cn/client" + uniConsoleClientSecret = "4c1f7fbf-c732-42b0-ab10-4634a8bbe834" + uniConsoleAppId = "__UNI__unicloud_console" + uniConsoleSpaceId = "dc-6nfabcn6ada8d3dd" +) + +func NewClient(username, password string) *Client { + client := &Client{ + username: username, + password: password, + } + client.serverlessClient = resty.New() + client.apiClient = resty.New(). + SetBaseURL("https://unicloud-api.dcloud.net.cn/unicloud/api"). + SetPreRequestHook(func(c *resty.Client, req *http.Request) error { + if client.apiUserToken != "" { + req.Header.Set("Token", client.apiUserToken) + } + + return nil + }) + + return client +} + +func (c *Client) WithTimeout(timeout time.Duration) *Client { + c.serverlessClient.SetTimeout(timeout) + return c +} + +func (c *Client) generateSignature(params map[string]any, secret string) string { + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + canonicalStr := "" + for i, k := range keys { + if i > 0 { + canonicalStr += "&" + } + canonicalStr += k + "=" + fmt.Sprintf("%v", params[k]) + } + + mac := hmac.New(md5.New, []byte(secret)) + mac.Write([]byte(canonicalStr)) + sign := mac.Sum(nil) + signHex := hex.EncodeToString(sign) + + return signHex +} + +func (c *Client) buildServerlessClientInfo(appId string) (_clientInfo map[string]any, _err error) { + return map[string]any{ + "PLATFORM": "web", + "OS": strings.ToUpper(runtime.GOOS), + "APPID": appId, + "DEVICEID": "certimate", + "LOCALE": "zh-Hans", + "osName": runtime.GOOS, + "appId": appId, + "appName": "uniCloud", + "deviceId": "certimate", + "deviceType": "pc", + "uniPlatform": "web", + "uniCompilerVersion": "4.45", + "uniRuntimeVersion": "4.45", + }, nil +} + +func (c *Client) buildServerlessPayloadInfo(appId, spaceId, target, method, action string, params, data interface{}) (map[string]any, error) { + clientInfo, err := c.buildServerlessClientInfo(appId) + if err != nil { + return nil, err + } + + functionArgsParams := make([]any, 0) + if params != nil { + functionArgsParams = append(functionArgsParams, params) + } + + functionArgs := map[string]any{ + "clientInfo": clientInfo, + "uniIdToken": c.serverlessJwtToken, + } + if method != "" { + functionArgs["method"] = method + functionArgs["params"] = make([]any, 0) + } + if action != "" { + type _obj struct{} + functionArgs["action"] = action + functionArgs["data"] = &_obj{} + } + if params != nil { + functionArgs["params"] = []any{params} + } + if data != nil { + functionArgs["data"] = data + } + + jsonb, err := json.Marshal(map[string]any{ + "functionTarget": target, + "functionArgs": functionArgs, + }) + if err != nil { + return nil, err + } + + payload := map[string]any{ + "method": "serverless.function.runtime.invoke", + "params": string(jsonb), + "spaceId": spaceId, + "timestamp": time.Now().UnixMilli(), + } + + return payload, nil +} + +func (c *Client) invokeServerless(endpoint, clientSecret, appId, spaceId, target, method, action string, params, data interface{}) (*resty.Response, error) { + if endpoint == "" { + return nil, fmt.Errorf("unicloud api error: endpoint cannot be empty") + } + + payload, err := c.buildServerlessPayloadInfo(appId, spaceId, target, method, action, params, data) + if err != nil { + return nil, fmt.Errorf("unicloud api error: failed to build request: %w", err) + } + + clientInfo, _ := c.buildServerlessClientInfo(appId) + clientInfoJsonb, _ := json.Marshal(clientInfo) + + sign := c.generateSignature(payload, clientSecret) + + req := c.serverlessClient.R(). + SetHeader("Origin", "https://unicloud.dcloud.net.cn"). + SetHeader("Referer", "https://unicloud.dcloud.net.cn"). + SetHeader("Content-Type", "application/json"). + SetHeader("X-Client-Info", string(clientInfoJsonb)). + SetHeader("X-Client-Token", c.serverlessJwtToken). + SetHeader("X-Serverless-Sign", sign). + SetBody(payload) + resp, err := req.Post(endpoint) + if err != nil { + return resp, fmt.Errorf("unicloud api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("unicloud api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return resp, nil +} + +func (c *Client) invokeServerlessWithResult(endpoint, clientSecret, appId, spaceId, target, method, action string, params, data interface{}, result BaseResponse) error { + resp, err := c.invokeServerless(endpoint, clientSecret, appId, spaceId, target, method, action, params, data) + 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("unicloud api error: failed to unmarshal response: %w", err) + } else if success := result.GetSuccess(); !success { + return fmt.Errorf("unicloud api error: code='%s', message='%s'", result.GetErrorCode(), result.GetErrorMessage()) + } + + return nil +} + +func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) { + req := c.apiClient.R() + 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.SetQueryParams(qs) + } else { + req = req.SetHeader("Content-Type", "application/json").SetBody(params) + } + + resp, err := req.Execute(method, path) + if err != nil { + return resp, fmt.Errorf("unicloud api error: failed to send request: %w", err) + } else if resp.IsError() { + return resp, fmt.Errorf("unicloud 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("unicloud api error: failed to unmarshal response: %w", err) + } else if retcode := result.GetReturnCode(); retcode != 0 { + return fmt.Errorf("unicloud api error: ret='%d', desc='%s'", retcode, result.GetReturnDesc()) + } + + return nil +} diff --git a/internal/pkg/sdk3rd/dcloud/unicloud/models.go b/internal/pkg/sdk3rd/dcloud/unicloud/models.go new file mode 100644 index 00000000..05b02db6 --- /dev/null +++ b/internal/pkg/sdk3rd/dcloud/unicloud/models.go @@ -0,0 +1,103 @@ +package unicloud + +type BaseResponse interface { + GetSuccess() bool + GetErrorCode() string + GetErrorMessage() string + + GetReturnCode() int32 + GetReturnDesc() string +} + +type baseResponse struct { + Success *bool `json:"success,omitempty"` + Header *map[string]string `json:"header,omitempty"` + Error *struct { + Code string `json:"code"` + Message string `json:"message"` + } `json:"error,omitempty"` + + ReturnCode *int32 `json:"ret,omitempty"` + ReturnDesc *string `json:"desc,omitempty"` +} + +func (r *baseResponse) GetReturnCode() int32 { + if r.ReturnCode != nil { + return *r.ReturnCode + } + return 0 +} + +func (r *baseResponse) GetReturnDesc() string { + if r.ReturnDesc != nil { + return *r.ReturnDesc + } + return "" +} + +func (r *baseResponse) GetSuccess() bool { + if r.Success != nil { + return *r.Success + } + return false +} + +func (r *baseResponse) GetErrorCode() string { + if r.Error != nil { + return r.Error.Code + } + return "" +} + +func (r *baseResponse) GetErrorMessage() string { + if r.Error != nil { + return r.Error.Message + } + return "" +} + +type loginParams struct { + Email string `json:"email,omitempty"` + Mobile string `json:"mobile,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password"` +} + +type loginResponse struct { + baseResponse + Data *struct { + Code int32 `json:"errCode"` + UID string `json:"uid"` + NewToken *struct { + Token string `json:"token"` + TokenExpired int64 `json:"tokenExpired"` + } `json:"newToken,omitempty"` + } `json:"data,omitempty"` +} + +type getUserTokenResponse struct { + baseResponse + Data *struct { + Code int32 `json:"code"` + Data *struct { + Result int32 `json:"ret"` + Description string `json:"desc"` + Data *struct { + Email string `json:"email"` + Token string `json:"token"` + } `json:"data,omitempty"` + } `json:"data,omitempty"` + } `json:"data,omitempty"` +} + +type CreateDomainWithCertRequest struct { + Provider string `json:"provider"` + SpaceId string `json:"spaceId"` + Domain string `json:"domain"` + Cert string `json:"cert"` + Key string `json:"key"` +} + +type CreateDomainWithCertResponse struct { + baseResponse +} diff --git a/ui/public/imgs/providers/unicloud.png b/ui/public/imgs/providers/unicloud.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b0ca9b306c72ea9ffa6335616358ddb21a78bf GIT binary patch literal 17955 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 className, const formSchema = z.object({ name: z .string({ message: t("access.form.name.placeholder") }) + .trim() .min(1, t("access.form.name.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + .max(64, t("common.errmsg.string_max", { max: 64 })), provider: z.nativeEnum(ACCESS_PROVIDERS, { message: usage === "ca-only" @@ -302,6 +303,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.UCLOUD: return ; + case ACCESS_PROVIDERS.UNICLOUD: + return ; case ACCESS_PROVIDERS.UPYUN: return ; case ACCESS_PROVIDERS.VERCEL: diff --git a/ui/src/components/access/AccessFormUniCloudConfig.tsx b/ui/src/components/access/AccessFormUniCloudConfig.tsx new file mode 100644 index 00000000..d281f1fe --- /dev/null +++ b/ui/src/components/access/AccessFormUniCloudConfig.tsx @@ -0,0 +1,68 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForUniCloud } from "@/domain/access"; + +type AccessFormUniCloudConfigFieldValues = Nullish; + +export type AccessFormUniCloudConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormUniCloudConfigFieldValues; + onValuesChange?: (values: AccessFormUniCloudConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormUniCloudConfigFieldValues => { + return { + username: "", + password: "", + }; +}; + +const AccessFormUniCloudConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormUniCloudConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + username: z.string().trim().nonempty(t("access.form.unicloud_username.placeholder")), + password: z.string().trim().nonempty(t("access.form.unicloud_password.placeholder")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormUniCloudConfig; diff --git a/ui/src/components/access/AccessFormUpyunConfig.tsx b/ui/src/components/access/AccessFormUpyunConfig.tsx index 8cc06d97..665c50cf 100644 --- a/ui/src/components/access/AccessFormUpyunConfig.tsx +++ b/ui/src/components/access/AccessFormUpyunConfig.tsx @@ -33,9 +33,9 @@ const AccessFormUpyunConfig = ({ form: formInst, formName, disabled, initialValu .max(64, t("common.errmsg.string_max", { max: 64 })), password: z .string() + .trim() .min(1, t("access.form.upyun_password.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + .max(64, t("common.errmsg.string_max", { max: 64 })), }); const formRule = createSchemaFieldRule(formSchema); diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 49ed12ec..0443327e 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -82,6 +82,7 @@ import DeployNodeConfigFormTencentCloudVODConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudWAFConfig from "./DeployNodeConfigFormTencentCloudWAFConfig"; import DeployNodeConfigFormUCloudUCDNConfig from "./DeployNodeConfigFormUCloudUCDNConfig.tsx"; import DeployNodeConfigFormUCloudUS3Config from "./DeployNodeConfigFormUCloudUS3Config.tsx"; +import DeployNodeConfigFormUniCloudWebHostConfig from "./DeployNodeConfigFormUniCloudWebHostConfig.tsx"; import DeployNodeConfigFormUpyunCDNConfig from "./DeployNodeConfigFormUpyunCDNConfig.tsx"; import DeployNodeConfigFormUpyunFileConfig from "./DeployNodeConfigFormUpyunFileConfig.tsx"; import DeployNodeConfigFormVolcEngineALBConfig from "./DeployNodeConfigFormVolcEngineALBConfig.tsx"; @@ -318,6 +319,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.UCLOUD_US3: return ; + case DEPLOYMENT_PROVIDERS.UNICLOUD_WEBHOST: + return ; case DEPLOYMENT_PROVIDERS.UPYUN_CDN: return ; case DEPLOYMENT_PROVIDERS.UPYUN_FILE: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUniCloudWebHostConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUniCloudWebHostConfig.tsx new file mode 100644 index 00000000..b16a7a39 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUniCloudWebHostConfig.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input, Select } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormUniCloudWebHostConfigFieldValues = Nullish<{ + spaceProvider: string; + spaceId: string; + domain: string; +}>; + +export type DeployNodeConfigFormUniCloudWebHostConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUniCloudWebHostConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUniCloudWebHostConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUniCloudWebHostConfigFieldValues => { + return { + spaceProvider: "tencent", + spaceId: "", + domain: "", + }; +}; + +const DeployNodeConfigFormUniCloudWebHostConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormUniCloudWebHostConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + spaceProvider: z.string().trim().nonempty(t("workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder")), + spaceId: z.string().trim().nonempty(t("workflow_node.deploy.form.unicloud_webhost_space_id.placeholder")), + domain: z.string().refine((v) => validDomainName(v), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ + + + + + + +
+ ); +}; + +export default DeployNodeConfigFormUniCloudWebHostConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 3582b071..69979aac 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -64,6 +64,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForTelegramBot | AccessConfigForTencentCloud | AccessConfigForUCloud + | AccessConfigForUniCloud | AccessConfigForUpyun | AccessConfigForVercel | AccessConfigForVolcEngine @@ -397,6 +398,11 @@ export type AccessConfigForUCloud = { projectId?: string; }; +export type AccessConfigForUniCloud = { + username: string; + password: string; +}; + export type AccessConfigForUpyun = { username: string; password: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 2d288a3c..594674f1 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -67,6 +67,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ TELEGRAMBOT: "telegrambot", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", + UNICLOUD: "unicloud", UPYUN: "upyun", VERCEL: "vercel", VOLCENGINE: "volcengine", @@ -127,17 +128,18 @@ export const accessProvidersMap: Map [ type, diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 98717976..bf453f1d 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -96,12 +96,6 @@ "access.form.bunny_api_key.label": "Bunny API key", "access.form.bunny_api_key.placeholder": "Please enter Bunny API key", "access.form.bunny_api_key.tooltip": "For more information, see https://docs.bunny.net/reference/bunnynet-api-overview", - "access.form.upyun_username.label": "UPYUN subaccount username", - "access.form.upyun_username.placeholder": "Please enter UPYUN subaccount username", - "access.form.upyun_username.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", - "access.form.upyun_password.label": "UPYUN subaccount password", - "access.form.upyun_password.placeholder": "Please enter UPYUN subaccount password", - "access.form.upyun_password.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", "access.form.baishan_api_token.label": "Baishan Cloud API token", "access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token", "access.form.baotapanel_server_url.label": "aaPanel server URL", @@ -413,6 +407,16 @@ "access.form.ucloud_project_id.label": "UCloud project ID (Optional)", "access.form.ucloud_project_id.placeholder": "Please enter UCloud project ID", "access.form.ucloud_project_id.tooltip": "For more information, see https://console.ucloud-global.com/uaccount/iam/project_manage", + "access.form.unicloud_username.label": "uniCloud username", + "access.form.unicloud_username.placeholder": "Please enter uniCloud username", + "access.form.unicloud_password.label": "uniCloud password", + "access.form.unicloud_password.placeholder": "Please enter uniCloud password", + "access.form.upyun_username.label": "UPYUN subaccount username", + "access.form.upyun_username.placeholder": "Please enter UPYUN subaccount username", + "access.form.upyun_username.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", + "access.form.upyun_password.label": "UPYUN subaccount password", + "access.form.upyun_password.placeholder": "Please enter UPYUN subaccount password", + "access.form.upyun_password.tooltip": "For more information, see https://console.upyun.com/account/subaccount/", "access.form.vercel_api_access_token.label": "Vercel API access token", "access.form.vercel_api_access_token.placeholder": "Please enter Vercel API access token", "access.form.vercel_api_access_token.tooltip": "For more information, see https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 85966786..9e59d9d0 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -138,6 +138,8 @@ "provider.ucloud": "UCloud", "provider.ucloud.ucdn": "UCloud - UCDN (UCloud Content Delivery Network)", "provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)", + "provider.unicloud": "uniCloud (DCloud)", + "provider.unicloud.webhost": "uniCloud (DCloud) - Web Host", "provider.upyun": "UPYUN", "provider.upyun.cdn": "UPYUN - CDN (Content Delivery Network)", "provider.upyun.file": "UPYUN - USS (Storage Service)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 02e7fe87..6de6eda8 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -696,6 +696,16 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "UCloud US3 domain", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 domain name", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see https://console.ucloud-global.com/ufile", + "workflow_node.deploy.form.unicloud_webhost.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the uniCloud, please create a GitHub Issue.", + "workflow_node.deploy.form.unicloud_webhost_space_provider.label": "uniCloud space provider", + "workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder": "Please select uniCloud space provider", + "workflow_node.deploy.form.unicloud_webhost_space_provider.option.aliyun.label": "Alibaba Cloud", + "workflow_node.deploy.form.unicloud_webhost_space_provider.option.tencent.label": "Tencent Cloud", + "workflow_node.deploy.form.unicloud_webhost_space_id.label": "uniCloud space ID", + "workflow_node.deploy.form.unicloud_webhost_space_id.placeholder": "uniCloud space ID", + "workflow_node.deploy.form.unicloud_webhost_space_id.tooltip": "For more information, see https://doc.dcloud.net.cn/uniCloud/concepts/space.html", + "workflow_node.deploy.form.unicloud_webhost_domain.label": "uniCloud Web host domain", + "workflow_node.deploy.form.unicloud_webhost_domain.placeholder": "uniCloud Web host domain", "workflow_node.deploy.form.upyun_cdn.guide": "Tips: This uses webpage simulator login and does not guarantee stability. If there are any changes to the UPYUN, please create a GitHub Issue.", "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 7e184d55..fb51668f 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -407,6 +407,10 @@ "access.form.ucloud_project_id.label": "优刻得项目 ID(可选)", "access.form.ucloud_project_id.placeholder": "请输入优刻得项目 ID", "access.form.ucloud_project_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/uaccount/iam/project_manage", + "access.form.unicloud_username.label": "uniCloud 控制台账号", + "access.form.unicloud_username.placeholder": "请输入 uniCloud 控制台账号", + "access.form.unicloud_password.label": "uniCloud 控制台密码", + "access.form.unicloud_password.placeholder": "请输入 uniCloud 控制台密码", "access.form.upyun_username.label": "又拍云子账号用户名", "access.form.upyun_username.placeholder": "请输入又拍云子账号用户名", "access.form.upyun_username.tooltip": "这是什么?请参阅 https://console.upyun.com/account/subaccount/

请关闭该账号的二次登录验证。", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index c4e126e0..27aa8d0e 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -138,6 +138,8 @@ "provider.ucloud": "优刻得", "provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", "provider.ucloud.us3": "优刻得 - 对象存储 US3", + "provider.unicloud": "uniCloud (DCloud)", + "provider.unicloud.webhost": "uniCloud (DCloud) - 前端网页托管", "provider.upyun": "又拍云", "provider.upyun.cdn": "又拍云 - 云分发 CDN", "provider.upyun.file": "又拍云 - 云存储 USS", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 6f13984e..6f8f09a6 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -695,6 +695,16 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ufile", + "workflow_node.deploy.form.unicloud_webhost.guide": "小贴士:由于 uniCloud 未提供相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇 uniCloud 接口变更,请到 GitHub 发起 Issue 告知。", + "workflow_node.deploy.form.unicloud_webhost_space_provider.label": "uniCloud 服务空间提供商", + "workflow_node.deploy.form.unicloud_webhost_space_provider.placeholder": "请选择 uniCloud 服务空间提供商", + "workflow_node.deploy.form.unicloud_webhost_space_provider.option.aliyun.label": "阿里云", + "workflow_node.deploy.form.unicloud_webhost_space_provider.option.tencent.label": "腾讯云", + "workflow_node.deploy.form.unicloud_webhost_space_id.label": "uniCloud 服务空间 ID", + "workflow_node.deploy.form.unicloud_webhost_space_id.placeholder": "请输入 uniCloud 服务空间 ID", + "workflow_node.deploy.form.unicloud_webhost_space_id.tooltip": "这是什么?请参阅 https://doc.dcloud.net.cn/uniCloud/concepts/space.html", + "workflow_node.deploy.form.unicloud_webhost_domain.label": "uniCloud 前端网页托管网站域名", + "workflow_node.deploy.form.unicloud_webhost_domain.placeholder": "请输入 uniCloud 前端网页托管网站域名", "workflow_node.deploy.form.upyun_cdn.guide": "小贴士:由于又拍云未提供相关 API,这里将使用网页模拟登录方式部署,但无法保证稳定性。如遇又拍云接口变更,请到 GitHub 发起 Issue 告知。", "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", From 037305d8cd1186d4007eaa1996f8c9102acef080 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 27 May 2025 04:31:26 +0800 Subject: [PATCH 3/3] update README --- README.md | 2 +- README_EN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2ab2fb7b..bc2596af 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决 - 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法; - 支持 PEM、PFX、JKS 等多种格式输出证书; - 支持 30+ 域名托管商(如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers)); -- 支持 80+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-hosting-providers)); +- 支持 90+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-hosting-providers)); - 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道; - 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构; - 更多特性等待探索。 diff --git a/README_EN.md b/README_EN.md index f94020a2..67bab154 100644 --- a/README_EN.md +++ b/README_EN.md @@ -39,7 +39,7 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate - Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC. - Supports various certificate formats such as PEM, PFX, JKS. - Supports more than 30+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers)); -- Supports more than 80+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-hosting-providers)); +- Supports more than 90+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-hosting-providers)); - Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more; - Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust Services,SSL.com, ZeroSSL, and more; - More features waiting to be discovered.