mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-09 14:09:52 +00:00
feat: allow fallback to use scp on deployment to ssh
This commit is contained in:
parent
5ee5460612
commit
9f7cffce21
@ -118,7 +118,7 @@ make local.run
|
|||||||
| 提供商 | 备注 |
|
| 提供商 | 备注 |
|
||||||
| :-------------------------------------- | :------------------------------------------------------------------ |
|
| :-------------------------------------- | :------------------------------------------------------------------ |
|
||||||
| 本地部署 | 可部署到本地服务器 |
|
| 本地部署 | 可部署到本地服务器 |
|
||||||
| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP) |
|
| SSH 部署 | 可部署到远程服务器(通过 SSH+SFTP/SCP) |
|
||||||
| Webhook 回调 | 可部署到 Webhook |
|
| Webhook 回调 | 可部署到 Webhook |
|
||||||
| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret |
|
| [Kubernetes](https://kubernetes.io/) | 可部署到 Kubernetes Secret |
|
||||||
| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 |
|
| [阿里云](https://www.aliyun.com/) | 可部署到阿里云 OSS、CDN、DCDN、SLB(CLB/ALB/NLB)、WAF、Live 等服务 |
|
||||||
|
@ -117,7 +117,7 @@ The following hosting providers are supported:
|
|||||||
| Provider | Remarks |
|
| Provider | Remarks |
|
||||||
| :---------------------------------------------- | :------------------------------------------------------------------------------- |
|
| :---------------------------------------------- | :------------------------------------------------------------------------------- |
|
||||||
| Local | Supports deployment to local servers |
|
| Local | Supports deployment to local servers |
|
||||||
| SSH | Supports deployment to remote servers (via SSH+SFTP) |
|
| SSH | Supports deployment to remote servers (via SSH+SFTP/SCP) |
|
||||||
| Webhook | Supports deployment to Webhook |
|
| Webhook | Supports deployment to Webhook |
|
||||||
| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret |
|
| [Kubernetes](https://kubernetes.io/) | Supports deployment to Kubernetes Secret |
|
||||||
| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), WAF, Live |
|
| [Alibaba Cloud](https://www.alibabacloud.com/) | Supports deployment to Alibaba Cloud OSS, CDN, DCDN, SLB(CLB/ALB/NLB), WAF, Live |
|
||||||
|
1
go.mod
1
go.mod
@ -29,6 +29,7 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.7
|
github.com/pkg/sftp v1.13.7
|
||||||
github.com/pocketbase/dbx v1.11.0
|
github.com/pocketbase/dbx v1.11.0
|
||||||
github.com/pocketbase/pocketbase v0.24.4
|
github.com/pocketbase/pocketbase v0.24.4
|
||||||
|
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246
|
||||||
github.com/qiniu/go-sdk/v7 v7.25.2
|
github.com/qiniu/go-sdk/v7 v7.25.2
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn v1.0.1084
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1084
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb v1.0.1084
|
||||||
|
3
go.sum
3
go.sum
@ -743,6 +743,8 @@ github.com/pocketbase/pocketbase v0.24.4 h1:kw/c23HccoxMV/19U9QlDcvNJgQ66vlUrxGQ
|
|||||||
github.com/pocketbase/pocketbase v0.24.4/go.mod h1:EfXV/8RUY76jA6g1RPNHjOuW7wTd2bz0QlvAI/RU8YY=
|
github.com/pocketbase/pocketbase v0.24.4/go.mod h1:EfXV/8RUY76jA6g1RPNHjOuW7wTd2bz0QlvAI/RU8YY=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
|
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246 h1:c4D8BPWLOxxdaxQLfLKQXH2YXY/E9yo3jrDSL54XrTw=
|
||||||
|
github.com/povsister/scp v0.0.0-20240802064259-28781e87b246/go.mod h1:i1Au86ZXK0ZalQNyBp2njCcyhSCR/QP/AMfILip+zNI=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||||
@ -933,6 +935,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
@ -350,6 +350,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
|
|||||||
SshPassword: access.Password,
|
SshPassword: access.Password,
|
||||||
SshKey: access.Key,
|
SshKey: access.Key,
|
||||||
SshKeyPassphrase: access.KeyPassphrase,
|
SshKeyPassphrase: access.KeyPassphrase,
|
||||||
|
UseSCP: maps.GetValueAsBool(options.ProviderDeployConfig, "useSCP"),
|
||||||
PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
|
PreCommand: maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
|
||||||
PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
|
PostCommand: maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
|
||||||
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))),
|
OutputFormat: providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))),
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
xerrors "github.com/pkg/errors"
|
xerrors "github.com/pkg/errors"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
|
"github.com/povsister/scp"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
@ -32,6 +33,8 @@ type SshDeployerConfig struct {
|
|||||||
SshKey string `json:"sshKey,omitempty"`
|
SshKey string `json:"sshKey,omitempty"`
|
||||||
// SSH 登录私钥口令。
|
// SSH 登录私钥口令。
|
||||||
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
|
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
|
||||||
|
// 是否回退使用 SCP。
|
||||||
|
UseSCP bool `json:"useSCP,omitempty"`
|
||||||
// 前置命令。
|
// 前置命令。
|
||||||
PreCommand string `json:"preCommand,omitempty"`
|
PreCommand string `json:"preCommand,omitempty"`
|
||||||
// 后置命令。
|
// 后置命令。
|
||||||
@ -112,13 +115,13 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
|
|||||||
// 上传证书和私钥文件
|
// 上传证书和私钥文件
|
||||||
switch d.config.OutputFormat {
|
switch d.config.OutputFormat {
|
||||||
case OUTPUT_FORMAT_PEM:
|
case OUTPUT_FORMAT_PEM:
|
||||||
if err := writeSftpFileString(client, d.config.OutputCertPath, certPem); err != nil {
|
if err := writeFileString(client, d.config.UseSCP, d.config.OutputCertPath, certPem); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
}
|
}
|
||||||
|
|
||||||
d.logger.Logt("certificate file uploaded")
|
d.logger.Logt("certificate file uploaded")
|
||||||
|
|
||||||
if err := writeSftpFileString(client, d.config.OutputKeyPath, privkeyPem); err != nil {
|
if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPem); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to upload private key file")
|
return nil, xerrors.Wrap(err, "failed to upload private key file")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +135,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
|
|||||||
|
|
||||||
d.logger.Logt("certificate transformed to PFX")
|
d.logger.Logt("certificate transformed to PFX")
|
||||||
|
|
||||||
if err := writeSftpFile(client, d.config.OutputCertPath, pfxData); err != nil {
|
if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, pfxData); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +149,7 @@ func (d *SshDeployer) Deploy(ctx context.Context, certPem string, privkeyPem str
|
|||||||
|
|
||||||
d.logger.Logt("certificate transformed to JKS")
|
d.logger.Logt("certificate transformed to JKS")
|
||||||
|
|
||||||
if err := writeSftpFile(client, d.config.OutputCertPath, jksData); err != nil {
|
if err := writeFile(client, d.config.UseSCP, d.config.OutputCertPath, jksData); err != nil {
|
||||||
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
return nil, xerrors.Wrap(err, "failed to upload certificate file")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,11 +226,47 @@ func execSshCommand(sshCli *ssh.Client, command string) (string, string, error)
|
|||||||
return stdoutBuf.String(), stderrBuf.String(), nil
|
return stdoutBuf.String(), stderrBuf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSftpFileString(sshCli *ssh.Client, path string, content string) error {
|
func writeFileString(sshCli *ssh.Client, useSCP bool, path string, content string) error {
|
||||||
return writeSftpFile(sshCli, path, []byte(content))
|
if useSCP {
|
||||||
|
return writeFileStringWithSCP(sshCli, path, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeFileStringWithSFTP(sshCli, path, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeSftpFile(sshCli *ssh.Client, path string, data []byte) error {
|
func writeFile(sshCli *ssh.Client, useSCP bool, path string, data []byte) error {
|
||||||
|
if useSCP {
|
||||||
|
return writeFileWithSCP(sshCli, path, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeFileWithSFTP(sshCli, path, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileStringWithSCP(sshCli *ssh.Client, path string, content string) error {
|
||||||
|
return writeFileWithSCP(sshCli, path, []byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileWithSCP(sshCli *ssh.Client, path string, data []byte) error {
|
||||||
|
scpCli, err := scp.NewClientFromExistingSSH(sshCli, &scp.ClientOption{})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to create scp client")
|
||||||
|
}
|
||||||
|
defer scpCli.Close()
|
||||||
|
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
err = scpCli.CopyToRemote(reader, path, &scp.FileTransferOption{})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Wrap(err, "failed to write to remote file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileStringWithSFTP(sshCli *ssh.Client, path string, content string) error {
|
||||||
|
return writeFileWithSFTP(sshCli, path, []byte(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFileWithSFTP(sshCli *ssh.Client, path string, data []byte) error {
|
||||||
sftpCli, err := sftp.NewClient(sshCli)
|
sftpCli, err := sftp.NewClient(sshCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Wrap(err, "failed to create sftp client")
|
return xerrors.Wrap(err, "failed to create sftp client")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
import { DownOutlined as DownOutlinedIcon } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Form, type FormInstance, Input, Select } from "antd";
|
import { Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -17,6 +17,7 @@ type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{
|
|||||||
jksStorepass?: string | null;
|
jksStorepass?: string | null;
|
||||||
preCommand?: string | null;
|
preCommand?: string | null;
|
||||||
postCommand?: string | null;
|
postCommand?: string | null;
|
||||||
|
useSCP?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type DeployNodeConfigFormSSHConfigProps = {
|
export type DeployNodeConfigFormSSHConfigProps = {
|
||||||
@ -89,6 +90,7 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
.string()
|
.string()
|
||||||
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
.max(20480, t("common.errmsg.string_max", { max: 20480 }))
|
||||||
.nullish(),
|
.nullish(),
|
||||||
|
useSCP: z.boolean().nullish(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
@ -261,6 +263,15 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_post_command.placeholder")} />
|
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_post_command.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="useSCP"
|
||||||
|
label={t("workflow_node.deploy.form.ssh_use_scp.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ssh_use_scp.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -291,6 +291,8 @@
|
|||||||
"workflow_node.deploy.form.ssh_post_command.placeholder": "Please enter command to be executed after uploading files",
|
"workflow_node.deploy.form.ssh_post_command.placeholder": "Please enter command to be executed after uploading files",
|
||||||
"workflow_node.deploy.form.ssh_preset_scripts.button": "Use preset scripts",
|
"workflow_node.deploy.form.ssh_preset_scripts.button": "Use preset scripts",
|
||||||
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - Reload nginx",
|
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - Reload nginx",
|
||||||
|
"workflow_node.deploy.form.ssh_use_scp.label": "Fallback to use SCP",
|
||||||
|
"workflow_node.deploy.form.ssh_use_scp.tooltip": "If the remote server does not support SFTP, please enable this option to fallback to SCP.",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "Tencent Cloud CDN domain",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "Tencent Cloud CDN domain",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "Please enter Tencent Cloud CDN domain name",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "Please enter Tencent Cloud CDN domain name",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "For more information, see <a href=\"https://console.tencentcloud.com/cdn\" target=\"_blank\">https://console.tencentcloud.com/cdn</a>",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "For more information, see <a href=\"https://console.tencentcloud.com/cdn\" target=\"_blank\">https://console.tencentcloud.com/cdn</a>",
|
||||||
|
@ -291,6 +291,8 @@
|
|||||||
"workflow_node.deploy.form.ssh_post_command.placeholder": "请输入保存文件后执行的命令",
|
"workflow_node.deploy.form.ssh_post_command.placeholder": "请输入保存文件后执行的命令",
|
||||||
"workflow_node.deploy.form.ssh_preset_scripts.button": "使用预设脚本",
|
"workflow_node.deploy.form.ssh_preset_scripts.button": "使用预设脚本",
|
||||||
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - 重启 nginx 进程",
|
"workflow_node.deploy.form.ssh_preset_scripts.option.reload_nginx.label": "POSIX Bash - 重启 nginx 进程",
|
||||||
|
"workflow_node.deploy.form.ssh_use_scp.label": "回退使用 SCP",
|
||||||
|
"workflow_node.deploy.form.ssh_use_scp.tooltip": "如果你的远程服务器不支持 SFTP,请开启此选项回退为 SCP。",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名(支持泛域名)",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.label": "腾讯云 CDN 加速域名(支持泛域名)",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.placeholder": "请输入腾讯云 CDN 加速域名",
|
||||||
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/cdn\" target=\"_blank\">https://console.cloud.tencent.com/cdn</a><br><br>泛域名表示形式为:*.example.com",
|
"workflow_node.deploy.form.tencentcloud_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.cloud.tencent.com/cdn\" target=\"_blank\">https://console.cloud.tencent.com/cdn</a><br><br>泛域名表示形式为:*.example.com",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user