Merge branch 'next' of https://github.com/YangRucheng/certimate into YangRucheng-next

This commit is contained in:
Fu Diwei 2025-09-06 00:37:28 +08:00
commit eb17efa256
10 changed files with 321 additions and 17 deletions

View File

@ -252,6 +252,7 @@ const (
DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn")
DeploymentProviderTypeHuaweiCloudELB = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-elb")
DeploymentProviderTypeHuaweiCloudSCM = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-scm")
DeploymentProviderTypeHuaweiCloudOBS = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-obs")
DeploymentProviderTypeHuaweiCloudWAF = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-waf")
DeploymentProviderTypeJDCloudALB = DeploymentProviderType(AccessProviderTypeJDCloud + "-alb")
DeploymentProviderTypeJDCloudCDN = DeploymentProviderType(AccessProviderTypeJDCloud + "-cdn")

View File

@ -0,0 +1,124 @@
package huaweicloudobs
import (
"bytes"
"context"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
"log/slog"
"net/http"
"strings"
"time"
"github.com/certimate-go/certimate/pkg/core"
)
type SSLDeployerProviderConfig struct {
// 华为云 AccessKeyId。
AccessKeyId string `json:"accessKeyId"`
// 华为云 SecretAccessKey。
SecretAccessKey string `json:"secretAccessKey"`
// 华为云 Bucket 对应的 Endpoint。
Endpoint string `json:"endpoint"`
// 华为云 OBS 桶名。
Bucket string `json:"bucket"`
// 自定义域名。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProvider, error) {
if config == nil {
return nil, errors.New("the configuration of the ssl deployer provider is nil")
}
config.Endpoint = strings.TrimPrefix(strings.TrimPrefix(config.Endpoint, "http://"), "https://")
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
}, nil
}
func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
if logger == nil {
d.logger = slog.New(slog.DiscardHandler)
} else {
d.logger = logger
}
}
// REF: https://support.huaweicloud.com/usermanual-obs/obs_06_3200.html
// REF: https://support.huaweicloud.com/api-obs/obs_04_0059.html
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
if d.config.Domain == "" {
return nil, fmt.Errorf("config `domain` is required")
}
url := fmt.Sprintf("https://%s.%s/?customdomain=%s", d.config.Bucket, d.config.Endpoint, d.config.Domain)
bodyXML := fmt.Sprintf(`
<CustomDomainConfiguration>
<Name>%s</Name>
<Certificate>%s</Certificate>
<CertificateChain>%s</CertificateChain>
<PrivateKey>%s</PrivateKey>
</CustomDomainConfiguration>`,
d.config.Bucket+"_"+d.config.Domain, certPEM, certPEM, privkeyPEM,
)
// 计算 Content-MD5Base64 编码)
md5sum := md5.Sum([]byte(bodyXML))
contentMD5 := base64.StdEncoding.EncodeToString(md5sum[:])
// 日期
date := time.Now().UTC().Format(http.TimeFormat)
// 构造签名字符串
method := "PUT"
contentType := "application/xml"
canonicalizedResource := fmt.Sprintf("/%s/?customdomain=%s", d.config.Bucket, d.config.Domain)
stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s\n%s", method, contentMD5, contentType, date, canonicalizedResource)
// HMAC-SHA1 签名
h := hmac.New(sha1.New, []byte(d.config.SecretAccessKey))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
// Authorization
authHeader := fmt.Sprintf("OBS %s:%s", d.config.AccessKeyId, signature)
// 创建请求
req, err := http.NewRequest(method, url, bytes.NewBuffer([]byte(bodyXML)))
if err != nil {
return nil, err
}
req.Header.Set("Date", date)
req.Header.Set("Authorization", authHeader)
req.Header.Set("Content-MD5", contentMD5)
req.Header.Set("Content-Type", contentType)
// 请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body := new(bytes.Buffer)
body.ReadFrom(resp.Body)
return nil, fmt.Errorf("HTTP request failed with status %d: %s", resp.StatusCode, body.String())
}
return &core.SSLDeployResult{}, nil
}

