From ac4c37524385952af9ed23680818a5314ded274b Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 10 Feb 2025 17:59:36 +0800 Subject: [PATCH 1/5] feat: add aliyun esa deployer --- go.mod | 2 + go.sum | 4 + internal/deployer/providers.go | 10 ++ internal/domain/provider.go | 1 + .../providers/aliyun-esa/aliyun_esa.go | 137 ++++++++++++++++++ .../providers/aliyun-esa/aliyun_esa_test.go | 80 ++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + .../DeployNodeConfigFormAliyunESAConfig.tsx | 78 ++++++++++ ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.common.json | 1 + .../i18n/locales/en/nls.workflow.nodes.json | 6 + ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 6 + 13 files changed, 331 insertions(+) create mode 100644 internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go create mode 100644 internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx diff --git a/go.mod b/go.mod index 6cd082bf..ac22c2c2 100644 --- a/go.mod +++ b/go.mod @@ -56,6 +56,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect + github.com/alibabacloud-go/esa-20240910 v1.0.0 // indirect + github.com/alibabacloud-go/esa-20240910/v2 v2.12.0 // indirect github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect diff --git a/go.sum b/go.sum index 7dde5dc8..edf5ce96 100644 --- a/go.sum +++ b/go.sum @@ -141,6 +141,10 @@ github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/ql github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= github.com/alibabacloud-go/endpoint-util v1.1.1 h1:ZkBv2/jnghxtU0p+upSU0GGzW1VL9GQdZO3mcSUTUy8= github.com/alibabacloud-go/endpoint-util v1.1.1/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/esa-20240910 v1.0.0 h1:mWWuq/OX9qcNaSvWqRmQKl7zvfZaZEqaQ7re7P4nTg4= +github.com/alibabacloud-go/esa-20240910 v1.0.0/go.mod h1:uQUr6wAz8x8ClsjeSfBuI63FdRd2LoNShmVzZknKaPk= +github.com/alibabacloud-go/esa-20240910/v2 v2.12.0 h1:DPyKQSVf+uba5SBEGgBpXRA8NpFuym1cSTKm1LzSxEo= +github.com/alibabacloud-go/esa-20240910/v2 v2.12.0/go.mod h1:P1w/+i7dE2xSXVHJznEOVImlLtqqrzUJQQk2AsyBJ6o= github.com/alibabacloud-go/live-20161101 v1.1.1 h1:rUGfA8RHmCMtQ5M3yMSyRde+yRXWqVecmiXBU3XrGJ8= github.com/alibabacloud-go/live-20161101 v1.1.1/go.mod h1:g84w6qeAodT0/IHdc0tEed2a8PyhQhYl7TAj3jGl4A4= github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 344c78e6..9bb3519f 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -9,6 +9,7 @@ import ( providerAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" providerAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" providerAliyunDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" + providerAliyunESA "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa" providerAliyunLive "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-live" providerAliyunNLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-nlb" providerAliyunOSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-oss" @@ -99,6 +100,15 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, }, logger) return deployer, logger, err + case domain.DeployProviderTypeAliyunESA: + deployer, err := providerAliyunESA.NewWithLogger(&providerAliyunESA.AliyunESADeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + SiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "siteId"), + }, logger) + return deployer, logger, err + case domain.DeployProviderTypeAliyunLive: deployer, err := providerAliyunLive.NewWithLogger(&providerAliyunLive.AliyunLiveDeployerConfig{ AccessKeyId: access.AccessKeyId, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 894f8007..0b3d7f8d 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -86,6 +86,7 @@ const ( DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") + DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go new file mode 100644 index 00000000..de7a1028 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go @@ -0,0 +1,137 @@ +package aliyunesa + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + aliyunEsa "github.com/alibabacloud-go/esa-20240910/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunESADeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 阿里云 ESA 站点 ID。 + SiteId int64 `json:"siteId"` +} + +type AliyunESADeployer struct { + config *AliyunESADeployerConfig + logger logger.Logger + sdkClient *aliyunEsa.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunESADeployer)(nil) + +func New(config *AliyunESADeployerConfig) (*AliyunESADeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *AliyunESADeployerConfig, logger logger.Logger) (*AliyunESADeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunESADeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunESADeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.SiteId == 0 { + return nil, errors.New("config `siteId` is required") + } + + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Logt("certificate file uploaded", upres) + + // 配置站点证书 + // REF: https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-setcertificate + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) + setCertificateReq := &aliyunEsa.SetCertificateRequest{ + SiteId: tea.Int64(d.config.SiteId), + Type: tea.String("cas"), + CasId: tea.Int64(certId), + } + setCertificateResp, err := d.sdkClient.SetCertificate(setCertificateReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'esa.SetCertificate'") + } + + d.logger.Logt("已配置站点证书", setCertificateResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunEsa.Client, error) { + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(fmt.Sprintf("esa.%s.aliyuncs.com", region)), + } + + client, err := aliyunEsa.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} + +func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { + casRegion := region + if casRegion != "" { + // 阿里云 CAS 服务接入点是独立于 ESA 服务的 + // 国内版固定接入点:华东一杭州 + // 国际版固定接入点:亚太东南一新加坡 + if casRegion != "" && !strings.HasPrefix(casRegion, "cn-") { + casRegion = "ap-southeast-1" + } else { + casRegion = "cn-hangzhou" + } + } + + uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: casRegion, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go new file mode 100644 index 00000000..980dc9a0 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa_test.go @@ -0,0 +1,80 @@ +package aliyunesa_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-esa" +) + +var ( + fInputCertPath string + fInputKeyPath string + fAccessKeyId string + fAccessKeySecret string + fRegion string + fSiteId int64 +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_ALIYUNESA_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "") + flag.StringVar(&fAccessKeySecret, argsPrefix+"ACCESSKEYSECRET", "", "") + flag.StringVar(&fRegion, argsPrefix+"REGION", "", "") + flag.Int64Var(&fSiteId, argsPrefix+"SITEID", "", "") +} + +/* +Shell command to run this test: + + go test -v ./aliyun_esa_test.go -args \ + --CERTIMATE_DEPLOYER_ALIYUNESA_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNESA_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYID="your-access-key-id" \ + --CERTIMATE_DEPLOYER_ALIYUNESA_ACCESSKEYSECRET="your-access-key-secret" \ + --CERTIMATE_DEPLOYER_ALIYUNOSS_REGION="cn-hangzhou" \ + --CERTIMATE_DEPLOYER_ALIYUNESA_SITEID="your-esa-site-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("ACCESSKEYID: %v", fAccessKeyId), + fmt.Sprintf("ACCESSKEYSECRET: %v", fAccessKeySecret), + fmt.Sprintf("REGION: %v", fRegion), + fmt.Sprintf("SITEID: %v", fSiteId), + }, "\n")) + + deployer, err := provider.New(&provider.AliyunESADeployerConfig{ + AccessKeyId: fAccessKeyId, + AccessKeySecret: fAccessKeySecret, + Region: fRegion, + SiteId: fSiteId, + }) + 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/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index c9b70570..41a91219 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -19,6 +19,7 @@ import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALB import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; import DeployNodeConfigFormAliyunCLBConfig from "./DeployNodeConfigFormAliyunCLBConfig"; import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDCDNConfig"; +import DeployNodeConfigFormAliyunESAConfig from "./DeployNodeConfigFormAliyunESAConfig"; import DeployNodeConfigFormAliyunLiveConfig from "./DeployNodeConfigFormAliyunLiveConfig"; import DeployNodeConfigFormAliyunNLBConfig from "./DeployNodeConfigFormAliyunNLBConfig"; import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSSConfig"; @@ -129,6 +130,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.ALIYUN_DCDN: return ; + case DEPLOY_PROVIDERS.ALIYUN_ESA: + return ; case DEPLOY_PROVIDERS.ALIYUN_LIVE: return ; case DEPLOY_PROVIDERS.ALIYUN_NLB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx new file mode 100644 index 00000000..338c0f97 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAliyunESAConfig.tsx @@ -0,0 +1,78 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAliyunESAConfigFieldValues = Nullish<{ + region: string; + siteId: string; +}>; + +export type DeployNodeConfigFormAliyunESAConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunESAConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunESAConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAliyunESAConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunESAConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAliyunESAConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_esa_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_esa_region.placeholder")) + .trim(), + siteId: z + .string({ message: t("workflow_node.deploy.form.aliyun_esa_site_id.placeholder") }) + .regex(/^[1-9]\d*$/, t("workflow_node.deploy.form.aliyun_esa_site_id.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAliyunESAConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 652f4332..2bc1e9bf 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -179,6 +179,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`, ALIYUN_CLB: `${ACCESS_PROVIDERS.ALIYUN}-clb`, ALIYUN_DCDN: `${ACCESS_PROVIDERS.ALIYUN}-dcdn`, + ALIYUN_ESA: `${ACCESS_PROVIDERS.ALIYUN}-esa`, ALIYUN_LIVE: `${ACCESS_PROVIDERS.ALIYUN}-live`, ALIYUN_NLB: `${ACCESS_PROVIDERS.ALIYUN}-nlb`, ALIYUN_OSS: `${ACCESS_PROVIDERS.ALIYUN}-oss`, @@ -233,6 +234,7 @@ export const deployProvidersMap: Maphttps://dcdn.console.aliyun.com", + "workflow_node.deploy.form.aliyun_esa_region.label": "Alibaba Cloud region", + "workflow_node.deploy.form.aliyun_esa_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_esa_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint", + "workflow_node.deploy.form.aliyun_esa_site_id.label": "Alibaba Cloud ESA site ID", + "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "Please enter Alibaba Cloud ESA site ID", + "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "For more information, see https://esa.console.aliyun.com/siteManage/list", "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud region", "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index b8b97842..15919d22 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -41,6 +41,7 @@ "common.provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN", "common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "common.provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN", + "common.provider.aliyun.esa": "阿里云 - 边缘安全加速 ESA", "common.provider.aliyun.dns": "阿里云 - 云解析 DNS", "common.provider.aliyun.live": "阿里云 - 视频直播 Live", "common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index d132f935..ea6b43ec 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -125,6 +125,12 @@ "workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名", "workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 https://dcdn.console.aliyun.com

