mirror of https://github.com/usual2970/certimate
				
				
				
			feat: add safeline deployer
							parent
							
								
									a40b078e9c
								
							
						
					
					
						commit
						c72dc0d2c4
					
				| 
						 | 
				
			
			@ -130,6 +130,7 @@ make local.run
 | 
			
		|||
| [七牛云](https://www.qiniu.com/)        | 可部署到七牛云 CDN、直播云等服务                                         |
 | 
			
		||||
| [多吉云](https://www.dogecloud.com/)    | 可部署到多吉云 CDN                                                       |
 | 
			
		||||
| [优刻得](https://www.ucloud.cn/)        | 可部署到优刻得 US3、UCDN 等服务                                          |
 | 
			
		||||
| [雷池](https://waf-ce.chaitin.cn/)      | 可部署到雷池 WAF                                                         |
 | 
			
		||||
| [宝塔面板](https://www.bt.cn/)          | 可部署到宝塔面板                                                         |
 | 
			
		||||
| [AWS](https://aws.amazon.com/)          | 可部署到 AWS CloudFront 等服务                                           |
 | 
			
		||||
| [BytePlus](https://www.byteplus.com/)   | 可部署到 BytePlus CDN 等服务                                             |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,8 +128,9 @@ The following hosting providers are supported:
 | 
			
		|||
| [Volcengine](https://www.volcengine.com/)       | Supports deployment to Volcengine TOS, CDN, DCDN, CLB, ImageX, Live              |
 | 
			
		||||
| [Qiniu Cloud](https://www.qiniu.com/)           | Supports deployment to Qiniu Cloud CDN, Pili                                     |
 | 
			
		||||
| [Doge Cloud](https://www.dogecloud.com/)        | Supports deployment to Doge Cloud CDN                                            |
 | 
			
		||||
| [BaoTa Panel](https://www.bt.cn/)               | Supports deployment to BaoTa Panel sites                                         |
 | 
			
		||||
| [UCloud](https://www.ucloud-global.com/)        | Supports deployment to UCloud US3, UCDN                                          |
 | 
			
		||||
| [SafeLine](https://waf.chaitin.com/)            | Supports deployment to SafeLine WAF                                              |
 | 
			
		||||
| [BaoTa Panel](https://www.bt.cn/)               | Supports deployment to BaoTa Panel sites                                         |
 | 
			
		||||
| [AWS](https://aws.amazon.com/)                  | Supports deployment to AWS CloudFront                                            |
 | 
			
		||||
| [BytePlus](https://www.byteplus.com/)           | Supports deployment to BytePlus CDN                                              |
 | 
			
		||||
| [Edgio](https://edg.io/)                        | Supports deployment to Edgio Applications                                        |
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,8 @@ import (
 | 
			
		|||
	pLocal "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/local"
 | 
			
		||||
	pQiniuCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-cdn"
 | 
			
		||||
	pQiniuPili "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/qiniu-pili"
 | 
			
		||||
	providerSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
 | 
			
		||||
	pSafeLine "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
 | 
			
		||||
	pSSH "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ssh"
 | 
			
		||||
	pTencentCloudCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cdn"
 | 
			
		||||
	pTencentCloudCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-clb"
 | 
			
		||||
	pTencentCloudCOS "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-cos"
 | 
			
		||||
| 
						 | 
				
			
			@ -405,6 +406,22 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case domain.DeployProviderTypeSafeLine:
 | 
			
		||||
		{
 | 
			
		||||
			access := domain.AccessConfigForSafeLine{}
 | 
			
		||||
			if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
 | 
			
		||||
				return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			deployer, err := pSafeLine.NewWithLogger(&pSafeLine.SafeLineDeployerConfig{
 | 
			
		||||
				ApiUrl:        access.ApiUrl,
 | 
			
		||||
				ApiToken:      access.ApiToken,
 | 
			
		||||
				ResourceType:  pSafeLine.ResourceType(maps.GetValueAsString(options.ProviderDeployConfig, "resourceType")),
 | 
			
		||||
				CertificateId: maps.GetValueAsInt32(options.ProviderDeployConfig, "certificateId"),
 | 
			
		||||
			}, logger)
 | 
			
		||||
			return deployer, logger, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case domain.DeployProviderTypeSSH:
 | 
			
		||||
		{
 | 
			
		||||
			access := domain.AccessConfigForSSH{}
 | 
			
		||||
| 
						 | 
				
			
			@ -412,7 +429,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
 | 
			
		|||
				return nil, nil, fmt.Errorf("failed to populate provider access config: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			deployer, err := providerSSH.NewWithLogger(&providerSSH.SshDeployerConfig{
 | 
			
		||||
			deployer, err := pSSH.NewWithLogger(&pSSH.SshDeployerConfig{
 | 
			
		||||
				SshHost:          access.Host,
 | 
			
		||||
				SshPort:          access.Port,
 | 
			
		||||
				SshUsername:      access.Username,
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +439,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, logger.Logger,
 | 
			
		|||
				UseSCP:           maps.GetValueAsBool(options.ProviderDeployConfig, "useSCP"),
 | 
			
		||||
				PreCommand:       maps.GetValueAsString(options.ProviderDeployConfig, "preCommand"),
 | 
			
		||||
				PostCommand:      maps.GetValueAsString(options.ProviderDeployConfig, "postCommand"),
 | 
			
		||||
				OutputFormat:     providerSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(providerSSH.OUTPUT_FORMAT_PEM))),
 | 
			
		||||
				OutputFormat:     pSSH.OutputFormatType(maps.GetValueOrDefaultAsString(options.ProviderDeployConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))),
 | 
			
		||||
				OutputCertPath:   maps.GetValueAsString(options.ProviderDeployConfig, "certPath"),
 | 
			
		||||
				OutputKeyPath:    maps.GetValueAsString(options.ProviderDeployConfig, "keyPath"),
 | 
			
		||||
				PfxPassword:      maps.GetValueAsString(options.ProviderDeployConfig, "pfxPassword"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -130,6 +130,11 @@ type AccessConfigForRainYun struct {
 | 
			
		|||
	ApiKey string `json:"apiKey"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessConfigForSafeLine struct {
 | 
			
		||||
	ApiUrl   string `json:"apiUrl"`
 | 
			
		||||
	ApiToken string `json:"apiToken"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AccessConfigForSSH struct {
 | 
			
		||||
	Host          string `json:"host"`
 | 
			
		||||
	Port          int32  `json:"port"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ const (
 | 
			
		|||
	AccessProviderTypePowerDNS     = AccessProviderType("powerdns")
 | 
			
		||||
	AccessProviderTypeQiniu        = AccessProviderType("qiniu")
 | 
			
		||||
	AccessProviderTypeRainYun      = AccessProviderType("rainyun")
 | 
			
		||||
	AccessProviderTypeSafeLine     = AccessProviderType("safeline") // 雷池(预留)
 | 
			
		||||
	AccessProviderTypeSafeLine     = AccessProviderType("safeline")
 | 
			
		||||
	AccessProviderTypeSSH          = AccessProviderType("ssh")
 | 
			
		||||
	AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud")
 | 
			
		||||
	AccessProviderTypeUCloud       = AccessProviderType("ucloud")
 | 
			
		||||
| 
						 | 
				
			
			@ -119,6 +119,7 @@ const (
 | 
			
		|||
	DeployProviderTypeLocal                 = DeployProviderType("local")
 | 
			
		||||
	DeployProviderTypeQiniuCDN              = DeployProviderType("qiniu-cdn")
 | 
			
		||||
	DeployProviderTypeQiniuPili             = DeployProviderType("qiniu-pili")
 | 
			
		||||
	DeployProviderTypeSafeLine              = DeployProviderType("safeline")
 | 
			
		||||
	DeployProviderTypeSSH                   = DeployProviderType("ssh")
 | 
			
		||||
	DeployProviderTypeTencentCloudCDN       = DeployProviderType("tencentcloud-cdn")
 | 
			
		||||
	DeployProviderTypeTencentCloudCLB       = DeployProviderType("tencentcloud-clb")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,10 +89,10 @@ func (d *HuaweiCloudWAFDeployer) Deploy(ctx context.Context, certPem string, pri
 | 
			
		|||
	upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Wrap(err, "failed to upload certificate file")
 | 
			
		||||
	} else {
 | 
			
		||||
		d.logger.Logt("certificate file uploaded", upres)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d.logger.Logt("certificate file uploaded", upres)
 | 
			
		||||
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case RESOURCE_TYPE_CERTIFICATE:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
package safeline
 | 
			
		||||
 | 
			
		||||
type ResourceType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 资源类型:替换指定证书。
 | 
			
		||||
	RESOURCE_TYPE_CERTIFICATE = ResourceType("certificate")
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
package safeline
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	xerrors "github.com/pkg/errors"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/deployer"
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/core/logger"
 | 
			
		||||
	safelinesdk "github.com/usual2970/certimate/internal/pkg/vendors/safeline-sdk"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SafeLineDeployerConfig struct {
 | 
			
		||||
	// 雷池 URL。
 | 
			
		||||
	ApiUrl string `json:"apiUrl"`
 | 
			
		||||
	// 雷池 API Token。
 | 
			
		||||
	ApiToken string `json:"apiToken"`
 | 
			
		||||
	// 部署资源类型。
 | 
			
		||||
	ResourceType ResourceType `json:"resourceType"`
 | 
			
		||||
	// 证书 ID。
 | 
			
		||||
	// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
 | 
			
		||||
	CertificateId int32 `json:"certificateId,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SafeLineDeployer struct {
 | 
			
		||||
	config    *SafeLineDeployerConfig
 | 
			
		||||
	logger    logger.Logger
 | 
			
		||||
	sdkClient *safelinesdk.SafeLineClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ deployer.Deployer = (*SafeLineDeployer)(nil)
 | 
			
		||||
 | 
			
		||||
func New(config *SafeLineDeployerConfig) (*SafeLineDeployer, error) {
 | 
			
		||||
	return NewWithLogger(config, logger.NewNilLogger())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWithLogger(config *SafeLineDeployerConfig, logger logger.Logger) (*SafeLineDeployer, error) {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
		return nil, errors.New("config is nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if logger == nil {
 | 
			
		||||
		return nil, errors.New("logger is nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := createSdkClient(config.ApiUrl, config.ApiToken)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Wrap(err, "failed to create sdk clients")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &SafeLineDeployer{
 | 
			
		||||
		logger:    logger,
 | 
			
		||||
		config:    config,
 | 
			
		||||
		sdkClient: client,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *SafeLineDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
 | 
			
		||||
	// 根据部署资源类型决定部署方式
 | 
			
		||||
	switch d.config.ResourceType {
 | 
			
		||||
	case RESOURCE_TYPE_CERTIFICATE:
 | 
			
		||||
		if err := d.deployToCertificate(ctx, certPem, privkeyPem); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, fmt.Errorf("unsupported resource type: %s", d.config.ResourceType)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &deployer.DeployResult{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *SafeLineDeployer) deployToCertificate(ctx context.Context, certPem string, privkeyPem string) error {
 | 
			
		||||
	if d.config.CertificateId == 0 {
 | 
			
		||||
		return errors.New("config `certificateId` is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 更新证书
 | 
			
		||||
	updateCertificateReq := &safelinesdk.UpdateCertificateRequest{
 | 
			
		||||
		Id:   d.config.CertificateId,
 | 
			
		||||
		Type: 2,
 | 
			
		||||
		Manual: &safelinesdk.UpdateCertificateRequestBodyManul{
 | 
			
		||||
			Crt: certPem,
 | 
			
		||||
			Key: privkeyPem,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	updateCertificateResp, err := d.sdkClient.UpdateCertificate(updateCertificateReq)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Wrap(err, "failed to execute sdk request 'safeline.UpdateCertificate'")
 | 
			
		||||
	} else {
 | 
			
		||||
		d.logger.Logt("已更新证书", updateCertificateResp)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createSdkClient(apiUrl, apiToken string) (*safelinesdk.SafeLineClient, error) {
 | 
			
		||||
	client := safelinesdk.NewSafeLineClient(apiUrl, apiToken)
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
package safeline_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/safeline"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	fInputCertPath string
 | 
			
		||||
	fInputKeyPath  string
 | 
			
		||||
	fApiUrl        string
 | 
			
		||||
	fApiToken      string
 | 
			
		||||
	fCertificateId int
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	argsPrefix := "CERTIMATE_DEPLOYER_SAFELINE_"
 | 
			
		||||
 | 
			
		||||
	flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
 | 
			
		||||
	flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
 | 
			
		||||
	flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "")
 | 
			
		||||
	flag.IntVar(&fCertificateId, argsPrefix+"CERTIFICATEID", 0, "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Shell command to run this test:
 | 
			
		||||
 | 
			
		||||
	go test -v ./safeline_test.go -args \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_SAFELINE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_SAFELINE_INPUTKEYPATH="/path/to/your-input-key.pem" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_SAFELINE_APIURL="your-safeline-url" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_SAFELINE_APITOKEN="your-safeline-api-token" \
 | 
			
		||||
	--CERTIMATE_DEPLOYER_SAFELINE_CERTIFICATEID="your-cerficiate-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("APIURL: %v", fApiUrl),
 | 
			
		||||
			fmt.Sprintf("APITOKEN: %v", fApiToken),
 | 
			
		||||
			fmt.Sprintf("CERTIFICATEID: %v", fCertificateId),
 | 
			
		||||
		}, "\n"))
 | 
			
		||||
 | 
			
		||||
		deployer, err := provider.New(&provider.SafeLineDeployerConfig{
 | 
			
		||||
			ApiUrl:        fApiUrl,
 | 
			
		||||
			ApiToken:      fApiToken,
 | 
			
		||||
			ResourceType:  provider.ResourceType("certificate"),
 | 
			
		||||
			CertificateId: fCertificateId,
 | 
			
		||||
		})
 | 
			
		||||
		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)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,8 +9,6 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-resty/resty/v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type BaoTaPanelClient struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -113,11 +111,7 @@ func (c *BaoTaPanelClient) sendRequestWithResult(path string, params map[string]
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonResp := make(map[string]any)
 | 
			
		||||
	if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil {
 | 
			
		||||
		return fmt.Errorf("baota: failed to parse response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := maps.Populate(jsonResp, &result); err != nil {
 | 
			
		||||
	if err := json.Unmarshal(resp.Body(), &result); err != nil {
 | 
			
		||||
		return fmt.Errorf("baota: failed to parse response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,19 @@ type BaseResponse interface {
 | 
			
		|||
	GetMsg() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type baseResponse struct {
 | 
			
		||||
	Code int    `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *baseResponse) GetCode() int {
 | 
			
		||||
	return r.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *baseResponse) GetMsg() string {
 | 
			
		||||
	return r.Msg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AddDomainResolutionRequest struct {
 | 
			
		||||
	ZoneName    string `json:"ym"`
 | 
			
		||||
	RecordType  string `json:"lx"`
 | 
			
		||||
| 
						 | 
				
			
			@ -15,17 +28,8 @@ type AddDomainResolutionRequest struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type AddDomainResolutionResponse struct {
 | 
			
		||||
	Code int    `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
	Data int    `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AddDomainResolutionResponse) GetCode() int {
 | 
			
		||||
	return r.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AddDomainResolutionResponse) GetMsg() string {
 | 
			
		||||
	return r.Msg
 | 
			
		||||
	baseResponse
 | 
			
		||||
	Data int `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ModifyDomainResolutionRequest struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -39,16 +43,7 @@ type ModifyDomainResolutionRequest struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type ModifyDomainResolutionResponse struct {
 | 
			
		||||
	Code int    `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ModifyDomainResolutionResponse) GetCode() int {
 | 
			
		||||
	return r.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ModifyDomainResolutionResponse) GetMsg() string {
 | 
			
		||||
	return r.Msg
 | 
			
		||||
	baseResponse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DeleteDomainResolutionRequest struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -57,16 +52,7 @@ type DeleteDomainResolutionRequest struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type DeleteDomainResolutionResponse struct {
 | 
			
		||||
	Code int    `json:"code"`
 | 
			
		||||
	Msg  string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DeleteDomainResolutionResponse) GetCode() int {
 | 
			
		||||
	return r.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *DeleteDomainResolutionResponse) GetMsg() string {
 | 
			
		||||
	return r.Msg
 | 
			
		||||
	baseResponse
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ListDomainResolutionRequest struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -76,8 +62,7 @@ type ListDomainResolutionRequest struct {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type ListDomainResolutionResponse struct {
 | 
			
		||||
	Code     int                 `json:"code"`
 | 
			
		||||
	Msg      string              `json:"msg"`
 | 
			
		||||
	baseResponse
 | 
			
		||||
	Count    int                 `json:"count"`
 | 
			
		||||
	Data     []*ResolutionRecord `json:"data"`
 | 
			
		||||
	Page     int                 `json:"page"`
 | 
			
		||||
| 
						 | 
				
			
			@ -92,11 +77,3 @@ type ResolutionRecord struct {
 | 
			
		|||
	RecordValue string `json:"jxz"`
 | 
			
		||||
	MX          int    `json:"mx"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ListDomainResolutionResponse) GetCode() int {
 | 
			
		||||
	return r.Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *ListDomainResolutionResponse) GetMsg() string {
 | 
			
		||||
	return r.Msg
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,6 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-resty/resty/v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/usual2970/certimate/internal/pkg/utils/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type GnameClient struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -131,9 +129,7 @@ func (c *GnameClient) sendRequest(path string, params map[string]any) (*resty.Re
 | 
			
		|||
	resp, err := req.Post(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("gname: failed to send request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if resp.IsError() {
 | 
			
		||||
	} else if resp.IsError() {
 | 
			
		||||
		return nil, fmt.Errorf("gname: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -146,11 +142,7 @@ func (c *GnameClient) sendRequestWithResult(path string, params map[string]any,
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jsonResp := make(map[string]any)
 | 
			
		||||
	if err := json.Unmarshal(resp.Body(), &jsonResp); err != nil {
 | 
			
		||||
		return fmt.Errorf("gname: failed to parse response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := maps.Populate(jsonResp, &result); err != nil {
 | 
			
		||||
	if err := json.Unmarshal(resp.Body(), &result); err != nil {
 | 
			
		||||
		return fmt.Errorf("gname: failed to parse response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
package safelinesdk
 | 
			
		||||
 | 
			
		||||
type BaseResponse interface {
 | 
			
		||||
	GetErrCode() *string
 | 
			
		||||
	GetErrMsg() *string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type baseResponse struct {
 | 
			
		||||
	ErrCode *string `json:"err,omitempty"`
 | 
			
		||||
	ErrMsg  *string `json:"msg,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *baseResponse) GetErrCode() *string {
 | 
			
		||||
	return r.ErrCode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *baseResponse) GetErrMsg() *string {
 | 
			
		||||
	return r.ErrMsg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateCertificateRequest struct {
 | 
			
		||||
	Id     int32                              `json:"id"`
 | 
			
		||||
	Type   int32                              `json:"type"`
 | 
			
		||||
	Manual *UpdateCertificateRequestBodyManul `json:"manual"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateCertificateRequestBodyManul struct {
 | 
			
		||||
	Crt string `json:"crt"`
 | 
			
		||||
	Key string `json:"key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UpdateCertificateResponse struct {
 | 
			
		||||
	baseResponse
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
package safelinesdk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-resty/resty/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SafeLineClient struct {
 | 
			
		||||
	apiHost  string
 | 
			
		||||
	apiToken string
 | 
			
		||||
	client   *resty.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSafeLineClient(apiHost, apiToken string) *SafeLineClient {
 | 
			
		||||
	client := resty.New()
 | 
			
		||||
 | 
			
		||||
	return &SafeLineClient{
 | 
			
		||||
		apiHost:  apiHost,
 | 
			
		||||
		apiToken: apiToken,
 | 
			
		||||
		client:   client,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *SafeLineClient) WithTimeout(timeout time.Duration) *SafeLineClient {
 | 
			
		||||
	c.client.SetTimeout(timeout)
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *SafeLineClient) UpdateCertificate(req *UpdateCertificateRequest) (*UpdateCertificateResponse, error) {
 | 
			
		||||
	params := make(map[string]any)
 | 
			
		||||
	jsonData, _ := json.Marshal(req)
 | 
			
		||||
	json.Unmarshal(jsonData, ¶ms)
 | 
			
		||||
 | 
			
		||||
	result := UpdateCertificateResponse{}
 | 
			
		||||
	err := c.sendRequestWithResult("/api/open/cert", params, &result)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *SafeLineClient) sendRequest(path string, params map[string]any) (*resty.Response, error) {
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		params = make(map[string]any)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := strings.TrimRight(c.apiHost, "/") + path
 | 
			
		||||
	req := c.client.R().
 | 
			
		||||
		SetHeader("Content-Type", "application/json").
 | 
			
		||||
		SetHeader("X-SLCE-API-TOKEN", c.apiToken).
 | 
			
		||||
		SetBody(params)
 | 
			
		||||
	resp, err := req.Post(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("safeline: failed to send request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if resp.IsError() {
 | 
			
		||||
		return nil, fmt.Errorf("safeline: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return resp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *SafeLineClient) sendRequestWithResult(path string, params map[string]any, result BaseResponse) error {
 | 
			
		||||
	resp, err := c.sendRequest(path, params)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := json.Unmarshal(resp.Body(), &result); err != nil {
 | 
			
		||||
		return fmt.Errorf("safeline: failed to parse response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if result.GetErrCode() != nil && *result.GetErrCode() != "" {
 | 
			
		||||
		if result.GetErrMsg() == nil {
 | 
			
		||||
			return fmt.Errorf("safeline api error: %s", *result.GetErrCode())
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf("safeline api error: %s, %s", *result.GetErrCode(), *result.GetErrMsg())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M461.750857 72.850286l-325.193143 282.331428L378.148571 580.096h125.659429L266.24 358.4 512 153.526857l289.645714 246.418286v69.339428l86.162286 56.685715V357.888L562.761143 74.532571 466.358857 72.850286z" fill="#45495A"></path><path d="M542.646857 446.317714h-22.820571l239.177143 222.354286-243.565715 209.334857-295.862857-246.710857V553.398857l-83.382857-53.394286v163.108572l336.457143 288.036571h90.331428l320.804572-282.404571-242.102857-222.427429z" fill="#45495A"></path></svg>
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ import AccessFormNS1Config from "./AccessFormNS1Config";
 | 
			
		|||
import AccessFormPowerDNSConfig from "./AccessFormPowerDNSConfig";
 | 
			
		||||
import AccessFormQiniuConfig from "./AccessFormQiniuConfig";
 | 
			
		||||
import AccessFormRainYunConfig from "./AccessFormRainYunConfig";
 | 
			
		||||
import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig";
 | 
			
		||||
import AccessFormSSHConfig from "./AccessFormSSHConfig";
 | 
			
		||||
import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig";
 | 
			
		||||
import AccessFormUCloudConfig from "./AccessFormUCloudConfig";
 | 
			
		||||
| 
						 | 
				
			
			@ -134,6 +135,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
 | 
			
		|||
        return <AccessFormQiniuConfig {...nestedFormProps} />;
 | 
			
		||||
      case ACCESS_PROVIDERS.RAINYUN:
 | 
			
		||||
        return <AccessFormRainYunConfig {...nestedFormProps} />;
 | 
			
		||||
      case ACCESS_PROVIDERS.SAFELINE:
 | 
			
		||||
        return <AccessFormSafeLineConfig {...nestedFormProps} />;
 | 
			
		||||
      case ACCESS_PROVIDERS.SSH:
 | 
			
		||||
        return <AccessFormSSHConfig {...nestedFormProps} />;
 | 
			
		||||
      case ACCESS_PROVIDERS.TENCENTCLOUD:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ export type AccessFormBaotaPanelConfigProps = {
 | 
			
		|||
 | 
			
		||||
const initFormModel = (): AccessFormBaotaPanelConfigFieldValues => {
 | 
			
		||||
  return {
 | 
			
		||||
    apiUrl: "",
 | 
			
		||||
    apiUrl: "http://<your-ipaddr>:8888/",
 | 
			
		||||
    apiKey: "",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ export type AccessFormPowerDNSConfigProps = {
 | 
			
		|||
 | 
			
		||||
const initFormModel = (): AccessFormPowerDNSConfigFieldValues => {
 | 
			
		||||
  return {
 | 
			
		||||
    apiUrl: "",
 | 
			
		||||
    apiUrl: "http://<your-ipaddr>:8082/",
 | 
			
		||||
    apiKey: "",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Form, type FormInstance, Input } from "antd";
 | 
			
		||||
import { createSchemaFieldRule } from "antd-zod";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import { type AccessConfigForSafeLine } from "@/domain/access";
 | 
			
		||||
 | 
			
		||||
type AccessFormSafeLineConfigFieldValues = Nullish<AccessConfigForSafeLine>;
 | 
			
		||||
 | 
			
		||||
export type AccessFormSafeLineConfigProps = {
 | 
			
		||||
  form: FormInstance;
 | 
			
		||||
  formName: string;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  initialValues?: AccessFormSafeLineConfigFieldValues;
 | 
			
		||||
  onValuesChange?: (values: AccessFormSafeLineConfigFieldValues) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const initFormModel = (): AccessFormSafeLineConfigFieldValues => {
 | 
			
		||||
  return {
 | 
			
		||||
    apiUrl: "http://<your-ipaddr>:9443/",
 | 
			
		||||
    apiToken: "",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const AccessFormSafeLineConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormSafeLineConfigProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    apiUrl: z.string().url(t("common.errmsg.url_invalid")),
 | 
			
		||||
    apiToken: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(1, t("access.form.safeline_api_token.placeholder"))
 | 
			
		||||
      .max(64, t("common.errmsg.string_max", { max: 64 }))
 | 
			
		||||
      .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="apiUrl"
 | 
			
		||||
        label={t("access.form.safeline_api_url.label")}
 | 
			
		||||
        rules={[formRule]}
 | 
			
		||||
        tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.safeline_api_url.tooltip") }}></span>}
 | 
			
		||||
      >
 | 
			
		||||
        <Input placeholder={t("access.form.safeline_api_url.placeholder")} />
 | 
			
		||||
      </Form.Item>
 | 
			
		||||
 | 
			
		||||
      <Form.Item
 | 
			
		||||
        name="apiToken"
 | 
			
		||||
        label={t("access.form.safeline_api_token.label")}
 | 
			
		||||
        rules={[formRule]}
 | 
			
		||||
        tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.safeline_api_token.tooltip") }}></span>}
 | 
			
		||||
      >
 | 
			
		||||
        <Input.Password autoComplete="new-password" placeholder={t("access.form.safeline_api_token.placeholder")} />
 | 
			
		||||
      </Form.Item>
 | 
			
		||||
    </Form>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default AccessFormSafeLineConfig;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +47,18 @@ const AccessProviderSelect = (props: AccessProviderSelectProps) => {
 | 
			
		|||
  return (
 | 
			
		||||
    <Select
 | 
			
		||||
      {...props}
 | 
			
		||||
      filterOption={(inputValue, option) => {
 | 
			
		||||
        if (!option) return false;
 | 
			
		||||
 | 
			
		||||
        const value = inputValue.toLowerCase();
 | 
			
		||||
        return option.value.toLowerCase().includes(value) || option.label.toLowerCase().includes(value);
 | 
			
		||||
      }}
 | 
			
		||||
      labelRender={({ label, value }) => {
 | 
			
		||||
        if (label) {
 | 
			
		||||
          return renderOption(value as string);
 | 
			
		||||
        if (!label) {
 | 
			
		||||
          return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        return renderOption(value as string);
 | 
			
		||||
      }}
 | 
			
		||||
      options={options}
 | 
			
		||||
      optionFilterProp={undefined}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,12 +33,18 @@ const ApplyDNSProviderSelect = (props: ApplyDNSProviderSelectProps) => {
 | 
			
		|||
  return (
 | 
			
		||||
    <Select
 | 
			
		||||
      {...props}
 | 
			
		||||
      filterOption={(inputValue, option) => {
 | 
			
		||||
        if (!option) return false;
 | 
			
		||||
 | 
			
		||||
        const value = inputValue.toLowerCase();
 | 
			
		||||
        return option.value.toLowerCase().includes(value) || option.label.toLowerCase().includes(value);
 | 
			
		||||
      }}
 | 
			
		||||
      labelRender={({ label, value }) => {
 | 
			
		||||
        if (label) {
 | 
			
		||||
          return renderOption(value as string);
 | 
			
		||||
        if (!label) {
 | 
			
		||||
          return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        return renderOption(value as string);
 | 
			
		||||
      }}
 | 
			
		||||
      options={options}
 | 
			
		||||
      optionFilterProp={undefined}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,12 +33,18 @@ const DeployProviderSelect = (props: DeployProviderSelectProps) => {
 | 
			
		|||
  return (
 | 
			
		||||
    <Select
 | 
			
		||||
      {...props}
 | 
			
		||||
      filterOption={(inputValue, option) => {
 | 
			
		||||
        if (!option) return false;
 | 
			
		||||
 | 
			
		||||
        const value = inputValue.toLowerCase();
 | 
			
		||||
        return option.value.toLowerCase().includes(value) || option.label.toLowerCase().includes(value);
 | 
			
		||||
      }}
 | 
			
		||||
      labelRender={({ label, value }) => {
 | 
			
		||||
        if (label) {
 | 
			
		||||
          return renderOption(value as string);
 | 
			
		||||
        if (!label) {
 | 
			
		||||
          return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return <Typography.Text type="secondary">{props.placeholder}</Typography.Text>;
 | 
			
		||||
        return renderOption(value as string);
 | 
			
		||||
      }}
 | 
			
		||||
      options={options}
 | 
			
		||||
      optionFilterProp={undefined}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ import DeployNodeConfigFormKubernetesSecretConfig from "./DeployNodeConfigFormKu
 | 
			
		|||
import DeployNodeConfigFormLocalConfig from "./DeployNodeConfigFormLocalConfig";
 | 
			
		||||
import DeployNodeConfigFormQiniuCDNConfig from "./DeployNodeConfigFormQiniuCDNConfig";
 | 
			
		||||
import DeployNodeConfigFormQiniuPiliConfig from "./DeployNodeConfigFormQiniuPiliConfig";
 | 
			
		||||
import DeployNodeConfigFormSafeLineConfig from "./DeployNodeConfigFormSafeLineConfig";
 | 
			
		||||
import DeployNodeConfigFormSSHConfig from "./DeployNodeConfigFormSSHConfig.tsx";
 | 
			
		||||
import DeployNodeConfigFormTencentCloudCDNConfig from "./DeployNodeConfigFormTencentCloudCDNConfig.tsx";
 | 
			
		||||
import DeployNodeConfigFormTencentCloudCLBConfig from "./DeployNodeConfigFormTencentCloudCLBConfig.tsx";
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +105,7 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
 | 
			
		|||
        .nonempty(t("workflow_node.deploy.form.provider_access.placeholder"))
 | 
			
		||||
        .refine(() => !!formInst.getFieldValue("provider"), t("workflow_node.deploy.form.provider.placeholder")),
 | 
			
		||||
      providerConfig: z.any(),
 | 
			
		||||
      skipOnLastSucceeded: z.boolean(),
 | 
			
		||||
      skipOnLastSucceeded: z.boolean().nullish(),
 | 
			
		||||
    });
 | 
			
		||||
    const formRule = createSchemaFieldRule(formSchema);
 | 
			
		||||
    const { form: formInst, formProps } = useAntdForm({
 | 
			
		||||
| 
						 | 
				
			
			@ -177,6 +178,8 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
 | 
			
		|||
          return <DeployNodeConfigFormQiniuCDNConfig {...nestedFormProps} />;
 | 
			
		||||
        case DEPLOY_PROVIDERS.QINIU_PILI:
 | 
			
		||||
          return <DeployNodeConfigFormQiniuPiliConfig {...nestedFormProps} />;
 | 
			
		||||
        case DEPLOY_PROVIDERS.SAFELINE:
 | 
			
		||||
          return <DeployNodeConfigFormSafeLineConfig {...nestedFormProps} />;
 | 
			
		||||
        case DEPLOY_PROVIDERS.SSH:
 | 
			
		||||
          return <DeployNodeConfigFormSSHConfig {...nestedFormProps} />;
 | 
			
		||||
        case DEPLOY_PROVIDERS.TENCENTCLOUD_CDN:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Form, type FormInstance, Input, Select } from "antd";
 | 
			
		||||
import { createSchemaFieldRule } from "antd-zod";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
 | 
			
		||||
import Show from "@/components/Show";
 | 
			
		||||
 | 
			
		||||
type DeployNodeConfigFormSafeLineConfigFieldValues = Nullish<{
 | 
			
		||||
  resourceType: string;
 | 
			
		||||
  certificateId?: string;
 | 
			
		||||
}>;
 | 
			
		||||
 | 
			
		||||
export type DeployNodeConfigFormSafeLineConfigProps = {
 | 
			
		||||
  form: FormInstance;
 | 
			
		||||
  formName: string;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  initialValues?: DeployNodeConfigFormSafeLineConfigFieldValues;
 | 
			
		||||
  onValuesChange?: (values: DeployNodeConfigFormSafeLineConfigFieldValues) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
 | 
			
		||||
 | 
			
		||||
const initFormModel = (): DeployNodeConfigFormSafeLineConfigFieldValues => {
 | 
			
		||||
  return {
 | 
			
		||||
    resourceType: RESOURCE_TYPE_CERTIFICATE,
 | 
			
		||||
    certificateId: "",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DeployNodeConfigFormSafeLineConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormSafeLineConfigProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
  const formSchema = z.object({
 | 
			
		||||
    resourceType: z.literal(RESOURCE_TYPE_CERTIFICATE, {
 | 
			
		||||
      message: t("workflow_node.deploy.form.safeline_resource_type.placeholder"),
 | 
			
		||||
    }),
 | 
			
		||||
    region: z
 | 
			
		||||
      .string({ message: t("workflow_node.deploy.form.safeline_region.placeholder") })
 | 
			
		||||
      .nonempty(t("workflow_node.deploy.form.safeline_region.placeholder"))
 | 
			
		||||
      .trim(),
 | 
			
		||||
    certificateId: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .max(64, t("common.errmsg.string_max", { max: 64 }))
 | 
			
		||||
      .trim()
 | 
			
		||||
      .nullish()
 | 
			
		||||
      .refine((v) => {
 | 
			
		||||
        if (fieldResourceType !== RESOURCE_TYPE_CERTIFICATE) return true;
 | 
			
		||||
        return /^\d+$/.test(v!);
 | 
			
		||||
      }, t("workflow_node.deploy.form.safeline_certificate_id.placeholder")),
 | 
			
		||||
  });
 | 
			
		||||
  const formRule = createSchemaFieldRule(formSchema);
 | 
			
		||||
 | 
			
		||||
  const fieldResourceType = Form.useWatch("resourceType", formInst);
 | 
			
		||||
 | 
			
		||||
  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="resourceType" label={t("workflow_node.deploy.form.safeline_resource_type.label")} rules={[formRule]}>
 | 
			
		||||
        <Select placeholder={t("workflow_node.deploy.form.safeline_resource_type.placeholder")}>
 | 
			
		||||
          <Select.Option key={RESOURCE_TYPE_CERTIFICATE} value={RESOURCE_TYPE_CERTIFICATE}>
 | 
			
		||||
            {t("workflow_node.deploy.form.safeline_resource_type.option.certificate.label")}
 | 
			
		||||
          </Select.Option>
 | 
			
		||||
        </Select>
 | 
			
		||||
      </Form.Item>
 | 
			
		||||
 | 
			
		||||
      <Show when={fieldResourceType === RESOURCE_TYPE_CERTIFICATE}>
 | 
			
		||||
        <Form.Item name="certificateId" label={t("workflow_node.deploy.form.safeline_certificate_id.label")} rules={[formRule]}>
 | 
			
		||||
          <Input placeholder={t("workflow_node.deploy.form.safeline_certificate_id.placeholder")} />
 | 
			
		||||
        </Form.Item>
 | 
			
		||||
      </Show>
 | 
			
		||||
    </Form>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DeployNodeConfigFormSafeLineConfig;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ export interface AccessModel extends BaseModel {
 | 
			
		|||
      | AccessConfigForPowerDNS
 | 
			
		||||
      | AccessConfigForQiniu
 | 
			
		||||
      | AccessConfigForRainYun
 | 
			
		||||
      | AccessConfigForSafeLine
 | 
			
		||||
      | AccessConfigForSSH
 | 
			
		||||
      | AccessConfigForTencentCloud
 | 
			
		||||
      | AccessConfigForUCloud
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +144,11 @@ export type AccessConfigForRainYun = {
 | 
			
		|||
  apiKey: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AccessConfigForSafeLine = {
 | 
			
		||||
  apiUrl: string;
 | 
			
		||||
  apiToken: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AccessConfigForSSH = {
 | 
			
		||||
  host: string;
 | 
			
		||||
  port: number;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
 | 
			
		|||
  POWERDNS: "powerdns",
 | 
			
		||||
  QINIU: "qiniu",
 | 
			
		||||
  RAINYUN: "rainyun",
 | 
			
		||||
  SAFELINE: "safeline",
 | 
			
		||||
  SSH: "ssh",
 | 
			
		||||
  TENCENTCLOUD: "tencentcloud",
 | 
			
		||||
  UCLOUD: "ucloud",
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +71,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
 | 
			
		|||
    [ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.UCLOUD, "provider.ucloud", "/imgs/providers/ucloud.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.SAFELINE, "provider.safeline", "/imgs/providers/safeline.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.EDGIO, "provider.edgio", "/imgs/providers/edgio.svg", [ACCESS_USAGES.DEPLOY]],
 | 
			
		||||
    [ACCESS_PROVIDERS.AZURE, "provider.azure", "/imgs/providers/azure.svg", [ACCESS_USAGES.APPLY]],
 | 
			
		||||
| 
						 | 
				
			
			@ -200,6 +202,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({
 | 
			
		|||
  LOCAL: `${ACCESS_PROVIDERS.LOCAL}`,
 | 
			
		||||
  QINIU_CDN: `${ACCESS_PROVIDERS.QINIU}-cdn`,
 | 
			
		||||
  QINIU_PILI: `${ACCESS_PROVIDERS.QINIU}-pili`,
 | 
			
		||||
  SAFELINE: `${ACCESS_PROVIDERS.SAFELINE}`,
 | 
			
		||||
  SSH: `${ACCESS_PROVIDERS.SSH}`,
 | 
			
		||||
  TENCENTCLOUD_CDN: `${ACCESS_PROVIDERS.TENCENTCLOUD}-cdn`,
 | 
			
		||||
  TENCENTCLOUD_CLB: `${ACCESS_PROVIDERS.TENCENTCLOUD}-clb`,
 | 
			
		||||
| 
						 | 
				
			
			@ -291,6 +294,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
 | 
			
		|||
    [DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER],
 | 
			
		||||
    [DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE],
 | 
			
		||||
    [DEPLOY_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOY_CATEGORIES.WEBSITE],
 | 
			
		||||
    [DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL],
 | 
			
		||||
  ].map(([type, name, category]) => [
 | 
			
		||||
    type,
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -147,6 +147,12 @@
 | 
			
		|||
  "access.form.rainyun_api_key.label": "Rain Yun API key",
 | 
			
		||||
  "access.form.rainyun_api_key.placeholder": "Please enter Rain Yun API key",
 | 
			
		||||
  "access.form.rainyun_api_key.tooltip": "For more information, see <a href=\"https://www.rainyun.com/docs/account/racc/setting#api%E5%AF%86%E9%92%A5\" target=\"_blank\">https://www.rainyun.com/docs/account/racc/setting</a>",
 | 
			
		||||
  "access.form.safeline_api_url.label": "SafeLine URL",
 | 
			
		||||
  "access.form.safeline_api_url.placeholder": "Please enter SafeLine URL",
 | 
			
		||||
  "access.form.safeline_api_url.tooltip": "For more information, see <a href=\"https://docs.waf.chaitin.com/en/tutorials/install#use-web-ui\" target=\"_blank\">https://docs.waf.chaitin.com/en/tutorials/install</a>",
 | 
			
		||||
  "access.form.safeline_api_token.label": "SafeLine API token",
 | 
			
		||||
  "access.form.safeline_api_token.placeholder": "Please enter SafeLine API token",
 | 
			
		||||
  "access.form.safeline_api_token.tooltip": "For more information, see <a href=\"https://docs.waf.chaitin.com/en/reference/articles/openapi\" target=\"_blank\">https://docs.waf.chaitin.com/en/reference/articles/openapi</a>",
 | 
			
		||||
  "access.form.ssh_host.label": "Server host",
 | 
			
		||||
  "access.form.ssh_host.placeholder": "Please enter server host",
 | 
			
		||||
  "access.form.ssh_port.label": "Server port",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -300,6 +300,11 @@
 | 
			
		|||
  "workflow_node.deploy.form.qiniu_pili_domain.label": "Qiniu Pili streaming domain",
 | 
			
		||||
  "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "Please enter Qiniu Pili streaming domain name",
 | 
			
		||||
  "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "For more information, see <a href=\"hhttps://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.label": "Resource type",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.placeholder": "Please select resource type",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "Certificate",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_certificate_id.label": "SafeLine certificate ID",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_certificate_id.placeholder": "Please enter SafeLine certificate ID",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.label": "File format",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.placeholder": "Please select file format",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM (*.pem, *.crt, *.key)",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -147,6 +147,12 @@
 | 
			
		|||
  "access.form.rainyun_api_key.label": "雨云 API 密钥",
 | 
			
		||||
  "access.form.rainyun_api_key.placeholder": "请输入雨云 API 密钥",
 | 
			
		||||
  "access.form.rainyun_api_key.tooltip": "这是什么?请参阅 <a href=\"https://www.rainyun.com/docs/account/racc/setting#api%E5%AF%86%E9%92%A5\" target=\"_blank\">https://www.rainyun.com/docs/account/racc/setting</a>",
 | 
			
		||||
  "access.form.safeline_api_url.label": "雷池 URL",
 | 
			
		||||
  "access.form.safeline_api_url.placeholder": "请输入雷池 URL",
 | 
			
		||||
  "access.form.safeline_api_url.tooltip": "这是什么?请参阅 <a href=\"https://docs.waf-ce.chaitin.cn/zh/%E4%B8%8A%E6%89%8B%E6%8C%87%E5%8D%97/%E5%AE%89%E8%A3%85%E9%9B%B7%E6%B1%A0#%E8%AE%BF%E9%97%AE%E9%9B%B7%E6%B1%A0%E6%8E%A7%E5%88%B6%E5%8F%B0\" target=\"_blank\">https://docs.waf-ce.chaitin.cn/zh/上手指南/安装雷池</a>",
 | 
			
		||||
  "access.form.safeline_api_token.label": "雷池 API Token",
 | 
			
		||||
  "access.form.safeline_api_token.placeholder": "请输入雷池 API Token",
 | 
			
		||||
  "access.form.safeline_api_token.tooltip": "这是什么?请参阅 <a href=\"https://docs.waf-ce.chaitin.cn/zh/%E6%9B%B4%E5%A4%9A%E6%8A%80%E6%9C%AF%E6%96%87%E6%A1%A3/OPENAPI\" target=\"_blank\">https://docs.waf-ce.chaitin.cn/zh/更多技术文档/OPENAPI</a>",
 | 
			
		||||
  "access.form.ssh_host.label": "服务器地址",
 | 
			
		||||
  "access.form.ssh_host.placeholder": "请输入服务器地址",
 | 
			
		||||
  "access.form.ssh_port.label": "服务器端口",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -300,6 +300,11 @@
 | 
			
		|||
  "workflow_node.deploy.form.qiniu_pili_domain.label": "七牛云视频直播流域名",
 | 
			
		||||
  "workflow_node.deploy.form.qiniu_pili_domain.placeholder": "请输入七牛云视频直播流域名",
 | 
			
		||||
  "workflow_node.deploy.form.qiniu_pili_domain.tooltip": "这是什么?请参阅 <a href=\"hhttps://portal.qiniu.com/hub\" target=\"_blank\">https://portal.qiniu.com/hub</a>",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.label": "证书替换方式",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.placeholder": "请选择证书替换方式",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_resource_type.option.certificate.label": "替换指定证书",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_certificate_id.label": "雷池证书 ID",
 | 
			
		||||
  "workflow_node.deploy.form.safeline_certificate_id.placeholder": "请输入雷池证书 ID",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.label": "文件格式",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.placeholder": "请选择文件格式",
 | 
			
		||||
  "workflow_node.deploy.form.ssh_format.option.pem.label": "PEM 格式(*.pem, *.crt, *.key)",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue