diff --git a/go.mod b/go.mod index fc4267b9..0571e260 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/alibabacloud-go/cas-20200407/v3 v3.0.1 github.com/alibabacloud-go/cdn-20180510/v5 v5.0.0 github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 + github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 github.com/alibabacloud-go/slb-20140515/v4 v4.0.9 github.com/alibabacloud-go/tea v1.2.2 github.com/alibabacloud-go/tea-utils/v2 v2.0.6 diff --git a/go.sum b/go.sum index 3475675b..10911699 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,7 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.7/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.8/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU= @@ -64,6 +65,8 @@ github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3 h1:LtyUVlgBEKyzWgQJurzXM6MXCt84sQr9cE5OKqYymko= +github.com/alibabacloud-go/nlb-20220430/v2 v2.0.3/go.mod h1:4a/RcBYeAhYowHzX+LMgnouz7NradnSKPKl14KS3B1U= github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= @@ -94,6 +97,7 @@ github.com/alibabacloud-go/tea-utils v1.3.6/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQ github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ= github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0= github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= diff --git a/internal/deployer/aliyun_nlb.go b/internal/deployer/aliyun_nlb.go new file mode 100644 index 00000000..8e4d8d84 --- /dev/null +++ b/internal/deployer/aliyun_nlb.go @@ -0,0 +1,230 @@ +package deployer + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + nlb20220430 "github.com/alibabacloud-go/nlb-20220430/v2/client" + "github.com/alibabacloud-go/tea/tea" + + "github.com/usual2970/certimate/internal/domain" + "github.com/usual2970/certimate/internal/pkg/core/uploader" +) + +type AliyunNLBDeployer struct { + option *DeployerOption + infos []string + + sdkClient *nlb20220430.Client + sslUploader uploader.Uploader +} + +func NewAliyunNLBDeployer(option *DeployerOption) (Deployer, error) { + access := &domain.AliyunAccess{} + json.Unmarshal([]byte(option.Access), access) + + client, err := (&AliyunNLBDeployer{}).createSdkClient( + access.AccessKeyId, + access.AccessKeySecret, + option.DeployConfig.GetConfigAsString("region"), + ) + if err != nil { + return nil, err + } + + uploader, err := uploader.NewAliyunCASUploader(&uploader.AliyunCASUploaderConfig{ + AccessKeyId: access.AccessKeyId, + AccessKeySecret: access.AccessKeySecret, + Region: option.DeployConfig.GetConfigAsString("region"), + }) + if err != nil { + return nil, err + } + + return &AliyunNLBDeployer{ + option: option, + infos: make([]string, 0), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *AliyunNLBDeployer) GetID() string { + return fmt.Sprintf("%s-%s", d.option.AccessRecord.GetString("name"), d.option.AccessRecord.Id) +} + +func (d *AliyunNLBDeployer) GetInfo() []string { + return d.infos +} + +func (d *AliyunNLBDeployer) Deploy(ctx context.Context) error { + switch d.option.DeployConfig.GetConfigAsString("resourceType") { + case "loadbalancer": + if err := d.deployToLoadbalancer(ctx); err != nil { + return err + } + case "listener": + if err := d.deployToListener(ctx); err != nil { + return err + } + default: + return errors.New("unsupported resource type") + } + + return nil +} + +func (d *AliyunNLBDeployer) createSdkClient(accessKeyId, accessKeySecret, region string) (*nlb20220430.Client, error) { + if region == "" { + region = "cn-hangzhou" // NLB 服务默认区域:华东一杭州 + } + + aConfig := &openapi.Config{ + AccessKeyId: tea.String(accessKeyId), + AccessKeySecret: tea.String(accessKeySecret), + } + + var endpoint string + switch region { + case "cn-hangzhou-finance": + endpoint = "nlb.cn-hangzhou.aliyuncs.com" + default: + endpoint = fmt.Sprintf("nlb.%s.aliyuncs.com", region) + } + aConfig.Endpoint = tea.String(endpoint) + + client, err := nlb20220430.NewClient(aConfig) + if err != nil { + return nil, err + } + + return client, nil +} + +func (d *AliyunNLBDeployer) deployToLoadbalancer(ctx context.Context) error { + aliLoadbalancerId := d.option.DeployConfig.GetConfigAsString("loadbalancerId") + if aliLoadbalancerId == "" { + return errors.New("`loadbalancerId` is required") + } + + // 查询负载均衡实例的详细信息 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getloadbalancerattribute + getLoadBalancerAttributeReq := &nlb20220430.GetLoadBalancerAttributeRequest{ + LoadBalancerId: tea.String(aliLoadbalancerId), + } + getLoadBalancerAttributeResp, err := d.sdkClient.GetLoadBalancerAttribute(getLoadBalancerAttributeReq) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'nlb.GetLoadBalancerAttribute': %w", err) + } + + d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例", getLoadBalancerAttributeResp)) + + // 查询监听列表 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-listlisteners + aliListenerIds := make([]string, 0) + listListenersPage := 1 + listListenersLimit := int32(100) + var listListenersToken *string = nil + for { + listListenersReq := &nlb20220430.ListListenersRequest{ + MaxResults: tea.Int32(listListenersLimit), + NextToken: listListenersToken, + LoadBalancerIds: []*string{tea.String(aliLoadbalancerId)}, + ListenerProtocol: tea.String("TCPSSL"), + } + listListenersResp, err := d.sdkClient.ListListeners(listListenersReq) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'nlb.ListListeners': %w", err) + } + + if listListenersResp.Body.Listeners != nil { + for _, listener := range listListenersResp.Body.Listeners { + aliListenerIds = append(aliListenerIds, *listener.ListenerId) + } + } + + if listListenersResp.Body.NextToken == nil { + break + } else { + listListenersToken = listListenersResp.Body.NextToken + listListenersPage += 1 + } + } + + d.infos = append(d.infos, toStr("已查询到 NLB 负载均衡实例下的全部 TCPSSL 监听", aliListenerIds)) + + // 上传证书到 SSL + uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey) + if err != nil { + return err + } + + d.infos = append(d.infos, toStr("已上传证书", uploadResult)) + + // 批量更新监听证书 + var errs []error + for _, aliListenerId := range aliListenerIds { + if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + + return nil +} + +func (d *AliyunNLBDeployer) deployToListener(ctx context.Context) error { + aliListenerId := d.option.DeployConfig.GetConfigAsString("listenerId") + if aliListenerId == "" { + return errors.New("`listenerId` is required") + } + + // 上传证书到 SSL + uploadResult, err := d.sslUploader.Upload(ctx, d.option.Certificate.Certificate, d.option.Certificate.PrivateKey) + if err != nil { + return err + } + + d.infos = append(d.infos, toStr("已上传证书", uploadResult)) + + // 更新监听 + if err := d.updateListenerCertificate(ctx, aliListenerId, uploadResult.CertId); err != nil { + return err + } + + return nil +} + +func (d *AliyunNLBDeployer) updateListenerCertificate(ctx context.Context, aliListenerId string, aliCertId string) error { + // 查询监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-getlistenerattribute + getListenerAttributeReq := &nlb20220430.GetListenerAttributeRequest{ + ListenerId: tea.String(aliListenerId), + } + getListenerAttributeResp, err := d.sdkClient.GetListenerAttribute(getListenerAttributeReq) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'nlb.GetListenerAttribute': %w", err) + } + + d.infos = append(d.infos, toStr("已查询到 NLB 监听配置", getListenerAttributeResp)) + + // 修改监听的属性 + // REF: https://help.aliyun.com/zh/slb/network-load-balancer/developer-reference/api-nlb-2022-04-30-updatelistenerattribute + updateListenerAttributeReq := &nlb20220430.UpdateListenerAttributeRequest{ + ListenerId: tea.String(aliListenerId), + CertificateIds: []*string{tea.String(aliCertId)}, + } + updateListenerAttributeResp, err := d.sdkClient.UpdateListenerAttribute(updateListenerAttributeReq) + if err != nil { + return fmt.Errorf("failed to execute sdk request 'nlb.UpdateListenerAttribute': %w", err) + } + + d.infos = append(d.infos, toStr("已更新 NLB 监听配置", updateListenerAttributeResp)) + + return nil +} diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 8e130b3b..3936da88 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -19,6 +19,8 @@ const ( targetAliyunCDN = "aliyun-cdn" targetAliyunESA = "aliyun-dcdn" targetAliyunCLB = "aliyun-clb" + targetAliyunALB = "aliyun-alb" + targetAliyunNLB = "aliyun-nlb" targetTencentCDN = "tencent-cdn" targetTencentCLB = "tencent-clb" targetTencentCOS = "tencent-cos" @@ -109,6 +111,10 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep return NewAliyunESADeployer(option) case targetAliyunCLB: return NewAliyunCLBDeployer(option) + case targetAliyunALB: + return NewAliyunALBDeployer(option) + case targetAliyunNLB: + return NewAliyunNLBDeployer(option) case targetTencentCDN: return NewTencentCDNDeployer(option) case targetTencentCLB: @@ -130,7 +136,7 @@ func getWithDeployConfig(record *models.Record, cert *applicant.Certificate, dep case targetK8sSecret: return NewK8sSecretDeployer(option) } - return nil, errors.New("not implemented") + return nil, errors.New("unsupported deploy target") } func getProduct(t string) string { diff --git a/ui/src/components/certimate/DeployEditDialog.tsx b/ui/src/components/certimate/DeployEditDialog.tsx index 395fd9c8..b368a1fb 100644 --- a/ui/src/components/certimate/DeployEditDialog.tsx +++ b/ui/src/components/certimate/DeployEditDialog.tsx @@ -13,6 +13,7 @@ import DeployToAliyunOSS from "./DeployToAliyunOSS"; import DeployToAliyunCDN from "./DeployToAliyunCDN"; import DeployToAliyunCLB from "./DeployToAliyunCLB"; import DeployToAliyunALB from "./DeployToAliyunALB"; +import DeployToAliyunNLB from "./DeployToAliyunNLB"; import DeployToTencentCDN from "./DeployToTencentCDN"; import DeployToTencentCLB from "./DeployToTencentCLB"; import DeployToTencentCOS from "./DeployToTencentCOS"; @@ -126,6 +127,9 @@ const DeployEditDialog = ({ trigger, deployConfig, onSave }: DeployEditDialogPro case "aliyun-alb": childComponent = ; break; + case "aliyun-nlb": + childComponent = ; + break; case "tencent-cdn": childComponent = ; break; diff --git a/ui/src/components/certimate/DeployToAliyunNLB.tsx b/ui/src/components/certimate/DeployToAliyunNLB.tsx new file mode 100644 index 00000000..38d6b1f7 --- /dev/null +++ b/ui/src/components/certimate/DeployToAliyunNLB.tsx @@ -0,0 +1,162 @@ +import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { z } from "zod"; +import { produce } from "immer"; + +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { useDeployEditContext } from "./DeployEdit"; + +const DeployToAliyunNLB = () => { + const { t } = useTranslation(); + + const { deploy: data, setDeploy, error, setError } = useDeployEditContext(); + + useEffect(() => { + if (!data.id) { + setDeploy({ + ...data, + config: { + region: "cn-hangzhou", + resourceType: "", + loadbalancerId: "", + listenerId: "", + }, + }); + } + }, []); + + useEffect(() => { + setError({}); + }, []); + + const formSchema = z + .object({ + region: z.string().min(1, t("domain.deployment.form.aliyun_nlb_region.placeholder")), + resourceType: z.union([z.literal("loadbalancer"), z.literal("listener")], { + message: t("domain.deployment.form.aliyun_nlb_resource_type.placeholder"), + }), + loadbalancerId: z.string().optional(), + listenerId: z.string().optional(), + }) + .refine((data) => (data.resourceType === "loadbalancer" ? !!data.loadbalancerId?.trim() : true), { + message: t("domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder"), + path: ["loadbalancerId"], + }) + .refine((data) => (data.resourceType === "listener" ? !!data.listenerId?.trim() : true), { + message: t("domain.deployment.form.aliyun_nlb_listener_id.placeholder"), + path: ["listenerId"], + }); + + useEffect(() => { + const res = formSchema.safeParse(data.config); + if (!res.success) { + setError({ + ...error, + region: res.error.errors.find((e) => e.path[0] === "region")?.message, + resourceType: res.error.errors.find((e) => e.path[0] === "resourceType")?.message, + loadbalancerId: res.error.errors.find((e) => e.path[0] === "loadbalancerId")?.message, + listenerId: res.error.errors.find((e) => e.path[0] === "listenerId")?.message, + }); + } else { + setError({ + ...error, + region: undefined, + resourceType: undefined, + loadbalancerId: undefined, + listenerId: undefined, + }); + } + }, [data]); + + return ( +
+
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.region = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.region}
+
+ +
+ + +
{error?.resourceType}
+
+ + {data?.config?.resourceType === "loadbalancer" ? ( +
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.loadbalancerId = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.loadbalancerId}
+
+ ) : ( + <> + )} + + {data?.config?.resourceType === "listener" ? ( +
+ + { + const newData = produce(data, (draft) => { + draft.config ??= {}; + draft.config.listenerId = e.target.value?.trim(); + }); + setDeploy(newData); + }} + /> +
{error?.listenerId}
+
+ ) : ( + <> + )} +
+ ); +}; + +export default DeployToAliyunNLB; diff --git a/ui/src/domain/domain.ts b/ui/src/domain/domain.ts index 8f090032..b2a51492 100644 --- a/ui/src/domain/domain.ts +++ b/ui/src/domain/domain.ts @@ -77,6 +77,7 @@ export const deployTargetsMap: Map = new Map ["aliyun-dcdn", "common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"], ["aliyun-clb", "common.provider.aliyun.clb", "/imgs/providers/aliyun.svg"], ["aliyun-alb", "common.provider.aliyun.alb", "/imgs/providers/aliyun.svg"], + ["aliyun-nlb", "common.provider.aliyun.nlb", "/imgs/providers/aliyun.svg"], ["tencent-cdn", "common.provider.tencent.cdn", "/imgs/providers/tencent.svg"], ["tencent-clb", "common.provider.tencent.clb", "/imgs/providers/tencent.svg"], ["tencent-cos", "common.provider.tencent.cos", "/imgs/providers/tencent.svg"], diff --git a/ui/src/i18n/locales/en/nls.common.json b/ui/src/i18n/locales/en/nls.common.json index dc6ad896..581d5e8e 100644 --- a/ui/src/i18n/locales/en/nls.common.json +++ b/ui/src/i18n/locales/en/nls.common.json @@ -58,6 +58,7 @@ "common.provider.aliyun.dcdn": "Alibaba Cloud - DCDN", "common.provider.aliyun.clb": "Alibaba Cloud - CLB", "common.provider.aliyun.alb": "Alibaba Cloud - ALB", + "common.provider.aliyun.nlb": "Alibaba Cloud - NLB", "common.provider.tencent": "Tencent Cloud", "common.provider.tencent.cdn": "Tencent Cloud - CDN", "common.provider.tencent.clb": "Tencent Cloud - CLB", diff --git a/ui/src/i18n/locales/en/nls.domain.json b/ui/src/i18n/locales/en/nls.domain.json index ddba1aae..a6e4ca83 100644 --- a/ui/src/i18n/locales/en/nls.domain.json +++ b/ui/src/i18n/locales/en/nls.domain.json @@ -81,6 +81,16 @@ "domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder": "Please enter ALB loadbalancer ID", "domain.deployment.form.aliyun_alb_listener_id.label": "Listener ID", "domain.deployment.form.aliyun_alb_listener_id.placeholder": "Please enter ALB listener ID", + "domain.deployment.form.aliyun_nlb_region.label": "Region", + "domain.deployment.form.aliyun_nlb_region.placeholder": "Please enter region (e.g. cn-hangzhou)", + "domain.deployment.form.aliyun_nlb_resource_type.label": "Resource Type", + "domain.deployment.form.aliyun_nlb_resource_type.placeholder": "Please select NLB resource type", + "domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label": "NLB LoadBalancer", + "domain.deployment.form.aliyun_nlb_resource_type.option.listener.label": "NLB Listener", + "domain.deployment.form.aliyun_nlb_loadbalancer_id.label": "LoadBalancer ID", + "domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder": "Please enter NLB loadbalancer ID", + "domain.deployment.form.aliyun_nlb_listener_id.label": "Listener ID", + "domain.deployment.form.aliyun_nlb_listener_id.placeholder": "Please enter NLB listener ID", "domain.deployment.form.tencent_cos_region.label": "Region", "domain.deployment.form.tencent_cos_region.placeholder": "Please enter region (e.g. ap-guangzhou)", "domain.deployment.form.tencent_cos_bucket.label": "Bucket", diff --git a/ui/src/i18n/locales/zh/nls.common.json b/ui/src/i18n/locales/zh/nls.common.json index b3e18f7a..455c3351 100644 --- a/ui/src/i18n/locales/zh/nls.common.json +++ b/ui/src/i18n/locales/zh/nls.common.json @@ -57,6 +57,7 @@ "common.provider.aliyun.dcdn": "阿里云 - 全站加速 DCDN", "common.provider.aliyun.clb": "阿里云 - 传统型负载均衡 CLB", "common.provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", + "common.provider.aliyun.nlb": "阿里云 - 网络型负载均衡 NLB", "common.provider.tencent": "腾讯云", "common.provider.tencent.cos": "腾讯云 - 对象存储 COS", "common.provider.tencent.cdn": "腾讯云 - 内容分发网络 CDN", diff --git a/ui/src/i18n/locales/zh/nls.domain.json b/ui/src/i18n/locales/zh/nls.domain.json index 1929440e..0e850ca0 100644 --- a/ui/src/i18n/locales/zh/nls.domain.json +++ b/ui/src/i18n/locales/zh/nls.domain.json @@ -66,7 +66,7 @@ "domain.deployment.form.aliyun_clb_resource_type.label": "替换方式", "domain.deployment.form.aliyun_clb_resource_type.placeholder": "请选择替换方式", "domain.deployment.form.aliyun_clb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 HTTPS 监听)", - "domain.deployment.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡器监听的证书", + "domain.deployment.form.aliyun_clb_resource_type.option.listener.label": "替换指定负载均衡监听的证书", "domain.deployment.form.aliyun_clb_loadbalancer_id.label": "负载均衡器 ID", "domain.deployment.form.aliyun_clb_loadbalancer_id.placeholder": "请输入负载均衡器 ID", "domain.deployment.form.aliyun_clb_listener_port.label": "监听端口", @@ -76,11 +76,21 @@ "domain.deployment.form.aliyun_alb_resource_type.label": "替换方式", "domain.deployment.form.aliyun_alb_resource_type.placeholder": "请选择替换方式", "domain.deployment.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 HTTPS 监听)", - "domain.deployment.form.aliyun_alb_resource_type.option.listener.label": "替换指定负载均衡器监听的证书", + "domain.deployment.form.aliyun_alb_resource_type.option.listener.label": "替换指定监听器的证书", "domain.deployment.form.aliyun_alb_loadbalancer_id.label": "负载均衡器 ID", "domain.deployment.form.aliyun_alb_loadbalancer_id.placeholder": "请输入负载均衡器 ID", "domain.deployment.form.aliyun_alb_listener_id.label": "监听器 ID", "domain.deployment.form.aliyun_alb_listener_id.placeholder": "请输入监听器 ID", + "domain.deployment.form.aliyun_nlb_region.label": "地域", + "domain.deployment.form.aliyun_nlb_region.placeholder": "请输入地域(如 cn-hangzhou)", + "domain.deployment.form.aliyun_nlb_resource_type.label": "替换方式", + "domain.deployment.form.aliyun_nlb_resource_type.placeholder": "请选择替换方式", + "domain.deployment.form.aliyun_nlb_resource_type.option.loadbalancer.label": "替换指定负载均衡器的全部监听的证书(仅支持 TCPSSL 监听)", + "domain.deployment.form.aliyun_nlb_resource_type.option.listener.label": "替换指定监听器的证书", + "domain.deployment.form.aliyun_nlb_loadbalancer_id.label": "负载均衡器 ID", + "domain.deployment.form.aliyun_nlb_loadbalancer_id.placeholder": "请输入负载均衡器 ID", + "domain.deployment.form.aliyun_nlb_listener_id.label": "监听器 ID", + "domain.deployment.form.aliyun_nlb_listener_id.placeholder": "请输入监听器 ID", "domain.deployment.form.tencent_cos_region.label": "地域", "domain.deployment.form.tencent_cos_region.placeholder": "请输入地域(如 ap-guangzhou)", "domain.deployment.form.tencent_cos_bucket.label": "存储桶",