泛域名表示形式为:*.example.com", + "workflow_node.deploy.form.aliyun_esa_region.label": "阿里云地域", + "workflow_node.deploy.form.aliyun_esa_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_esa_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint", + "workflow_node.deploy.form.aliyun_esa_site_id.label": "阿里云 ESA 站点 ID", + "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "请输入阿里云 ESA 站点 ID", + "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "这是什么?请参阅 https://esa.console.aliyun.com/siteManage/list", "workflow_node.deploy.form.aliyun_live_region.label": "阿里云地域", "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", From 316bd58b68ddbc3171bd7e9e73296dda72f4f0b9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 10 Feb 2025 21:07:28 +0800 Subject: [PATCH 2/5] feat: add aliyun cas-deploy deployer --- internal/deployer/providers.go | 15 +- internal/domain/provider.go | 1 + .../aliyun-cas-deploy/aliyun_cas_deploy.go | 187 ++++++++++++++ .../providers/aliyun-esa/aliyun_esa.go | 1 + .../providers/aliyun-waf/aliyun_waf.go | 1 + .../provider/ApplyDNSProviderPicker.tsx | 17 +- .../provider/DeployProviderPicker.tsx | 17 +- .../workflow/node/DeployNodeConfigForm.tsx | 5 +- ...loyNodeConfigFormAliyunCASDeployConfig.tsx | 235 ++++++++++++++++++ .../components/workflow/node/_SharedNode.tsx | 2 +- ui/src/domain/provider.ts | 2 + ui/src/i18n/locales/en/nls.access.json | 2 +- ui/src/i18n/locales/en/nls.common.json | 1 + ui/src/i18n/locales/en/nls.settings.json | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 86 ++++--- ui/src/i18n/locales/zh/nls.access.json | 4 +- ui/src/i18n/locales/zh/nls.common.json | 1 + .../i18n/locales/zh/nls.workflow.nodes.json | 92 ++++--- 18 files changed, 581 insertions(+), 90 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormAliyunCASDeployConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 9bb3519f..269f1130 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -2,10 +2,12 @@ package deployer import ( "fmt" + "strings" "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/deployer" providerAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb" + providerAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy" providerAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn" providerAliyunCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-clb" providerAliyunDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-dcdn" @@ -42,6 +44,7 @@ import ( providerWebhook "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/webhook" "github.com/usual2970/certimate/internal/pkg/core/logger" "github.com/usual2970/certimate/internal/pkg/utils/maps" + "github.com/usual2970/certimate/internal/pkg/utils/slices" ) func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, error) { @@ -52,7 +55,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, NOTICE: If you add new constant, please keep ASCII order. */ switch options.Provider { - case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF: + case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunWAF: { access := domain.AccessConfigForAliyun{} if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { @@ -72,6 +75,16 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, }, logger) return deployer, logger, err + case domain.DeployProviderTypeAliyunCASDeploy: + deployer, err := providerAliyunCASDeploy.NewWithLogger(&providerAliyunCASDeploy.AliyunCASDeployDeployerConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + ContactIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "contactIds"), ";"), func(s string) bool { return s != "" }), + }, logger) + return deployer, logger, err + case domain.DeployProviderTypeAliyunCDN: deployer, err := providerAliyunCDN.NewWithLogger(&providerAliyunCDN.AliyunCDNDeployerConfig{ AccessKeyId: access.AccessKeyId, diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 0b3d7f8d..8d192aaf 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -83,6 +83,7 @@ type DeployProviderType string */ const ( DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") + DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-cas-deploy") DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") diff --git a/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go new file mode 100644 index 00000000..31dc66b8 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aliyun-cas-deploy/aliyun_cas_deploy.go @@ -0,0 +1,187 @@ +package aliyuncasdeploy + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + aliyunCas "github.com/alibabacloud-go/cas-20200407/v3/client" + aliyunOpen "github.com/alibabacloud-go/darabonba-openapi/v2/client" + "github.com/alibabacloud-go/tea/tea" + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aliyun-cas" +) + +type AliyunCASDeployDeployerConfig struct { + // 阿里云 AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // 阿里云 AccessKeySecret。 + AccessKeySecret string `json:"accessKeySecret"` + // 阿里云地域。 + Region string `json:"region"` + // 阿里云云产品资源 ID 数组。 + ResourceIds []string `json:"resourceIds"` + // 阿里云云联系人 ID 数组。 + // 零值时默认使用账号下第一个联系人。 + ContactIds []string `json:"contactIds"` +} + +type AliyunCASDeployDeployer struct { + config *AliyunCASDeployDeployerConfig + logger logger.Logger + sdkClient *aliyunCas.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*AliyunCASDeployDeployer)(nil) + +func New(config *AliyunCASDeployDeployerConfig) (*AliyunCASDeployDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *AliyunCASDeployDeployerConfig, logger logger.Logger) (*AliyunCASDeployDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := createSslUploader(config.AccessKeyId, config.AccessKeySecret, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &AliyunCASDeployDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunCASDeployDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if len(d.config.ResourceIds) == 0 { + return nil, errors.New("config `resourceIds` is required") + } + + // 上传证书到 CAS + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Logt("certificate file uploaded", upres) + + contactIds := d.config.ContactIds + if len(contactIds) == 0 { + // 获取联系人列表 + // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact + listContactReq := &aliyunCas.ListContactRequest{} + listContactReq.ShowSize = tea.Int32(1) + listContactReq.CurrentPage = tea.Int32(1) + listContactResp, err := d.sdkClient.ListContact(listContactReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.ListContact'") + } + + if len(listContactResp.Body.ContactList) > 0 { + contactIds = []string{fmt.Sprintf("%d", listContactResp.Body.ContactList[0].ContactId)} + } + } + + // 创建部署任务 + // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-createdeploymentjob + createDeploymentJobReq := &aliyunCas.CreateDeploymentJobRequest{ + Name: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), + JobType: tea.String("user"), + CertIds: tea.String(upres.CertId), + ResourceIds: tea.String(strings.Join(d.config.ResourceIds, ",")), + ContactIds: tea.String(strings.Join(contactIds, ",")), + } + createDeploymentJobResp, err := d.sdkClient.CreateDeploymentJob(createDeploymentJobReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.CreateDeploymentJob'") + } + + d.logger.Logt("已创建部署任务", createDeploymentJobResp) + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-describedeploymentjob + for { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + describeDeploymentJobReq := &aliyunCas.DescribeDeploymentJobRequest{ + JobId: createDeploymentJobResp.Body.JobId, + } + describeDeploymentJobResp, err := d.sdkClient.DescribeDeploymentJob(describeDeploymentJobReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cas.DescribeDeploymentJob'") + } + + if describeDeploymentJobResp.Body.Status == nil || *describeDeploymentJobResp.Body.Status == "editing" { + return nil, errors.New("部署任务状态异常") + } + + if *describeDeploymentJobResp.Body.Status == "success" || *describeDeploymentJobResp.Body.Status == "error" { + d.logger.Logt("已获取部署任务详情", describeDeploymentJobResp) + break + } + + d.logger.Logt("部署任务未完成 ...") + time.Sleep(time.Second * 5) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunCas.Client, error) { + if region == "" { + region = "cn-hangzhou" // CAS 服务默认区域:华东一杭州 + } + + // 接入点一览 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints + var endpoint string + switch region { + case "cn-hangzhou": + endpoint = "cas.aliyuncs.com" + default: + endpoint = fmt.Sprintf("cas.%s.aliyuncs.com", region) + } + + config := &aliyunOpen.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + Endpoint: tea.String(endpoint), + } + + client, err := aliyunCas.NewClient(config) + if err != nil { + return nil, err + } + + return client, nil +} + +func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) { + uploader, err := uploaderp.New(&uploaderp.AliyunCASUploaderConfig{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + Region: region, + }) + return uploader, err +} diff --git a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go index de7a1028..a09f8bbf 100644 --- a/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go +++ b/internal/pkg/core/deployer/providers/aliyun-esa/aliyun_esa.go @@ -101,6 +101,7 @@ func (d *AliyunESADeployer) Deploy(ctx context.Context, certPem string, privkeyP } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunEsa.Client, error) { + // 接入点一览 https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint config := &aliyunOpen.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), diff --git a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go index 75c634b8..1cc7aecd 100644 --- a/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go +++ b/internal/pkg/core/deployer/providers/aliyun-waf/aliyun_waf.go @@ -114,6 +114,7 @@ func (d *AliyunWAFDeployer) Deploy(ctx context.Context, certPem string, privkeyP } func createSdkClient(accessKeyId, accessKeySecret, region string) (*aliyunWaf.Client, error) { + // 接入点一览:https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint config := &aliyunOpen.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), diff --git a/ui/src/components/provider/ApplyDNSProviderPicker.tsx b/ui/src/components/provider/ApplyDNSProviderPicker.tsx index f297716f..10fa39ff 100644 --- a/ui/src/components/provider/ApplyDNSProviderPicker.tsx +++ b/ui/src/components/provider/ApplyDNSProviderPicker.tsx @@ -1,6 +1,6 @@ -import { memo, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Avatar, Card, Col, Empty, Flex, Input, Row, Typography } from "antd"; +import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd"; import Show from "@/components/Show"; import { applyDNSProvidersMap } from "@/domain/provider"; @@ -8,20 +8,27 @@ import { applyDNSProvidersMap } from "@/domain/provider"; export type ApplyDNSProviderPickerProps = { className?: string; style?: React.CSSProperties; + autoFocus?: boolean; placeholder?: string; onSelect?: (value: string) => void; }; -const ApplyDNSProviderPicker = ({ className, style, placeholder, onSelect }: ApplyDNSProviderPickerProps) => { +const ApplyDNSProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: ApplyDNSProviderPickerProps) => { const { t } = useTranslation(); const [keyword, setKeyword] = useState(); + const keywordInputRef = useRef(null); + useEffect(() => { + if (autoFocus) { + setTimeout(() => keywordInputRef.current?.focus(), 1); + } + }, []); const providers = Array.from(applyDNSProvidersMap.values()); const filteredProviders = providers.filter((provider) => { if (keyword) { const value = keyword.toLowerCase(); - return provider.type.toLowerCase().includes(value) || provider.name.toLowerCase().includes(value); + return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value); } return true; @@ -33,7 +40,7 @@ const ApplyDNSProviderPicker = ({ className, style, placeholder, onSelect }: App return (
- setKeyword(e.target.value.trim())} /> + setKeyword(e.target.value.trim())} />
0} fallback={}> diff --git a/ui/src/components/provider/DeployProviderPicker.tsx b/ui/src/components/provider/DeployProviderPicker.tsx index 537638bf..c90debb5 100644 --- a/ui/src/components/provider/DeployProviderPicker.tsx +++ b/ui/src/components/provider/DeployProviderPicker.tsx @@ -1,6 +1,6 @@ -import { memo, useState } from "react"; +import { memo, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Avatar, Card, Col, Empty, Flex, Input, Row, Typography } from "antd"; +import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Typography } from "antd"; import Show from "@/components/Show"; import { deployProvidersMap } from "@/domain/provider"; @@ -8,20 +8,27 @@ import { deployProvidersMap } from "@/domain/provider"; export type DeployProviderPickerProps = { className?: string; style?: React.CSSProperties; + autoFocus?: boolean; placeholder?: string; onSelect?: (value: string) => void; }; -const DeployProviderPicker = ({ className, style, placeholder, onSelect }: DeployProviderPickerProps) => { +const DeployProviderPicker = ({ className, style, autoFocus, placeholder, onSelect }: DeployProviderPickerProps) => { const { t } = useTranslation(); const [keyword, setKeyword] = useState(); + const keywordInputRef = useRef(null); + useEffect(() => { + if (autoFocus) { + setTimeout(() => keywordInputRef.current?.focus(), 1); + } + }, []); const providers = Array.from(deployProvidersMap.values()); const filteredProviders = providers.filter((provider) => { if (keyword) { const value = keyword.toLowerCase(); - return provider.type.toLowerCase().includes(value) || provider.name.toLowerCase().includes(value); + return provider.type.toLowerCase().includes(value) || t(provider.name).toLowerCase().includes(value); } return true; @@ -33,7 +40,7 @@ const DeployProviderPicker = ({ className, style, placeholder, onSelect }: Deplo return (
- setKeyword(e.target.value.trim())} /> + setKeyword(e.target.value.trim())} />
0} fallback={}> diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 41a91219..7d4f3dbd 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -16,6 +16,7 @@ import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks import { useWorkflowStore } from "@/stores/workflow"; import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig"; +import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig"; import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig"; import DeployNodeConfigFormAliyunCLBConfig from "./DeployNodeConfigFormAliyunCLBConfig"; import DeployNodeConfigFormAliyunDCDNConfig from "./DeployNodeConfigFormAliyunDCDNConfig"; @@ -124,6 +125,8 @@ const DeployNodeConfigForm = forwardRef; + case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOYMENT_JOB: + return ; case DEPLOY_PROVIDERS.ALIYUN_CLB: return ; case DEPLOY_PROVIDERS.ALIYUN_CDN: @@ -264,7 +267,7 @@ const DeployNodeConfigForm = forwardRef } + fallback={} > ; + +export type DeployNodeConfigFormAliyunCASDeployConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAliyunCASDeployConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAliyunCASDeployConfigFieldValues) => void; +}; + +const MULTIPLE_INPUT_DELIMITER = ";"; + +const initFormModel = (): DeployNodeConfigFormAliyunCASDeployConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormAliyunCASDeployConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormAliyunCASDeployConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder")) + .trim(), + resourceIds: z.string({ message: t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.placeholder") }).refine((v) => { + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid")), + contactIds: z + .string({ message: t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.placeholder") }) + .nullish() + .refine((v) => { + if (!v) return true; + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const fieldResourceIds = Form.useWatch("resourceIds", formInst); + const fieldContactIds = Form.useWatch("contactIds", formInst); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + + { + formInst.setFieldValue("resourceIds", e.target.value); + }} + /> + + + + + } + onChange={(value) => { + formInst.setFieldValue("resourceIds", value); + }} + /> + + + + } + > + + + { + formInst.setFieldValue("contactIds", e.target.value); + }} + /> + + + + + } + onChange={(value) => { + formInst.setFieldValue("contactIds", value); + }} + /> + + + + + } /> + +
+ ); +}; + +const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceIds: z.array(z.string()).refine((v) => { + return v.every((e) => !e?.trim() || /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + name: "workflowNodeDeployConfigFormAliyunCASResourceIdsModalInput", + initialValues: { resourceIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, + onSubmit: (values) => { + onChange?.( + values.resourceIds + .map((e) => e.trim()) + .filter((e) => !!e) + .join(MULTIPLE_INPUT_DELIMITER) + ); + }, + }); + + return ( + + + + + + ); +}); + +const ContactIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + contactIds: z.array(z.string()).refine((v) => { + return v.every((e) => !e?.trim() || /^[1-9]\d*$/.test(e)); + }, t("workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + name: "workflowNodeDeployConfigFormAliyunCASDeploymentJobContactIdsModalInput", + initialValues: { contactIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, + onSubmit: (values) => { + onChange?.( + values.contactIds + .map((e) => e.trim()) + .filter((e) => !!e) + .join(MULTIPLE_INPUT_DELIMITER) + ); + }, + }); + + return ( + + + + + + ); +}); + +export default DeployNodeConfigFormAliyunCASDeployConfig; diff --git a/ui/src/components/workflow/node/_SharedNode.tsx b/ui/src/components/workflow/node/_SharedNode.tsx index f2a4df60..a7b88e4c 100644 --- a/ui/src/components/workflow/node/_SharedNode.tsx +++ b/ui/src/components/workflow/node/_SharedNode.tsx @@ -260,7 +260,7 @@ const SharedNodeConfigDrawer = ({ const oldValues = Object.fromEntries(Object.entries(node.config ?? {}).filter(([_, value]) => value !== null && value !== undefined)); const newValues = Object.fromEntries(Object.entries(getFormValues()).filter(([_, value]) => value !== null && value !== undefined)); - const changed = !isEqual(oldValues, newValues); + const changed = !isEqual(oldValues, {}) && !isEqual(oldValues, newValues); const { promise, resolve, reject } = Promise.withResolvers(); if (changed) { diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 2bc1e9bf..6e0758a1 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -176,6 +176,7 @@ export const applyDNSProvidersMap: Maphttps://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave blank to use the Pod's ServiceAccount.", + "access.form.k8s_kubeconfig.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

Leave it blank to use the Pod's ServiceAccount.", "access.form.namedotcom_username.label": "Name.com username", "access.form.namedotcom_username.placeholder": "Please enter Name.com username", "access.form.namedotcom_username.tooltip": "For more information, see https://www.name.com/account/settings/api", diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index 658d3441..bd872635 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -38,6 +38,7 @@ "common.provider.acmehttpreq": "Http Request (ACME Proxy)", "common.provider.aliyun": "Alibaba Cloud", "common.provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", + "common.provider.aliyun.cas-deploy": "Alibaba Cloud - via CAS (Certificate Management Service) Deployment Job", "common.provider.aliyun.cdn": "Alibaba Cloud - CDN (Content Delivery Network)", "common.provider.aliyun.clb": "Alibaba Cloud - CLB (Classic Load Balancer)", "common.provider.aliyun.dcdn": "Alibaba Cloud - DCDN (Dynamic Route for Content Delivery Network)", diff --git a/ui/src/i18n/locales/en/nls.settings.json b/ui/src/i18n/locales/en/nls.settings.json index 7a12b408..0bb6ba40 100644 --- a/ui/src/i18n/locales/en/nls.settings.json +++ b/ui/src/i18n/locales/en/nls.settings.json @@ -30,7 +30,7 @@ "settings.notification.push_test.pushed": "Sent", "settings.notification.channel.form.bark_server_url.label": "Server URL", "settings.notification.channel.form.bark_server_url.placeholder": "Please enter server URL", - "settings.notification.channel.form.bark_server_url.tooltip": "For more information, see https://bark.day.app/

Leave blank to use the default Bark server.", + "settings.notification.channel.form.bark_server_url.tooltip": "For more information, see https://bark.day.app/

Leave it blank to use the default Bark server.", "settings.notification.channel.form.bark_device_key.label": "Device key", "settings.notification.channel.form.bark_device_key.placeholder": "Please enter device key", "settings.notification.channel.form.bark_device_key.tooltip": "For more information, see https://bark.day.app/", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 974ab95d..defe4d35 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -37,8 +37,8 @@ "workflow_node.apply.form.provider_access.placeholder": "Please select an authorization of DNS provider", "workflow_node.apply.form.provider_access.tooltip": "Used to manage DNS records during ACME DNS-01 authentication.", "workflow_node.apply.form.provider_access.button": "Create", - "workflow_node.apply.form.aws_route53_region.label": "AWS Region", - "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS region (e.g. us-east-1)", + "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 Region", + "workflow_node.apply.form.aws_route53_region.placeholder": "Please enter AWS Route53 region (e.g. us-east-1)", "workflow_node.apply.form.aws_route53_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 hosted zone ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "Please enter AWS Route53 hosted zone ID", @@ -57,11 +57,11 @@ "workflow_node.apply.form.dns_propagation_timeout.label": "DNS propagation timeout (Optional)", "workflow_node.apply.form.dns_propagation_timeout.placeholder": "Please enter DNS propagation timeout", "workflow_node.apply.form.dns_propagation_timeout.unit": "seconds", - "workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the maximum waiting time for DNS propagation checks during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.

Leave blank to use the default value provided by the provider.", + "workflow_node.apply.form.dns_propagation_timeout.tooltip": "It determines the maximum waiting time for DNS propagation checks during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.

Leave it blank to use the default value provided by the provider.", "workflow_node.apply.form.dns_ttl.label": "DNS TTL (Optional)", "workflow_node.apply.form.dns_ttl.placeholder": "Please enter DNS TTL", "workflow_node.apply.form.dns_ttl.unit": "seconds", - "workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.

Leave blank to use the default value provided by the provider.", + "workflow_node.apply.form.dns_ttl.tooltip": "It determines the time to live for DNS record during ACME DNS-01 authentication. If you don't understand this option, just keep it by default.

Leave it blank to use the default value provided by the provider.", "workflow_node.apply.form.disable_follow_cname.label": "Disable CNAME following", "workflow_node.apply.form.disable_follow_cname.tooltip": "It determines whether to disable CNAME following during ACME DNS-01 authentication. If you don't understand this option, just keep it by default. Learn more.", "workflow_node.apply.form.disable_ari.label": "Disable ARI", @@ -91,8 +91,8 @@ "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "ALB load balancer", "workflow_node.deploy.form.aliyun_alb_resource_type.option.listener.label": "ALB listener", - "workflow_node.deploy.form.aliyun_alb_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_alb_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_alb_region.label": "Alibaba Cloud ALB region", + "workflow_node.deploy.form.aliyun_alb_region.placeholder": "Please enter Alibaba Cloud ALB region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_alb_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/slb/application-load-balancer/product-overview/supported-regions-and-zones", "workflow_node.deploy.form.aliyun_alb_loadbalancer_id.label": "Alibaba Cloud ALB load balancer ID", "workflow_node.deploy.form.aliyun_alb_loadbalancer_id.placeholder": "Please enter Alibaba Cloud ALB load balancer ID", @@ -103,12 +103,28 @@ "workflow_node.deploy.form.aliyun_alb_snidomain.label": "Alibaba Cloud ALB SNI domain (Optional)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "Please enter Alibaba Cloud ALB SNI domain name", "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "For more information, see https://slb.console.aliyun.com/alb", + "workflow_node.deploy.form.aliyun_cas_deploy.guide": "TIPS: You need to go to the Alibaba Cloud console to check the actual deployment results by yourself, because Alibaba Cloud deployment tasks are running asynchronously.", + "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "Alibaba Cloud CAS region", + "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "Please enter Alibaba Cloud CAS region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cas_deploy_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/endpoints", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.label": "Alibaba Cloud resource IDs", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.placeholder": "Please enter Alibaba Cloud resource IDs (separated by semicolons)", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid": "Please enter a valid Alibaba Cloud resource ID", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources

Supports Alibaba Cloud products only.", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title": "Change Alibaba Cloud resource IDs", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.placeholder": "Please enter Alibaba Cloud resouce ID", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.label": "Alibaba Cloud contact IDs (Optional)", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.placeholder": "Please enter Alibaba Cloud contact IDs (separated by semicolons)", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid": "Please enter a valid Alibaba Cloud contact ID", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "For more information, see https://www.alibabacloud.com/help/en/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

Leave it blank to use the first system contact.", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "Change Alibaba Cloud contact IDs", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "Please enter Alibaba Cloud contact ID", "workflow_node.deploy.form.aliyun_clb_resource_type.label": "Resource type", "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "CLB load balancer", "workflow_node.deploy.form.aliyun_clb_resource_type.option.listener.label": "CLB listener", - "workflow_node.deploy.form.aliyun_clb_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_clb_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_clb_region.label": "Alibaba Cloud CLB region", + "workflow_node.deploy.form.aliyun_clb_region.placeholder": "Please enter Alibaba Cloud CLB region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_clb_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/slb/classic-load-balancer/product-overview/regions-that-support-clb", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.label": "Alibaba Cloud CLB load balancer ID", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder": "Please enter Alibaba Cloud CLB load balancer ID", @@ -125,14 +141,14 @@ "workflow_node.deploy.form.aliyun_dcdn_domain.label": "Alibaba Cloud DCDN domain", "workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "Please enter Alibaba Cloud DCDN domain name", "workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "For more information, see https://dcdn.console.aliyun.com", - "workflow_node.deploy.form.aliyun_esa_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_esa_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_esa_region.label": "Alibaba Cloud ESA region", + "workflow_node.deploy.form.aliyun_esa_region.placeholder": "Please enter Alibaba Cloud ESA region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_esa_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint", "workflow_node.deploy.form.aliyun_esa_site_id.label": "Alibaba Cloud ESA site ID", "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "Please enter Alibaba Cloud ESA site ID", "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "For more information, see https://esa.console.aliyun.com/siteManage/list", - "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_live_region.label": "Alibaba Cloud Live region", + "workflow_node.deploy.form.aliyun_live_region.placeholder": "Please enter Alibaba Cloud Live region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/live/product-overview/supported-regions", "workflow_node.deploy.form.aliyun_live_domain.label": "Alibaba Cloud live streaming domain", "workflow_node.deploy.form.aliyun_live_domain.placeholder": "Please enter Alibaba Cloud live streaming domain name", @@ -141,8 +157,8 @@ "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.loadbalancer.label": "NLB load balancer", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.listener.label": "NLB listener", - "workflow_node.deploy.form.aliyun_nlb_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_nlb_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_nlb_region.label": "Alibaba Cloud NLB region", + "workflow_node.deploy.form.aliyun_nlb_region.placeholder": "Please enter Alibaba Cloud NLB region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_nlb_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/slb/network-load-balancer/product-overview/regions-that-support-nlb", "workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.label": "Alibaba Cloud NLB load balancer ID", "workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.placeholder": "Please enter Alibaba Cloud NLB load balancer ID", @@ -150,8 +166,8 @@ "workflow_node.deploy.form.aliyun_nlb_listener_id.label": "Alibaba Cloud NLB listener ID", "workflow_node.deploy.form.aliyun_nlb_listener_id.placeholder": "Please enter Alibaba Cloud NLB listener ID", "workflow_node.deploy.form.aliyun_nlb_listener_id.tooltip": "For more information, see https://slb.console.aliyun.com/nlb", - "workflow_node.deploy.form.aliyun_oss_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_oss_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_oss_region.label": "Alibaba Cloud OSS region", + "workflow_node.deploy.form.aliyun_oss_region.placeholder": "Please enter Alibaba Cloud OSS region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_oss_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/oss/user-guide/regions-and-endpoints", "workflow_node.deploy.form.aliyun_oss_bucket.label": "Alibaba Cloud OSS bucket", "workflow_node.deploy.form.aliyun_oss_bucket.placeholder": "Please enter Alibaba Cloud OSS bucket name", @@ -159,14 +175,14 @@ "workflow_node.deploy.form.aliyun_oss_domain.label": "Alibaba Cloud OSS domain", "workflow_node.deploy.form.aliyun_oss_domain.placeholder": "Please enter Alibaba Cloud OSS domain name", "workflow_node.deploy.form.aliyun_oss_domain.tooltip": "For more information, see https://oss.console.aliyun.com", - "workflow_node.deploy.form.aliyun_waf_region.label": "Alibaba Cloud region", - "workflow_node.deploy.form.aliyun_waf_region.placeholder": "Please enter Alibaba Cloud region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_waf_region.label": "Alibaba Cloud WAF region", + "workflow_node.deploy.form.aliyun_waf_region.placeholder": "Please enter Alibaba Cloud WAF region (e.g. cn-hangzhou)", "workflow_node.deploy.form.aliyun_waf_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "Please enter Alibaba Cloud WAF instance ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "For more information, see https://waf.console.aliyun.com", - "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS Region", - "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS region (e.g. us-east-1)", + "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront Region", + "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS CloudFront region (e.g. us-east-1)", "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront distribution ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "Please enter AWS CloudFront distribution ID", @@ -183,8 +199,8 @@ "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.huaweicloud_cdn_region.label": "Huawei Cloud region", - "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "Please enter Huawei Cloud region (e.g. cn-north-1)", + "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", "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "Huawei Cloud CDN domain", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "Please enter Huawei Cloud CDN domain name", @@ -194,8 +210,8 @@ "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.certificate.label": "ELB certificate", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "ELB load balancer", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.listener.label": "ELB listener", - "workflow_node.deploy.form.huaweicloud_elb_region.label": "Huawei Cloud region", - "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "Please enter Huawei Cloud region (e.g. cn-north-1)", + "workflow_node.deploy.form.huaweicloud_elb_region.label": "Huawei Cloud ELB region", + "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "Please enter Huawei Cloud ELB region (e.g. cn-north-1)", "workflow_node.deploy.form.huaweicloud_elb_region.tooltip": "For more information, see https://console-intl.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.deploy.form.huaweicloud_elb_certificate_id.label": "Huawei Cloud ELB certificate ID", "workflow_node.deploy.form.huaweicloud_elb_certificate_id.placeholder": "Please enter Huawei Cloud ELB certificate ID", @@ -304,12 +320,12 @@ "workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "For more information, see https://console.tencentcloud.com/cdn", "workflow_node.deploy.form.tencentcloud_clb_resource_type.label": "Resource type", "workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder": "Please select resource type", - "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ssl_deploy.label": "Through SSL deploy", + "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ssl_deploy.label": "Via SSL deploy", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.loadbalancer.label": "CLB instance", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.listener.label": "CLB listener", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ruledomain.label": "CLB rule domain", - "workflow_node.deploy.form.tencentcloud_clb_region.label": "Tencent Cloud region", - "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "Please enter Tencent Cloud region (e.g. ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_clb_region.label": "Tencent Cloud CLB region", + "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "Please enter Tencent Cloud CLB region (e.g. ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_clb_region.tooltip": "For more information, see https://www.tencentcloud.com/document/product/214/13629", "workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.label": "Tencent Cloud CLB instance ID", "workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.placeholder": "Please enter Tencent Cloud CLB instance ID", @@ -323,8 +339,8 @@ "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "Tencent Cloud CLB domain", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "Please enter Tencent Cloud CLB domain name", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "For more information, see https://console.tencentcloud.com/clb", - "workflow_node.deploy.form.tencentcloud_cos_region.label": "Tencent Cloud region", - "workflow_node.deploy.form.tencentcloud_cos_region.placeholder": "Please enter Tencent Cloud region (e.g. ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_cos_region.label": "Tencent Cloud COS region", + "workflow_node.deploy.form.tencentcloud_cos_region.placeholder": "Please enter Tencent Cloud COS region (e.g. ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_cos_region.tooltip": "For more information, see https://www.tencentcloud.com/document/product/436/6224", "workflow_node.deploy.form.tencentcloud_cos_bucket.label": "Tencent Cloud COS bucket", "workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder": "Please enter Tencent Cloud COS bucket name", @@ -347,8 +363,8 @@ "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "UCloud UCDN domain ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "Please enter UCloud UCDN domain ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "For more information, see https://console.ucloud-global.com/ucdn", - "workflow_node.deploy.form.ucloud_us3_region.label": "UCloud region", - "workflow_node.deploy.form.ucloud_us3_region.placeholder": "Please enter VolcEngine region (e.g. cn-bj2)", + "workflow_node.deploy.form.ucloud_us3_region.label": "UCloud US3 region", + "workflow_node.deploy.form.ucloud_us3_region.placeholder": "Please enter UCloud US3 region (e.g. cn-bj2)", "workflow_node.deploy.form.ucloud_us3_region.tooltip": "For more information, see https://www.ucloud-global.com/en/docs/api/summary/regionlist", "workflow_node.deploy.form.ucloud_us3_bucket.label": "UCloud US3 bucket", "workflow_node.deploy.form.ucloud_us3_bucket.placeholder": "Please enter UCloud US3 bucket name", @@ -362,8 +378,8 @@ "workflow_node.deploy.form.volcengine_clb_resource_type.label": "Resource type", "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.volcengine_clb_resource_type.option.listener.label": "CLB listener", - "workflow_node.deploy.form.volcengine_clb_region.label": "VolcEngine region", - "workflow_node.deploy.form.volcengine_clb_region.placeholder": "Please enter VolcEngine region (e.g. cn-beijing)", + "workflow_node.deploy.form.volcengine_clb_region.label": "VolcEngine CLB region", + "workflow_node.deploy.form.volcengine_clb_region.placeholder": "Please enter VolcEngine CLB region (e.g. cn-beijing)", "workflow_node.deploy.form.volcengine_clb_region.tooltip": "For more information, see https://www.volcengine.com/docs/6406/74892", "workflow_node.deploy.form.volcengine_clb_listener_id.label": "VolcEngine CLB listener ID", "workflow_node.deploy.form.volcengine_clb_listener_id.placeholder": "Please enter VolcEngine CLB listener ID", @@ -374,8 +390,8 @@ "workflow_node.deploy.form.volcengine_live_domain.label": "VolcEngine Live streaming domain", "workflow_node.deploy.form.volcengine_live_domain.placeholder": "Please enter VolcEngine Live streaming domain name", "workflow_node.deploy.form.volcengine_live_domain.tooltip": "For more information, see https://console.volcengine.com/live", - "workflow_node.deploy.form.volcengine_tos_region.label": "VolcEngine region", - "workflow_node.deploy.form.volcengine_tos_region.placeholder": "Please enter VolcEngine region (e.g. cn-beijing)", + "workflow_node.deploy.form.volcengine_tos_region.label": "VolcEngine TOS region", + "workflow_node.deploy.form.volcengine_tos_region.placeholder": "Please enter VolcEngine TOS region (e.g. cn-beijing)", "workflow_node.deploy.form.volcengine_tos_region.tooltip": "For more information, see https://www.volcengine.com/docs/6349/107356", "workflow_node.deploy.form.volcengine_tos_bucket.label": "VolcEngine TOS bucket", "workflow_node.deploy.form.volcengine_tos_bucket.placeholder": "Please enter VolcEngine TOS bucket name", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index d0c9037d..73fe7105 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -147,11 +147,11 @@ "access.form.ssh_username.placeholder": "请输入用户名", "access.form.ssh_password.label": "密码", "access.form.ssh_password.placeholder": "请输入密码", - "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一。", + "access.form.ssh_password.tooltip": "使用密码连接到 SSH 时必填。
该字段与密钥文件字段二选一,如果同时填写优先使用 SSH 密钥登录。", "access.form.ssh_key.label": "SSH 密钥", "access.form.ssh_key.placeholder": "请输入 SSH 密钥文件", "access.form.ssh_key.upload": "选择文件", - "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一。", + "access.form.ssh_key.tooltip": "使用 SSH 密钥连接到 SSH 时必填。
该字段与密码字段二选一,如果同时填写优先使用 SSH 密钥登录。", "access.form.ssh_key_passphrase.label": "SSH 密钥口令", "access.form.ssh_key_passphrase.placeholder": "请输入 SSH 密钥口令", "access.form.ssh_key_passphrase.tooltip": "使用 SSH 密钥连接到 SSH 时选填。", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 15919d22..bb0a2b50 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -38,6 +38,7 @@ "common.provider.acmehttpreq": "Http Request (ACME Proxy)", "common.provider.aliyun": "阿里云", "common.provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", + "common.provider.aliyun.cas-deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", "common.provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN", "common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "common.provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index ea6b43ec..6a6732fd 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -37,9 +37,8 @@ "workflow_node.apply.form.provider_access.placeholder": "请选择 DNS 提供商授权", "workflow_node.apply.form.provider_access.tooltip": "用于 ACME DNS-01 认证时操作域名解析记录,注意与部署阶段所需的主机提供商相区分。", "workflow_node.apply.form.provider_access.button": "新建", - "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。", - "workflow_node.apply.form.aws_route53_region.label": "AWS 区域", - "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS 区域(例如:us-east-1)", + "workflow_node.apply.form.aws_route53_region.label": "AWS Route53 服务区域", + "workflow_node.apply.form.aws_route53_region.placeholder": "请输入 AWS Route53 服务区域(例如:us-east-1)", "workflow_node.apply.form.aws_route53_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.apply.form.aws_route53_hosted_zone_id.label": "AWS Route53 托管区域 ID", "workflow_node.apply.form.aws_route53_hosted_zone_id.placeholder": "请输入 AWS Route53 托管区域 ID", @@ -58,11 +57,11 @@ "workflow_node.apply.form.dns_propagation_timeout.label": "DNS 传播检查超时时间(可选)", "workflow_node.apply.form.dns_propagation_timeout.placeholder": "请输入 DNS 传播检查超时时间", "workflow_node.apply.form.dns_propagation_timeout.unit": "秒", - "workflow_node.apply.form.dns_propagation_timeout.tooltip": "在 ACME DNS-01 认证时等待 DNS 传播检查的最长时间。如果你不了解此选项的用途,保持默认即可。

为空时,将使用提供商提供的默认值。", + "workflow_node.apply.form.dns_propagation_timeout.tooltip": "在 ACME DNS-01 认证时等待 DNS 传播检查的最长时间。如果你不了解此选项的用途,保持默认即可。

不填写时,将使用提供商提供的默认值。", "workflow_node.apply.form.dns_ttl.label": "DNS 解析 TTL(可选)", "workflow_node.apply.form.dns_ttl.placeholder": "请输入 DNS 解析 TTL", "workflow_node.apply.form.dns_ttl.unit": "秒", - "workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。

为空时,将使用提供商提供的默认值。", + "workflow_node.apply.form.dns_ttl.tooltip": "在 ACME DNS-01 认证时 DNS 解析记录的 TTL。如果你不了解此选项的用途,保持默认即可。

不填写时,将使用提供商提供的默认值。", "workflow_node.apply.form.disable_follow_cname.label": "关闭 CNAME 跟随", "workflow_node.apply.form.disable_follow_cname.tooltip": "在 ACME DNS-01 认证时是否关闭 CNAME 跟随。如果你不了解该选项的用途,保持默认即可。点此了解更多。", "workflow_node.apply.form.disable_ari.label": "关闭 ARI 续期", @@ -83,6 +82,7 @@ "workflow_node.deploy.form.provider_access.placeholder": "请选择主机提供商授权", "workflow_node.deploy.form.provider_access.tooltip": "用于部署证书,注意与申请阶段所需的 DNS 提供商相区分。", "workflow_node.deploy.form.provider_access.button": "新建", + "workflow_node.deploy.form.provider_access.guide_for_local": "小贴士:由于表单限制,你同样需要为本地部署选择一个授权 —— 即使它是空白的。", "workflow_node.deploy.form.certificate.label": "待部署证书", "workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书", "workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请阶段。如果选项为空请先确保前序节点配置正确。", @@ -91,8 +91,8 @@ "workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_alb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", - "workflow_node.deploy.form.aliyun_alb_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_alb_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_alb_region.label": "阿里云 ALB 服务地域", + "workflow_node.deploy.form.aliyun_alb_region.placeholder": "请输入阿里云 ALB 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_alb_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/slb/application-load-balancer/product-overview/supported-regions-and-zones", "workflow_node.deploy.form.aliyun_alb_loadbalancer_id.label": "阿里云 ALB 负载均衡器 ID", "workflow_node.deploy.form.aliyun_alb_loadbalancer_id.placeholder": "请输入阿里云 ALB 负载均衡器 ID", @@ -102,13 +102,29 @@ "workflow_node.deploy.form.aliyun_alb_listener_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb", "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名", - "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

为空时,将替换监听器的默认证书。", + "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

不填写时,将替换监听器的默认证书。", + "workflow_node.deploy.form.aliyun_cas_deploy.guide": "小贴士:由于阿里云部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", + "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "阿里云 CAS 服务地域", + "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cas_deploy_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.label": "阿里云云产品资源 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.placeholder": "请输入阿里云云产品资源 ID(多个值请用半角分号隔开)", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.errmsg.invalid": "请输入正确的阿里云云产品资源 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcloudresources

仅支持阿里云产品,注意与各产品本身的实例 ID 区分。", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.title": "修改阿里云云产品资源 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_resource_ids.multiple_input_modal.placeholder": "请输入阿里云云产品资源 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.label": "阿里云联系人 ID(可选)", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.placeholder": "请输入阿里云联系人 ID(多个值请用半角分号隔开)", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.errmsg.invalid": "请输入正确的阿里云联系人 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/api-cas-2020-04-07-listcontact

不填写时,将使用系统联系人列表中的第一个。", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.title": "修改阿里云联系人 ID", + "workflow_node.deploy.form.aliyun_cas_deploy_contact_ids.multiple_input_modal.placeholder": "请输入阿里云联系人 ID", "workflow_node.deploy.form.aliyun_clb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.aliyun_clb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听的证书", "workflow_node.deploy.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", - "workflow_node.deploy.form.aliyun_clb_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_clb_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_clb_region.label": "阿里云 CLB 服务地域", + "workflow_node.deploy.form.aliyun_clb_region.placeholder": "请输入阿里云 CLB 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_clb_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/slb/classic-load-balancer/product-overview/regions-that-support-clb", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.label": "阿里云 CLB 负载均衡器 ID", "workflow_node.deploy.form.aliyun_clb_loadbalancer_id.placeholder": "请输入阿里云 CLB 负载均衡器 ID", @@ -118,21 +134,21 @@ "workflow_node.deploy.form.aliyun_clb_listener_port.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", "workflow_node.deploy.form.aliyun_clb_snidomain.label": "阿里云 CLB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名", - "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb

为空时,将替换监听器的默认证书。", + "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb

不填写时,将替换监听器的默认证书。", "workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 https://cdn.console.aliyun.com

泛域名表示形式为:*.example.com", "workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名", "workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 https://dcdn.console.aliyun.com

泛域名表示形式为:*.example.com", - "workflow_node.deploy.form.aliyun_esa_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_esa_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_esa_region.label": "阿里云 ESA 服务地域", + "workflow_node.deploy.form.aliyun_esa_region.placeholder": "请输入阿里云 ESA 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_esa_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/edge-security-acceleration/esa/api-esa-2024-09-10-endpoint", "workflow_node.deploy.form.aliyun_esa_site_id.label": "阿里云 ESA 站点 ID", "workflow_node.deploy.form.aliyun_esa_site_id.placeholder": "请输入阿里云 ESA 站点 ID", "workflow_node.deploy.form.aliyun_esa_site_id.tooltip": "这是什么?请参阅 https://esa.console.aliyun.com/siteManage/list", - "workflow_node.deploy.form.aliyun_live_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_live_region.label": "阿里云视频直播服务地域", + "workflow_node.deploy.form.aliyun_live_region.placeholder": "请输入阿里云视频直播服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_live_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/live/product-overview/supported-regions", "workflow_node.deploy.form.aliyun_live_domain.label": "阿里云视频直播流域名(支持泛域名)", "workflow_node.deploy.form.aliyun_live_domain.placeholder": "请输入阿里云视频直播流域名", @@ -141,8 +157,8 @@ "workflow_node.deploy.form.aliyun_nlb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书", "workflow_node.deploy.form.aliyun_nlb_resource_type.option.listener.label": "替换指定负载均衡监听器的证书", - "workflow_node.deploy.form.aliyun_nlb_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_nlb_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_nlb_region.label": "阿里云 NLB 服务地域", + "workflow_node.deploy.form.aliyun_nlb_region.placeholder": "请输入阿里云 NLB 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_nlb_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/slb/network-load-balancer/product-overview/regions-that-support-nlb", "workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.label": "阿里云 NLB 负载均衡器 ID", "workflow_node.deploy.form.aliyun_nlb_loadbalancer_id.placeholder": "请输入阿里云 NLB 负载均衡器 ID", @@ -150,8 +166,8 @@ "workflow_node.deploy.form.aliyun_nlb_listener_id.label": "阿里云 NLB 监听器 ID", "workflow_node.deploy.form.aliyun_nlb_listener_id.placeholder": "请输入阿里云 NLB 监听器 ID", "workflow_node.deploy.form.aliyun_nlb_listener_id.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/nlb", - "workflow_node.deploy.form.aliyun_oss_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_oss_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_oss_region.label": "阿里云 OSS 服务地域", + "workflow_node.deploy.form.aliyun_oss_region.placeholder": "请输入阿里云 OSS 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_oss_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints", "workflow_node.deploy.form.aliyun_oss_bucket.label": "阿里云 OSS 存储桶名", "workflow_node.deploy.form.aliyun_oss_bucket.placeholder": "请输入阿里云 OSS 存储桶名", @@ -159,14 +175,14 @@ "workflow_node.deploy.form.aliyun_oss_domain.label": "阿里云 OSS 自定义域名", "workflow_node.deploy.form.aliyun_oss_domain.placeholder": "请输入阿里云 OSS 自定义域名", "workflow_node.deploy.form.aliyun_oss_domain.tooltip": "这是什么?请参阅 see https://oss.console.aliyun.com", - "workflow_node.deploy.form.aliyun_waf_region.label": "阿里云地域", - "workflow_node.deploy.form.aliyun_waf_region.placeholder": "请输入阿里云地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_waf_region.label": "阿里云 WAF 服务地域", + "workflow_node.deploy.form.aliyun_waf_region.placeholder": "请输入阿里云 WAF 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_waf_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/waf/web-application-firewall-3-0/developer-reference/api-waf-openapi-2021-10-01-endpoint", "workflow_node.deploy.form.aliyun_waf_instance_id.label": "阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.placeholder": "请输入阿里云 WAF 实例 ID", "workflow_node.deploy.form.aliyun_waf_instance_id.tooltip": "这是什么?请参阅 https://waf.console.aliyun.com", - "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS 区域", - "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS 区域(例如:us-east-1)", + "workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront 服务区域", + "workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS CloudFront 服务区域(例如:us-east-1)", "workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID", @@ -183,8 +199,8 @@ "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.huaweicloud_cdn_region.label": "华为云区域", - "workflow_node.deploy.form.huaweicloud_cdn_region.placeholder": "请输入华为云区域(例如:cn-north-1)", + "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", "workflow_node.deploy.form.huaweicloud_cdn_domain.label": "华为云 CDN 加速域名", "workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "请输入华为云 CDN 加速域名", @@ -194,8 +210,8 @@ "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.certificate.label": "替换指定证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS 监听器的证书", "workflow_node.deploy.form.huaweicloud_elb_resource_type.option.listener.label": "替换指定监听器的证书", - "workflow_node.deploy.form.huaweicloud_elb_region.label": "华为云区域", - "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "请输入华为云区域(例如:cn-north-1)", + "workflow_node.deploy.form.huaweicloud_elb_region.label": "华为云 ELB 服务区域", + "workflow_node.deploy.form.huaweicloud_elb_region.placeholder": "请输入华为云 ELB 服务区域(例如:cn-north-1)", "workflow_node.deploy.form.huaweicloud_elb_region.tooltip": "这是什么?请参阅 https://console.huaweicloud.com/apiexplorer/#/endpoint", "workflow_node.deploy.form.huaweicloud_elb_certificate_id.label": "华为云 ELB 证书 ID", "workflow_node.deploy.form.huaweicloud_elb_certificate_id.placeholder": "请输入华为云 ELB 证书 ID", @@ -308,8 +324,8 @@ "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.loadbalancer.label": "替换指定实例下的全部 HTTPS/TCPSSL/QUIC 监听器的证书", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.listener.label": "替换指定监听器的证书", "workflow_node.deploy.form.tencentcloud_clb_resource_type.option.ruledomain.label": "替换指定七层监听转发规则域名的证书", - "workflow_node.deploy.form.tencentcloud_clb_region.label": "腾讯云地域", - "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "请输入腾讯云地域(例如:ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_clb_region.label": "腾讯云 CLB 产品地域", + "workflow_node.deploy.form.tencentcloud_clb_region.placeholder": "请输入腾讯云 CLB 服务地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_clb_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/214/33415", "workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.label": "腾讯云 CLB 实例 ID", "workflow_node.deploy.form.tencentcloud_clb_loadbalancer_id.placeholder": "请输入腾讯云 CLB 实例 ID", @@ -319,12 +335,12 @@ "workflow_node.deploy.form.tencentcloud_clb_listener_id.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", "workflow_node.deploy.form.tencentcloud_clb_snidomain.label": "腾讯云 CLB SNI 域名(可选)", "workflow_node.deploy.form.tencentcloud_clb_snidomain.placeholder": "请输入腾讯云 CLB SNI 域名", - "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb

为空时,将替换监听器的默认证书。", + "workflow_node.deploy.form.tencentcloud_clb_snidomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb

不填写时,将替换监听器的默认证书。", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.label": "腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.placeholder": "请输入腾讯云 CLB 七层转发规则域名", "workflow_node.deploy.form.tencentcloud_clb_ruledomain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/clb", - "workflow_node.deploy.form.tencentcloud_cos_region.label": "腾讯云地域", - "workflow_node.deploy.form.tencentcloud_cos_region.placeholder": "请输入腾讯云地域(例如:ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_cos_region.label": "腾讯云 COS 产品地域", + "workflow_node.deploy.form.tencentcloud_cos_region.placeholder": "请输入腾讯云 COS 产品地域(例如:ap-guangzhou)", "workflow_node.deploy.form.tencentcloud_cos_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/436/6224", "workflow_node.deploy.form.tencentcloud_cos_bucket.label": "腾讯云 COS 存储桶名", "workflow_node.deploy.form.tencentcloud_cos_bucket.placeholder": "请输入腾讯云 COS 存储桶名", @@ -347,8 +363,8 @@ "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "请输入优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ucdn", - "workflow_node.deploy.form.ucloud_us3_region.label": "优刻得地域", - "workflow_node.deploy.form.ucloud_us3_region.placeholder": "优刻得地域(例如:cn-bj2)", + "workflow_node.deploy.form.ucloud_us3_region.label": "优刻得 US3 服务地域", + "workflow_node.deploy.form.ucloud_us3_region.placeholder": "优刻得 US3 服务地域(例如:cn-bj2)", "workflow_node.deploy.form.ucloud_us3_region.tooltip": "这是什么?请参阅 https://docs.ucloud.cn/api/summary/regionlist", "workflow_node.deploy.form.ucloud_us3_bucket.label": "优刻得 US3 存储桶名", "workflow_node.deploy.form.ucloud_us3_bucket.placeholder": "请输入优刻得 US3 存储桶名", @@ -362,8 +378,8 @@ "workflow_node.deploy.form.volcengine_clb_resource_type.label": "证书替换方式", "workflow_node.deploy.form.volcengine_clb_resource_type.placeholder": "请选择证书替换方式", "workflow_node.deploy.form.volcengine_clb_resource_type.option.listener.label": "替换指定监听器的证书", - "workflow_node.deploy.form.volcengine_clb_region.label": "火山引擎地域", - "workflow_node.deploy.form.volcengine_clb_region.placeholder": "请输入火山引擎地域(例如:cn-beijing)", + "workflow_node.deploy.form.volcengine_clb_region.label": "火山引擎 CLB 服务地域", + "workflow_node.deploy.form.volcengine_clb_region.placeholder": "请输入火山引擎 CLB 服务地域(例如:cn-beijing)", "workflow_node.deploy.form.volcengine_clb_region.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6406/74892", "workflow_node.deploy.form.volcengine_clb_listener_id.label": "火山引擎 CLB 监听器 ID", "workflow_node.deploy.form.volcengine_clb_listener_id.placeholder": "请输入火山引擎 CLB 监听器 ID", @@ -374,8 +390,8 @@ "workflow_node.deploy.form.volcengine_live_domain.label": "火山引擎视频直播流域名(支持泛域名)", "workflow_node.deploy.form.volcengine_live_domain.placeholder": "请输入火山引擎视频直播流域名", "workflow_node.deploy.form.volcengine_live_domain.tooltip": "这是什么?请参阅 https://console.volcengine.com/live

泛域名表示形式为:*.example.com", - "workflow_node.deploy.form.volcengine_tos_region.label": "火山引擎地域", - "workflow_node.deploy.form.volcengine_tos_region.placeholder": "请输入火山引擎地域(例如:cn-beijing", + "workflow_node.deploy.form.volcengine_tos_region.label": "火山引擎 TOS 服务地域", + "workflow_node.deploy.form.volcengine_tos_region.placeholder": "请输入火山引擎 TOS 服务地域(例如:cn-beijing", "workflow_node.deploy.form.volcengine_tos_region.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6349/107356", "workflow_node.deploy.form.volcengine_tos_bucket.label": "火山引擎 TOS 存储桶名", "workflow_node.deploy.form.volcengine_tos_bucket.placeholder": "请输入火山引擎 TOS 存储桶名", From 6673871db2458e0b22d8cdf21fa0acd38553ddad Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 10 Feb 2025 22:19:08 +0800 Subject: [PATCH 3/5] feat: add tencent cloud ssl-deploy deployer --- internal/deployer/providers.go | 13 +- internal/domain/provider.go | 73 ++++---- .../providers/tencentcloud-clb/defines.go | 2 +- .../tencentcloud-clb/tencentcloud_clb.go | 6 +- .../tencentcloud-clb/tencentcloud_clb_test.go | 2 +- .../tencentcloud_ssl_deploy.go | 156 +++++++++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 5 +- ...loyNodeConfigFormTencentCloudCLBConfig.tsx | 16 +- ...eConfigFormTencentCloudSSLDeployConfig.tsx | 159 ++++++++++++++++++ ui/src/domain/provider.ts | 6 +- ui/src/i18n/locales/en/nls.common.json | 3 +- .../i18n/locales/en/nls.workflow.nodes.json | 10 ++ ui/src/i18n/locales/zh/nls.common.json | 3 +- .../i18n/locales/zh/nls.workflow.nodes.json | 15 +- 14 files changed, 414 insertions(+), 55 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudSSLDeployConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 269f1130..4c45b724 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -34,6 +34,7 @@ import ( providerTencentCloudCSS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-css" providerTencentCloudECDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ecdn" providerTencentCloudEO "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-eo" + providerTencentCloudSSLDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy" providerUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" providerUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" providerVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" @@ -387,7 +388,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, return deployer, logger, err } - case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO: + case domain.DeployProviderTypeTencentCloudCDN, domain.DeployProviderTypeTencentCloudCLB, domain.DeployProviderTypeTencentCloudCOS, domain.DeployProviderTypeTencentCloudCSS, domain.DeployProviderTypeTencentCloudECDN, domain.DeployProviderTypeTencentCloudEO, domain.DeployProviderTypeTencentCloudSSLDeploy: { access := domain.AccessConfigForTencentCloud{} if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { @@ -450,6 +451,16 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, }, logger) return deployer, logger, err + case domain.DeployProviderTypeTencentCloudSSLDeploy: + deployer, err := providerTencentCloudSSLDeploy.NewWithLogger(&providerTencentCloudSSLDeploy.TencentCloudSSLDeployDeployerConfig{ + SecretId: access.SecretId, + SecretKey: access.SecretKey, + Region: maps.GetValueAsString(options.ProviderDeployConfig, "region"), + ResourceType: maps.GetValueAsString(options.ProviderDeployConfig, "resourceType"), + ResourceIds: slices.Filter(strings.Split(maps.GetValueAsString(options.ProviderDeployConfig, "resourceIds"), ";"), func(s string) bool { return s != "" }), + }, logger) + return deployer, logger, err + default: break } diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 8d192aaf..cfbe2d70 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -82,40 +82,41 @@ type DeployProviderType string NOTICE: If you add new constant, please keep ASCII order. */ const ( - DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") - DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-cas-deploy") - DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") - DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") - DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") - DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") - DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") - DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") - DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") - DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") - DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") - DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") - DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") - DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") - DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications") - DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn") - DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb") - DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") - DeployProviderTypeLocal = DeployProviderType("local") - DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") - DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") - DeployProviderTypeSSH = DeployProviderType("ssh") - DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") - DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb") - DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos") - DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") - DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") - DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") - DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") - DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") - DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") - DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") - DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") - DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") - DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") - DeployProviderTypeWebhook = DeployProviderType("webhook") + DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb") + DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy") + DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn") + DeployProviderTypeAliyunCLB = DeployProviderType("aliyun-clb") + DeployProviderTypeAliyunDCDN = DeployProviderType("aliyun-dcdn") + DeployProviderTypeAliyunESA = DeployProviderType("aliyun-esa") + DeployProviderTypeAliyunLive = DeployProviderType("aliyun-live") + DeployProviderTypeAliyunNLB = DeployProviderType("aliyun-nlb") + DeployProviderTypeAliyunOSS = DeployProviderType("aliyun-oss") + DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") + DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") + DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") + DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") + DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") + DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications") + DeployProviderTypeHuaweiCloudCDN = DeployProviderType("huaweicloud-cdn") + DeployProviderTypeHuaweiCloudELB = DeployProviderType("huaweicloud-elb") + DeployProviderTypeKubernetesSecret = DeployProviderType("k8s-secret") + DeployProviderTypeLocal = DeployProviderType("local") + DeployProviderTypeQiniuCDN = DeployProviderType("qiniu-cdn") + DeployProviderTypeQiniuPili = DeployProviderType("qiniu-pili") + DeployProviderTypeSSH = DeployProviderType("ssh") + DeployProviderTypeTencentCloudCDN = DeployProviderType("tencentcloud-cdn") + DeployProviderTypeTencentCloudCLB = DeployProviderType("tencentcloud-clb") + DeployProviderTypeTencentCloudCOS = DeployProviderType("tencentcloud-cos") + DeployProviderTypeTencentCloudCSS = DeployProviderType("tencentcloud-css") + DeployProviderTypeTencentCloudECDN = DeployProviderType("tencentcloud-ecdn") + DeployProviderTypeTencentCloudEO = DeployProviderType("tencentcloud-eo") + DeployProviderTypeTencentCloudSSLDeploy = DeployProviderType("tencentcloud-ssldeploy") + DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") + DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") + DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") + DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") + DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") + DeployProviderTypeVolcEngineLive = DeployProviderType("volcengine-live") + DeployProviderTypeVolcEngineTOS = DeployProviderType("volcengine-tos") + DeployProviderTypeWebhook = DeployProviderType("webhook") ) diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go index 47eedfb0..7e7eace9 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/defines.go @@ -4,7 +4,7 @@ type DeployResourceType string const ( // 资源类型:通过 SSL 服务部署到云资源实例。 - DEPLOY_RESOURCE_USE_SSLDEPLOY = DeployResourceType("ssl-deploy") + DEPLOY_RESOURCE_VIA_SSLDEPLOY = DeployResourceType("ssl-deploy") // 资源类型:部署到指定负载均衡器。 DEPLOY_RESOURCE_LOADBALANCER = DeployResourceType("loadbalancer") // 资源类型:部署到指定监听器。 diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go index d67eb383..e6982817 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb.go @@ -96,8 +96,8 @@ func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, pr // 根据部署资源类型决定部署方式 switch d.config.ResourceType { - case DEPLOY_RESOURCE_USE_SSLDEPLOY: - if err := d.deployToInstanceUseSsl(ctx, upres.CertId); err != nil { + case DEPLOY_RESOURCE_VIA_SSLDEPLOY: + if err := d.deployViaSslService(ctx, upres.CertId); err != nil { return nil, err } @@ -123,7 +123,7 @@ func (d *TencentCloudCLBDeployer) Deploy(ctx context.Context, certPem string, pr return &deployer.DeployResult{}, nil } -func (d *TencentCloudCLBDeployer) deployToInstanceUseSsl(ctx context.Context, cloudCertId string) error { +func (d *TencentCloudCLBDeployer) deployViaSslService(ctx context.Context, cloudCertId string) error { if d.config.LoadbalancerId == "" { return errors.New("config `loadbalancerId` is required") } diff --git a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go index 0af11dff..74a1e23e 100644 --- a/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go +++ b/internal/pkg/core/deployer/providers/tencentcloud-clb/tencentcloud_clb_test.go @@ -68,7 +68,7 @@ func TestDeploy(t *testing.T) { SecretId: fSecretId, SecretKey: fSecretKey, Region: fRegion, - ResourceType: provider.DEPLOY_RESOURCE_USE_SSLDEPLOY, + ResourceType: provider.DEPLOY_RESOURCE_VIA_SSLDEPLOY, LoadbalancerId: fLoadbalancerId, ListenerId: fListenerId, Domain: fDomain, diff --git a/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go new file mode 100644 index 00000000..37c00ea4 --- /dev/null +++ b/internal/pkg/core/deployer/providers/tencentcloud-ssl-deploy/tencentcloud_ssl_deploy.go @@ -0,0 +1,156 @@ +package tencentcloudssldeploy + +import ( + "context" + "errors" + "fmt" + "time" + + xerrors "github.com/pkg/errors" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + tcSsl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploaderp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/tencentcloud-ssl" +) + +type TencentCloudSSLDeployDeployerConfig struct { + // 腾讯云 SecretId。 + SecretId string `json:"secretId"` + // 腾讯云 SecretKey。 + SecretKey string `json:"secretKey"` + // 腾讯云地域。 + Region string `json:"region"` + // 腾讯云云资源类型。 + ResourceType string `json:"resourceType"` + // 腾讯云云资源 ID 数组。 + ResourceIds []string `json:"resourceIds"` +} + +type TencentCloudSSLDeployDeployer struct { + config *TencentCloudSSLDeployDeployerConfig + logger logger.Logger + sdkClient *tcSsl.Client + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*TencentCloudSSLDeployDeployer)(nil) + +func New(config *TencentCloudSSLDeployDeployerConfig) (*TencentCloudSSLDeployDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *TencentCloudSSLDeployDeployerConfig, logger logger.Logger) (*TencentCloudSSLDeployDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.SecretId, config.SecretKey, config.Region) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploaderp.New(&uploaderp.TencentCloudSSLUploaderConfig{ + SecretId: config.SecretId, + SecretKey: config.SecretKey, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &TencentCloudSSLDeployDeployer{ + logger: logger, + config: config, + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *TencentCloudSSLDeployDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.ResourceType == "" { + return nil, errors.New("config `resourceType` is required") + } + if len(d.config.ResourceIds) == 0 { + return nil, errors.New("config `resourceIds` is required") + } + + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } + + d.logger.Logt("certificate file uploaded", upres) + + // 证书部署到云资源实例列表 + // REF: https://cloud.tencent.com/document/product/400/91667 + deployCertificateInstanceReq := tcSsl.NewDeployCertificateInstanceRequest() + deployCertificateInstanceReq.CertificateId = common.StringPtr(upres.CertId) + deployCertificateInstanceReq.ResourceType = common.StringPtr(d.config.ResourceType) + deployCertificateInstanceReq.InstanceIdList = common.StringPtrs(d.config.ResourceIds) + deployCertificateInstanceReq.Status = common.Int64Ptr(1) + deployCertificateInstanceResp, err := d.sdkClient.DeployCertificateInstance(deployCertificateInstanceReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DeployCertificateInstance'") + } else if deployCertificateInstanceResp.Response == nil || deployCertificateInstanceResp.Response.DeployRecordId == nil { + return nil, errors.New("failed to create deploy record") + } + + d.logger.Logt("已部署证书到云资源实例", deployCertificateInstanceResp.Response) + + // 循环获取部署任务详情,等待任务状态变更 + // REF: https://cloud.tencent.com.cn/document/api/400/91658 + for { + if ctx.Err() != nil { + return nil, ctx.Err() + } + + describeHostDeployRecordDetailReq := tcSsl.NewDescribeHostDeployRecordDetailRequest() + describeHostDeployRecordDetailReq.DeployRecordId = common.StringPtr(fmt.Sprintf("%d", *deployCertificateInstanceResp.Response.DeployRecordId)) + describeHostDeployRecordDetailReq.Limit = common.Uint64Ptr(100) + describeHostDeployRecordDetailResp, err := d.sdkClient.DescribeHostDeployRecordDetail(describeHostDeployRecordDetailReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'ssl.DescribeHostDeployRecordDetail'") + } + + if describeHostDeployRecordDetailResp.Response.TotalCount == nil { + return nil, errors.New("部署任务状态异常") + } else { + acc := int64(0) + if describeHostDeployRecordDetailResp.Response.SuccessTotalCount != nil { + acc += *describeHostDeployRecordDetailResp.Response.SuccessTotalCount + } + if describeHostDeployRecordDetailResp.Response.FailedTotalCount != nil { + acc += *describeHostDeployRecordDetailResp.Response.FailedTotalCount + } + + if acc == *describeHostDeployRecordDetailResp.Response.TotalCount { + d.logger.Logt("已获取部署任务详情", describeHostDeployRecordDetailResp) + break + } + } + + d.logger.Logt("部署任务未完成 ...") + time.Sleep(time.Second * 5) + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(secretId, secretKey, region string) (*tcSsl.Client, error) { + credential := common.NewCredential(secretId, secretKey) + + client, err := tcSsl.NewClient(credential, region, profile.NewClientProfile()) + if err != nil { + return nil, err + } + + return client, nil +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 7d4f3dbd..fe03a6ab 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -43,6 +43,7 @@ import DeployNodeConfigFormTencentCloudCOSConfig from "./DeployNodeConfigFormTen import DeployNodeConfigFormTencentCloudCSSConfig from "./DeployNodeConfigFormTencentCloudCSSConfig.tsx"; import DeployNodeConfigFormTencentCloudECDNConfig from "./DeployNodeConfigFormTencentCloudECDNConfig.tsx"; import DeployNodeConfigFormTencentCloudEOConfig from "./DeployNodeConfigFormTencentCloudEOConfig.tsx"; +import DeployNodeConfigFormTencentCloudSSLDeployConfig from "./DeployNodeConfigFormTencentCloudSSLDeployConfig"; import DeployNodeConfigFormUCloudUCDNConfig from "./DeployNodeConfigFormUCloudUCDNConfig.tsx"; import DeployNodeConfigFormUCloudUS3Config from "./DeployNodeConfigFormUCloudUS3Config.tsx"; import DeployNodeConfigFormVolcEngineCDNConfig from "./DeployNodeConfigFormVolcEngineCDNConfig.tsx"; @@ -125,7 +126,7 @@ const DeployNodeConfigForm = forwardRef; - case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOYMENT_JOB: + case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY: return ; case DEPLOY_PROVIDERS.ALIYUN_CLB: return ; @@ -179,6 +180,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.TENCENTCLOUD_EO: return ; + case DEPLOY_PROVIDERS.TENCENTCLOUD_SSL_DEPLOY: + return ; case DEPLOY_PROVIDERS.UCLOUD_UCDN: return ; case DEPLOY_PROVIDERS.UCLOUD_US3: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx index dd6f6ead..2e7dc127 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormTencentCloudCLBConfig.tsx @@ -22,14 +22,14 @@ export type DeployNodeConfigFormTencentCloudCLBConfigProps = { onValuesChange?: (values: DeployNodeConfigFormTencentCloudCLBConfigFieldValues) => void; }; -const RESOURCE_TYPE_SSLDEPLOY = "ssl-deploy" as const; +const RESOURCE_TYPE_VIA_SSLDEPLOY = "ssl-deploy" as const; const RESOURCE_TYPE_LOADBALANCER = "loadbalancer" as const; const RESOURCE_TYPE_LISTENER = "listener" as const; const RESOURCE_TYPE_RULEDOMAIN = "ruledomain" as const; const initFormModel = (): DeployNodeConfigFormTencentCloudCLBConfigFieldValues => { return { - resourceType: RESOURCE_TYPE_SSLDEPLOY, + resourceType: RESOURCE_TYPE_VIA_SSLDEPLOY, }; }; @@ -44,7 +44,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ const formSchema = z.object({ resourceType: z.union( - [z.literal(RESOURCE_TYPE_SSLDEPLOY), z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER), z.literal(RESOURCE_TYPE_RULEDOMAIN)], + [z.literal(RESOURCE_TYPE_VIA_SSLDEPLOY), z.literal(RESOURCE_TYPE_LOADBALANCER), z.literal(RESOURCE_TYPE_LISTENER), z.literal(RESOURCE_TYPE_RULEDOMAIN)], { message: t("workflow_node.deploy.form.tencentcloud_clb_resource_type.placeholder") } ), region: z @@ -62,7 +62,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ .trim() .nullish() .refine( - (v) => ![RESOURCE_TYPE_SSLDEPLOY, RESOURCE_TYPE_LISTENER, RESOURCE_TYPE_RULEDOMAIN].includes(fieldResourceType) || !!v?.trim(), + (v) => ![RESOURCE_TYPE_VIA_SSLDEPLOY, RESOURCE_TYPE_LISTENER, RESOURCE_TYPE_RULEDOMAIN].includes(fieldResourceType) || !!v?.trim(), t("workflow_node.deploy.form.tencentcloud_clb_listener_id.placeholder") ), domain: z @@ -89,7 +89,7 @@ const DeployNodeConfigFormTencentCloudCLBConfig = ({ > + + + } + > + + + { + formInst.setFieldValue("resourceIds", e.target.value); + }} + /> + + + + + } + onChange={(value) => { + formInst.setFieldValue("resourceIds", value); + }} + /> + + + + + } /> + + + ); +}; + +const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: string; trigger?: React.ReactNode; onChange?: (value: string) => void }) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + resourceIds: z.array(z.string()).refine((v) => { + return v.every((e) => !e?.trim() || /^[A-Za-z0-9._-]+$/.test(e)); + }, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm({ + name: "workflowNodeDeployConfigFormTencentCloudSSLDeployResourceIdsModalInput", + initialValues: { resourceIds: value?.split(MULTIPLE_INPUT_DELIMITER) }, + onSubmit: (values) => { + onChange?.( + values.resourceIds + .map((e) => e.trim()) + .filter((e) => !!e) + .join(MULTIPLE_INPUT_DELIMITER) + ); + }, + }); + + return ( + + + + + + ); +}); + +export default DeployNodeConfigFormTencentCloudSSLDeployConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 6e0758a1..2edf0531 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -176,7 +176,7 @@ export const applyDNSProvidersMap: Maphttps://console.tencentcloud.com/edgeone", + "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "TIPS: You need to go to the Tencent Cloud console to check the actual deployment results by yourself, because Tencent Cloud deployment tasks are running asynchronously.", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "Tencent Cloud service region", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "Please enter Tencent Cloud service region (e.g. ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.tooltip": "For more information, see https://www.tencentcloud.com/document/product/1007/36573", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.label": "Tencent Cloud resource IDs", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.placeholder": "Please enter Tencent Cloud resource IDs (separated by semicolons)", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid": "Please enter a valid Tencent Cloud resource ID", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.tooltip": "For more information, see https://cloud.tencent.com.cn/document/product/400/91667", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title": "Change Tencent Cloud resource IDs", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.placeholder": "Please enter Tencent Cloud resouce ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "UCloud UCDN domain ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "Please enter UCloud UCDN domain ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "For more information, see https://console.ucloud-global.com/ucdn", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index bb0a2b50..06ee5f73 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -38,7 +38,7 @@ "common.provider.acmehttpreq": "Http Request (ACME Proxy)", "common.provider.aliyun": "阿里云", "common.provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", - "common.provider.aliyun.cas-deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", + "common.provider.aliyun.cas_deploy": "阿里云 - 通过数字证书管理服务 CAS 创建部署任务", "common.provider.aliyun.cdn": "阿里云 - 内容分发网络 CDN", "common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "common.provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN", @@ -89,6 +89,7 @@ "common.provider.tencentcloud.dns": "腾讯云 - 云解析 DNS", "common.provider.tencentcloud.ecdn": "腾讯云 - 全站加速网络 ECDN", "common.provider.tencentcloud.eo": "腾讯云 - 边缘安全加速平台 EdgeOne", + "common.provider.tencentcloud.ssl_deploy": "腾讯云 - 通过 SSL 证书服务创建部署任务", "common.provider.ucloud": "优刻得", "common.provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", "common.provider.ucloud.us3": "优刻得 - 对象存储 US3", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 6a6732fd..a4436d4e 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -103,7 +103,7 @@ "workflow_node.deploy.form.aliyun_alb_snidomain.label": "阿里云 ALB 扩展域名(可选)", "workflow_node.deploy.form.aliyun_alb_snidomain.placeholder": "请输入阿里云 ALB 扩展域名", "workflow_node.deploy.form.aliyun_alb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/alb

不填写时,将替换监听器的默认证书。", - "workflow_node.deploy.form.aliyun_cas_deploy.guide": "小贴士:由于阿里云部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", + "workflow_node.deploy.form.aliyun_cas_deploy.guide": "小贴士:由于阿里云证书部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往阿里云控制台查询。", "workflow_node.deploy.form.aliyun_cas_deploy_region.label": "阿里云 CAS 服务地域", "workflow_node.deploy.form.aliyun_cas_deploy_region.placeholder": "请输入阿里云 CAS 服务地域(例如:cn-hangzhou)", "workflow_node.deploy.form.aliyun_cas_deploy_region.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ssl-certificate/developer-reference/endpoints", @@ -360,6 +360,19 @@ "workflow_node.deploy.form.tencentcloud_eo_domain.label": "腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.placeholder": "请输入腾讯云 EdgeOne 加速域名", "workflow_node.deploy.form.tencentcloud_eo_domain.tooltip": "这是什么?请参阅 https://console.cloud.tencent.com/edgeone", + "workflow_node.deploy.form.tencentcloud_ssl_deploy.guide": "小贴士:由于腾讯云证书部署任务是异步的,此节点若执行成功仅代表已创建部署任务,实际部署结果需要你自行前往腾讯云控制台查询。", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.label": "腾讯云云产品地域", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.placeholder": "请输入腾讯云云产品地域(例如:ap-guangzhou)", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_region.tooltip": "这是什么?请参阅 https://cloud.tencent.com.cn/document/product/400/41659", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_type.label": "腾讯云云产品资源类型", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_type.placeholder": "请输入腾讯云产品资源类型", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_type.tooltip": "这是什么?请参阅 https://cloud.tencent.com.cn/document/product/400/91667", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.label": "腾讯云云产品资源 ID", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.placeholder": "请输入腾讯云云产品资源 ID(多个值请用半角分号隔开)", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid": "请输入正确的腾讯云云产品资源 ID", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.tooltip": "这是什么?请参阅 https://cloud.tencent.com.cn/document/product/400/91667

注意与各产品本身的实例 ID 区分。", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.title": "修改腾讯云云产品资源 ID", + "workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.multiple_input_modal.placeholder": "请输入腾讯云云产品资源 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.label": "优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.placeholder": "请输入优刻得 UCDN 域名 ID", "workflow_node.deploy.form.ucloud_ucdn_domain_id.tooltip": "这是什么?请参阅 https://console.ucloud.cn/ucdn", From 81fe230be4685aaeba09568867f7f0d453626586 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 10 Feb 2025 23:56:01 +0800 Subject: [PATCH 4/5] feat: add baota panel deployer --- internal/deployer/providers.go | 16 +++ internal/domain/access.go | 5 + internal/domain/provider.go | 2 + .../baotapanel-site/baotapanel-site.go | 81 +++++++++++++ .../baotapanel-site/baotapanel-site_test.go | 75 ++++++++++++ internal/pkg/vendors/btpanel-sdk/api.go | 26 +++++ internal/pkg/vendors/btpanel-sdk/client.go | 107 ++++++++++++++++++ internal/pkg/vendors/gname-sdk/client.go | 10 +- migrations/1739202463_updated_access.go | 99 ++++++++++++++++ ui/public/imgs/providers/baotapanel.svg | 1 + ui/src/components/access/AccessForm.tsx | 3 + .../access/AccessFormBaotaPanelConfig.tsx | 72 ++++++++++++ .../workflow/node/DeployNodeConfigForm.tsx | 3 + ...ployNodeConfigFormBaotaPanelSiteConfig.tsx | 64 +++++++++++ ui/src/domain/access.ts | 6 + ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 + ui/src/i18n/locales/en/nls.common.json | 2 + .../i18n/locales/en/nls.workflow.nodes.json | 3 + ui/src/i18n/locales/zh/nls.access.json | 6 + ui/src/i18n/locales/zh/nls.common.json | 2 + .../i18n/locales/zh/nls.workflow.nodes.json | 3 + 22 files changed, 591 insertions(+), 5 deletions(-) create mode 100644 internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site.go create mode 100644 internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site_test.go create mode 100644 internal/pkg/vendors/btpanel-sdk/api.go create mode 100644 internal/pkg/vendors/btpanel-sdk/client.go create mode 100644 migrations/1739202463_updated_access.go create mode 100644 ui/public/imgs/providers/baotapanel.svg create mode 100644 ui/src/components/access/AccessFormBaotaPanelConfig.tsx create mode 100644 ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 4c45b724..056ce160 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -18,6 +18,7 @@ import ( providerAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" providerAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" providerBaiduCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-cdn" + providerBaotaPanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" providerBytePlusCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/byteplus-cdn" providerDogeCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/dogecloud-cdn" providerEdgioApplications "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/edgio-applications" @@ -210,6 +211,21 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger, } } + case domain.DeployProviderTypeBaotaPanelSite: + { + access := domain.AccessConfigForBaotaPanel{} + if err := maps.Decode(options.ProviderAccessConfig, &access); err != nil { + return nil, nil, fmt.Errorf("failed to decode provider access config: %w", err) + } + + deployer, err := providerBaotaPanelSite.NewWithLogger(&providerBaotaPanelSite.BaotaPanelSiteDeployerConfig{ + ApiUrl: access.ApiUrl, + ApiKey: access.ApiKey, + SiteName: maps.GetValueAsString(options.ProviderDeployConfig, "siteName"), + }, logger) + return deployer, logger, err + } + case domain.DeployProviderTypeBytePlusCDN: { access := domain.AccessConfigForBytePlus{} diff --git a/internal/domain/access.go b/internal/domain/access.go index c8448c21..e6d94764 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -54,6 +54,11 @@ type AccessConfigForBaiduCloud struct { SecretAccessKey string `json:"secretAccessKey"` } +type AccessConfigForBaotaPanel struct { + ApiUrl string `json:"apiUrl"` + ApiKey string `json:"apiKey"` +} + type AccessConfigForBytePlus struct { AccessKey string `json:"accessKey"` SecretKey string `json:"secretKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index cfbe2d70..49bf2cee 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -14,6 +14,7 @@ const ( AccessProviderTypeAWS = AccessProviderType("aws") AccessProviderTypeAzure = AccessProviderType("azure") AccessProviderTypeBaiduCloud = AccessProviderType("baiducloud") + AccessProviderTypeBaotaPanel = AccessProviderType("baotapanel") AccessProviderTypeBytePlus = AccessProviderType("byteplus") AccessProviderTypeCloudflare = AccessProviderType("cloudflare") AccessProviderTypeClouDNS = AccessProviderType("cloudns") @@ -94,6 +95,7 @@ const ( DeployProviderTypeAliyunWAF = DeployProviderType("aliyun-waf") DeployProviderTypeAWSCloudFront = DeployProviderType("aws-cloudfront") DeployProviderTypeBaiduCloudCDN = DeployProviderType("baiducloud-cdn") + DeployProviderTypeBaotaPanelSite = DeployProviderType("baotapanel-site") DeployProviderTypeBytePlusCDN = DeployProviderType("byteplus-cdn") DeployProviderTypeDogeCloudCDN = DeployProviderType("dogecloud-cdn") DeployProviderTypeEdgioApplications = DeployProviderType("edgio-applications") diff --git a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site.go b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site.go new file mode 100644 index 00000000..336e2a5d --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site.go @@ -0,0 +1,81 @@ +package baotapanelsite + +import ( + "context" + "errors" + + xerrors "github.com/pkg/errors" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/logger" + btsdk "github.com/usual2970/certimate/internal/pkg/vendors/btpanel-sdk" +) + +type BaotaPanelSiteDeployerConfig struct { + // 宝塔面板地址。 + ApiUrl string `json:"apiUrl"` + // 宝塔面板接口密钥。 + ApiKey string `json:"apiKey"` + // 站点名称 + SiteName string `json:"siteName"` +} + +type BaotaPanelSiteDeployer struct { + config *BaotaPanelSiteDeployerConfig + logger logger.Logger + sdkClient *btsdk.BaoTaPanelClient +} + +var _ deployer.Deployer = (*BaotaPanelSiteDeployer)(nil) + +func New(config *BaotaPanelSiteDeployerConfig) (*BaotaPanelSiteDeployer, error) { + return NewWithLogger(config, logger.NewNilLogger()) +} + +func NewWithLogger(config *BaotaPanelSiteDeployerConfig, logger logger.Logger) (*BaotaPanelSiteDeployer, error) { + if config == nil { + return nil, errors.New("config is nil") + } + + if logger == nil { + return nil, errors.New("logger is nil") + } + + client, err := createSdkClient(config.ApiUrl, config.ApiKey) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + return &BaotaPanelSiteDeployer{ + logger: logger, + config: config, + sdkClient: client, + }, nil +} + +func (d *BaotaPanelSiteDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + if d.config.SiteName == "" { + return nil, errors.New("config `siteName` is required") + } + + // 设置站点 SSL 证书 + setSiteSSLReq := &btsdk.SetSiteSSLRequest{ + SiteName: d.config.SiteName, + Type: "1", + Key: privkeyPem, + Csr: certPem, + } + setSiteSSLResp, err := d.sdkClient.SetSiteSSL(setSiteSSLReq) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'bt.SetSiteSSL'") + } + + d.logger.Logt("已设置站点 SSL 证书", setSiteSSLResp) + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(apiUrl, apiKey string) (*btsdk.BaoTaPanelClient, error) { + client := btsdk.NewBaoTaPanelClient(apiUrl, apiKey) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site_test.go b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site_test.go new file mode 100644 index 00000000..486e8428 --- /dev/null +++ b/internal/pkg/core/deployer/providers/baotapanel-site/baotapanel-site_test.go @@ -0,0 +1,75 @@ +package baotapanelsite_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baotapanel-site" +) + +var ( + fInputCertPath string + fInputKeyPath string + fApiUrl string + fApiKey string + fSiteName string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_BAOTAPANELSITE_" + + 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", "", "") +} + +/* +Shell command to run this test: + + go test -v ./baotapanel_site_test.go -args \ + --CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_BAOTAPANELSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIURL="your-baota-panel-url" \ + --CERTIMATE_DEPLOYER_BAOTAPANELSITE_APIKEY="your-baota-panel-key" \ + --CERTIMATE_DEPLOYER_BAOTAPANELSITE_SITENAME="your-baota-site-name" +*/ +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), + }, "\n")) + + deployer, err := provider.New(&provider.BaotaPanelSiteDeployerConfig{ + ApiUrl: fApiUrl, + ApiKey: fApiKey, + SiteName: fSiteName, + }) + 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/vendors/btpanel-sdk/api.go b/internal/pkg/vendors/btpanel-sdk/api.go new file mode 100644 index 00000000..5c58e3df --- /dev/null +++ b/internal/pkg/vendors/btpanel-sdk/api.go @@ -0,0 +1,26 @@ +package btpanelsdk + +type BaseResponse interface { + GetStatus() *bool + GetMsg() *string +} + +type SetSiteSSLRequest struct { + Type string `json:"type"` + SiteName string `json:"siteName"` + Key string `json:"key"` + Csr string `json:"csr"` +} + +type SetSiteSSLResponse struct { + Status *bool `json:"status,omitempty"` + Msg *string `json:"msg,omitempty"` +} + +func (r *SetSiteSSLResponse) GetStatus() *bool { + return r.Status +} + +func (r *SetSiteSSLResponse) GetMsg() *string { + return r.Msg +} diff --git a/internal/pkg/vendors/btpanel-sdk/client.go b/internal/pkg/vendors/btpanel-sdk/client.go new file mode 100644 index 00000000..43e3d415 --- /dev/null +++ b/internal/pkg/vendors/btpanel-sdk/client.go @@ -0,0 +1,107 @@ +package btpanelsdk + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/go-resty/resty/v2" + + "github.com/usual2970/certimate/internal/pkg/utils/maps" +) + +type BaoTaPanelClient struct { + apiHost string + apiKey string + client *resty.Client +} + +func NewBaoTaPanelClient(apiHost, apiKey string) *BaoTaPanelClient { + client := resty.New() + + return &BaoTaPanelClient{ + apiHost: apiHost, + apiKey: apiKey, + client: client, + } +} + +func (c *BaoTaPanelClient) WithTimeout(timeout time.Duration) *BaoTaPanelClient { + c.client.SetTimeout(timeout) + return c +} + +func (c *BaoTaPanelClient) SetSiteSSL(req *SetSiteSSLRequest) (*SetSiteSSLResponse, error) { + params := make(map[string]any) + jsonData, _ := json.Marshal(req) + json.Unmarshal(jsonData, ¶ms) + + result := SetSiteSSLResponse{} + err := c.sendRequestWithResult("/site?action=SetSSL", params, &result) + if err != nil { + return nil, err + } + return &result, nil +} + +func (c *BaoTaPanelClient) generateSignature(timestamp string) string { + keyMd5 := md5.Sum([]byte(c.apiKey)) + keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:])) + + signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex)) + signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:])) + return signMd5Hex +} + +func (c *BaoTaPanelClient) sendRequest(path string, params map[string]any) (*resty.Response, error) { + if params == nil { + params = make(map[string]any) + } + + timestamp := time.Now().Unix() + params["request_time"] = timestamp + params["request_token"] = c.generateSignature(fmt.Sprintf("%d", timestamp)) + + url := strings.TrimRight(c.apiHost, "/") + path + req := c.client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(params) + resp, err := req.Post(url) + if err != nil { + return nil, fmt.Errorf("baota: failed to send request: %w", err) + } + + if resp.IsError() { + return nil, fmt.Errorf("baota: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + } + + return resp, nil +} + +func (c *BaoTaPanelClient) sendRequestWithResult(path string, params map[string]any, result BaseResponse) error { + resp, err := c.sendRequest(path, params) + if err != nil { + return err + } + + jsonResp := make(map[string]any) + if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil { + return fmt.Errorf("baota: failed to parse response: %w", err) + } + if err := maps.Decode(jsonResp, &result); err != nil { + return fmt.Errorf("baota: failed to parse response: %w", err) + } + + if result.GetStatus() != nil && !*result.GetStatus() { + if result.GetMsg() == nil { + return fmt.Errorf("baota api error: unknown error") + } else { + return fmt.Errorf("baota api error: %s", result.GetMsg()) + } + } + + return nil +} diff --git a/internal/pkg/vendors/gname-sdk/client.go b/internal/pkg/vendors/gname-sdk/client.go index d034cfeb..6bf4e2db 100644 --- a/internal/pkg/vendors/gname-sdk/client.go +++ b/internal/pkg/vendors/gname-sdk/client.go @@ -130,11 +130,11 @@ func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Re SetFormData(data) resp, err := req.Post(url) if err != nil { - return nil, fmt.Errorf("failed to send request: %w", err) + return nil, fmt.Errorf("gname: failed to send request: %w", err) } if resp.IsError() { - return nil, fmt.Errorf("unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) + return nil, fmt.Errorf("gname: unexpected status code: %d, %s", resp.StatusCode(), resp.Body()) } return resp, nil @@ -148,14 +148,14 @@ func (c *GnameClient) sendRequestWithResult(path string, params map[string]any, jsonResp := make(map[string]any) if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil { - return fmt.Errorf("failed to parse response: %w", err) + return fmt.Errorf("gname: failed to parse response: %w", err) } if err := maps.Decode(jsonResp, &result); err != nil { - return fmt.Errorf("failed to parse response: %w", err) + return fmt.Errorf("gname: failed to parse response: %w", err) } if result.GetCode() != 1 { - return fmt.Errorf("API error: %s", result.GetMsg()) + return fmt.Errorf("gname api error: %s", result.GetMsg()) } return nil diff --git a/migrations/1739202463_updated_access.go b/migrations/1739202463_updated_access.go new file mode 100644 index 00000000..ccaffbce --- /dev/null +++ b/migrations/1739202463_updated_access.go @@ -0,0 +1,99 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update field + if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{ + "hidden": false, + "id": "hwy7m03o", + "maxSelect": 1, + "name": "provider", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "acmehttpreq", + "aliyun", + "aws", + "azure", + "baiducloud", + "baotapanel", + "byteplus", + "cloudflare", + "dogecloud", + "godaddy", + "huaweicloud", + "k8s", + "local", + "namedotcom", + "namesilo", + "powerdns", + "qiniu", + "ssh", + "tencentcloud", + "ucloud", + "volcengine", + "webhook" + ] + }`)); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update field + if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{ + "hidden": false, + "id": "hwy7m03o", + "maxSelect": 1, + "name": "provider", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "acmehttpreq", + "aliyun", + "aws", + "azure", + "baiducloud", + "byteplus", + "cloudflare", + "dogecloud", + "godaddy", + "huaweicloud", + "k8s", + "local", + "namedotcom", + "namesilo", + "powerdns", + "qiniu", + "ssh", + "tencentcloud", + "ucloud", + "volcengine", + "webhook" + ] + }`)); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/ui/public/imgs/providers/baotapanel.svg b/ui/public/imgs/providers/baotapanel.svg new file mode 100644 index 00000000..34ab8ec8 --- /dev/null +++ b/ui/public/imgs/providers/baotapanel.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index c6c44d2d..d9632f28 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -14,6 +14,7 @@ import AccessFormAliyunConfig from "./AccessFormAliyunConfig"; import AccessFormAWSConfig from "./AccessFormAWSConfig"; import AccessFormAzureConfig from "./AccessFormAzureConfig"; import AccessFormBaiduCloudConfig from "./AccessFormBaiduCloudConfig"; +import AccessFormBaotaPanelConfig from "./AccessFormBaotaPanelConfig"; import AccessFormBytePlusConfig from "./AccessFormBytePlusConfig"; import AccessFormCloudflareConfig from "./AccessFormCloudflareConfig"; import AccessFormClouDNSConfig from "./AccessFormClouDNSConfig"; @@ -99,6 +100,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.BAIDUCLOUD: return ; + case ACCESS_PROVIDERS.BAOTAPANEL: + return ; case ACCESS_PROVIDERS.BYTEPLUS: return ; case ACCESS_PROVIDERS.CLOUDFLARE: diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx new file mode 100644 index 00000000..4f619c1c --- /dev/null +++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx @@ -0,0 +1,72 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForBaotaPanel } from "@/domain/access"; + +type AccessFormBaotaPanelConfigFieldValues = Nullish; + +export type AccessFormBaotaPanelConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormBaotaPanelConfigFieldValues; + onValuesChange?: (values: AccessFormBaotaPanelConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormBaotaPanelConfigFieldValues => { + return { + apiUrl: "", + apiKey: "", + }; +}; + +const AccessFormBaotaPanelConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormBaotaPanelConfigProps) => { + const { t } = useTranslation(); + + 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(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormBaotaPanelConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index fe03a6ab..92ce46cc 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -27,6 +27,7 @@ import DeployNodeConfigFormAliyunOSSConfig from "./DeployNodeConfigFormAliyunOSS import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig"; import DeployNodeConfigFormBaiduCloudCDNConfig from "./DeployNodeConfigFormBaiduCloudCDNConfig"; +import DeployNodeConfigFormBaotaPanelSiteConfig from "./DeployNodeConfigFormBaotaPanelSiteConfig"; import DeployNodeConfigFormBytePlusCDNConfig from "./DeployNodeConfigFormBytePlusCDNConfig"; import DeployNodeConfigFormDogeCloudCDNConfig from "./DeployNodeConfigFormDogeCloudCDNConfig"; import DeployNodeConfigFormEdgioApplicationsConfig from "./DeployNodeConfigFormEdgioApplicationsConfig"; @@ -148,6 +149,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOY_PROVIDERS.BAIDUCLOUD_CDN: return ; + case DEPLOY_PROVIDERS.BAOTAPANEL_SITE: + return ; case DEPLOY_PROVIDERS.BYTEPLUS_CDN: return ; case DEPLOY_PROVIDERS.DOGECLOUD_CDN: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx new file mode 100644 index 00000000..aa891245 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormBaotaPanelSiteConfig.tsx @@ -0,0 +1,64 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormBaotaPanelSiteConfigFieldValues = Nullish<{ + siteName: string; +}>; + +export type DeployNodeConfigFormBaotaPanelSiteConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormBaotaPanelSiteConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormBaotaPanelSiteConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormBaotaPanelSiteConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormBaotaPanelSiteConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: DeployNodeConfigFormBaotaPanelSiteConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + siteName: z + .string({ message: t("workflow_node.deploy.form.baotapanel_site_name.placeholder") }) + .nonempty(t("workflow_node.deploy.form.baotapanel_site_name.placeholder")) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default DeployNodeConfigFormBaotaPanelSiteConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 1e229751..ed7f8559 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -13,6 +13,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForAWS | AccessConfigForAzure | AccessConfigForBaiduCloud + | AccessConfigForBaotaPanel | AccessConfigForBytePlus | AccessConfigForCloudflare | AccessConfigForClouDNS @@ -68,6 +69,11 @@ export type AccessConfigForBaiduCloud = { secretAccessKey: string; }; +export type AccessConfigForBaotaPanel = { + apiUrl: string; + apiKey: string; +}; + export type AccessConfigForBytePlus = { accessKey: string; secretKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 2edf0531..15562238 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -9,6 +9,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ AWS: "aws", AZURE: "azure", BAIDUCLOUD: "baiducloud", + BAOTAPANEL: "baotapanel", BYTEPLUS: "byteplus", CLOUDFLARE: "cloudflare", CLOUDNS: "cloudns", @@ -70,6 +71,7 @@ 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 e2c94fc7..25f8765d 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -63,6 +63,12 @@ "access.form.baiducloud_secret_access_key.label": "Baidu Cloud SecretAccessKey", "access.form.baiducloud_secret_access_key.placeholder": "Please enter Baidu Cloud SecretAccessKey", "access.form.baiducloud_secret_access_key.tooltip": "For more information, see https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en", + "access.form.baotapanel_api_url.label": "BaoTa Panel URL", + "access.form.baotapanel_api_url.placeholder": "Please enter BaoTa Panel URL", + "access.form.baotapanel_api_url.tooltip": "For more information, see https://www.bt.cn/bbs/thread-20376-1-1.html", + "access.form.baotapanel_api_key.label": "BaoTa Panel API key", + "access.form.baotapanel_api_key.placeholder": "Please enter BaoTa Panel API key", + "access.form.baotapanel_api_key.tooltip": "For more information, see https://www.bt.cn/bbs/thread-113890-1-1.html", "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.common.json b/ui/src/i18n/locales/en/nls.common.json index c8408cd0..5b305561 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -55,6 +55,8 @@ "common.provider.azure.dns": "Azure - DNS", "common.provider.baiducloud": "Baidu Cloud", "common.provider.baiducloud.cdn": "Baidu Cloud - CDN (Content Delivery Network)", + "common.provider.baotapanel": "BaoTa Panel", + "common.provider.baotapanel.site": "BaoTa Panel - Site", "common.provider.byteplus": "BytePlus", "common.provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)", "common.provider.cloudflare": "Cloudflare", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index e5894fe5..494269a5 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -190,6 +190,9 @@ "workflow_node.deploy.form.baiducloud_cdn_domain.label": "Baidu Cloud CDN domain", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "Please enter Baidu Cloud CDN domain name", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "For more information, see https://console.bce.baidu.com/cdn", + "workflow_node.deploy.form.baotapanel_site_name.label": "BaoTa Panel site name", + "workflow_node.deploy.form.baotapanel_site_name.placeholder": "Please enter BaoTa Panel site name", + "workflow_node.deploy.form.baotapanel_site_name.tooltip": "Usually equal to the website domain name.", "workflow_node.deploy.form.byteplus_cdn_domain.label": "BytePlus CDN domain", "workflow_node.deploy.form.byteplus_cdn_domain.placeholder": "Please enter BytePlus CDN domain name", "workflow_node.deploy.form.byteplus_cdn_domain.tooltip": "For more information, see https://console.byteplus.com/cdn", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 73fe7105..3581a527 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -63,6 +63,12 @@ "access.form.baiducloud_secret_access_key.label": "百度智能云 SecretAccessKey", "access.form.baiducloud_secret_access_key.placeholder": "请输入百度智能云 SecretAccessKey", "access.form.baiducloud_secret_access_key.tooltip": "这是什么?请参阅 https://cloud.baidu.com/doc/Reference/s/jjwvz2e3p", + "access.form.baotapanel_api_url.label": "宝塔面板 URL", + "access.form.baotapanel_api_url.placeholder": "请输入宝塔面板 URL", + "access.form.baotapanel_api_url.tooltip": "这是什么?请参阅 https://www.bt.cn/bbs/thread-20376-1-1.html", + "access.form.baotapanel_api_key.label": "宝塔面板接口密钥", + "access.form.baotapanel_api_key.placeholder": "请输入宝塔面板接口密钥", + "access.form.baotapanel_api_key.tooltip": "这是什么?请参阅 https://www.bt.cn/bbs/thread-113890-1-1.html", "access.form.byteplus_access_key.label": "BytePlus AccessKey", "access.form.byteplus_access_key.placeholder": "请输入 BytePlus AccessKey", "access.form.byteplus_access_key.tooltip": "这是什么?请参阅 https://docs.byteplus.com/zh-CN/docs/byteplus-platform/docs-managing-keys", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index 06ee5f73..cfc23730 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -55,6 +55,8 @@ "common.provider.azure.dns": "Azure - DNS", "common.provider.baiducloud": "百度智能云", "common.provider.baiducloud.cdn": "百度智能云 - 内容分发网络 CDN", + "common.provider.baotapanel": "宝塔面板", + "common.provider.baotapanel.site": "宝塔面板 - 网站", "common.provider.byteplus": "BytePlus", "common.provider.byteplus.cdn": "BytePlus - 内容分发网络 CDN", "common.provider.cloudflare": "Cloudflare", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a4436d4e..2f79e949 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -190,6 +190,9 @@ "workflow_node.deploy.form.baiducloud_cdn_domain.label": "百度智能云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.baiducloud_cdn_domain.placeholder": "请输入百度智能云 CDN 加速域名", "workflow_node.deploy.form.baiducloud_cdn_domain.tooltip": "这是什么?请参阅 https://console.bce.baidu.com/cdn

泛域名表示形式为:*.example.com", + "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.byteplus_cdn_domain.label": "BytePlus CDN 域名(支持泛域名)", "workflow_node.deploy.form.byteplus_cdn_domain.placeholder": "请输入 BytePlus CDN 域名", "workflow_node.deploy.form.byteplus_cdn_domain.tooltip": "这是什么?请参阅 https://console.byteplus.com/cdn

泛域名表示形式为:*.example.com", From 45de2cf1db2700376bed2dc6deb89ebcd457c437 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Tue, 11 Feb 2025 00:01:09 +0800 Subject: [PATCH 5/5] edit README --- README.md | 35 ++++++++++++++++++----------------- README_EN.md | 3 ++- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b7cfc56e..6f90fb32 100644 --- a/README.md +++ b/README.md @@ -116,23 +116,24 @@ make local.run [展开查看] -| 提供商 | 备注 | -| :-------------------------------------- | :------------------------------------------------------------------ | -| 本地部署 | 可部署到本地服务器 | -| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP/SCP) | -| Webhook 回调 | 可部署到 Webhook | -| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret | -| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 | -| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 | -| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 | -| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 | -| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 | -| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 | -| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | -| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | -| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | -| [AWS](https://aws.amazon.com/) | 可部署到 AWS CloudFront 等服务 | -| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 | +| 提供商 | 备注 | +| :-------------------------------------- | :----------------------------------------------------------------------- | +| 本地部署 | 可部署到本地服务器 | +| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP/SCP) | +| Webhook 回调 | 可部署到 Webhook | +| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret | +| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、ESA、SLB(CLB/ALB/NLB)、WAF、Live 等服务 | +| [腾讯云](https://cloud.tencent.com/) | 可部署到腾讯云 COS、CDN、ECDN、EdgeOne、CLB、CSS 等服务 | +| [百度智能云](https://cloud.baidu.com/) | 可部署到百度智能云 CDN 等服务 | +| [华为云](https://www.huaweicloud.com/) | 可部署到华为云 CDN、ELB 等服务 | +| [火山引擎](https://www.volcengine.com/) | 可部署到火山引擎 TOS、CDN、DCDN、CLB、Live 等服务 | +| [七牛云](https://www.qiniu.com/) | 可部署到七牛云 CDN、直播云等服务 | +| [多吉云](https://www.dogecloud.com/) | 可部署到多吉云 CDN | +| [优刻得](https://www.ucloud.cn/) | 可部署到优刻得 US3、UCDN 等服务 | +| [宝塔面板](https://www.bt.cn/) | 可部署到宝塔面板 | +| [AWS](https://aws.amazon.com/) | 可部署到 AWS CloudFront 等服务 | +| [BytePlus](https://www.byteplus.com/) | 可部署到 BytePlus CDN 等服务 | +| [Edgio](https://edg.io/) | 可部署到 Edgio Applications 等服务 | diff --git a/README_EN.md b/README_EN.md index c2174fad..d5300b07 100644 --- a/README_EN.md +++ b/README_EN.md @@ -128,9 +128,10 @@ The following hosting providers are supported: | [Volcengine](https://www.volcengine.com/) | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, Live | | [Qiniu Cloud](https://www.qiniu.com/) | Supports deployment to Qiniu Cloud CDN, Pili | | [Doge Cloud](https://www.dogecloud.com/) | Supports deployment to Doge Cloud CDN | -| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | +| [BaoTa Panel](https://www.bt.cn/) | Supports deployment to BaoTa Panel sites | | [UCloud](https://www.ucloud-global.com/) | Supports deployment to UCloud US3, UCDN | | [AWS](https://aws.amazon.com/) | Supports deployment to AWS CloudFront | +| [BytePlus](https://www.byteplus.com/) | Supports deployment to BytePlus CDN | | [Edgio](https://edg.io/) | Supports deployment to Edgio Applications |