View File

@ -0,0 +1,85 @@
package huaweicloudobs_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/certimate-go/certimate/pkg/core/ssl-deployer/providers/huaweicloud-obs"
)
var (
fInputCertPath string
fInputKeyPath string
fAccessKeyId string
fSecretAccessKey string
fEndpoint string
fBucket string
fDomain string
)
func init() {
argsPrefix := "CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fAccessKeyId, argsPrefix+"ACCESSKEYID", "", "")
flag.StringVar(&fSecretAccessKey, argsPrefix+"SECRETACCESSKEY", "", "")
flag.StringVar(&fEndpoint, argsPrefix+"ENDPOINT", "", "")
flag.StringVar(&fBucket, argsPrefix+"BUCKET", "", "")
flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "")
}
/*
Shell command to run this test:
go test -v ./huaweicloud_obs_test.go -args \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_ACCESSKEYID="your-access-key-id" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_SECRETACCESSKEY="your-secret-access-key" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_ENDPOINT="https://your-endpoint" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_BUCKET="your-bucket" \
--CERTIMATE_SSLDEPLOYER_HUAWEICLOUDCDN_DOMAIN="example.com"
*/
func TestDeploy(t *testing.T) {
flag.Parse()
t.Run("Deploy", func(t *testing.T) {
t.Log(strings.Join([]string{
"args:",
fmt.Sprintf("INPUTCERTPATH: %v", fInputCertPath),
fmt.Sprintf("INPUTKEYPATH: %v", fInputKeyPath),
fmt.Sprintf("ACCESSKEYID: %v", fAccessKeyId),
fmt.Sprintf("SECRETACCESSKEY: %v", fSecretAccessKey),
fmt.Sprintf("ENDPOINT: %v", fEndpoint),
fmt.Sprintf("BUCKET: %v", fBucket),
fmt.Sprintf("DOMAIN: %v", fDomain),
}, "\n"))
deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{
AccessKeyId: fAccessKeyId,
SecretAccessKey: fSecretAccessKey,
Endpoint: fEndpoint,
Bucket: fBucket,
Domain: fDomain,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@ -0,0 +1,84 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
import { validDomainName } from "@/utils/validators";
import { useFormNestedFieldsContext } from "./_context";
const BizDeployNodeConfigFieldsProviderHuaweiCloudOBS = () => {
const { i18n, t } = useTranslation();
const { parentNamePath } = useFormNestedFieldsContext();
const formSchema = z.object({
[parentNamePath]: getSchema({ i18n }),
});
const formRule = createSchemaFieldRule(formSchema);
const initialValues = getInitialValues();
return (
<>
<Form.Item
name={[parentNamePath, "endpoint"]}
initialValue={initialValues.endpoint}
label={t("workflow_node.deploy.form.huaweicloud_obs_endpoint.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_obs_endpoint.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "bucket"]}
initialValue={initialValues.bucket}
label={t("workflow_node.deploy.form.huaweicloud_obs_bucket.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder")} />
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
label={t("workflow_node.deploy.form.huaweicloud_obs_domain.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.huaweicloud_obs_domain.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.huaweicloud_obs_domain.placeholder")} />
</Form.Item>
</>
);
};
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
endpoint: "",
bucket: "",
domain: "",
};
};
const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> }) => {
const { t } = i18n;
return z.object({
endpoint: z
.string(t("workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder"))
.nonempty(t("workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder")),
bucket: z
.string(t("workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder"))
.nonempty(t("workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder")),
domain: z
.string(t("workflow_node.deploy.form.huaweicloud_obs_domain.placeholder"))
.refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
};
const _default = Object.assign(BizDeployNodeConfigFieldsProviderHuaweiCloudOBS, {
getInitialValues,
getSchema,
});
export default _default;

View File

@ -64,6 +64,7 @@ import BizDeployNodeConfigFieldsProviderGcoreCDN from "./BizDeployNodeConfigFiel
import BizDeployNodeConfigFieldsProviderGoEdge from "./BizDeployNodeConfigFieldsProviderGoEdge";
import BizDeployNodeConfigFieldsProviderHuaweiCloudCDN from "./BizDeployNodeConfigFieldsProviderHuaweiCloudCDN";
import BizDeployNodeConfigFieldsProviderHuaweiCloudELB from "./BizDeployNodeConfigFieldsProviderHuaweiCloudELB";
import BizDeployNodeConfigFieldsProviderHuaweiCloudOBS from "./BizDeployNodeConfigFieldsProviderHuaweiCloudOBS";
import BizDeployNodeConfigFieldsProviderHuaweiCloudWAF from "./BizDeployNodeConfigFieldsProviderHuaweiCloudWAF";
import BizDeployNodeConfigFieldsProviderJDCloudALB from "./BizDeployNodeConfigFieldsProviderJDCloudALB";
import BizDeployNodeConfigFieldsProviderJDCloudCDN from "./BizDeployNodeConfigFieldsProviderJDCloudCDN";
@ -322,6 +323,9 @@ const BizDeployNodeConfigForm = ({ node, ...props }: BizDeployNodeConfigFormProp
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB: {
return BizDeployNodeConfigFieldsProviderHuaweiCloudELB;
}
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_OBS: {
return BizDeployNodeConfigFieldsProviderHuaweiCloudOBS;
}
case DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF: {
return BizDeployNodeConfigFieldsProviderHuaweiCloudWAF;
}

View File

@ -485,6 +485,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
HUAWEICLOUD_CDN: `${ACCESS_PROVIDERS.HUAWEICLOUD}-cdn`,
HUAWEICLOUD_ELB: `${ACCESS_PROVIDERS.HUAWEICLOUD}-elb`,
HUAWEICLOUD_SCM: `${ACCESS_PROVIDERS.HUAWEICLOUD}-scm`,
HUAWEICLOUD_OBS: `${ACCESS_PROVIDERS.HUAWEICLOUD}-obs`,
HUAWEICLOUD_WAF: `${ACCESS_PROVIDERS.HUAWEICLOUD}-waf`,
JDCLOUD_ALB: `${ACCESS_PROVIDERS.JDCLOUD}-alb`,
JDCLOUD_CDN: `${ACCESS_PROVIDERS.JDCLOUD}-cdn`,
@ -605,6 +606,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
[DEPLOYMENT_PROVIDERS.BAIDUCLOUD_APPBLB, "provider.baiducloud.appblb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
[DEPLOYMENT_PROVIDERS.BAIDUCLOUD_CERT, "provider.baiducloud.cert_upload", DEPLOYMENT_CATEGORIES.SSL],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_CDN, "provider.huaweicloud.cdn", DEPLOYMENT_CATEGORIES.CDN],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_OBS, "provider.huaweicloud.obs", DEPLOYMENT_CATEGORIES.STORAGE],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_ELB, "provider.huaweicloud.elb", DEPLOYMENT_CATEGORIES.LOADBALANCE],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_WAF, "provider.huaweicloud.waf", DEPLOYMENT_CATEGORIES.FIREWALL],
[DEPLOYMENT_PROVIDERS.HUAWEICLOUD_SCM, "provider.huaweicloud.scm_upload", DEPLOYMENT_CATEGORIES.SSL],

View File

@ -96,6 +96,7 @@
"provider.huaweicloud.dns": "Huawei Cloud - DNS",
"provider.huaweicloud.elb": "Huawei Cloud - ELB (Elastic Load Balance)",
"provider.huaweicloud.scm_upload": "Huawei Cloud - Upload to SCM (SSL Certificate Manager)",
"provider.huaweicloud.obs": "Huawei Cloud - OBS (Object Storage Service)",
"provider.huaweicloud.waf": "Huawei Cloud - WAF (Web Application Firewall)",
"provider.jdcloud": "JD Cloud",
"provider.jdcloud.alb": "JD Cloud - ALB (Application Load Balancer)",

View File

@ -17,7 +17,6 @@
"workflow_node.start.form.trigger_cron.tooltip": "Exactly 5 space separated segments, in standard <em>crontab</em> rules.",
"workflow_node.start.form.trigger_cron.help": "Expected execution time for the last 5 times (the actual time zone is based on the server):",
"workflow_node.start.form.trigger_cron.guide": "If you have multiple workflows, it is recommended to set them to run at different times of the day instead of always running at a specific time. And please don't always set it to midnight every day to avoid spikes in traffic. <br><br>Reference links:<br>1. <a href=\"https://letsencrypt.org/docs/rate-limits/\" target=\"_blank\">Lets Encrypt rate limits</a><br>2. <a href=\"https://letsencrypt.org/docs/faq/#why-should-my-let-s-encrypt-acme-client-run-at-a-random-time\" target=\"_blank\">Why should my Lets Encrypt (ACME) client run at a random time?</a>",
"workflow_node.apply.label": "Request certificate",
"workflow_node.apply.help": "Apply for SSL certificate issuance from the certificate authority.",
"workflow_node.apply.default_name": "Application",
@ -126,7 +125,6 @@
"workflow_node.apply.form.skip_before_expiry_days.suffix": ", skip to re-apply.",
"workflow_node.apply.form.skip_before_expiry_days.unit": "days",
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "Be careful not to exceed the validity period limit of the issued certificate, otherwise the certificate may never be renewed.",
"workflow_node.upload.label": "Upload certificate",
"workflow_node.upload.help": "Upload the user's existing SSL certificate.",
"workflow_node.upload.default_name": "Uploading",
@ -137,7 +135,6 @@
"workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
"workflow_node.upload.form.private_key.label": "Private key (PEM format)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
"workflow_node.monitor.label": "Monitor certificate",
"workflow_node.monitor.help": "Obtain the SSL certificate of the website through HTTPS protocol.",
"workflow_node.monitor.default_name": "Monitoring",
@ -152,7 +149,6 @@
"workflow_node.monitor.form.domain.help": "Notes: It is only required when the host is an IP address.",
"workflow_node.monitor.form.request_path.label": "Request path (Optional)",
"workflow_node.monitor.form.request_path.placeholder": "Please enter request path",
"workflow_node.deploy.label": "Deploy certificate",
"workflow_node.deploy.help": "Invoke the APIs of the service provider to deploy the SSL certificate.",
"workflow_node.deploy.default_name": "Deployment",
@ -522,6 +518,15 @@
"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",
"workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "For more information, see <a href=\"https://console-intl.huaweicloud.com/cdn\" target=\"_blank\">https://console-intl.huaweicloud.com/cdn</a>",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.label": "Huawei Cloud OBS Endpoint",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder": "Please enter the Huawei Cloud OBS endpoint (e.g., obs.cn-north-4.myhuaweicloud.com)",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.tooltip": "What is this? Please refer to <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html</a>",
"workflow_node.deploy.form.huaweicloud_obs_bucket.label": "Huawei Cloud COS Bucket Name",
"workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "Please enter the Huawei Cloud COS bucket name",
"workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip": "What is this? Please refer to <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html</a>",
"workflow_node.deploy.form.huaweicloud_obs_domain.label": "Huawei Cloud COS Custom Domain",
"workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "Please enter the Huawei Cloud COS custom domain",
"workflow_node.deploy.form.huaweicloud_obs_domain.tooltip": "What is this? Please refer to <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html</a>",
"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 <a href=\"https://console-intl.huaweicloud.com/apiexplorer/#/endpoint?locale=en-us\" target=\"_blank\">https://console-intl.huaweicloud.com/apiexplorer/#/endpoint</a>",
@ -1021,7 +1026,6 @@
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "skip",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "not skip",
"workflow_node.notify.label": "Send notification",
"workflow_node.notify.help": "Invoke the APIs of the service provider to push message notifications.",
"workflow_node.notify.default_name": "Notification",
@ -1083,7 +1087,6 @@
"workflow_node.condition.default_name": "Parallel",
"workflow_node.condition.default_name.template_certtest_on_expire_soon": "If the certificate will be expiring soon ...",
"workflow_node.condition.default_name.template_certtest_on_expired": "If the certificate has expired ...",
"workflow_node.branch_block.label": "Branch",
"workflow_node.branch_block.default_name": "Branch",
"workflow_node.branch_block.state.no": "Enter Unconditionally",
@ -1112,7 +1115,6 @@
"workflow_node.branch_block.form.expression.value.option.true.label": "True",
"workflow_node.branch_block.form.expression.value.option.false.label": "False",
"workflow_node.branch_block.form.expression.add_condition.button": "Add condition",
"workflow_node.try_catch.label": "Execution result branch",
"workflow_node.try_catch.help": "Attempt to execute subsequent nodes, and when any node fails to execute, interrupt and enter the execution failure branch.",
"workflow_node.try_catch.default_name": "Try to ...",

View File

@ -1,4 +1,4 @@
{
{
"provider.1panel": "1Panel",
"provider.1panel.console": "1Panel - 面板自身",
"provider.1panel.site": "1Panel - 网站",
@ -96,6 +96,7 @@
"provider.huaweicloud.dns": "华为云 - 云解析 DNS",
"provider.huaweicloud.elb": "华为云 - 弹性负载均衡 ELB",
"provider.huaweicloud.scm_upload": "华为云 - 上传到云证书管理服务 SCM",
"provider.huaweicloud.obs": "华为云 - 对象储存 OBS",
"provider.huaweicloud.waf": "华为云 - Web 应用防火墙 WAF",
"provider.jdcloud": "京东云",
"provider.jdcloud.alb": "京东云 - 应用负载均衡 ALB",
@ -181,7 +182,6 @@
"provider.wecombot": "企业微信群机器人",
"provider.westcn": "西部数码",
"provider.zerossl": "ZeroSSL",
"provider.category.all": "全部",
"provider.category.cdn": "CDN",
"provider.category.storage": "文件存储",

View File

@ -17,7 +17,6 @@
"workflow_node.start.form.trigger_cron.tooltip": "五段式表达式,使用 <em>crontab</em> 标准语法规则。<br>支持使用任意值(即 <strong>*</strong>)、值列表分隔符(即 <strong>,</strong>)、值的范围(即 <strong>-</strong>)、步骤值(即 <strong>/</strong>)等四种表达式。",
"workflow_node.start.form.trigger_cron.help": "预计最近 5 次运行时间(实际时区以服务器设置为准):",
"workflow_node.start.form.trigger_cron.guide": "如果你有多个工作流,建议将它们设置为在一天中的多个时间段运行,而非总是在相同的特定时间。也不要总是设置为每日零时,以免遭遇证书颁发机构的流量高峰。<br><br>参考链接:<br>1. <a href=\"https://letsencrypt.org/zh-cn/docs/rate-limits/\" target=\"_blank\">Lets Encrypt 速率限制</a><br>2. <a href=\"https://letsencrypt.org/zh-cn/docs/faq/#%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E7%9A%84-let-s-encrypt-acme-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%90%AF%E5%8A%A8%E6%97%B6%E9%97%B4%E5%BA%94%E5%BD%93%E9%9A%8F%E6%9C%BA\" target=\"_blank\">为什么我的 Lets Encrypt (ACME) 客户端启动时间应当随机?</a>",
"workflow_node.apply.label": "申请签发证书",
"workflow_node.apply.help": "从证书颁发机构申请签发 SSL 证书。",
"workflow_node.apply.default_name": "申请",
@ -125,7 +124,6 @@
"workflow_node.apply.form.skip_before_expiry_days.suffix": "时,再次运行工作流时跳过此申请节点。",
"workflow_node.apply.form.skip_before_expiry_days.unit": "天",
"workflow_node.apply.form.skip_before_expiry_days.tooltip": "注意不要超过颁发的证书最大有效期,否则证书可能永远不会续期。",
"workflow_node.upload.label": "上传自有证书",
"workflow_node.upload.help": "上传用户已有的本地 SSL 证书。",
"workflow_node.upload.default_name": "上传",
@ -136,7 +134,6 @@
"workflow_node.upload.form.certificate.placeholder": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
"workflow_node.upload.form.private_key.label": "私钥文件PEM 格式)",
"workflow_node.upload.form.private_key.placeholder": "-----BEGIN (RSA|EC) PRIVATE KEY-----...-----END(RSA|EC) PRIVATE KEY-----",
"workflow_node.monitor.label": "监控网站证书",
"workflow_node.monitor.help": "通过 HTTPS 协议获取指定网站的 SSL 证书。",
"workflow_node.monitor.default_name": "监控",
@ -151,7 +148,6 @@
"workflow_node.monitor.form.domain.help": "提示:仅当主机地址为 IP 时需要输入。",
"workflow_node.monitor.form.request_path.label": "请求路径(可选)",
"workflow_node.monitor.form.request_path.placeholder": "请输入请求路径",
"workflow_node.deploy.label": "部署证书到 ...",
"workflow_node.deploy.help": "调用服务提供商相关 API将 SSL 证书部署到指定的目标。",
"workflow_node.deploy.default_name": "部署",
@ -520,6 +516,15 @@
"workflow_node.deploy.form.huaweicloud_cdn_domain.label": "华为云 CDN 加速域名",
"workflow_node.deploy.form.huaweicloud_cdn_domain.placeholder": "请输入华为云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.huaweicloud_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/cdn\" target=\"_blank\">https://console.huaweicloud.com/cdn</a>",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.label": "华为云 OBS 接口端点",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.placeholder": "请输入华为云 OBS 接口端点例如obs.cn-north-4.myhuaweicloud.com",
"workflow_node.deploy.form.huaweicloud_obs_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html</a>",
"workflow_node.deploy.form.huaweicloud_obs_bucket.label": "华为云 COS 存储桶名",
"workflow_node.deploy.form.huaweicloud_obs_bucket.placeholder": "请输入华为云 COS 存储桶名",
"workflow_node.deploy.form.huaweicloud_obs_bucket.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html</a>",
"workflow_node.deploy.form.huaweicloud_obs_domain.label": "华为云 COS 自定义域名",
"workflow_node.deploy.form.huaweicloud_obs_domain.placeholder": "请输入华为云 COS 自定义域名",
"workflow_node.deploy.form.huaweicloud_obs_domain.tooltip": "这是什么?请参阅 <a href=\"https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html\" target=\"_blank\">https://support.huaweicloud.com/productdesc-obs/obs_03_0207.html</a>",
"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": "这是什么?请参阅 <a href=\"https://console.huaweicloud.com/apiexplorer/#/endpoint\" target=\"_blank\">https://console.huaweicloud.com/apiexplorer/#/endpoint</a>",
@ -1019,7 +1024,6 @@
"workflow_node.deploy.form.skip_on_last_succeeded.suffix": "此部署节点。",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过",
"workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过",
"workflow_node.notify.label": "推送通知",
"workflow_node.notify.help": "调用服务提供商相关 API将消息通知推送到指定的目标。",
"workflow_node.notify.default_name": "通知",
@ -1110,14 +1114,11 @@
"workflow_node.branch_block.form.expression.value.option.true.label": "真",
"workflow_node.branch_block.form.expression.value.option.false.label": "假",
"workflow_node.branch_block.form.expression.add_condition.button": "添加条件",
"workflow_node.try_catch.label": "执行结果分支",
"workflow_node.try_catch.help": "尝试执行后续节点,当任一节点执行失败后,中断并进入执行失败分支。",
"workflow_node.try_catch.default_name": "尝试执行…",
"workflow_node.catch_block.label": "执行失败分支",
"workflow_node.catch_block.default_name": "若执行失败…",
"workflow_node.end.label": "结束",
"workflow_node.end.help": "中止工作流运行并退出。通常是一个工作流的最后一个节点。",
"workflow_node.end.default_name": "结束"