mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 09:21:56 +08:00
commit
db2654ff84
@ -36,7 +36,7 @@ release:
|
|||||||
archives:
|
archives:
|
||||||
- id: archive_noncgo
|
- id: archive_noncgo
|
||||||
builds: [build_noncgo]
|
builds: [build_noncgo]
|
||||||
format: zip
|
format: "zip"
|
||||||
files:
|
files:
|
||||||
- CHANGELOG.md
|
- CHANGELOG.md
|
||||||
- LICENSE.md
|
- LICENSE.md
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
## 前提条件
|
## 前提条件
|
||||||
|
|
||||||
- Go 1.22+ (用于修改 Go 代码)
|
- Go 1.24+ (用于修改 Go 代码)
|
||||||
- Node 20+ (用于修改 UI)
|
- Node 20+ (用于修改 UI)
|
||||||
|
|
||||||
如果还没有这样做,你可以 fork Certimate 的主仓库,并克隆到本地以便进行修改:
|
如果还没有这样做,你可以 fork Certimate 的主仓库,并克隆到本地以便进行修改:
|
||||||
|
@ -9,7 +9,7 @@ Thank you for taking the time to improve Certimate! Below is a guide for submitt
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Go 1.22+ (for Go code changes)
|
- Go 1.24+ (for Go code changes)
|
||||||
- Node 20+ (for Admin UI changes)
|
- Node 20+ (for Admin UI changes)
|
||||||
|
|
||||||
If you haven't done so already, you can fork the Certimate repository and clone your fork to work locally:
|
If you haven't done so already, you can fork the Certimate repository and clone your fork to work locally:
|
||||||
|
@ -48,8 +48,8 @@ Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决
|
|||||||
- 灵活的工作流编排方式,证书从申请到部署完全自动化;
|
- 灵活的工作流编排方式,证书从申请到部署完全自动化;
|
||||||
- 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法;
|
- 支持单域名、多域名、泛域名证书,可选 RSA、ECC 签名算法;
|
||||||
- 支持 PEM、PFX、JKS 等多种格式输出证书;
|
- 支持 PEM、PFX、JKS 等多种格式输出证书;
|
||||||
- 支持 20+ 域名托管商(如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers));
|
- 支持 30+ 域名托管商(如阿里云、腾讯云、Cloudflare 等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-dns-providers));
|
||||||
- 支持 70+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers));
|
- 支持 80+ 部署目标(如 Kubernetes、CDN、WAF、负载均衡等,[点此查看完整清单](https://docs.certimate.me/docs/reference/providers#supported-host-providers));
|
||||||
- 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道;
|
- 支持邮件、钉钉、飞书、企业微信、Webhook 等多种通知渠道;
|
||||||
- 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构;
|
- 支持 Let's Encrypt、Buypass、Google Trust Services、SSL.com、ZeroSSL 等多种 ACME 证书颁发机构;
|
||||||
- 更多特性等待探索。
|
- 更多特性等待探索。
|
||||||
|
@ -38,8 +38,8 @@ Certimate aims to provide users with a secure and user-friendly SSL certificate
|
|||||||
- Flexible workflow orchestration, fully automation from certificate application to deployment;
|
- Flexible workflow orchestration, fully automation from certificate application to deployment;
|
||||||
- Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC.
|
- Supports single-domain, multi-domain, wildcard certificates, with options for RSA or ECC.
|
||||||
- Supports various certificate formats such as PEM, PFX, JKS.
|
- Supports various certificate formats such as PEM, PFX, JKS.
|
||||||
- Supports more than 20+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers));
|
- Supports more than 30+ domain registrars (e.g., Alibaba Cloud, Tencent Cloud, Cloudflare, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-dns-providers));
|
||||||
- Supports more than 70+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers));
|
- Supports more than 80+ deployment targets (e.g., Kubernetes, CDN, WAF, load balancers, etc. [Check out this link](https://docs.certimate.me/en/docs/reference/providers#supported-host-providers));
|
||||||
- Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more;
|
- Supports multiple notification channels including email, DingTalk, Feishu, WeCom, Webhook, and more;
|
||||||
- Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust Services,SSL.com, ZeroSSL, and more;
|
- Supports multiple ACME CAs including Let's Encrypt, Buypass, Google Trust Services,SSL.com, ZeroSSL, and more;
|
||||||
- More features waiting to be discovered.
|
- More features waiting to be discovered.
|
||||||
|
@ -27,6 +27,8 @@ import (
|
|||||||
pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
|
pNamecheap "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namecheap"
|
||||||
pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
|
pNameDotCom "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namedotcom"
|
||||||
pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
|
pNameSilo "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/namesilo"
|
||||||
|
pNetcup "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/netcup"
|
||||||
|
pNetlify "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/netlify"
|
||||||
pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
|
pNS1 "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/ns1"
|
||||||
pPorkbun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/porkbun"
|
pPorkbun "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/porkbun"
|
||||||
pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
|
pPowerDNS "github.com/usual2970/certimate/internal/pkg/core/applicant/acme-dns-01/lego-providers/powerdns"
|
||||||
@ -402,6 +404,38 @@ func createApplicantProvider(options *applicantProviderOptions) (challenge.Provi
|
|||||||
return applicant, err
|
return applicant, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case domain.ACMEDns01ProviderTypeNetcup:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForNetcup{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
applicant, err := pNetcup.NewChallengeProvider(&pNetcup.ChallengeProviderConfig{
|
||||||
|
CustomerNumber: access.CustomerNumber,
|
||||||
|
ApiKey: access.ApiKey,
|
||||||
|
ApiPassword: access.ApiPassword,
|
||||||
|
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||||
|
DnsTTL: options.DnsTTL,
|
||||||
|
})
|
||||||
|
return applicant, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case domain.ACMEDns01ProviderTypeNetlify:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForNetlify{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
applicant, err := pNetlify.NewChallengeProvider(&pNetlify.ChallengeProviderConfig{
|
||||||
|
ApiToken: access.ApiToken,
|
||||||
|
DnsPropagationTimeout: options.DnsPropagationTimeout,
|
||||||
|
DnsTTL: options.DnsTTL,
|
||||||
|
})
|
||||||
|
return applicant, err
|
||||||
|
}
|
||||||
|
|
||||||
case domain.ACMEDns01ProviderTypeNS1:
|
case domain.ACMEDns01ProviderTypeNS1:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForNS1{}
|
access := domain.AccessConfigForNS1{}
|
||||||
|
@ -52,6 +52,7 @@ import (
|
|||||||
pJDCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-vod"
|
pJDCloudVOD "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/jdcloud-vod"
|
||||||
pK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
|
pK8sSecret "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/k8s-secret"
|
||||||
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
|
pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
|
||||||
|
pNetlifySite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/netlify-site"
|
||||||
pProxmoxVE "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/proxmoxve"
|
pProxmoxVE "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/proxmoxve"
|
||||||
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
|
pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
|
||||||
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
|
pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
|
||||||
@ -306,6 +307,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
||||||
|
CertificateArn: maputil.GetString(options.ProviderExtendedConfig, "certificateArn"),
|
||||||
})
|
})
|
||||||
return deployer, err
|
return deployer, err
|
||||||
|
|
||||||
@ -581,6 +583,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
|
|
||||||
deployer, err := pGoEdge.NewDeployer(&pGoEdge.DeployerConfig{
|
deployer, err := pGoEdge.NewDeployer(&pGoEdge.DeployerConfig{
|
||||||
ApiUrl: access.ApiUrl,
|
ApiUrl: access.ApiUrl,
|
||||||
|
ApiRole: access.ApiRole,
|
||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
AccessKey: access.AccessKey,
|
AccessKey: access.AccessKey,
|
||||||
AllowInsecureConnections: access.AllowInsecureConnections,
|
AllowInsecureConnections: access.AllowInsecureConnections,
|
||||||
@ -698,6 +701,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"),
|
PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"),
|
||||||
OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))),
|
OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))),
|
||||||
OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"),
|
OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"),
|
||||||
|
OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"),
|
||||||
|
OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"),
|
||||||
OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"),
|
OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"),
|
||||||
PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||||
JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||||
@ -725,6 +730,20 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
return deployer, err
|
return deployer, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case domain.DeploymentProviderTypeNetlifySite:
|
||||||
|
{
|
||||||
|
access := domain.AccessConfigForNetlify{}
|
||||||
|
if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployer, err := pNetlifySite.NewDeployer(&pNetlifySite.DeployerConfig{
|
||||||
|
ApiToken: access.ApiToken,
|
||||||
|
SiteId: maputil.GetString(options.ProviderExtendedConfig, "siteId"),
|
||||||
|
})
|
||||||
|
return deployer, err
|
||||||
|
}
|
||||||
|
|
||||||
case domain.DeploymentProviderTypeProxmoxVE:
|
case domain.DeploymentProviderTypeProxmoxVE:
|
||||||
{
|
{
|
||||||
access := domain.AccessConfigForProxmoxVE{}
|
access := domain.AccessConfigForProxmoxVE{}
|
||||||
@ -830,6 +849,8 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"),
|
PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"),
|
||||||
OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))),
|
OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))),
|
||||||
OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"),
|
OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"),
|
||||||
|
OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"),
|
||||||
|
OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"),
|
||||||
OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"),
|
OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"),
|
||||||
PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"),
|
||||||
JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"),
|
||||||
|
@ -149,6 +149,7 @@ type AccessConfigForGoDaddy struct {
|
|||||||
|
|
||||||
type AccessConfigForGoEdge struct {
|
type AccessConfigForGoEdge struct {
|
||||||
ApiUrl string `json:"apiUrl"`
|
ApiUrl string `json:"apiUrl"`
|
||||||
|
ApiRole string `json:"apiRole"`
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||||
@ -198,6 +199,16 @@ type AccessConfigForNameSilo struct {
|
|||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AccessConfigForNetcup struct {
|
||||||
|
CustomerNumber string `json:"customerNumber"`
|
||||||
|
ApiKey string `json:"apiKey"`
|
||||||
|
ApiPassword string `json:"apiPassword"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessConfigForNetlify struct {
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
}
|
||||||
|
|
||||||
type AccessConfigForNS1 struct {
|
type AccessConfigForNS1 struct {
|
||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ type AccessProviderType string
|
|||||||
*/
|
*/
|
||||||
const (
|
const (
|
||||||
AccessProviderType1Panel = AccessProviderType("1panel")
|
AccessProviderType1Panel = AccessProviderType("1panel")
|
||||||
|
AccessProviderTypeACMECA = AccessProviderType("acmeca") // ACME CA(预留)
|
||||||
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
|
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
|
||||||
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
|
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留)
|
||||||
AccessProviderTypeAliyun = AccessProviderType("aliyun")
|
AccessProviderTypeAliyun = AccessProviderType("aliyun")
|
||||||
@ -36,6 +37,7 @@ const (
|
|||||||
AccessProviderTypeEdgio = AccessProviderType("edgio")
|
AccessProviderTypeEdgio = AccessProviderType("edgio")
|
||||||
AccessProviderTypeEmail = AccessProviderType("email")
|
AccessProviderTypeEmail = AccessProviderType("email")
|
||||||
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
|
AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留)
|
||||||
|
AccessProviderTypeFlexCDN = AccessProviderType("flexcdn") // FlexCDN(预留)
|
||||||
AccessProviderTypeGname = AccessProviderType("gname")
|
AccessProviderTypeGname = AccessProviderType("gname")
|
||||||
AccessProviderTypeGcore = AccessProviderType("gcore")
|
AccessProviderTypeGcore = AccessProviderType("gcore")
|
||||||
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
|
AccessProviderTypeGoDaddy = AccessProviderType("godaddy")
|
||||||
@ -47,11 +49,14 @@ const (
|
|||||||
AccessProviderTypeLarkBot = AccessProviderType("larkbot")
|
AccessProviderTypeLarkBot = AccessProviderType("larkbot")
|
||||||
AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt")
|
AccessProviderTypeLetsEncrypt = AccessProviderType("letsencrypt")
|
||||||
AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging")
|
AccessProviderTypeLetsEncryptStaging = AccessProviderType("letsencryptstaging")
|
||||||
|
AccessProviderTypeLeCDN = AccessProviderType("lecdn") // LeCDN(预留)
|
||||||
AccessProviderTypeLocal = AccessProviderType("local")
|
AccessProviderTypeLocal = AccessProviderType("local")
|
||||||
AccessProviderTypeMattermost = AccessProviderType("mattermost")
|
AccessProviderTypeMattermost = AccessProviderType("mattermost")
|
||||||
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
|
AccessProviderTypeNamecheap = AccessProviderType("namecheap")
|
||||||
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
|
AccessProviderTypeNameDotCom = AccessProviderType("namedotcom")
|
||||||
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
|
AccessProviderTypeNameSilo = AccessProviderType("namesilo")
|
||||||
|
AccessProviderTypeNetcup = AccessProviderType("netcup")
|
||||||
|
AccessProviderTypeNetlify = AccessProviderType("netlify")
|
||||||
AccessProviderTypeNS1 = AccessProviderType("ns1")
|
AccessProviderTypeNS1 = AccessProviderType("ns1")
|
||||||
AccessProviderTypePorkbun = AccessProviderType("porkbun")
|
AccessProviderTypePorkbun = AccessProviderType("porkbun")
|
||||||
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
|
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
|
||||||
@ -130,6 +135,8 @@ const (
|
|||||||
ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap)
|
ACMEDns01ProviderTypeNamecheap = ACMEDns01ProviderType(AccessProviderTypeNamecheap)
|
||||||
ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom)
|
ACMEDns01ProviderTypeNameDotCom = ACMEDns01ProviderType(AccessProviderTypeNameDotCom)
|
||||||
ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo)
|
ACMEDns01ProviderTypeNameSilo = ACMEDns01ProviderType(AccessProviderTypeNameSilo)
|
||||||
|
ACMEDns01ProviderTypeNetcup = ACMEDns01ProviderType(AccessProviderTypeNetcup)
|
||||||
|
ACMEDns01ProviderTypeNetlify = ACMEDns01ProviderType(AccessProviderTypeNetlify)
|
||||||
ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
|
ACMEDns01ProviderTypeNS1 = ACMEDns01ProviderType(AccessProviderTypeNS1)
|
||||||
ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
|
ACMEDns01ProviderTypePorkbun = ACMEDns01ProviderType(AccessProviderTypePorkbun)
|
||||||
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
|
ACMEDns01ProviderTypePowerDNS = ACMEDns01ProviderType(AccessProviderTypePowerDNS)
|
||||||
@ -165,6 +172,7 @@ const (
|
|||||||
DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos")
|
DeploymentProviderTypeAliyunDDoS = DeploymentProviderType(AccessProviderTypeAliyun + "-ddos")
|
||||||
DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa")
|
DeploymentProviderTypeAliyunESA = DeploymentProviderType(AccessProviderTypeAliyun + "-esa")
|
||||||
DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc")
|
DeploymentProviderTypeAliyunFC = DeploymentProviderType(AccessProviderTypeAliyun + "-fc")
|
||||||
|
DeploymentProviderTypeAliyunGA = DeploymentProviderType(AccessProviderTypeAliyun + "-ga") // 阿里云全球加速(预留)
|
||||||
DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live")
|
DeploymentProviderTypeAliyunLive = DeploymentProviderType(AccessProviderTypeAliyun + "-live")
|
||||||
DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb")
|
DeploymentProviderTypeAliyunNLB = DeploymentProviderType(AccessProviderTypeAliyun + "-nlb")
|
||||||
DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss")
|
DeploymentProviderTypeAliyunOSS = DeploymentProviderType(AccessProviderTypeAliyun + "-oss")
|
||||||
@ -186,6 +194,7 @@ const (
|
|||||||
DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly)
|
DeploymentProviderTypeCdnfly = DeploymentProviderType(AccessProviderTypeCdnfly)
|
||||||
DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn")
|
DeploymentProviderTypeDogeCloudCDN = DeploymentProviderType(AccessProviderTypeDogeCloud + "-cdn")
|
||||||
DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications")
|
DeploymentProviderTypeEdgioApplications = DeploymentProviderType(AccessProviderTypeEdgio + "-applications")
|
||||||
|
DeploymentProviderTypeFlexCDN = DeploymentProviderType(AccessProviderTypeFlexCDN) // FlexCDN(预留)
|
||||||
DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn")
|
DeploymentProviderTypeGcoreCDN = DeploymentProviderType(AccessProviderTypeGcore + "-cdn")
|
||||||
DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge)
|
DeploymentProviderTypeGoEdge = DeploymentProviderType(AccessProviderTypeGoEdge)
|
||||||
DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn")
|
DeploymentProviderTypeHuaweiCloudCDN = DeploymentProviderType(AccessProviderTypeHuaweiCloud + "-cdn")
|
||||||
@ -197,7 +206,9 @@ const (
|
|||||||
DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live")
|
DeploymentProviderTypeJDCloudLive = DeploymentProviderType(AccessProviderTypeJDCloud + "-live")
|
||||||
DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod")
|
DeploymentProviderTypeJDCloudVOD = DeploymentProviderType(AccessProviderTypeJDCloud + "-vod")
|
||||||
DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret")
|
DeploymentProviderTypeKubernetesSecret = DeploymentProviderType(AccessProviderTypeKubernetes + "-secret")
|
||||||
|
DeploymentProviderTypeLeCDN = DeploymentProviderType(AccessProviderTypeLeCDN) // LeCDN(预留)
|
||||||
DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal)
|
DeploymentProviderTypeLocal = DeploymentProviderType(AccessProviderTypeLocal)
|
||||||
|
DeploymentProviderTypeNetlifySite = DeploymentProviderType(AccessProviderTypeNetlify + "-site")
|
||||||
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
|
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
|
||||||
DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn")
|
DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn")
|
||||||
DeploymentProviderTypeQiniuKodo = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo")
|
DeploymentProviderTypeQiniuKodo = DeploymentProviderType(AccessProviderTypeQiniu + "-kodo")
|
||||||
@ -228,7 +239,9 @@ const (
|
|||||||
DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex")
|
DeploymentProviderTypeVolcEngineImageX = DeploymentProviderType(AccessProviderTypeVolcEngine + "-imagex")
|
||||||
DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live")
|
DeploymentProviderTypeVolcEngineLive = DeploymentProviderType(AccessProviderTypeVolcEngine + "-live")
|
||||||
DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos")
|
DeploymentProviderTypeVolcEngineTOS = DeploymentProviderType(AccessProviderTypeVolcEngine + "-tos")
|
||||||
|
DeploymentProviderTypeWangsuCDN = DeploymentProviderType(AccessProviderTypeWangsu + "-cdn") // 网宿 CDN(预留)
|
||||||
DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro")
|
DeploymentProviderTypeWangsuCDNPro = DeploymentProviderType(AccessProviderTypeWangsu + "-cdnpro")
|
||||||
|
DeploymentProviderTypeWangsuCert = DeploymentProviderType(AccessProviderTypeWangsu + "-cert") // 网宿证书管理(预留)
|
||||||
DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook)
|
DeploymentProviderTypeWebhook = DeploymentProviderType(AccessProviderTypeWebhook)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package netcup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netcup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChallengeProviderConfig struct {
|
||||||
|
CustomerNumber string `json:"customerNumber"`
|
||||||
|
ApiKey string `json:"apiKey"`
|
||||||
|
ApiPassword string `json:"apiPassword"`
|
||||||
|
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
|
||||||
|
DnsTTL int32 `json:"dnsTTL,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
|
||||||
|
if config == nil {
|
||||||
|
panic("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConfig := netcup.NewDefaultConfig()
|
||||||
|
providerConfig.Customer = config.CustomerNumber
|
||||||
|
providerConfig.Key = config.ApiKey
|
||||||
|
providerConfig.Password = config.ApiPassword
|
||||||
|
if config.DnsPropagationTimeout != 0 {
|
||||||
|
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||||
|
}
|
||||||
|
if config.DnsTTL != 0 {
|
||||||
|
providerConfig.TTL = int(config.DnsTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := netcup.NewDNSProviderConfig(providerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package netcup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
"github.com/go-acme/lego/v4/providers/dns/netlify"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChallengeProviderConfig struct {
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"`
|
||||||
|
DnsTTL int32 `json:"dnsTTL,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChallengeProvider(config *ChallengeProviderConfig) (challenge.Provider, error) {
|
||||||
|
if config == nil {
|
||||||
|
panic("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
providerConfig := netlify.NewDefaultConfig()
|
||||||
|
providerConfig.Token = config.ApiToken
|
||||||
|
if config.DnsPropagationTimeout != 0 {
|
||||||
|
providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second
|
||||||
|
}
|
||||||
|
if config.DnsTTL != 0 {
|
||||||
|
providerConfig.TTL = int(config.DnsTTL)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := netlify.NewDNSProviderConfig(providerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
|
||||||
|
|
||||||
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
alislb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
alislb "github.com/alibabacloud-go/slb-20140515/v4/client"
|
||||||
@ -310,22 +309,10 @@ func createSdkClient(accessKeyId, accessKeySecret, region string) (*alislb.Clien
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
|
func createSslUploader(accessKeyId, accessKeySecret, region string) (uploader.Uploader, error) {
|
||||||
casRegion := region
|
|
||||||
if casRegion != "" {
|
|
||||||
// 阿里云 CAS 服务接入点是独立于 CLB 服务的
|
|
||||||
// 国内版固定接入点:华东一杭州
|
|
||||||
// 国际版固定接入点:亚太东南一新加坡
|
|
||||||
if !strings.HasPrefix(casRegion, "cn-") {
|
|
||||||
casRegion = "ap-southeast-1"
|
|
||||||
} else {
|
|
||||||
casRegion = "cn-hangzhou"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||||
AccessKeyId: accessKeyId,
|
AccessKeyId: accessKeyId,
|
||||||
AccessKeySecret: accessKeySecret,
|
AccessKeySecret: accessKeySecret,
|
||||||
Region: casRegion,
|
Region: region,
|
||||||
})
|
})
|
||||||
return uploader, err
|
return uploader, err
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
aws "github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
awscfg "github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
awscred "github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
awsacm "github.com/aws/aws-sdk-go-v2/service/acm"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
|
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
|
||||||
|
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerConfig struct {
|
type DeployerConfig struct {
|
||||||
@ -17,11 +23,15 @@ type DeployerConfig struct {
|
|||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
// AWS 区域。
|
// AWS 区域。
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
// ACM 证书 ARN。
|
||||||
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
|
CertificateArn string `json:"certificateArn,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeployerProvider struct {
|
type DeployerProvider struct {
|
||||||
config *DeployerConfig
|
config *DeployerConfig
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
sdkClient *awsacm.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +42,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
|||||||
panic("config is nil")
|
panic("config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||||
AccessKeyId: config.AccessKeyId,
|
AccessKeyId: config.AccessKeyId,
|
||||||
SecretAccessKey: config.SecretAccessKey,
|
SecretAccessKey: config.SecretAccessKey,
|
||||||
@ -44,6 +59,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
|||||||
return &DeployerProvider{
|
return &DeployerProvider{
|
||||||
config: config,
|
config: config,
|
||||||
logger: slog.Default(),
|
logger: slog.Default(),
|
||||||
|
sdkClient: client,
|
||||||
sslUploader: uploader,
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -59,6 +75,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.CertificateArn == "" {
|
||||||
// 上传证书到 ACM
|
// 上传证书到 ACM
|
||||||
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
|
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,6 +83,40 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
} else {
|
} else {
|
||||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 提取服务器证书
|
||||||
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入证书
|
||||||
|
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
|
||||||
|
importCertificateReq := &awsacm.ImportCertificateInput{
|
||||||
|
CertificateArn: aws.String(d.config.CertificateArn),
|
||||||
|
Certificate: ([]byte)(serverCertPEM),
|
||||||
|
CertificateChain: ([]byte)(intermediaCertPEM),
|
||||||
|
PrivateKey: ([]byte)(privkeyPEM),
|
||||||
|
}
|
||||||
|
importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
|
||||||
|
d.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &deployer.DeployResult{}, nil
|
return &deployer.DeployResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsacm.Client, error) {
|
||||||
|
cfg, err := awscfg.LoadDefaultConfig(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := awsacm.NewFromConfig(cfg, func(o *awsacm.Options) {
|
||||||
|
o.Region = region
|
||||||
|
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
|
||||||
|
})
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ type DeployerConfig struct {
|
|||||||
// Key Vault 名称。
|
// Key Vault 名称。
|
||||||
KeyVaultName string `json:"keyvaultName"`
|
KeyVaultName string `json:"keyvaultName"`
|
||||||
// Key Vault 证书名称。
|
// Key Vault 证书名称。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateName string `json:"certificateName,omitempty"`
|
CertificateName string `json:"certificateName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ type DeployerConfig struct {
|
|||||||
// 加速域名(支持泛域名)。
|
// 加速域名(支持泛域名)。
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId string `json:"certificateId,omitempty"`
|
CertificateId string `json:"certificateId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +20,11 @@ type DeployerConfig struct {
|
|||||||
ApiKey string `json:"apiKey"`
|
ApiKey string `json:"apiKey"`
|
||||||
// 是否允许不安全的连接。
|
// 是否允许不安全的连接。
|
||||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||||
// 站点类型。
|
// 网站类型。
|
||||||
SiteType string `json:"siteType"`
|
SiteType string `json:"siteType"`
|
||||||
// 站点名称(单个)。
|
// 网站名称(单个)。
|
||||||
SiteName string `json:"siteName,omitempty"`
|
SiteName string `json:"siteName,omitempty"`
|
||||||
// 站点名称(多个)。
|
// 网站名称(多个)。
|
||||||
SiteNames []string `json:"siteNames,omitempty"`
|
SiteNames []string `json:"siteNames,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
// 上传证书
|
// 上传证书
|
||||||
|
// REF: https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post
|
||||||
createCertificateReq := &cfsdk.CreateCertificateRequest{
|
createCertificateReq := &cfsdk.CreateCertificateRequest{
|
||||||
Certificate: certPEM,
|
Certificate: certPEM,
|
||||||
CertificateKey: privkeyPEM,
|
CertificateKey: privkeyPEM,
|
||||||
|
@ -56,18 +56,18 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
// 提取 Edgio 所需的服务端证书和中间证书内容
|
// 提取服务器证书和中间证书
|
||||||
privateCertPEM, intermediateCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传 TLS 证书
|
// 上传 TLS 证书
|
||||||
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
// REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts
|
||||||
uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
|
uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{
|
||||||
EnvironmentID: d.config.EnvironmentId,
|
EnvironmentID: d.config.EnvironmentId,
|
||||||
PrimaryCert: privateCertPEM,
|
PrimaryCert: serverCertPEM,
|
||||||
IntermediateCert: intermediateCertPEM,
|
IntermediateCert: intermediaCertPEM,
|
||||||
PrivateKey: privkeyPEM,
|
PrivateKey: privkeyPEM,
|
||||||
}
|
}
|
||||||
uploadTlsCertResp, err := d.sdkClient.UploadTlsCert(uploadTlsCertReq)
|
uploadTlsCertResp, err := d.sdkClient.UploadTlsCert(uploadTlsCertReq)
|
||||||
|
@ -24,7 +24,7 @@ type DeployerConfig struct {
|
|||||||
// CDN 资源 ID。
|
// CDN 资源 ID。
|
||||||
ResourceId int64 `json:"resourceId"`
|
ResourceId int64 `json:"resourceId"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId int64 `json:"certificateId,omitempty"`
|
CertificateId int64 `json:"certificateId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
ValidateRootCA: false,
|
ValidateRootCA: false,
|
||||||
}
|
}
|
||||||
changeCertificateResp, err := d.sdkClients.SSLCerts.Update(context.TODO(), getCertificateDetailResp.ID, changeCertificateReq)
|
changeCertificateResp, err := d.sdkClients.SSLCerts.Update(context.TODO(), getCertificateDetailResp.ID, changeCertificateReq)
|
||||||
d.logger.Debug("sdk request 'sslcerts.Create'", slog.Any("request", changeCertificateReq), slog.Any("response", changeCertificateResp))
|
d.logger.Debug("sdk request 'sslcerts.Update'", slog.Any("sslId", getCertificateDetailResp.ID), slog.Any("request", changeCertificateReq), slog.Any("response", changeCertificateResp))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Update': %w", err)
|
return nil, fmt.Errorf("failed to execute sdk request 'sslcerts.Update': %w", err)
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ import (
|
|||||||
type DeployerConfig struct {
|
type DeployerConfig struct {
|
||||||
// GoEdge URL。
|
// GoEdge URL。
|
||||||
ApiUrl string `json:"apiUrl"`
|
ApiUrl string `json:"apiUrl"`
|
||||||
// GoEdge 用户 AccessKeyId。
|
// GoEdge 用户角色。
|
||||||
|
ApiRole string `json:"apiRole"`
|
||||||
|
// GoEdge AccessKeyId。
|
||||||
AccessKeyId string `json:"accessKeyId"`
|
AccessKeyId string `json:"accessKeyId"`
|
||||||
// GoEdge 用户 AccessKey。
|
// GoEdge AccessKey。
|
||||||
AccessKey string `json:"accessKey"`
|
AccessKey string `json:"accessKey"`
|
||||||
// 是否允许不安全的连接。
|
// 是否允许不安全的连接。
|
||||||
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
|
||||||
@ -44,7 +46,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
|||||||
panic("config is nil")
|
panic("config is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := createSdkClient(config.ApiUrl, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections)
|
client, err := createSdkClient(config.ApiUrl, config.ApiRole, config.AccessKeyId, config.AccessKey, config.AllowInsecureConnections)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
||||||
}
|
}
|
||||||
@ -116,11 +118,15 @@ func (d *DeployerProvider) deployToCertificate(ctx context.Context, certPEM stri
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSdkClient(apiUrl, accessKeyId, accessKey string, skipTlsVerify bool) (*goedgesdk.Client, error) {
|
func createSdkClient(apiUrl, apiRole, accessKeyId, accessKey string, skipTlsVerify bool) (*goedgesdk.Client, error) {
|
||||||
if _, err := url.Parse(apiUrl); err != nil {
|
if _, err := url.Parse(apiUrl); err != nil {
|
||||||
return nil, errors.New("invalid goedge api url")
|
return nil, errors.New("invalid goedge api url")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if apiRole != "user" && apiRole != "admin" {
|
||||||
|
return nil, errors.New("invalid goedge api role")
|
||||||
|
}
|
||||||
|
|
||||||
if accessKeyId == "" {
|
if accessKeyId == "" {
|
||||||
return nil, errors.New("invalid goedge access key id")
|
return nil, errors.New("invalid goedge access key id")
|
||||||
}
|
}
|
||||||
@ -129,7 +135,7 @@ func createSdkClient(apiUrl, accessKeyId, accessKey string, skipTlsVerify bool)
|
|||||||
return nil, errors.New("invalid goedge access key")
|
return nil, errors.New("invalid goedge access key")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := goedgesdk.NewClient(apiUrl, "user", accessKeyId, accessKey)
|
client := goedgesdk.NewClient(apiUrl, apiRole, accessKeyId, accessKey)
|
||||||
if skipTlsVerify {
|
if skipTlsVerify {
|
||||||
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
client.WithTLSConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,12 @@ type DeployerConfig struct {
|
|||||||
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
||||||
// 输出证书文件路径。
|
// 输出证书文件路径。
|
||||||
OutputCertPath string `json:"outputCertPath,omitempty"`
|
OutputCertPath string `json:"outputCertPath,omitempty"`
|
||||||
|
// 输出服务器证书文件路径。
|
||||||
|
// 选填。
|
||||||
|
OutputServerCertPath string `json:"outputServerCertPath,omitempty"`
|
||||||
|
// 输出中间证书文件路径。
|
||||||
|
// 选填。
|
||||||
|
OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"`
|
||||||
// 输出私钥文件路径。
|
// 输出私钥文件路径。
|
||||||
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||||
// PFX 导出密码。
|
// PFX 导出密码。
|
||||||
@ -69,6 +75,12 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
|
// 提取服务器证书和中间证书
|
||||||
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 执行前置命令
|
// 执行前置命令
|
||||||
if d.config.PreCommand != "" {
|
if d.config.PreCommand != "" {
|
||||||
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand)
|
stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand)
|
||||||
@ -86,6 +98,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
}
|
}
|
||||||
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
|
d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath))
|
||||||
|
|
||||||
|
if d.config.OutputServerCertPath != "" {
|
||||||
|
if err := fileutil.WriteString(d.config.OutputServerCertPath, serverCertPEM); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
|
||||||
|
}
|
||||||
|
d.logger.Info("ssl server certificate file saved", slog.String("path", d.config.OutputServerCertPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.config.OutputIntermediaCertPath != "" {
|
||||||
|
if err := fileutil.WriteString(d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
|
||||||
|
}
|
||||||
|
d.logger.Info("ssl intermedia certificate file saved", slog.String("path", d.config.OutputIntermediaCertPath))
|
||||||
|
}
|
||||||
|
|
||||||
if err := fileutil.WriteString(d.config.OutputKeyPath, privkeyPEM); err != nil {
|
if err := fileutil.WriteString(d.config.OutputKeyPath, privkeyPEM); err != nil {
|
||||||
return nil, fmt.Errorf("failed to save private key file: %w", err)
|
return nil, fmt.Errorf("failed to save private key file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
package netlifysite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/usual2970/certimate/internal/pkg/core/deployer"
|
||||||
|
netlifysdk "github.com/usual2970/certimate/internal/pkg/sdk3rd/netlify"
|
||||||
|
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeployerConfig struct {
|
||||||
|
// netlify API Token。
|
||||||
|
ApiToken string `json:"apiToken"`
|
||||||
|
// netlify 网站 ID。
|
||||||
|
SiteId string `json:"siteId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeployerProvider struct {
|
||||||
|
config *DeployerConfig
|
||||||
|
logger *slog.Logger
|
||||||
|
sdkClient *netlifysdk.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ deployer.Deployer = (*DeployerProvider)(nil)
|
||||||
|
|
||||||
|
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
||||||
|
if config == nil {
|
||||||
|
panic("config is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := createSdkClient(config.ApiToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create sdk client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DeployerProvider{
|
||||||
|
config: config,
|
||||||
|
logger: slog.Default(),
|
||||||
|
sdkClient: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
||||||
|
if logger == nil {
|
||||||
|
d.logger = slog.Default()
|
||||||
|
} else {
|
||||||
|
d.logger = logger
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
|
if d.config.SiteId == "" {
|
||||||
|
return nil, errors.New("config `siteId` is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取服务器证书和中间证书
|
||||||
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传网站证书
|
||||||
|
// REF: https://open-api.netlify.com/#tag/sniCertificate/operation/provisionSiteTLSCertificate
|
||||||
|
provisionSiteTLSCertificateReq := &netlifysdk.ProvisionSiteTLSCertificateParams{
|
||||||
|
Certificate: serverCertPEM,
|
||||||
|
CACertificates: intermediaCertPEM,
|
||||||
|
Key: privkeyPEM,
|
||||||
|
}
|
||||||
|
provisionSiteTLSCertificateResp, err := d.sdkClient.ProvisionSiteTLSCertificate(d.config.SiteId, provisionSiteTLSCertificateReq)
|
||||||
|
d.logger.Debug("sdk request 'netlify.provisionSiteTLSCertificate'", slog.String("siteId", d.config.SiteId), slog.Any("request", provisionSiteTLSCertificateReq), slog.Any("response", provisionSiteTLSCertificateResp))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute sdk request 'netlify.provisionSiteTLSCertificate': %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deployer.DeployResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSdkClient(apiToken string) (*netlifysdk.Client, error) {
|
||||||
|
if apiToken == "" {
|
||||||
|
return nil, errors.New("invalid netlify api token")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := netlifysdk.NewClient(apiToken)
|
||||||
|
return client, nil
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package netlifysite_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/netlify-site"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
fInputCertPath string
|
||||||
|
fInputKeyPath string
|
||||||
|
fApiToken string
|
||||||
|
fSiteId int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
argsPrefix := "CERTIMATE_DEPLOYER_NETLIFYSITE_"
|
||||||
|
|
||||||
|
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
|
||||||
|
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
|
||||||
|
flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
|
||||||
|
flag.Int64Var(&fSiteId, argsPrefix+"SITEID", 0, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Shell command to run this test:
|
||||||
|
|
||||||
|
go test -v ./netlify_site_test.go -args \
|
||||||
|
--CERTIMATE_DEPLOYER_NETLIFYSITE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_NETLIFYSITE_INPUTKEYPATH="/path/to/your-input-key.pem" \
|
||||||
|
--CERTIMATE_DEPLOYER_NETLIFYSITE_APITOKEN="your-api-token" \
|
||||||
|
--CERTIMATE_DEPLOYER_NETLIFYSITE_SITEID="your-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("APITOKEN: %v", fApiToken),
|
||||||
|
fmt.Sprintf("SITEID: %v", fSiteId),
|
||||||
|
}, "\n"))
|
||||||
|
|
||||||
|
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
|
||||||
|
ApiToken: fApiToken,
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -41,6 +41,12 @@ type DeployerConfig struct {
|
|||||||
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
OutputFormat OutputFormatType `json:"outputFormat,omitempty"`
|
||||||
// 输出证书文件路径。
|
// 输出证书文件路径。
|
||||||
OutputCertPath string `json:"outputCertPath,omitempty"`
|
OutputCertPath string `json:"outputCertPath,omitempty"`
|
||||||
|
// 输出服务器证书文件路径。
|
||||||
|
// 选填。
|
||||||
|
OutputServerCertPath string `json:"outputServerCertPath,omitempty"`
|
||||||
|
// 输出中间证书文件路径。
|
||||||
|
// 选填。
|
||||||
|
OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"`
|
||||||
// 输出私钥文件路径。
|
// 输出私钥文件路径。
|
||||||
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
OutputKeyPath string `json:"outputKeyPath,omitempty"`
|
||||||
// PFX 导出密码。
|
// PFX 导出密码。
|
||||||
@ -85,6 +91,12 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
|
// 提取服务器证书和中间证书
|
||||||
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 连接
|
// 连接
|
||||||
client, err := createSshClient(
|
client, err := createSshClient(
|
||||||
d.config.SshHost,
|
d.config.SshHost,
|
||||||
@ -118,6 +130,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
}
|
}
|
||||||
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
|
d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath))
|
||||||
|
|
||||||
|
if d.config.OutputServerCertPath != "" {
|
||||||
|
if err := writeFileString(client, d.config.UseSCP, d.config.OutputServerCertPath, serverCertPEM); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save server certificate file: %w", err)
|
||||||
|
}
|
||||||
|
d.logger.Info("ssl server certificate file uploaded", slog.String("path", d.config.OutputServerCertPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.config.OutputIntermediaCertPath != "" {
|
||||||
|
if err := writeFileString(client, d.config.UseSCP, d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err)
|
||||||
|
}
|
||||||
|
d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath))
|
||||||
|
}
|
||||||
|
|
||||||
if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPEM); err != nil {
|
if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPEM); err != nil {
|
||||||
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
return nil, fmt.Errorf("failed to upload private key file: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ type DeployerConfig struct {
|
|||||||
// 加速域名(支持泛域名)。
|
// 加速域名(支持泛域名)。
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId string `json:"certificateId,omitempty"`
|
CertificateId string `json:"certificateId,omitempty"`
|
||||||
// Webhook ID。
|
// Webhook ID。
|
||||||
// 选填。
|
// 选填。
|
||||||
|
@ -75,6 +75,12 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
return nil, fmt.Errorf("failed to parse x509: %w", err)
|
return nil, fmt.Errorf("failed to parse x509: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提取服务器证书和中间证书
|
||||||
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 处理 Webhook URL
|
// 处理 Webhook URL
|
||||||
webhookUrl, err := url.Parse(d.config.WebhookUrl)
|
webhookUrl, err := url.Parse(d.config.WebhookUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -134,6 +140,8 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
|||||||
replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName)
|
replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName)
|
||||||
replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";"))
|
replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";"))
|
||||||
replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM)
|
replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM)
|
||||||
|
replaceJsonValueRecursively(webhookData, "${SERVER_CERTIFICATE}", serverCertPEM)
|
||||||
|
replaceJsonValueRecursively(webhookData, "${INTERMEDIA_CERTIFICATE}", intermediaCertPEM)
|
||||||
replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM)
|
replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM)
|
||||||
|
|
||||||
if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART {
|
if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART {
|
||||||
|
@ -65,9 +65,11 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 AWS 业务参数
|
// 提取服务器证书
|
||||||
scertPEM, _ := certutil.ConvertCertificateToPEM(certX509)
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
bcertPEM := certPEM
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 获取证书列表,避免重复上传
|
// 获取证书列表,避免重复上传
|
||||||
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
|
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
|
||||||
@ -145,8 +147,8 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE
|
|||||||
// 导入证书
|
// 导入证书
|
||||||
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
|
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
|
||||||
importCertificateReq := &awsacm.ImportCertificateInput{
|
importCertificateReq := &awsacm.ImportCertificateInput{
|
||||||
Certificate: ([]byte)(scertPEM),
|
Certificate: ([]byte)(serverCertPEM),
|
||||||
CertificateChain: ([]byte)(bcertPEM),
|
CertificateChain: ([]byte)(intermediaCertPEM),
|
||||||
PrivateKey: ([]byte)(privkeyPEM),
|
PrivateKey: ([]byte)(privkeyPEM),
|
||||||
}
|
}
|
||||||
importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
|
importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package onepanelsdk
|
package onepanel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package onepanelsdk
|
package onepanel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
@ -97,7 +97,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("1panel api error: failed to parse response: %w", err)
|
return fmt.Errorf("1panel api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
||||||
return fmt.Errorf("1panel api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("1panel api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package onepanelsdk
|
package onepanel
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetCode() int32
|
GetCode() int32
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package baishansdk
|
package baishan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package baishansdk
|
package baishan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -93,7 +93,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("baishan api error: failed to parse response: %w", err)
|
return fmt.Errorf("baishan api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode != 0 {
|
} else if errcode := result.GetCode(); errcode != 0 {
|
||||||
return fmt.Errorf("baishan api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("baishan api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package baishansdk
|
package baishan
|
||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package btpanelsdk
|
package btpanel
|
||||||
|
|
||||||
func (c *Client) ConfigSavePanelSSL(req *ConfigSavePanelSSLRequest) (*ConfigSavePanelSSLResponse, error) {
|
func (c *Client) ConfigSavePanelSSL(req *ConfigSavePanelSSLRequest) (*ConfigSavePanelSSLResponse, error) {
|
||||||
resp := &ConfigSavePanelSSLResponse{}
|
resp := &ConfigSavePanelSSLResponse{}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package btpanelsdk
|
package btpanel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
@ -104,7 +104,7 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B
|
|||||||
if result.GetMessage() == nil {
|
if result.GetMessage() == nil {
|
||||||
return fmt.Errorf("baota api error: unknown error")
|
return fmt.Errorf("baota api error: unknown error")
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("baota api error: %s", *result.GetMessage())
|
return fmt.Errorf("baota api error: message='%s'", *result.GetMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package btpanelsdk
|
package btpanel
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetStatus() *bool
|
GetStatus() *bool
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package bunnysdk
|
package bunny
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package bunnysdk
|
package bunny
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package bunnysdk
|
package bunny
|
||||||
|
|
||||||
type AddCustomCertificateRequest struct {
|
type AddCustomCertificateRequest struct {
|
||||||
Hostname string `json:"Hostname"`
|
Hostname string `json:"Hostname"`
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cacheflysdk
|
package cachefly
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cacheflysdk
|
package cachefly
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cacheflysdk
|
package cachefly
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetMessage() string
|
GetMessage() string
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cdnflysdk
|
package cdnfly
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cdnflysdk
|
package cdnfly
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@ -89,7 +89,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("cdnfly api error: failed to parse response: %w", err)
|
return fmt.Errorf("cdnfly api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode != "" && errcode != "0" {
|
} else if errcode := result.GetCode(); errcode != "" && errcode != "0" {
|
||||||
return fmt.Errorf("cdnfly api error: %s - %s", errcode, result.GetMessage())
|
return fmt.Errorf("cdnfly api error: code='%s', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cdnflysdk
|
package cdnfly
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dnslasdk
|
package dnsla
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dnslasdk
|
package dnsla
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -78,7 +78,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("dnsla api error: failed to parse response: %w", err)
|
return fmt.Errorf("dnsla api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
||||||
return fmt.Errorf("dnsla api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("dnsla api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dnslasdk
|
package dnsla
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetCode() int32
|
GetCode() int32
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dogecloudsdk
|
package dogecloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package dogecloudsdk
|
package dogecloud
|
||||||
|
|
||||||
type BaseResponse struct {
|
type BaseResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
Code *int `json:"code,omitempty"`
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package gnamesdk
|
package gname
|
||||||
|
|
||||||
func (c *Client) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) {
|
func (c *Client) AddDomainResolution(req *AddDomainResolutionRequest) (*AddDomainResolutionResponse, error) {
|
||||||
resp := &AddDomainResolutionResponse{}
|
resp := &AddDomainResolutionResponse{}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package gnamesdk
|
package gname
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
@ -97,7 +97,7 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("gname api error: failed to parse response: %w", err)
|
return fmt.Errorf("gname api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode != 1 {
|
} else if errcode := result.GetCode(); errcode != 1 {
|
||||||
return fmt.Errorf("gname api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("gname api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package gnamesdk
|
package gname
|
||||||
|
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func (c *Client) getAccessToken() error {
|
func (c *Client) getAccessToken() error {
|
||||||
req := &getAPIAccessTokenRequest{
|
req := &getAPIAccessTokenRequest{
|
||||||
Type: c.apiUserType,
|
Type: c.apiRole,
|
||||||
AccessKeyId: c.accessKeyId,
|
AccessKeyId: c.accessKeyId,
|
||||||
AccessKey: c.accessKey,
|
AccessKey: c.accessKey,
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
apiHost string
|
apiHost string
|
||||||
apiUserType string
|
apiRole string
|
||||||
accessKeyId string
|
accessKeyId string
|
||||||
accessKey string
|
accessKey string
|
||||||
|
|
||||||
@ -25,12 +25,12 @@ type Client struct {
|
|||||||
client *resty.Client
|
client *resty.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(apiHost, apiUserType, accessKeyId, accessKey string) *Client {
|
func NewClient(apiHost, apiRole, accessKeyId, accessKey string) *Client {
|
||||||
client := resty.New()
|
client := resty.New()
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
apiHost: strings.TrimRight(apiHost, "/"),
|
apiHost: strings.TrimRight(apiHost, "/"),
|
||||||
apiUserType: apiUserType,
|
apiRole: apiRole,
|
||||||
accessKeyId: accessKeyId,
|
accessKeyId: accessKeyId,
|
||||||
accessKey: accessKey,
|
accessKey: accessKey,
|
||||||
client: client,
|
client: client,
|
||||||
@ -96,7 +96,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("goedge api error: failed to parse response: %w", err)
|
return fmt.Errorf("goedge api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode != 200 {
|
} else if errcode := result.GetCode(); errcode != 200 {
|
||||||
return fmt.Errorf("goedge api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("goedge api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
17
internal/pkg/sdk3rd/netlify/api.go
Normal file
17
internal/pkg/sdk3rd/netlify/api.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package netlify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) ProvisionSiteTLSCertificate(siteId string, params *ProvisionSiteTLSCertificateParams) (*ProvisionSiteTLSCertificateResponse, error) {
|
||||||
|
if siteId == "" {
|
||||||
|
return nil, fmt.Errorf("netlify api error: invalid parameter: SiteId")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &ProvisionSiteTLSCertificateResponse{}
|
||||||
|
err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/sites/%s/ssl", url.PathEscape(siteId)), params, nil, resp)
|
||||||
|
return resp, err
|
||||||
|
}
|
97
internal/pkg/sdk3rd/netlify/client.go
Normal file
97
internal/pkg/sdk3rd/netlify/client.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package netlify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
apiToken string
|
||||||
|
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(apiToken string) *Client {
|
||||||
|
client := resty.New()
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
apiToken: apiToken,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WithTimeout(timeout time.Duration) *Client {
|
||||||
|
c.client.SetTimeout(timeout)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequest(method string, path string, queryParams interface{}, payloadParams interface{}) (*resty.Response, error) {
|
||||||
|
req := c.client.R().SetHeader("Authorization", "Bearer "+c.apiToken)
|
||||||
|
req.Method = method
|
||||||
|
req.URL = "https://api.netlify.com/api/v1" + path
|
||||||
|
|
||||||
|
if queryParams != nil {
|
||||||
|
qs := make(map[string]string)
|
||||||
|
temp := make(map[string]any)
|
||||||
|
jsonb, _ := json.Marshal(queryParams)
|
||||||
|
json.Unmarshal(jsonb, &temp)
|
||||||
|
for k, v := range temp {
|
||||||
|
if v != nil {
|
||||||
|
qs[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req = req.SetQueryParams(qs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(method, http.MethodGet) {
|
||||||
|
qs := make(map[string]string)
|
||||||
|
if payloadParams != nil {
|
||||||
|
temp := make(map[string]any)
|
||||||
|
jsonb, _ := json.Marshal(payloadParams)
|
||||||
|
json.Unmarshal(jsonb, &temp)
|
||||||
|
for k, v := range temp {
|
||||||
|
if v != nil {
|
||||||
|
qs[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req.SetQueryParams(qs)
|
||||||
|
} else {
|
||||||
|
req = req.
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetBody(payloadParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := req.Send()
|
||||||
|
if err != nil {
|
||||||
|
return resp, fmt.Errorf("netlify api error: failed to send request: %w", err)
|
||||||
|
} else if resp.IsError() {
|
||||||
|
return resp, fmt.Errorf("netlify api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.Body())
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequestWithResult(method string, path string, queryParams interface{}, payloadParams interface{}, result BaseResponse) error {
|
||||||
|
resp, err := c.sendRequest(method, path, queryParams, payloadParams)
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil {
|
||||||
|
json.Unmarshal(resp.Body(), &result)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
|
return fmt.Errorf("netlify api error: failed to parse response: %w", err)
|
||||||
|
} else if errcode := result.GetCode(); errcode != 0 {
|
||||||
|
return fmt.Errorf("netlify api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
40
internal/pkg/sdk3rd/netlify/models.go
Normal file
40
internal/pkg/sdk3rd/netlify/models.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package netlify
|
||||||
|
|
||||||
|
type BaseResponse interface {
|
||||||
|
GetCode() int32
|
||||||
|
GetMessage() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseResponse struct {
|
||||||
|
Code *int32 `json:"code,omitempty"`
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseResponse) GetCode() int32 {
|
||||||
|
if r.Code != nil {
|
||||||
|
return *r.Code
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *baseResponse) GetMessage() string {
|
||||||
|
if r.Message != nil {
|
||||||
|
return *r.Message
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProvisionSiteTLSCertificateParams struct {
|
||||||
|
Certificate string `json:"certificate"`
|
||||||
|
CACertificates string `json:"key"`
|
||||||
|
Key string `json:"ca_certificates"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProvisionSiteTLSCertificateResponse struct {
|
||||||
|
baseResponse
|
||||||
|
Domains []string `json:"domains,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
ExpiresAt string `json:"expires_at,omitempty"`
|
||||||
|
CreatedAt string `json:"created_at,omitempty"`
|
||||||
|
UpdatedAt string `json:"updated_at,omitempty"`
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package qiniusdk
|
package qiniu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package qiniusdk
|
package qiniu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package qiniusdk
|
package qiniu
|
||||||
|
|
||||||
type BaseResponse struct {
|
type BaseResponse struct {
|
||||||
Code *int `json:"code,omitempty"`
|
Code *int `json:"code,omitempty"`
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package rainyunsdk
|
package rainyun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package rainyunsdk
|
package rainyun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -67,7 +67,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
return fmt.Errorf("rainyun api error: failed to parse response: %w", err)
|
return fmt.Errorf("rainyun api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
} else if errcode := result.GetCode(); errcode/100 != 2 {
|
||||||
return fmt.Errorf("rainyun api error: %d - %s", errcode, result.GetMessage())
|
return fmt.Errorf("rainyun api error: code='%d', message='%s'", errcode, result.GetMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package rainyunsdk
|
package rainyun
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetCode() int32
|
GetCode() int32
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package safelinesdk
|
package safeline
|
||||||
|
|
||||||
func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
func (c *Client) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
|
||||||
resp := &UpdateCertificateResponse{}
|
resp := &UpdateCertificateResponse{}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package safelinesdk
|
package safeline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@ -66,9 +66,9 @@ func (c *Client) sendRequestWithResult(path string, params interface{}, result B
|
|||||||
return fmt.Errorf("safeline api error: failed to parse response: %w", err)
|
return fmt.Errorf("safeline api error: failed to parse response: %w", err)
|
||||||
} else if errcode := result.GetErrCode(); errcode != nil && *errcode != "" {
|
} else if errcode := result.GetErrCode(); errcode != nil && *errcode != "" {
|
||||||
if result.GetErrMsg() == nil {
|
if result.GetErrMsg() == nil {
|
||||||
return fmt.Errorf("safeline api error: %s", *errcode)
|
return fmt.Errorf("safeline api error: code='%s'", *errcode)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("safeline api error: %s - %s", *errcode, *result.GetErrMsg())
|
return fmt.Errorf("safeline api error: code='%s', message='%s'", *errcode, *result.GetErrMsg())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package safelinesdk
|
package safeline
|
||||||
|
|
||||||
type BaseResponse interface {
|
type BaseResponse interface {
|
||||||
GetErrCode() *string
|
GetErrCode() *string
|
||||||
|
@ -90,7 +90,7 @@ func (c *Client) sendRequestWithResult(method string, path string, params interf
|
|||||||
} else if tdata := tresp.GetData(); tdata == nil {
|
} else if tdata := tresp.GetData(); tdata == nil {
|
||||||
return fmt.Errorf("upyun api error: empty data")
|
return fmt.Errorf("upyun api error: empty data")
|
||||||
} else if errcode := tdata.GetErrorCode(); errcode > 0 {
|
} else if errcode := tdata.GetErrorCode(); errcode > 0 {
|
||||||
return fmt.Errorf("upyun api error: %d - %s", errcode, tdata.GetErrorMessage())
|
return fmt.Errorf("upyun api error: code='%d', message='%s'", errcode, tdata.GetErrorMessage())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -12,9 +12,9 @@ import (
|
|||||||
//
|
//
|
||||||
// 出参:
|
// 出参:
|
||||||
// - serverCertPEM: 服务器证书的 PEM 内容。
|
// - serverCertPEM: 服务器证书的 PEM 内容。
|
||||||
// - interCertPEM: 中间证书的 PEM 内容。
|
// - intermediaCertPEM: 中间证书的 PEM 内容。
|
||||||
// - err: 错误。
|
// - err: 错误。
|
||||||
func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCertPEM string, err error) {
|
func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, intermediaCertPEM string, err error) {
|
||||||
pemBlocks := make([]*pem.Block, 0)
|
pemBlocks := make([]*pem.Block, 0)
|
||||||
pemData := []byte(certPEM)
|
pemData := []byte(certPEM)
|
||||||
for {
|
for {
|
||||||
@ -28,7 +28,7 @@ func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCert
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverCertPEM = ""
|
serverCertPEM = ""
|
||||||
interCertPEM = ""
|
intermediaCertPEM = ""
|
||||||
|
|
||||||
if len(pemBlocks) == 0 {
|
if len(pemBlocks) == 0 {
|
||||||
return "", "", errors.New("failed to decode PEM block")
|
return "", "", errors.New("failed to decode PEM block")
|
||||||
@ -40,9 +40,9 @@ func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCert
|
|||||||
|
|
||||||
if len(pemBlocks) > 1 {
|
if len(pemBlocks) > 1 {
|
||||||
for i := 1; i < len(pemBlocks); i++ {
|
for i := 1; i < len(pemBlocks); i++ {
|
||||||
interCertPEM += string(pem.EncodeToMemory(pemBlocks[i]))
|
intermediaCertPEM += string(pem.EncodeToMemory(pemBlocks[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return serverCertPEM, interCertPEM, nil
|
return serverCertPEM, intermediaCertPEM, nil
|
||||||
}
|
}
|
||||||
|
44
migrations/1747314000_upgrade.go
Normal file
44
migrations/1747314000_upgrade.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pocketbase/pocketbase/core"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(app core.App) error {
|
||||||
|
// migrate data
|
||||||
|
{
|
||||||
|
accesses, err := app.FindAllRecords("access")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, access := range accesses {
|
||||||
|
changed := false
|
||||||
|
|
||||||
|
if access.GetString("provider") == "goedge" {
|
||||||
|
config := make(map[string]any)
|
||||||
|
if err := access.UnmarshalJSONField("config", &config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config["apiRole"] = "user"
|
||||||
|
access.Set("config", config)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
err = app.Save(access)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, func(app core.App) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
277
ui/package-lock.json
generated
277
ui/package-lock.json
generated
@ -10,6 +10,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@ant-design/pro-components": "^2.8.7",
|
"@ant-design/pro-components": "^2.8.7",
|
||||||
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
|
"@codemirror/language": "^6.11.0",
|
||||||
|
"@codemirror/legacy-modes": "^6.5.1",
|
||||||
|
"@uiw/codemirror-extensions-basic-setup": "^4.23.12",
|
||||||
|
"@uiw/codemirror-theme-vscode": "^4.23.12",
|
||||||
|
"@uiw/react-codemirror": "^4.23.12",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"antd": "^5.25.1",
|
"antd": "^5.25.1",
|
||||||
"antd-zod": "^6.1.0",
|
"antd-zod": "^6.1.0",
|
||||||
@ -2107,6 +2114,121 @@
|
|||||||
"react": ">=16.12.0"
|
"react": ">=16.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.18.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||||
|
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.8.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||||
|
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-json": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@lezer/json": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-yaml": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.2.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"@lezer/yaml": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/language/-/language-6.11.0.tgz",
|
||||||
|
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/legacy-modes": {
|
||||||
|
"version": "6.5.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/legacy-modes/-/legacy-modes-6.5.1.tgz",
|
||||||
|
"integrity": "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.8.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||||
|
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/search/-/search-6.5.10.tgz",
|
||||||
|
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/theme-one-dark": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.36.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@codemirror/view/-/view-6.36.8.tgz",
|
||||||
|
"integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ctrl/tinycolor": {
|
"node_modules/@ctrl/tinycolor": {
|
||||||
"version": "3.6.1",
|
"version": "3.6.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
|
||||||
@ -2839,6 +2961,52 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/json": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@lezer/json/-/json-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/yaml": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@lezer/yaml/-/yaml-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -3628,6 +3796,86 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@uiw/codemirror-extensions-basic-setup": {
|
||||||
|
"version": "4.23.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.12.tgz",
|
||||||
|
"integrity": "sha512-l9vuiXOTFDBetYrRLDmz3jDxQHDsrVAZ2Y6dVfmrqi2AsulsDu+y7csW0JsvaMqo79rYkaIZg8yeqmDgMb7VyQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/autocomplete": ">=6.0.0",
|
||||||
|
"@codemirror/commands": ">=6.0.0",
|
||||||
|
"@codemirror/language": ">=6.0.0",
|
||||||
|
"@codemirror/lint": ">=6.0.0",
|
||||||
|
"@codemirror/search": ">=6.0.0",
|
||||||
|
"@codemirror/state": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@uiw/codemirror-theme-vscode": {
|
||||||
|
"version": "4.23.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.23.12.tgz",
|
||||||
|
"integrity": "sha512-ePBaUQiixrpmSoZJWCGXUStKmcM8G0VBv3UqwPR+kNGBjqDife76Gbhv77izSeEI3zRPzL+683BOdclkvWnsMg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@uiw/codemirror-themes": "4.23.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@uiw/codemirror-themes": {
|
||||||
|
"version": "4.23.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@uiw/codemirror-themes/-/codemirror-themes-4.23.12.tgz",
|
||||||
|
"integrity": "sha512-8etEByfS9yttFZW0rcWhdZc7/JXJKRWlU5lHmJCI3GydZNGCzydNA+HtK9nWKpJUndVc58Q2sqSC5OIcwq8y6A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/language": ">=6.0.0",
|
||||||
|
"@codemirror/state": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@uiw/react-codemirror": {
|
||||||
|
"version": "4.23.12",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.23.12.tgz",
|
||||||
|
"integrity": "sha512-yseqWdzoAAGAW7i/NiU8YrfSLVOEBjQvSx1KpDTFVV/nn0AlAZoDVTIPEBgdXrPlVUQoCrwgpEaj3uZCklk9QA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.6",
|
||||||
|
"@codemirror/commands": "^6.1.0",
|
||||||
|
"@codemirror/state": "^6.1.1",
|
||||||
|
"@codemirror/theme-one-dark": "^6.0.0",
|
||||||
|
"@uiw/codemirror-extensions-basic-setup": "4.23.12",
|
||||||
|
"codemirror": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://jaywcjlove.github.io/#/sponsor"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babel/runtime": ">=7.11.0",
|
||||||
|
"@codemirror/state": ">=6.0.0",
|
||||||
|
"@codemirror/theme-one-dark": ">=6.0.0",
|
||||||
|
"@codemirror/view": ">=6.0.0",
|
||||||
|
"codemirror": ">=6.0.0",
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@umijs/route-utils": {
|
"node_modules/@umijs/route-utils": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@umijs/route-utils/-/route-utils-4.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@umijs/route-utils/-/route-utils-4.0.1.tgz",
|
||||||
@ -4337,6 +4585,20 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
|
||||||
@ -4403,6 +4665,11 @@
|
|||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||||
|
},
|
||||||
"node_modules/cron-parser": {
|
"node_modules/cron-parser": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-5.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/cron-parser/-/cron-parser-5.2.0.tgz",
|
||||||
@ -8689,6 +8956,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||||
|
},
|
||||||
"node_modules/stylis": {
|
"node_modules/stylis": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz",
|
"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz",
|
||||||
@ -9363,6 +9635,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||||
|
},
|
||||||
"node_modules/warning": {
|
"node_modules/warning": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
|
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
|
||||||
|
@ -12,6 +12,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@ant-design/pro-components": "^2.8.7",
|
"@ant-design/pro-components": "^2.8.7",
|
||||||
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
|
"@codemirror/lang-yaml": "^6.1.2",
|
||||||
|
"@codemirror/language": "^6.11.0",
|
||||||
|
"@codemirror/legacy-modes": "^6.5.1",
|
||||||
|
"@uiw/codemirror-extensions-basic-setup": "^4.23.12",
|
||||||
|
"@uiw/codemirror-theme-vscode": "^4.23.12",
|
||||||
|
"@uiw/react-codemirror": "^4.23.12",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"antd": "^5.25.1",
|
"antd": "^5.25.1",
|
||||||
"antd-zod": "^6.1.0",
|
"antd-zod": "^6.1.0",
|
||||||
|
BIN
ui/public/imgs/providers/netcup.png
Normal file
BIN
ui/public/imgs/providers/netcup.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
BIN
ui/public/imgs/providers/netlify.png
Normal file
BIN
ui/public/imgs/providers/netlify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
97
ui/src/components/CodeInput.tsx
Normal file
97
ui/src/components/CodeInput.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { useMemo, useRef } from "react";
|
||||||
|
import { json } from "@codemirror/lang-json";
|
||||||
|
import { yaml } from "@codemirror/lang-yaml";
|
||||||
|
import { StreamLanguage } from "@codemirror/language";
|
||||||
|
import { powerShell } from "@codemirror/legacy-modes/mode/powershell";
|
||||||
|
import { shell } from "@codemirror/legacy-modes/mode/shell";
|
||||||
|
import { basicSetup } from "@uiw/codemirror-extensions-basic-setup";
|
||||||
|
import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
|
||||||
|
import CodeMirror, { type ReactCodeMirrorProps, type ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
||||||
|
import { useFocusWithin } from "ahooks";
|
||||||
|
import { theme } from "antd";
|
||||||
|
|
||||||
|
import { useBrowserTheme } from "@/hooks";
|
||||||
|
import { mergeCls } from "@/utils/css";
|
||||||
|
|
||||||
|
export interface CodeInputProps extends Omit<ReactCodeMirrorProps, "extensions" | "lang" | "theme"> {
|
||||||
|
disabled?: boolean;
|
||||||
|
language?: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeInput = ({ className, style, disabled, language, ...props }: CodeInputProps) => {
|
||||||
|
const { token: themeToken } = theme.useToken();
|
||||||
|
|
||||||
|
const { theme: browserTheme } = useBrowserTheme();
|
||||||
|
|
||||||
|
const cmRef = useRef<ReactCodeMirrorRef>(null);
|
||||||
|
const isFocusWithin = useFocusWithin(cmRef.current?.editor);
|
||||||
|
|
||||||
|
const cmTheme = useMemo(() => {
|
||||||
|
if (browserTheme === "dark") {
|
||||||
|
return vscodeDark;
|
||||||
|
}
|
||||||
|
return vscodeLight;
|
||||||
|
}, [browserTheme]);
|
||||||
|
|
||||||
|
const cmExtensions = useMemo(() => {
|
||||||
|
const temp: NonNullable<ReactCodeMirrorProps["extensions"]> = [
|
||||||
|
basicSetup({
|
||||||
|
foldGutter: false,
|
||||||
|
dropCursor: false,
|
||||||
|
allowMultipleSelections: false,
|
||||||
|
indentOnInput: false,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const langs = Array.isArray(language) ? language : [language];
|
||||||
|
langs.forEach((lang) => {
|
||||||
|
switch (lang) {
|
||||||
|
case "shell":
|
||||||
|
temp.push(StreamLanguage.define(shell));
|
||||||
|
break;
|
||||||
|
case "json":
|
||||||
|
temp.push(json());
|
||||||
|
break;
|
||||||
|
case "powershell":
|
||||||
|
temp.push(StreamLanguage.define(powerShell));
|
||||||
|
break;
|
||||||
|
case "yaml":
|
||||||
|
temp.push(yaml());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}, [language]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={mergeCls(className, `hover:border-[${themeToken.colorPrimaryBorderHover}]`)}
|
||||||
|
style={{
|
||||||
|
...(style ?? {}),
|
||||||
|
border: `1px solid ${isFocusWithin ? (themeToken.Input?.activeBorderColor ?? themeToken.colorPrimaryBorder) : themeToken.colorBorder}`,
|
||||||
|
borderRadius: `${themeToken.borderRadius}px`,
|
||||||
|
backgroundColor: disabled ? themeToken.colorBgContainerDisabled : themeToken.colorBgContainer,
|
||||||
|
boxShadow: isFocusWithin ? themeToken.Input?.activeShadow : undefined,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CodeMirror
|
||||||
|
ref={cmRef}
|
||||||
|
height="100%"
|
||||||
|
style={{ height: "100%" }}
|
||||||
|
{...props}
|
||||||
|
basicSetup={{
|
||||||
|
foldGutter: false,
|
||||||
|
dropCursor: false,
|
||||||
|
allowMultipleSelections: false,
|
||||||
|
indentOnInput: false,
|
||||||
|
}}
|
||||||
|
extensions={cmExtensions}
|
||||||
|
theme={cmTheme}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeInput;
|
51
ui/src/components/TextFileInput.tsx
Normal file
51
ui/src/components/TextFileInput.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { type ChangeEvent, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
|
||||||
|
import { Button, type ButtonProps, Input, Space, type UploadProps } from "antd";
|
||||||
|
import { type TextAreaProps } from "antd/es/input/TextArea";
|
||||||
|
|
||||||
|
import { mergeCls } from "@/utils/css";
|
||||||
|
import { readFileContent } from "@/utils/file";
|
||||||
|
|
||||||
|
export interface TextFileInputProps extends Omit<TextAreaProps, "onChange"> {
|
||||||
|
accept?: UploadProps["accept"];
|
||||||
|
uploadButtonProps?: Omit<ButtonProps, "disabled" | "onClick">;
|
||||||
|
uploadText?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextFileInput = ({ className, style, accept, disabled, readOnly, uploadText, uploadButtonProps, onChange, ...props }: TextFileInputProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleButtonClick = () => {
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { files } = e.target as HTMLInputElement;
|
||||||
|
if (files?.length) {
|
||||||
|
const value = await readFileContent(files[0]);
|
||||||
|
onChange?.(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space className={mergeCls("w-full", className)} style={style} direction="vertical" size="small">
|
||||||
|
<Input.TextArea {...props} disabled={disabled} readOnly={readOnly} onChange={(e) => onChange?.(e.target.value)} />
|
||||||
|
{!readOnly && (
|
||||||
|
<>
|
||||||
|
<Button {...uploadButtonProps} block disabled={disabled} icon={<UploadOutlinedIcon />} onClick={handleButtonClick}>
|
||||||
|
{uploadText ?? t("common.text.import_from_file")}
|
||||||
|
</Button>
|
||||||
|
<input ref={fileInputRef} type="file" style={{ display: "none" }} accept={accept} onChange={handleFileChange} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextFileInput;
|
@ -46,6 +46,8 @@ import AccessFormMattermostConfig from "./AccessFormMattermostConfig";
|
|||||||
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
|
import AccessFormNamecheapConfig from "./AccessFormNamecheapConfig";
|
||||||
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
|
import AccessFormNameDotComConfig from "./AccessFormNameDotComConfig";
|
||||||
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
|
import AccessFormNameSiloConfig from "./AccessFormNameSiloConfig";
|
||||||
|
import AccessFormNetcupConfig from "./AccessFormNetcupConfig";
|
||||||
|
import AccessFormNetlifyConfig from "./AccessFormNetlifyConfig";
|
||||||
import AccessFormNS1Config from "./AccessFormNS1Config";
|
import AccessFormNS1Config from "./AccessFormNS1Config";
|
||||||
import AccessFormPorkbunConfig from "./AccessFormPorkbunConfig";
|
import AccessFormPorkbunConfig from "./AccessFormPorkbunConfig";
|
||||||
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
|
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
|
||||||
@ -242,6 +244,10 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
|
|||||||
return <AccessFormNameDotComConfig {...nestedFormProps} />;
|
return <AccessFormNameDotComConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.NAMESILO:
|
case ACCESS_PROVIDERS.NAMESILO:
|
||||||
return <AccessFormNameSiloConfig {...nestedFormProps} />;
|
return <AccessFormNameSiloConfig {...nestedFormProps} />;
|
||||||
|
case ACCESS_PROVIDERS.NETCUP:
|
||||||
|
return <AccessFormNetcupConfig {...nestedFormProps} />;
|
||||||
|
case ACCESS_PROVIDERS.NETLIFY:
|
||||||
|
return <AccessFormNetlifyConfig {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.NS1:
|
case ACCESS_PROVIDERS.NS1:
|
||||||
return <AccessFormNS1Config {...nestedFormProps} />;
|
return <AccessFormNS1Config {...nestedFormProps} />;
|
||||||
case ACCESS_PROVIDERS.PORKBUN:
|
case ACCESS_PROVIDERS.PORKBUN:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Form, type FormInstance, Input, Switch } from "antd";
|
import { Form, type FormInstance, Input, Radio, Switch } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ export type AccessFormGoEdgeConfigProps = {
|
|||||||
const initFormModel = (): AccessFormGoEdgeConfigFieldValues => {
|
const initFormModel = (): AccessFormGoEdgeConfigFieldValues => {
|
||||||
return {
|
return {
|
||||||
apiUrl: "http://<your-host-addr>:7788/",
|
apiUrl: "http://<your-host-addr>:7788/",
|
||||||
|
apiRole: "user",
|
||||||
accessKeyId: "",
|
accessKeyId: "",
|
||||||
accessKey: "",
|
accessKey: "",
|
||||||
};
|
};
|
||||||
@ -28,6 +29,9 @@ const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialVal
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
|
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
|
||||||
|
role: z.union([z.literal("user"), z.literal("admin")], {
|
||||||
|
message: t("access.form.goedge_api_role.placeholder"),
|
||||||
|
}),
|
||||||
accessKeyId: z
|
accessKeyId: z
|
||||||
.string()
|
.string()
|
||||||
.min(1, t("access.form.goedge_access_key_id.placeholder"))
|
.min(1, t("access.form.goedge_access_key_id.placeholder"))
|
||||||
@ -59,6 +63,10 @@ const AccessFormGoEdgeConfig = ({ form: formInst, formName, disabled, initialVal
|
|||||||
<Input placeholder={t("access.form.goedge_api_url.placeholder")} />
|
<Input placeholder={t("access.form.goedge_api_url.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item name="apiRole" label={t("access.form.goedge_api_role.label")} rules={[formRule]}>
|
||||||
|
<Radio.Group options={["user", "admin"].map((s) => ({ label: t(`access.form.goedge_api_role.option.${s}.label`), value: s }))} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="accessKeyId"
|
name="accessKeyId"
|
||||||
label={t("access.form.goedge_access_key_id.label")}
|
label={t("access.form.goedge_access_key_id.label")}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
|
import { Form, type FormInstance } from "antd";
|
||||||
import { Button, Form, type FormInstance, Input, Upload, type UploadFile, type UploadProps } from "antd";
|
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import TextFileInput from "@/components/TextFileInput";
|
||||||
import { type AccessConfigForKubernetes } from "@/domain/access";
|
import { type AccessConfigForKubernetes } from "@/domain/access";
|
||||||
import { readFileContent } from "@/utils/file";
|
|
||||||
|
|
||||||
type AccessFormKubernetesConfigFieldValues = Nullish<AccessConfigForKubernetes>;
|
type AccessFormKubernetesConfigFieldValues = Nullish<AccessConfigForKubernetes>;
|
||||||
|
|
||||||
@ -34,24 +32,6 @@ const AccessFormKubernetesConfig = ({ form: formInst, formName, disabled, initia
|
|||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
const fieldKubeConfig = Form.useWatch("kubeConfig", formInst);
|
|
||||||
const [fieldKubeFileList, setFieldKubeFileList] = useState<UploadFile[]>([]);
|
|
||||||
useEffect(() => {
|
|
||||||
setFieldKubeFileList(initialValues?.kubeConfig?.trim() ? [{ uid: "-1", name: "kubeconfig", status: "done" }] : []);
|
|
||||||
}, [initialValues?.kubeConfig]);
|
|
||||||
|
|
||||||
const handleKubeFileChange: UploadProps["onChange"] = async ({ file }) => {
|
|
||||||
if (file && file.status !== "removed") {
|
|
||||||
formInst.setFieldValue("kubeConfig", await readFileContent(file.originFileObj ?? (file as unknown as File)));
|
|
||||||
setFieldKubeFileList([file]);
|
|
||||||
} else {
|
|
||||||
formInst.setFieldValue("kubeConfig", "");
|
|
||||||
setFieldKubeFileList([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
onValuesChange?.(formInst.getFieldsValue(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
onValuesChange?.(values);
|
onValuesChange?.(values);
|
||||||
};
|
};
|
||||||
@ -65,16 +45,13 @@ const AccessFormKubernetesConfig = ({ form: formInst, formName, disabled, initia
|
|||||||
name={formName}
|
name={formName}
|
||||||
onValuesChange={handleFormChange}
|
onValuesChange={handleFormChange}
|
||||||
>
|
>
|
||||||
<Form.Item name="kubeConfig" noStyle rules={[formRule]}>
|
|
||||||
<Input.TextArea autoComplete="new-password" hidden placeholder={t("access.form.k8s_kubeconfig.placeholder")} value={fieldKubeConfig} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
name="kubeConfig"
|
||||||
label={t("access.form.k8s_kubeconfig.label")}
|
label={t("access.form.k8s_kubeconfig.label")}
|
||||||
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.k8s_kubeconfig.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Upload beforeUpload={() => false} fileList={fieldKubeFileList} maxCount={1} onChange={handleKubeFileChange}>
|
<TextFileInput allowClear autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("access.form.k8s_kubeconfig.placeholder")} />
|
||||||
<Button icon={<UploadOutlinedIcon />}>{t("access.form.k8s_kubeconfig.upload")}</Button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
79
ui/src/components/access/AccessFormNetcupConfig.tsx
Normal file
79
ui/src/components/access/AccessFormNetcupConfig.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { type AccessConfigForNetcup } from "@/domain/access";
|
||||||
|
|
||||||
|
type AccessFormNetcupConfigFieldValues = Nullish<AccessConfigForNetcup>;
|
||||||
|
|
||||||
|
export type AccessFormNetcupConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: AccessFormNetcupConfigFieldValues;
|
||||||
|
onValuesChange?: (values: AccessFormNetcupConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): AccessFormNetcupConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
customerNumber: "",
|
||||||
|
apiKey: "",
|
||||||
|
apiPassword: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessFormNetcupConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNetcupConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
customerNumber: z.string().nonempty(t("access.form.netcup_customer_number.placeholder")).trim(),
|
||||||
|
apiKey: z.string().nonempty(t("access.form.netcup_api_key.placeholder")).trim(),
|
||||||
|
apiPassword: z.string().nonempty(t("access.form.netcup_api_password.placeholder")).trim(),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="customerNumber"
|
||||||
|
label={t("access.form.netcup_customer_number.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.netcup_customer_number.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input autoComplete="new-password" placeholder={t("access.form.netcup_customer_number.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="apiKey"
|
||||||
|
label={t("access.form.netcup_api_key.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.netcup_api_key.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("access.form.netcup_api_key.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="apiPassword"
|
||||||
|
label={t("access.form.netcup_api_password.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.netcup_api_password.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("access.form.netcup_api_password.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessFormNetcupConfig;
|
57
ui/src/components/access/AccessFormNetlifyConfig.tsx
Normal file
57
ui/src/components/access/AccessFormNetlifyConfig.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { type AccessConfigForNetlify } from "@/domain/access";
|
||||||
|
|
||||||
|
type AccessFormNetlifyConfigFieldValues = Nullish<AccessConfigForNetlify>;
|
||||||
|
|
||||||
|
export type AccessFormNetlifyConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: AccessFormNetlifyConfigFieldValues;
|
||||||
|
onValuesChange?: (values: AccessFormNetlifyConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): AccessFormNetlifyConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
apiToken: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccessFormNetlifyConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormNetlifyConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
apiToken: z.string().nonempty(t("access.form.netlify_api_token.placeholder")).trim(),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="apiToken"
|
||||||
|
label={t("access.form.netlify_api_token.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.netlify_api_token.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input.Password autoComplete="new-password" placeholder={t("access.form.netlify_api_token.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccessFormNetlifyConfig;
|
@ -1,12 +1,10 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
|
import { Form, type FormInstance, Input, InputNumber } from "antd";
|
||||||
import { Button, Form, type FormInstance, Input, InputNumber, Upload, type UploadFile, type UploadProps } from "antd";
|
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import TextFileInput from "@/components/TextFileInput";
|
||||||
import { type AccessConfigForSSH } from "@/domain/access";
|
import { type AccessConfigForSSH } from "@/domain/access";
|
||||||
import { readFileContent } from "@/utils/file";
|
|
||||||
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
|
import { validDomainName, validIPv4Address, validIPv6Address, validPortNumber } from "@/utils/validators";
|
||||||
|
|
||||||
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
|
type AccessFormSSHConfigFieldValues = Nullish<AccessConfigForSSH>;
|
||||||
@ -59,24 +57,6 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
|||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
const fieldKey = Form.useWatch("key", formInst);
|
|
||||||
const [fieldKeyFileList, setFieldKeyFileList] = useState<UploadFile[]>([]);
|
|
||||||
useEffect(() => {
|
|
||||||
setFieldKeyFileList(initialValues?.key?.trim() ? [{ uid: "-1", name: "sshkey", status: "done" }] : []);
|
|
||||||
}, [initialValues?.key]);
|
|
||||||
|
|
||||||
const handleKeyFileChange: UploadProps["onChange"] = async ({ file }) => {
|
|
||||||
if (file && file.status !== "removed") {
|
|
||||||
formInst.setFieldValue("key", await readFileContent(file.originFileObj ?? (file as unknown as File)));
|
|
||||||
setFieldKeyFileList([file]);
|
|
||||||
} else {
|
|
||||||
formInst.setFieldValue("key", "");
|
|
||||||
setFieldKeyFileList([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
onValuesChange?.(formInst.getFieldsValue(true));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
onValuesChange?.(values);
|
onValuesChange?.(values);
|
||||||
};
|
};
|
||||||
@ -104,48 +84,36 @@ const AccessFormSSHConfig = ({ form: formInst, formName, disabled, initialValues
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
|
||||||
<div className="w-1/2">
|
|
||||||
<Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}>
|
<Form.Item name="username" label={t("access.form.ssh_username.label")} rules={[formRule]}>
|
||||||
<Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
|
<Input autoComplete="new-password" placeholder={t("access.form.ssh_username.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-1/2">
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
label={t("access.form.ssh_password.label")}
|
label={t("access.form.ssh_password.label")}
|
||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_password.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} />
|
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_password.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<Form.Item
|
||||||
<div className="w-1/2">
|
name="key"
|
||||||
<Form.Item name="key" noStyle rules={[formRule]}>
|
label={t("access.form.ssh_key.label")}
|
||||||
<Input.TextArea autoComplete="new-password" hidden placeholder={t("access.form.ssh_key.placeholder")} value={fieldKey} />
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<TextFileInput allowClear autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("access.form.ssh_key.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("access.form.ssh_key.label")} tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key.tooltip") }}></span>}>
|
|
||||||
<Upload beforeUpload={() => false} fileList={fieldKeyFileList} maxCount={1} onChange={handleKeyFileChange}>
|
|
||||||
<Button icon={<UploadOutlinedIcon />}>{t("access.form.ssh_key.upload")}</Button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-1/2">
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="keyPassphrase"
|
name="keyPassphrase"
|
||||||
label={t("access.form.ssh_key_passphrase.label")}
|
label={t("access.form.ssh_key_passphrase.label")}
|
||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key_passphrase.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.ssh_key_passphrase.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input.Password autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
|
<Input.Password allowClear autoComplete="new-password" placeholder={t("access.form.ssh_key_passphrase.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { Alert, Button, Dropdown, Form, type FormInstance, Input, Select, Switch
|
|||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import CodeInput from "@/components/CodeInput";
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { type AccessConfigForWebhook } from "@/domain/access";
|
import { type AccessConfigForWebhook } from "@/domain/access";
|
||||||
|
|
||||||
@ -105,8 +106,8 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
formInst.setFieldValue("headers", value);
|
formInst.setFieldValue("headers", value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWebhookDataForDeploymentBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
const handleWebhookDataForDeploymentBlur = () => {
|
||||||
const value = e.target.value;
|
const value = formInst.getFieldValue("defaultDataForDeployment");
|
||||||
try {
|
try {
|
||||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||||
formInst.setFieldValue("defaultDataForDeployment", json);
|
formInst.setFieldValue("defaultDataForDeployment", json);
|
||||||
@ -115,8 +116,8 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWebhookDataForNotificationBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
const handleWebhookDataForNotificationBlur = () => {
|
||||||
const value = e.target.value;
|
const value = formInst.getFieldValue("defaultDataForNotification");
|
||||||
try {
|
try {
|
||||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||||
formInst.setFieldValue("defaultDataForNotification", json);
|
formInst.setFieldValue("defaultDataForNotification", json);
|
||||||
@ -279,7 +280,7 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.webhook_headers.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.webhook_headers.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("access.form.webhook_headers.placeholder")} onBlur={handleWebhookHeadersBlur} />
|
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("access.form.webhook_headers.placeholder")} onBlur={handleWebhookHeadersBlur} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Show when={!usage || usage === "deployment"}>
|
<Show when={!usage || usage === "deployment"}>
|
||||||
@ -297,9 +298,11 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="defaultDataForDeployment" rules={[formRule]}>
|
<Form.Item name="defaultDataForDeployment" rules={[formRule]}>
|
||||||
<Input.TextArea
|
<CodeInput
|
||||||
allowClear
|
height="auto"
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language="json"
|
||||||
placeholder={t("access.form.webhook_default_data_for_deployment.placeholder")}
|
placeholder={t("access.form.webhook_default_data_for_deployment.placeholder")}
|
||||||
onBlur={handleWebhookDataForDeploymentBlur}
|
onBlur={handleWebhookDataForDeploymentBlur}
|
||||||
/>
|
/>
|
||||||
@ -338,9 +341,11 @@ const AccessFormWebhookConfig = ({ form: formInst, formName, disabled, initialVa
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="defaultDataForNotification" rules={[formRule]}>
|
<Form.Item name="defaultDataForNotification" rules={[formRule]}>
|
||||||
<Input.TextArea
|
<CodeInput
|
||||||
allowClear
|
height="auto"
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language="json"
|
||||||
placeholder={t("access.form.webhook_default_data_for_notification.placeholder")}
|
placeholder={t("access.form.webhook_default_data_for_notification.placeholder")}
|
||||||
onBlur={handleWebhookDataForNotificationBlur}
|
onBlur={handleWebhookDataForNotificationBlur}
|
||||||
/>
|
/>
|
||||||
|
@ -75,7 +75,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input.TextArea value={data.certificate} variant="filled" rows={5} autoSize={{ maxRows: 5 }} readOnly />
|
<Input.TextArea value={data.certificate} variant="filled" autoSize={{ minRows: 5, maxRows: 5 }} readOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@ -92,7 +92,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => {
|
|||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input.TextArea value={data.privateKey} variant="filled" rows={5} autoSize={{ maxRows: 5 }} readOnly />
|
<Input.TextArea value={data.privateKey} variant="filled" autoSize={{ minRows: 5, maxRows: 5 }} readOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ const NotifyTemplateForm = ({ className, style }: NotifyTemplateFormProps) => {
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
autoSize={{ minRows: 3, maxRows: 5 }}
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
placeholder={t("settings.notification.template.form.message.placeholder")}
|
placeholder={t("settings.notification.template.form.message.placeholder")}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
|
@ -4,6 +4,7 @@ import { Avatar, Card, Col, Empty, Flex, Input, type InputRef, Row, Tag, Typogra
|
|||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvidersMap } from "@/domain/provider";
|
import { ACCESS_USAGES, type AccessProvider, type AccessUsageType, accessProvidersMap } from "@/domain/provider";
|
||||||
|
import { mergeCls } from "@/utils/css";
|
||||||
|
|
||||||
export type AccessProviderPickerProps = {
|
export type AccessProviderPickerProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -73,17 +74,23 @@ const AccessProviderPicker = ({ className, style, autoFocus, filter, placeholder
|
|||||||
return (
|
return (
|
||||||
<Col key={index} xs={24} md={12} span={8}>
|
<Col key={index} xs={24} md={12} span={8}>
|
||||||
<Card
|
<Card
|
||||||
className="h-20 w-full overflow-hidden shadow-sm"
|
className={mergeCls("h-20 w-full overflow-hidden shadow-sm", provider.builtin ? " cursor-not-allowed" : "")}
|
||||||
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
|
styles={{ body: { height: "100%", padding: "0.5rem 1rem" } }}
|
||||||
hoverable
|
hoverable
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
if (provider.builtin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
handleProviderTypeSelect(provider.type);
|
handleProviderTypeSelect(provider.type);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
<Flex className="size-full overflow-hidden" align="center" gap={8}>
|
||||||
<Avatar src={provider.icon} size="small" />
|
<Avatar src={provider.icon} size="small" />
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<Typography.Text className="mb-1 line-clamp-1">{t(provider.name)}</Typography.Text>
|
<Typography.Text className="mb-1 line-clamp-1" type={provider.builtin ? "secondary" : undefined}>
|
||||||
|
{t(provider.name)}
|
||||||
|
</Typography.Text>
|
||||||
<div className="origin-left scale-[80%]">
|
<div className="origin-left scale-[80%]">
|
||||||
<Show when={provider.builtin}>
|
<Show when={provider.builtin}>
|
||||||
<Tag>{t("access.props.provider.builtin")}</Tag>
|
<Tag>{t("access.props.provider.builtin")}</Tag>
|
||||||
|
@ -57,6 +57,7 @@ import DeployNodeConfigFormJDCloudLiveConfig from "./DeployNodeConfigFormJDCloud
|
|||||||
import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudVODConfig";
|
import DeployNodeConfigFormJDCloudVODConfig from "./DeployNodeConfigFormJDCloudVODConfig";
|
||||||
import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig";
|
import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKubernetesSecretConfig";
|
||||||
import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
|
import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
|
||||||
|
import DeployNodeConfigFormNetlifySiteConfig from "./DeployNodeConfigFormNetlifySiteConfig";
|
||||||
import DeployNodeConfigFormProxmoxVEConfig from "./DeployNodeConfigFormProxmoxVEConfig";
|
import DeployNodeConfigFormProxmoxVEConfig from "./DeployNodeConfigFormProxmoxVEConfig";
|
||||||
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
|
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
|
||||||
import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig";
|
import DeployNodeConfigFormQiniuKodoConfig from "./DeployNodeConfigFormQiniuKodoConfig";
|
||||||
@ -260,6 +261,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
|
|||||||
return <DeployNodeConfigFormKubernetesSecretConfig {...nestedFormProps} />;
|
return <DeployNodeConfigFormKubernetesSecretConfig {...nestedFormProps} />;
|
||||||
case DEPLOYMENT_PROVIDERS.LOCAL:
|
case DEPLOYMENT_PROVIDERS.LOCAL:
|
||||||
return <DeployNodeConfigFormLocalConfig {...nestedFormProps} />;
|
return <DeployNodeConfigFormLocalConfig {...nestedFormProps} />;
|
||||||
|
case DEPLOYMENT_PROVIDERS.NETLIFY_SITE:
|
||||||
|
return <DeployNodeConfigFormNetlifySiteConfig {...nestedFormProps} />;
|
||||||
case DEPLOYMENT_PROVIDERS.PROXMOXVE:
|
case DEPLOYMENT_PROVIDERS.PROXMOXVE:
|
||||||
return <DeployNodeConfigFormProxmoxVEConfig {...nestedFormProps} />;
|
return <DeployNodeConfigFormProxmoxVEConfig {...nestedFormProps} />;
|
||||||
case DEPLOYMENT_PROVIDERS.QINIU_CDN:
|
case DEPLOYMENT_PROVIDERS.QINIU_CDN:
|
||||||
|
@ -5,6 +5,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{
|
type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{
|
||||||
region: string;
|
region: string;
|
||||||
|
certificateArn?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type DeployNodeConfigFormAWSACMConfigProps = {
|
export type DeployNodeConfigFormAWSACMConfigProps = {
|
||||||
@ -27,6 +28,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled,
|
|||||||
.string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") })
|
.string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") })
|
||||||
.nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder"))
|
.nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder"))
|
||||||
.trim(),
|
.trim(),
|
||||||
|
certificateArn: z.string({ message: t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder") }).nullish(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
@ -51,6 +53,15 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled,
|
|||||||
>
|
>
|
||||||
<Input placeholder={t("workflow_node.deploy.form.aws_acm_region.placeholder")} />
|
<Input placeholder={t("workflow_node.deploy.form.aws_acm_region.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="certificateArn"
|
||||||
|
label={t("workflow_node.deploy.form.aws_acm_certificate_arn.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aws_acm_certificate_arn.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
|
|||||||
certificateName: z
|
certificateName: z
|
||||||
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
|
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
|
||||||
.nullish()
|
.nullish()
|
||||||
.refine((v) =>{
|
.refine((v) => {
|
||||||
if (!v) return true;
|
if (!v) return true;
|
||||||
return /^[a-zA-Z0-9-]{1,127}$/.test(v);
|
return /^[a-zA-Z0-9-]{1,127}$/.test(v);
|
||||||
}, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")),
|
}, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")),
|
||||||
|
@ -4,20 +4,23 @@ import { Alert, Button, Dropdown, Form, type FormInstance, Input, Select } from
|
|||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import CodeInput from "@/components/CodeInput";
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
||||||
|
|
||||||
type DeployNodeConfigFormLocalConfigFieldValues = Nullish<{
|
type DeployNodeConfigFormLocalConfigFieldValues = Nullish<{
|
||||||
format: string;
|
format: string;
|
||||||
certPath: string;
|
certPath: string;
|
||||||
keyPath?: string | null;
|
certPathForServerOnly?: string;
|
||||||
pfxPassword?: string | null;
|
certPathForIntermediaOnly?: string;
|
||||||
jksAlias?: string | null;
|
keyPath?: string;
|
||||||
jksKeypass?: string | null;
|
pfxPassword?: string;
|
||||||
jksStorepass?: string | null;
|
jksAlias?: string;
|
||||||
shellEnv?: string | null;
|
jksKeypass?: string;
|
||||||
preCommand?: string | null;
|
jksStorepass?: string;
|
||||||
postCommand?: string | null;
|
shellEnv?: string;
|
||||||
|
preCommand?: string;
|
||||||
|
postCommand?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type DeployNodeConfigFormLocalConfigProps = {
|
export type DeployNodeConfigFormLocalConfigProps = {
|
||||||
@ -49,6 +52,8 @@ export const initPresetScript = (
|
|||||||
key: "sh_backup_files" | "ps_backup_files" | "sh_reload_nginx" | "ps_binding_iis" | "ps_binding_netsh" | "ps_binding_rdp",
|
key: "sh_backup_files" | "ps_backup_files" | "sh_reload_nginx" | "ps_binding_iis" | "ps_binding_netsh" | "ps_binding_rdp",
|
||||||
params?: {
|
params?: {
|
||||||
certPath?: string;
|
certPath?: string;
|
||||||
|
certPathForServerOnly?: string;
|
||||||
|
certPathForIntermediaOnly?: string;
|
||||||
keyPath?: string;
|
keyPath?: string;
|
||||||
pfxPassword?: string;
|
pfxPassword?: string;
|
||||||
jksAlias?: string;
|
jksAlias?: string;
|
||||||
@ -74,19 +79,22 @@ if (Test-Path -Path "${params?.keyPath || "<your-key-path>"}" -PathType Leaf) {
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
case "sh_reload_nginx":
|
case "sh_reload_nginx":
|
||||||
return `sudo service nginx reload`;
|
return `# *** 需要 root 权限 ***
|
||||||
|
|
||||||
|
sudo service nginx reload
|
||||||
|
`.trim();
|
||||||
|
|
||||||
case "ps_binding_iis":
|
case "ps_binding_iis":
|
||||||
return `# 需要管理员权限
|
return `# *** 需要管理员权限 ***
|
||||||
|
|
||||||
# 请将以下变量替换为实际值
|
# 请将以下变量替换为实际值
|
||||||
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径
|
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径(与表单中保持一致)
|
||||||
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码
|
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码(与表单中保持一致)
|
||||||
$siteName = "<your-site-name>" # IIS 网站名称
|
$siteName = "<your-site-name>" # IIS 网站名称
|
||||||
$domain = "<your-domain-name>" # 域名
|
$domain = "<your-domain-name>" # 域名
|
||||||
$ipaddr = "<your-binding-ip>" # 绑定 IP,“*”表示所有 IP 绑定
|
$ipaddr = "<your-binding-ip>" # 绑定 IP,“*”表示所有 IP 绑定
|
||||||
$port = "<your-binding-port>" # 绑定端口
|
$port = "<your-binding-port>" # 绑定端口
|
||||||
|
|
||||||
|
|
||||||
# 导入证书到本地计算机的个人存储区
|
# 导入证书到本地计算机的个人存储区
|
||||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
||||||
# 获取 Thumbprint
|
# 获取 Thumbprint
|
||||||
@ -108,16 +116,16 @@ Remove-Item -Path "$pfxPath" -Force
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
case "ps_binding_netsh":
|
case "ps_binding_netsh":
|
||||||
return `# 需要管理员权限
|
return `# *** 需要管理员权限 ***
|
||||||
|
|
||||||
# 请将以下变量替换为实际值
|
# 请将以下变量替换为实际值
|
||||||
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径
|
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径(与表单中保持一致)
|
||||||
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码
|
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码(与表单中保持一致)
|
||||||
$ipaddr = "<your-binding-ip>" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名。
|
$ipaddr = "<your-binding-ip>" # 绑定 IP,“0.0.0.0”表示所有 IP 绑定,可填入域名
|
||||||
$port = "<your-binding-port>" # 绑定端口
|
$port = "<your-binding-port>" # 绑定端口
|
||||||
|
|
||||||
$addr = $ipaddr + ":" + $port
|
|
||||||
|
|
||||||
# 导入证书到本地计算机的个人存储区
|
# 导入证书到本地计算机的个人存储区
|
||||||
|
$addr = $ipaddr + ":" + $port
|
||||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
||||||
# 获取 Thumbprint
|
# 获取 Thumbprint
|
||||||
$thumbprint = $cert.Thumbprint
|
$thumbprint = $cert.Thumbprint
|
||||||
@ -131,10 +139,11 @@ Remove-Item -Path "$pfxPath" -Force
|
|||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
case "ps_binding_rdp":
|
case "ps_binding_rdp":
|
||||||
return `# 需要管理员权限
|
return `# *** 需要管理员权限 ***
|
||||||
|
|
||||||
# 请将以下变量替换为实际值
|
# 请将以下变量替换为实际值
|
||||||
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径
|
$pfxPath = "${params?.certPath || "<your-cert-path>"}" # PFX 文件路径(与表单中保持一致)
|
||||||
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码
|
$pfxPassword = "${params?.pfxPassword || "<your-pfx-password>"}" # PFX 密码(与表单中保持一致)
|
||||||
|
|
||||||
# 导入证书到本地计算机的个人存储区
|
# 导入证书到本地计算机的个人存储区
|
||||||
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
$cert = Import-PfxCertificate -FilePath "$pfxPath" -CertStoreLocation Cert:\\LocalMachine\\My -Password (ConvertTo-SecureString -String "$pfxPassword" -AsPlainText -Force) -Exportable
|
||||||
@ -159,6 +168,16 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
.min(1, t("workflow_node.deploy.form.local_cert_path.tooltip"))
|
.min(1, t("workflow_node.deploy.form.local_cert_path.tooltip"))
|
||||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
.trim(),
|
.trim(),
|
||||||
|
certPathForServerOnly: z
|
||||||
|
.string()
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
|
.trim()
|
||||||
|
.nullish(),
|
||||||
|
certPathForIntermediaOnly: z
|
||||||
|
.string()
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
|
.trim()
|
||||||
|
.nullish(),
|
||||||
keyPath: z
|
keyPath: z
|
||||||
.string()
|
.string()
|
||||||
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
@ -325,6 +344,24 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
>
|
>
|
||||||
<Input placeholder={t("workflow_node.deploy.form.local_key_path.placeholder")} />
|
<Input placeholder={t("workflow_node.deploy.form.local_key_path.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="certPathForServerOnly"
|
||||||
|
label={t("workflow_node.deploy.form.local_servercert_path.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.local_servercert_path.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.local_servercert_path.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="certPathForIntermediaOnly"
|
||||||
|
label={t("workflow_node.deploy.form.local_intermediacert_path.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.local_intermediacert_path.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.local_intermediacert_path.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={fieldFormat === FORMAT_PFX}>
|
<Show when={fieldFormat === FORMAT_PFX}>
|
||||||
@ -407,7 +444,13 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="preCommand" rules={[formRule]}>
|
<Form.Item name="preCommand" rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.local_pre_command.placeholder")} />
|
<CodeInput
|
||||||
|
height="auto"
|
||||||
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language={["shell", "powershell"]}
|
||||||
|
placeholder={t("workflow_node.deploy.form.local_pre_command.placeholder")}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -437,7 +480,13 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="postCommand" rules={[formRule]}>
|
<Form.Item name="postCommand" rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.local_post_command.placeholder")} />
|
<CodeInput
|
||||||
|
height="auto"
|
||||||
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language={["shell", "powershell"]}
|
||||||
|
placeholder={t("workflow_node.deploy.form.local_post_command.placeholder")}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
type DeployNodeConfigFormNetlifySiteConfigFieldValues = Nullish<{
|
||||||
|
siteId: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type DeployNodeConfigFormNetlifySiteConfigProps = {
|
||||||
|
form: FormInstance;
|
||||||
|
formName: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
initialValues?: DeployNodeConfigFormNetlifySiteConfigFieldValues;
|
||||||
|
onValuesChange?: (values: DeployNodeConfigFormNetlifySiteConfigFieldValues) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initFormModel = (): DeployNodeConfigFormNetlifySiteConfigFieldValues => {
|
||||||
|
return {
|
||||||
|
siteId: "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeployNodeConfigFormNetlifySiteConfig = ({
|
||||||
|
form: formInst,
|
||||||
|
formName,
|
||||||
|
disabled,
|
||||||
|
initialValues,
|
||||||
|
onValuesChange,
|
||||||
|
}: DeployNodeConfigFormNetlifySiteConfigProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
siteId: z.string().nonempty(t("workflow_node.deploy.form.netlify_site_id.placeholder")),
|
||||||
|
});
|
||||||
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
|
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
|
||||||
|
onValuesChange?.(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
form={formInst}
|
||||||
|
disabled={disabled}
|
||||||
|
initialValues={initialValues ?? initFormModel()}
|
||||||
|
layout="vertical"
|
||||||
|
name={formName}
|
||||||
|
onValuesChange={handleFormChange}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="siteId"
|
||||||
|
label={t("workflow_node.deploy.form.netlify_site_id.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.netlify_site_id.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.netlify_site_id.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeployNodeConfigFormNetlifySiteConfig;
|
@ -4,21 +4,24 @@ import { Button, Dropdown, Form, type FormInstance, Input, Select, Switch } from
|
|||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import CodeInput from "@/components/CodeInput";
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
import { CERTIFICATE_FORMATS } from "@/domain/certificate";
|
||||||
|
|
||||||
import { initPresetScript } from "./DeployNodeConfigFormLocalConfig";
|
import { initPresetScript as _initPresetScript } from "./DeployNodeConfigFormLocalConfig";
|
||||||
|
|
||||||
type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{
|
type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{
|
||||||
format: string;
|
format: string;
|
||||||
certPath: string;
|
certPath: string;
|
||||||
keyPath?: string | null;
|
certPathForServerOnly?: string;
|
||||||
pfxPassword?: string | null;
|
certPathForIntermediaOnly?: string;
|
||||||
jksAlias?: string | null;
|
keyPath?: string;
|
||||||
jksKeypass?: string | null;
|
pfxPassword?: string;
|
||||||
jksStorepass?: string | null;
|
jksAlias?: string;
|
||||||
preCommand?: string | null;
|
jksKeypass?: string;
|
||||||
postCommand?: string | null;
|
jksStorepass?: string;
|
||||||
|
preCommand?: string;
|
||||||
|
postCommand?: string;
|
||||||
useSCP?: boolean;
|
useSCP?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@ -42,6 +45,125 @@ const initFormModel = (): DeployNodeConfigFormSSHConfigFieldValues => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initPresetScript = (
|
||||||
|
key: Parameters<typeof _initPresetScript>[0] | "sh_replace_synologydsm_ssl" | "sh_replace_fnos_ssl",
|
||||||
|
params?: Parameters<typeof _initPresetScript>[1]
|
||||||
|
) => {
|
||||||
|
switch (key) {
|
||||||
|
case "sh_replace_synologydsm_ssl":
|
||||||
|
return `# *** 需要 root 权限 ***
|
||||||
|
# 脚本参考 https://github.com/catchdave/ssl-certs/blob/main/replace_synology_ssl_certs.sh
|
||||||
|
|
||||||
|
# 请将以下变量替换为实际值
|
||||||
|
$tmpFullchainPath = "${params?.certPath || "<your-fullchain-cert-path>"}" # 证书文件路径(与表单中保持一致)
|
||||||
|
$tmpCertPath = "${params?.certPathForServerOnly || "<your-server-cert-path>"}" # 服务器证书文件路径(与表单中保持一致)
|
||||||
|
$tmpKeyPath = "${params?.keyPath || "<your-key-path>"}" # 私钥文件路径(与表单中保持一致)
|
||||||
|
|
||||||
|
DEBUG=1
|
||||||
|
error_exit() { echo "[ERROR] $1"; exit 1; }
|
||||||
|
warn() { echo "[WARN] $1"; }
|
||||||
|
info() { echo "[INFO] $1"; }
|
||||||
|
debug() { [[ "\${DEBUG}" ]] && echo "[DEBUG] $1"; }
|
||||||
|
|
||||||
|
certs_src_dir="/usr/syno/etc/certificate/system/default"
|
||||||
|
target_cert_dirs=(
|
||||||
|
"/usr/syno/etc/certificate/system/FQDN"
|
||||||
|
"/usr/local/etc/certificate/ScsiTarget/pkg-scsi-plugin-server/"
|
||||||
|
"/usr/local/etc/certificate/SynologyDrive/SynologyDrive/"
|
||||||
|
"/usr/local/etc/certificate/WebDAVServer/webdav/"
|
||||||
|
"/usr/local/etc/certificate/ActiveBackup/ActiveBackup/"
|
||||||
|
"/usr/syno/etc/certificate/smbftpd/ftpd/")
|
||||||
|
|
||||||
|
# 获取证书目录
|
||||||
|
default_dir_name=$(</usr/syno/etc/certificate/_archive/DEFAULT)
|
||||||
|
if [[ -n "$default_dir_name" ]]; then
|
||||||
|
target_cert_dirs+=("/usr/syno/etc/certificate/_archive/\${default_dir_name}")
|
||||||
|
debug "Default cert directory found: '/usr/syno/etc/certificate/_archive/\${default_dir_name}'"
|
||||||
|
else
|
||||||
|
warn "No default directory found. Probably unusual? Check: 'cat /usr/syno/etc/certificate/_archive/DEFAULT'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 获取反向代理证书目录
|
||||||
|
for proxy in /usr/syno/etc/certificate/ReverseProxy/*/; do
|
||||||
|
debug "Found proxy dir: \${proxy}"
|
||||||
|
target_cert_dirs+=("\${proxy}")
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ "\${DEBUG}" ]] && set -x
|
||||||
|
|
||||||
|
# 复制文件
|
||||||
|
cp -rf "$tmpFullchainPath" "\${certs_src_dir}/fullchain.pem" || error_exit "Halting because of error moving fullchain file"
|
||||||
|
cp -rf "$tmpCertPath" "\${certs_src_dir}/cert.pem" || error_exit "Halting because of error moving cert file"
|
||||||
|
cp -rf "$tmpKeyPath" "\${certs_src_dir}/privkey.pem" || error_exit "Halting because of error moving privkey file"
|
||||||
|
chown root:root "\${certs_src_dir}/"{privkey,fullchain,cert}.pem || error_exit "Halting because of error chowning files"
|
||||||
|
info "Certs moved from /tmp & chowned."
|
||||||
|
|
||||||
|
# 替换证书
|
||||||
|
for target_dir in "\${target_cert_dirs[@]}"; do
|
||||||
|
if [[ ! -d "$target_dir" ]]; then
|
||||||
|
debug "Target cert directory '$target_dir' not found, skipping..."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
info "Copying certificates to '$target_dir'"
|
||||||
|
if ! (cp "\${certs_src_dir}/"{privkey,fullchain,cert}.pem "$target_dir/" && \
|
||||||
|
chown root:root "$target_dir/"{privkey,fullchain,cert}.pem); then
|
||||||
|
warn "Error copying or chowning certs to \${target_dir}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
info "Rebooting all the things..."
|
||||||
|
/usr/syno/bin/synosystemctl restart nmbd
|
||||||
|
/usr/syno/bin/synosystemctl restart avahi
|
||||||
|
/usr/syno/bin/synosystemctl restart ldap-server
|
||||||
|
/usr/syno/bin/synopkg is_onoff ScsiTarget 1>/dev/null && /usr/syno/bin/synopkg restart ScsiTarget
|
||||||
|
/usr/syno/bin/synopkg is_onoff SynologyDrive 1>/dev/null && /usr/syno/bin/synopkg restart SynologyDrive
|
||||||
|
/usr/syno/bin/synopkg is_onoff WebDAVServer 1>/dev/null && /usr/syno/bin/synopkg restart WebDAVServer
|
||||||
|
/usr/syno/bin/synopkg is_onoff ActiveBackup 1>/dev/null && /usr/syno/bin/synopkg restart ActiveBackup
|
||||||
|
if ! /usr/syno/bin/synow3tool --gen-all && sudo /usr/syno/bin/synosystemctl restart nginx; then
|
||||||
|
warn "nginx failed to restart"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Completed"
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
case "sh_replace_fnos_ssl":
|
||||||
|
return `# *** 需要 root 权限 ***
|
||||||
|
# 脚本参考 https://github.com/lfgyx/fnos_certificate_update/blob/main/src/update_cert.sh
|
||||||
|
|
||||||
|
# 请将以下变量替换为实际值
|
||||||
|
# 飞牛证书实际存放路径请在 \`/usr/trim/etc/network_cert_all.conf\` 中查看,注意不要修改文件名
|
||||||
|
$tmpFullchainPath = "${params?.certPath || "<your-fullchain-cert-path>"}" # 证书文件路径(与表单中保持一致)
|
||||||
|
$tmpCertPath = "${params?.certPathForServerOnly || "<your-server-cert-path>"}" # 服务器证书文件路径(与表单中保持一致)
|
||||||
|
$tmpKeyPath = "${params?.keyPath || "<your-key-path>"}" # 私钥文件路径(与表单中保持一致)
|
||||||
|
$fnFullchainPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/fullchain.crt" # 飞牛证书文件路径
|
||||||
|
$fnCertPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/example.com.crt" # 飞牛服务器证书文件路径
|
||||||
|
$fnKeyPath = "/usr/trim/var/trim_connect/ssls/example.com/1234567890/example.com.key" # 飞牛私钥文件路径
|
||||||
|
$domain = "<your-domain-name>" # 域名
|
||||||
|
|
||||||
|
# 复制文件
|
||||||
|
cp -rf "$tmpFullchainPath" "$fnFullchainPath"
|
||||||
|
cp -rf "$tmpCertPath" "$fnCertPath"
|
||||||
|
cp -rf "$tmpKeyPath" "$fnKeyPath"
|
||||||
|
chmod 755 "$fnCertPath"
|
||||||
|
chmod 755 "$fnKeyPath"
|
||||||
|
chmod 755 "$fnFullchainPath"
|
||||||
|
|
||||||
|
# 更新数据库
|
||||||
|
NEW_EXPIRY_DATE=$(openssl x509 -enddate -noout -in "$fnCertPath" | sed "s/^.*=\\(.*\\)$/\\1/")
|
||||||
|
NEW_EXPIRY_TIMESTAMP=$(date -d "$NEW_EXPIRY_DATE" +%s%3N)
|
||||||
|
psql -U postgres -d trim_connect -c "UPDATE cert SET valid_to=$NEW_EXPIRY_TIMESTAMP WHERE domain='$domain'"
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
systemctl restart webdav.service
|
||||||
|
systemctl restart smbftpd.service
|
||||||
|
systemctl restart trim_nginx.service
|
||||||
|
`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _initPresetScript(key as Parameters<typeof _initPresetScript>[0], params);
|
||||||
|
};
|
||||||
|
|
||||||
const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormSSHConfigProps) => {
|
const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormSSHConfigProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -60,6 +182,16 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
.trim()
|
.trim()
|
||||||
.nullish()
|
.nullish()
|
||||||
.refine((v) => fieldFormat !== FORMAT_PEM || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_key_path.tooltip") }),
|
.refine((v) => fieldFormat !== FORMAT_PEM || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_key_path.tooltip") }),
|
||||||
|
certPathForServerOnly: z
|
||||||
|
.string()
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
|
.trim()
|
||||||
|
.nullish(),
|
||||||
|
certPathForIntermediaOnly: z
|
||||||
|
.string()
|
||||||
|
.max(256, t("common.errmsg.string_max", { max: 256 }))
|
||||||
|
.trim()
|
||||||
|
.nullish(),
|
||||||
pfxPassword: z
|
pfxPassword: z
|
||||||
.string()
|
.string()
|
||||||
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
.max(64, t("common.errmsg.string_max", { max: 256 }))
|
||||||
@ -147,6 +279,24 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
const handlePresetPostScriptClick = (key: string) => {
|
const handlePresetPostScriptClick = (key: string) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "sh_reload_nginx":
|
case "sh_reload_nginx":
|
||||||
|
{
|
||||||
|
formInst.setFieldValue("postCommand", initPresetScript(key));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "sh_replace_synologydsm_ssl":
|
||||||
|
case "sh_replace_fnos_ssl":
|
||||||
|
{
|
||||||
|
const presetScriptParams = {
|
||||||
|
certPath: formInst.getFieldValue("certPath"),
|
||||||
|
certPathForServerOnly: formInst.getFieldValue("certPathForServerOnly"),
|
||||||
|
certPathForIntermediaOnly: formInst.getFieldValue("certPathForIntermediaOnly"),
|
||||||
|
keyPath: formInst.getFieldValue("keyPath"),
|
||||||
|
};
|
||||||
|
formInst.setFieldValue("postCommand", initPresetScript(key, presetScriptParams));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "ps_binding_iis":
|
case "ps_binding_iis":
|
||||||
case "ps_binding_netsh":
|
case "ps_binding_netsh":
|
||||||
case "ps_binding_rdp":
|
case "ps_binding_rdp":
|
||||||
@ -206,6 +356,24 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
>
|
>
|
||||||
<Input placeholder={t("workflow_node.deploy.form.ssh_key_path.placeholder")} />
|
<Input placeholder={t("workflow_node.deploy.form.ssh_key_path.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="certPathForServerOnly"
|
||||||
|
label={t("workflow_node.deploy.form.ssh_servercert_path.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ssh_servercert_path.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.ssh_servercert_path.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="certPathForIntermediaOnly"
|
||||||
|
label={t("workflow_node.deploy.form.ssh_intermediacert_path.label")}
|
||||||
|
rules={[formRule]}
|
||||||
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.ssh_intermediacert_path.tooltip") }}></span>}
|
||||||
|
>
|
||||||
|
<Input placeholder={t("workflow_node.deploy.form.ssh_intermediacert_path.placeholder")} />
|
||||||
|
</Form.Item>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={fieldFormat === FORMAT_PFX}>
|
<Show when={fieldFormat === FORMAT_PFX}>
|
||||||
@ -248,10 +416,6 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Form.Item label={t("workflow_node.deploy.form.ssh_shell_env.label")}>
|
|
||||||
<Select options={[{ value: t("workflow_node.deploy.form.ssh_shell_env.value") }]} value={t("workflow_node.deploy.form.ssh_shell_env.value")} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item className="mb-0" htmlFor="null">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
<label className="mb-1 block">
|
<label className="mb-1 block">
|
||||||
<div className="flex w-full items-center justify-between gap-4">
|
<div className="flex w-full items-center justify-between gap-4">
|
||||||
@ -278,7 +442,13 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="preCommand" rules={[formRule]}>
|
<Form.Item name="preCommand" rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_pre_command.placeholder")} />
|
<CodeInput
|
||||||
|
height="auto"
|
||||||
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language={["shell", "powershell"]}
|
||||||
|
placeholder={t("workflow_node.deploy.form.ssh_pre_command.placeholder")}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
@ -291,11 +461,13 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: ["sh_reload_nginx", "ps_binding_iis", "ps_binding_netsh", "ps_binding_rdp"].map((key) => ({
|
items: ["sh_reload_nginx", "sh_replace_synologydsm_ssl", "sh_replace_fnos_ssl", "ps_binding_iis", "ps_binding_netsh", "ps_binding_rdp"].map(
|
||||||
|
(key) => ({
|
||||||
key,
|
key,
|
||||||
label: t(`workflow_node.deploy.form.ssh_preset_scripts.option.${key}.label`),
|
label: t(`workflow_node.deploy.form.ssh_preset_scripts.option.${key}.label`),
|
||||||
onClick: () => handlePresetPostScriptClick(key),
|
onClick: () => handlePresetPostScriptClick(key),
|
||||||
})),
|
})
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
>
|
>
|
||||||
@ -308,7 +480,13 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<Form.Item name="postCommand" rules={[formRule]}>
|
<Form.Item name="postCommand" rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 1, maxRows: 5 }} placeholder={t("workflow_node.deploy.form.ssh_post_command.placeholder")} />
|
<CodeInput
|
||||||
|
height="auto"
|
||||||
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language={["shell", "powershell"]}
|
||||||
|
placeholder={t("workflow_node.deploy.form.ssh_post_command.placeholder")}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ const DeployNodeConfigFormTencentCloudSSLDeployConfig = ({
|
|||||||
if (!v) return false;
|
if (!v) return false;
|
||||||
return String(v)
|
return String(v)
|
||||||
.split(MULTIPLE_INPUT_DELIMITER)
|
.split(MULTIPLE_INPUT_DELIMITER)
|
||||||
.every((e) => /^[A-Za-z0-9*._-]+$/.test(e));
|
.every((e) => /^[A-Za-z0-9*._-|]+$/.test(e));
|
||||||
}, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")),
|
}, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
@ -138,7 +138,7 @@ const ResourceIdsModalInput = memo(({ value, trigger, onChange }: { value?: stri
|
|||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
resourceIds: z.array(z.string()).refine((v) => {
|
resourceIds: z.array(z.string()).refine((v) => {
|
||||||
return v.every((e) => !e?.trim() || /^[A-Za-z0-9*._-]+$/.test(e));
|
return v.every((e) => !e?.trim() || /^[A-Za-z0-9*._-|]+$/.test(e));
|
||||||
}, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")),
|
}, t("workflow_node.deploy.form.tencentcloud_ssl_deploy_resource_ids.errmsg.invalid")),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, Form, type FormInstance, Input } from "antd";
|
import { Alert, Form, type FormInstance } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import CodeInput from "@/components/CodeInput";
|
||||||
|
|
||||||
type DeployNodeConfigFormWebhookConfigFieldValues = Nullish<{
|
type DeployNodeConfigFormWebhookConfigFieldValues = Nullish<{
|
||||||
webhookData: string;
|
webhookData: string;
|
||||||
}>;
|
}>;
|
||||||
@ -39,8 +41,8 @@ const DeployNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled,
|
|||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
const handleWebhookDataBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
const handleWebhookDataBlur = () => {
|
||||||
const value = e.target.value;
|
const value = formInst.getFieldValue("webhookData");
|
||||||
try {
|
try {
|
||||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||||
formInst.setFieldValue("webhookData", json);
|
formInst.setFieldValue("webhookData", json);
|
||||||
@ -68,9 +70,11 @@ const DeployNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled,
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<CodeInput
|
||||||
allowClear
|
height="auto"
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language="json"
|
||||||
placeholder={t("workflow_node.deploy.form.webhook_data.placeholder")}
|
placeholder={t("workflow_node.deploy.form.webhook_data.placeholder")}
|
||||||
onBlur={handleWebhookDataBlur}
|
onBlur={handleWebhookDataBlur}
|
||||||
/>
|
/>
|
||||||
|
@ -177,7 +177,7 @@ const NotifyNodeConfigForm = forwardRef<NotifyNodeConfigFormInstance, NotifyNode
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
|
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
|
||||||
<Input.TextArea autoSize={{ minRows: 3, maxRows: 5 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item className="mb-0" htmlFor="null">
|
<Form.Item className="mb-0" htmlFor="null">
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Alert, Form, type FormInstance, Input } from "antd";
|
import { Alert, Form, type FormInstance } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import CodeInput from "@/components/CodeInput";
|
||||||
|
|
||||||
type NotifyNodeConfigFormWebhookConfigFieldValues = Nullish<{
|
type NotifyNodeConfigFormWebhookConfigFieldValues = Nullish<{
|
||||||
webhookData: string;
|
webhookData: string;
|
||||||
}>;
|
}>;
|
||||||
@ -39,8 +41,8 @@ const NotifyNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled,
|
|||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
|
|
||||||
const handleWebhookDataBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
const handleWebhookDataBlur = () => {
|
||||||
const value = e.target.value;
|
const value = formInst.getFieldValue("webhookData");
|
||||||
try {
|
try {
|
||||||
const json = JSON.stringify(JSON.parse(value), null, 2);
|
const json = JSON.stringify(JSON.parse(value), null, 2);
|
||||||
formInst.setFieldValue("webhookData", json);
|
formInst.setFieldValue("webhookData", json);
|
||||||
@ -68,9 +70,11 @@ const NotifyNodeConfigFormWebhookConfig = ({ form: formInst, formName, disabled,
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.webhook_data.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.webhook_data.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input.TextArea
|
<CodeInput
|
||||||
allowClear
|
height="auto"
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
minHeight="64px"
|
||||||
|
maxHeight="256px"
|
||||||
|
language="json"
|
||||||
placeholder={t("workflow_node.notify.form.webhook_data.placeholder")}
|
placeholder={t("workflow_node.notify.form.webhook_data.placeholder")}
|
||||||
onBlur={handleWebhookDataBlur}
|
onBlur={handleWebhookDataBlur}
|
||||||
/>
|
/>
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { forwardRef, memo, useImperativeHandle } from "react";
|
import { forwardRef, memo, useImperativeHandle } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UploadOutlined as UploadOutlinedIcon } from "@ant-design/icons";
|
import { Form, type FormInstance, Input } from "antd";
|
||||||
import { Button, Form, type FormInstance, Input, Upload, type UploadProps } from "antd";
|
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { validateCertificate, validatePrivateKey } from "@/api/certificates";
|
import { validateCertificate, validatePrivateKey } from "@/api/certificates";
|
||||||
|
import TextFileInput from "@/components/TextFileInput";
|
||||||
import { type WorkflowNodeConfigForUpload } from "@/domain/workflow";
|
import { type WorkflowNodeConfigForUpload } from "@/domain/workflow";
|
||||||
import { useAntdForm } from "@/hooks";
|
import { useAntdForm } from "@/hooks";
|
||||||
import { getErrMsg } from "@/utils/error";
|
import { getErrMsg } from "@/utils/error";
|
||||||
import { readFileContent } from "@/utils/file";
|
|
||||||
|
|
||||||
type UploadNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForUpload>;
|
type UploadNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForUpload>;
|
||||||
|
|
||||||
@ -70,12 +69,9 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
} as UploadNodeConfigFormInstance;
|
} as UploadNodeConfigFormInstance;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCertificateFileChange: UploadProps["onChange"] = async ({ file }) => {
|
const handleCertificateChange = async (value: string) => {
|
||||||
if (file && file.status !== "removed") {
|
|
||||||
const certificate = await readFileContent(file.originFileObj ?? (file as unknown as File));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await validateCertificate(certificate);
|
const resp = await validateCertificate(value);
|
||||||
formInst.setFields([
|
formInst.setFields([
|
||||||
{
|
{
|
||||||
name: "domains",
|
name: "domains",
|
||||||
@ -83,7 +79,7 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "certificate",
|
name: "certificate",
|
||||||
value: certificate,
|
value: value,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -94,42 +90,33 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "certificate",
|
name: "certificate",
|
||||||
value: "",
|
value: value,
|
||||||
errors: [getErrMsg(e)],
|
errors: [getErrMsg(e)],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
formInst.setFieldValue("certificate", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
onValuesChange?.(formInst.getFieldsValue(true));
|
onValuesChange?.(formInst.getFieldsValue(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePrivateKeyFileChange: UploadProps["onChange"] = async ({ file }) => {
|
const handlePrivateKeyChange = async (value: string) => {
|
||||||
if (file && file.status !== "removed") {
|
|
||||||
const privateKey = await readFileContent(file.originFileObj ?? (file as unknown as File));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await validatePrivateKey(privateKey);
|
await validatePrivateKey(value);
|
||||||
formInst.setFields([
|
formInst.setFields([
|
||||||
{
|
{
|
||||||
name: "privateKey",
|
name: "privateKey",
|
||||||
value: privateKey,
|
value: value,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
formInst.setFields([
|
formInst.setFields([
|
||||||
{
|
{
|
||||||
name: "privateKey",
|
name: "privateKey",
|
||||||
value: "",
|
value: value,
|
||||||
errors: [getErrMsg(e)],
|
errors: [getErrMsg(e)],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
formInst.setFieldValue("privateKey", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
onValuesChange?.(formInst.getFieldsValue(true));
|
onValuesChange?.(formInst.getFieldsValue(true));
|
||||||
};
|
};
|
||||||
@ -141,23 +128,19 @@ const UploadNodeConfigForm = forwardRef<UploadNodeConfigFormInstance, UploadNode
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}>
|
<Form.Item name="certificate" label={t("workflow_node.upload.form.certificate.label")} rules={[formRule]}>
|
||||||
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.certificate.placeholder")} />
|
<TextFileInput
|
||||||
</Form.Item>
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
|
placeholder={t("workflow_node.upload.form.certificate.placeholder")}
|
||||||
<Form.Item>
|
onChange={handleCertificateChange}
|
||||||
<Upload beforeUpload={() => false} maxCount={1} onChange={handleCertificateFileChange}>
|
/>
|
||||||
<Button icon={<UploadOutlinedIcon />}>{t("workflow_node.upload.form.certificate.button")}</Button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}>
|
<Form.Item name="privateKey" label={t("workflow_node.upload.form.private_key.label")} rules={[formRule]}>
|
||||||
<Input.TextArea readOnly autoSize={{ minRows: 5, maxRows: 5 }} placeholder={t("workflow_node.upload.form.private_key.placeholder")} />
|
<TextFileInput
|
||||||
</Form.Item>
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
|
placeholder={t("workflow_node.upload.form.private_key.placeholder")}
|
||||||
<Form.Item>
|
onChange={handlePrivateKeyChange}
|
||||||
<Upload beforeUpload={() => false} maxCount={1} onChange={handlePrivateKeyFileChange}>
|
/>
|
||||||
<Button icon={<UploadOutlinedIcon />}>{t("workflow_node.upload.form.private_key.button")}</Button>
|
|
||||||
</Upload>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
@ -41,6 +41,8 @@ export interface AccessModel extends BaseModel {
|
|||||||
| AccessConfigForNamecheap
|
| AccessConfigForNamecheap
|
||||||
| AccessConfigForNameDotCom
|
| AccessConfigForNameDotCom
|
||||||
| AccessConfigForNameSilo
|
| AccessConfigForNameSilo
|
||||||
|
| AccessConfigForNetcup
|
||||||
|
| AccessConfigForNetlify
|
||||||
| AccessConfigForPorkbun
|
| AccessConfigForPorkbun
|
||||||
| AccessConfigForPowerDNS
|
| AccessConfigForPowerDNS
|
||||||
| AccessConfigForProxmoxVE
|
| AccessConfigForProxmoxVE
|
||||||
@ -199,6 +201,7 @@ export type AccessConfigForGoDaddy = {
|
|||||||
|
|
||||||
export type AccessConfigForGoEdge = {
|
export type AccessConfigForGoEdge = {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
|
apiRole: string;
|
||||||
accessKeyId: string;
|
accessKeyId: string;
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
allowInsecureConnections?: boolean;
|
allowInsecureConnections?: boolean;
|
||||||
@ -248,6 +251,16 @@ export type AccessConfigForNameSilo = {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AccessConfigForNetcup = {
|
||||||
|
customerNumber: string;
|
||||||
|
apiKey: string;
|
||||||
|
apiPassword: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AccessConfigForNetlify = {
|
||||||
|
apiToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AccessConfigForNS1 = {
|
export type AccessConfigForNS1 = {
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
};
|
};
|
||||||
|
@ -43,6 +43,8 @@ export const ACCESS_PROVIDERS = Object.freeze({
|
|||||||
NAMECHEAP: "namecheap",
|
NAMECHEAP: "namecheap",
|
||||||
NAMEDOTCOM: "namedotcom",
|
NAMEDOTCOM: "namedotcom",
|
||||||
NAMESILO: "namesilo",
|
NAMESILO: "namesilo",
|
||||||
|
NETCUP: "netcup",
|
||||||
|
NETLIFY: "netlify",
|
||||||
NS1: "ns1",
|
NS1: "ns1",
|
||||||
PORKBUN: "porkbun",
|
PORKBUN: "porkbun",
|
||||||
POWERDNS: "powerdns",
|
POWERDNS: "powerdns",
|
||||||
@ -105,6 +107,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
|||||||
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
[ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||||
[ACCESS_PROVIDERS.BUNNY, "provider.bunny", "/imgs/providers/bunny.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
[ACCESS_PROVIDERS.BUNNY, "provider.bunny", "/imgs/providers/bunny.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||||
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
[ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||||
|
[ACCESS_PROVIDERS.NETLIFY, "provider.netlify", "/imgs/providers/netlify.png", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||||
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
[ACCESS_PROVIDERS.RAINYUN, "provider.rainyun", "/imgs/providers/rainyun.svg", [ACCESS_USAGES.DNS, ACCESS_USAGES.HOSTING]],
|
||||||
|
|
||||||
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
|
[ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.HOSTING]],
|
||||||
@ -133,6 +136,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
|
|||||||
[ACCESS_PROVIDERS.NAMECHEAP, "provider.namecheap", "/imgs/providers/namecheap.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.NAMECHEAP, "provider.namecheap", "/imgs/providers/namecheap.svg", [ACCESS_USAGES.DNS]],
|
||||||
[ACCESS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom", "/imgs/providers/namedotcom.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.NAMEDOTCOM, "provider.namedotcom", "/imgs/providers/namedotcom.svg", [ACCESS_USAGES.DNS]],
|
||||||
[ACCESS_PROVIDERS.NAMESILO, "provider.namesilo", "/imgs/providers/namesilo.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.NAMESILO, "provider.namesilo", "/imgs/providers/namesilo.svg", [ACCESS_USAGES.DNS]],
|
||||||
|
[ACCESS_PROVIDERS.NETCUP, "provider.netcup", "/imgs/providers/netcup.png", [ACCESS_USAGES.DNS]],
|
||||||
[ACCESS_PROVIDERS.NS1, "provider.ns1", "/imgs/providers/ns1.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.NS1, "provider.ns1", "/imgs/providers/ns1.svg", [ACCESS_USAGES.DNS]],
|
||||||
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.PORKBUN, "provider.porkbun", "/imgs/providers/porkbun.svg", [ACCESS_USAGES.DNS]],
|
||||||
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
|
[ACCESS_PROVIDERS.VERCEL, "provider.vercel", "/imgs/providers/vercel.svg", [ACCESS_USAGES.DNS]],
|
||||||
@ -249,6 +253,8 @@ export const ACME_DNS01_PROVIDERS = Object.freeze({
|
|||||||
NAMECHEAP: `${ACCESS_PROVIDERS.NAMECHEAP}`,
|
NAMECHEAP: `${ACCESS_PROVIDERS.NAMECHEAP}`,
|
||||||
NAMEDOTCOM: `${ACCESS_PROVIDERS.NAMEDOTCOM}`,
|
NAMEDOTCOM: `${ACCESS_PROVIDERS.NAMEDOTCOM}`,
|
||||||
NAMESILO: `${ACCESS_PROVIDERS.NAMESILO}`,
|
NAMESILO: `${ACCESS_PROVIDERS.NAMESILO}`,
|
||||||
|
NETCUP: `${ACCESS_PROVIDERS.NETCUP}`,
|
||||||
|
NETLIFY: `${ACCESS_PROVIDERS.NETLIFY}`,
|
||||||
NS1: `${ACCESS_PROVIDERS.NS1}`,
|
NS1: `${ACCESS_PROVIDERS.NS1}`,
|
||||||
PORKBUN: `${ACCESS_PROVIDERS.PORKBUN}`,
|
PORKBUN: `${ACCESS_PROVIDERS.PORKBUN}`,
|
||||||
POWERDNS: `${ACCESS_PROVIDERS.POWERDNS}`,
|
POWERDNS: `${ACCESS_PROVIDERS.POWERDNS}`,
|
||||||
@ -299,6 +305,8 @@ export const acmeDns01ProvidersMap: Map<ACMEDns01Provider["type"] | string, ACME
|
|||||||
[ACME_DNS01_PROVIDERS.NAMECHEAP, "provider.namecheap"],
|
[ACME_DNS01_PROVIDERS.NAMECHEAP, "provider.namecheap"],
|
||||||
[ACME_DNS01_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"],
|
[ACME_DNS01_PROVIDERS.NAMEDOTCOM, "provider.namedotcom"],
|
||||||
[ACME_DNS01_PROVIDERS.NAMESILO, "provider.namesilo"],
|
[ACME_DNS01_PROVIDERS.NAMESILO, "provider.namesilo"],
|
||||||
|
[ACME_DNS01_PROVIDERS.NETCUP, "provider.netcup"],
|
||||||
|
[ACME_DNS01_PROVIDERS.NETLIFY, "provider.netlify"],
|
||||||
[ACME_DNS01_PROVIDERS.NS1, "provider.ns1"],
|
[ACME_DNS01_PROVIDERS.NS1, "provider.ns1"],
|
||||||
[ACME_DNS01_PROVIDERS.PORKBUN, "provider.porkbun"],
|
[ACME_DNS01_PROVIDERS.PORKBUN, "provider.porkbun"],
|
||||||
[ACME_DNS01_PROVIDERS.VERCEL, "provider.vercel"],
|
[ACME_DNS01_PROVIDERS.VERCEL, "provider.vercel"],
|
||||||
@ -370,6 +378,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({
|
|||||||
JDCLOUD_VOD: `${ACCESS_PROVIDERS.JDCLOUD}-vod`,
|
JDCLOUD_VOD: `${ACCESS_PROVIDERS.JDCLOUD}-vod`,
|
||||||
KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`,
|
KUBERNETES_SECRET: `${ACCESS_PROVIDERS.KUBERNETES}-secret`,
|
||||||
LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
|
LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
|
||||||
|
NETLIFY_SITE: `${ACCESS_PROVIDERS.NETLIFY}-site`,
|
||||||
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
|
PROXMOXVE: `${ACCESS_PROVIDERS.PROXMOXVE}`,
|
||||||
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
|
QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
|
||||||
QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`,
|
QINIU_KODO: `${ACCESS_PROVIDERS.QINIU}-kodo`,
|
||||||
@ -505,6 +514,7 @@ export const deploymentProvidersMap: Map<DeploymentProvider["type"] | string, De
|
|||||||
[DEPLOYMENT_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOYMENT_CATEGORIES.CDN],
|
[DEPLOYMENT_PROVIDERS.CACHEFLY, "provider.cachefly", DEPLOYMENT_CATEGORIES.CDN],
|
||||||
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
|
[DEPLOYMENT_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOYMENT_CATEGORIES.CDN],
|
||||||
[DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOYMENT_CATEGORIES.WEBSITE],
|
[DEPLOYMENT_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOYMENT_CATEGORIES.WEBSITE],
|
||||||
|
[DEPLOYMENT_PROVIDERS.NETLIFY_SITE, "provider.netlify.site", DEPLOYMENT_CATEGORIES.WEBSITE],
|
||||||
[DEPLOYMENT_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOYMENT_CATEGORIES.CDN],
|
[DEPLOYMENT_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOYMENT_CATEGORIES.CDN],
|
||||||
[DEPLOYMENT_PROVIDERS.GOEDGE, "provider.goedge", DEPLOYMENT_CATEGORIES.CDN],
|
[DEPLOYMENT_PROVIDERS.GOEDGE, "provider.goedge", DEPLOYMENT_CATEGORIES.CDN],
|
||||||
[DEPLOYMENT_PROVIDERS["1PANEL_SITE"], "provider.1panel.site", DEPLOYMENT_CATEGORIES.WEBSITE],
|
[DEPLOYMENT_PROVIDERS["1PANEL_SITE"], "provider.1panel.site", DEPLOYMENT_CATEGORIES.WEBSITE],
|
||||||
|
@ -1 +1 @@
|
|||||||
export const version = "v0.3.11";
|
export const version = "v0.3.12";
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user