feat: add 1panel deployer

This commit is contained in:
Fu Diwei 2025-03-07 15:43:40 +08:00
parent 6ccbdeb89a
commit 29dda4ec66
32 changed files with 1098 additions and 10 deletions

View File

@ -6,6 +6,8 @@ import (
"github.com/usual2970/certimate/internal/domain"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
p1PanelConsole "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console"
p1PanelSite "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site"
pAliyunALB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-alb"
pAliyunCASDeploy "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cas-deploy"
pAliyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-cdn"
@ -69,6 +71,35 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) {
NOTICE: If you add new constant, please keep ASCII order.
*/
switch options.Provider {
case domain.DeployProviderType1PanelConsole, domain.DeployProviderType1PanelSite:
{
access := domain.AccessConfigFor1Panel{}
if err := maps.Populate(options.ProviderAccessConfig, &access); err != nil {
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
}
switch options.Provider {
case domain.DeployProviderType1PanelConsole:
deployer, err := p1PanelConsole.NewDeployer(&p1PanelConsole.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
AutoRestart: maps.GetValueAsBool(options.ProviderDeployConfig, "autoRestart"),
})
return deployer, err
case domain.DeployProviderType1PanelSite:
deployer, err := p1PanelSite.NewDeployer(&p1PanelSite.DeployerConfig{
ApiUrl: access.ApiUrl,
ApiKey: access.ApiKey,
WebsiteId: maps.GetValueAsInt64(options.ProviderDeployConfig, "websiteId"),
})
return deployer, err
default:
break
}
}
case domain.DeployProviderTypeAliyunALB, domain.DeployProviderTypeAliyunCASDeploy, domain.DeployProviderTypeAliyunCDN, domain.DeployProviderTypeAliyunCLB, domain.DeployProviderTypeAliyunDCDN, domain.DeployProviderTypeAliyunESA, domain.DeployProviderTypeAliyunLive, domain.DeployProviderTypeAliyunNLB, domain.DeployProviderTypeAliyunOSS, domain.DeployProviderTypeAliyunVOD, domain.DeployProviderTypeAliyunWAF:
{
access := domain.AccessConfigForAliyun{}

View File

@ -24,6 +24,11 @@ func (a *Access) UnmarshalConfigToMap() (map[string]any, error) {
return config, nil
}
type AccessConfigFor1Panel struct {
ApiUrl string `json:"apiUrl"`
ApiKey string `json:"apiKey"`
}
type AccessConfigForACMEHttpReq struct {
Endpoint string `json:"endpoint"`
Mode string `json:"mode,omitempty"`

View File

@ -9,7 +9,7 @@ type AccessProviderType string
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
AccessProviderType1Panel = AccessProviderType("1panel") // 1Panel预留
AccessProviderType1Panel = AccessProviderType("1panel")
AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq")
AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai预留
AccessProviderTypeAliyun = AccessProviderType("aliyun")
@ -108,6 +108,8 @@ type DeployProviderType string
NOTICE: If you add new constant, please keep ASCII order.
*/
const (
DeployProviderType1PanelConsole = DeployProviderType("1panel-console")
DeployProviderType1PanelSite = DeployProviderType("1panel-site")
DeployProviderTypeAliyunALB = DeployProviderType("aliyun-alb")
DeployProviderTypeAliyunCASDeploy = DeployProviderType("aliyun-casdeploy")
DeployProviderTypeAliyunCDN = DeployProviderType("aliyun-cdn")

View File

@ -0,0 +1,88 @@
package onepanelconsole
import (
"context"
"errors"
"net/url"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 是否自动重启。
AutoRestart bool `json:"autoRestart"`
}
type DeployerProvider struct {
config *DeployerConfig
logger logger.Logger
sdkClient *opsdk.Client
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &DeployerProvider{
config: config,
logger: logger.NewNilLogger(),
sdkClient: client,
}, nil
}
func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider {
d.logger = logger
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
// 设置面板 SSL 证书
updateSystemSSLReq := &opsdk.UpdateSystemSSLRequest{
Cert: certPem,
Key: privkeyPem,
SSL: "enable",
SSLType: "import-paste",
}
if d.config.AutoRestart {
updateSystemSSLReq.AutoRestart = "true"
} else {
updateSystemSSLReq.AutoRestart = "false"
}
updateSystemSSLResp, err := d.sdkClient.UpdateSystemSSL(updateSystemSSLReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateSystemSSL'")
} else {
d.logger.Logt("已设置面板 SSL 证书", updateSystemSSLResp)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
return client, nil
}

View File

@ -0,0 +1,71 @@
package onepanelconsole_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-console"
)
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fApiKey string
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./1panel_console_test.go -args \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key"
*/
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("APIKEY: %v", fApiKey),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ApiKey: fApiKey,
AutoRestart: true,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@ -0,0 +1,120 @@
package onepanelsite
import (
"context"
"errors"
"net/url"
"strconv"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/logger"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl"
opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk"
)
type DeployerConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
// 网站 ID。
WebsiteId int64 `json:"websiteId"`
}
type DeployerProvider struct {
config *DeployerConfig
logger logger.Logger
sdkClient *opsdk.Client
sslUploader uploader.Uploader
}
var _ deployer.Deployer = (*DeployerProvider)(nil)
func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
ApiUrl: config.ApiUrl,
ApiKey: config.ApiKey,
})
if err != nil {
return nil, xerrors.Wrap(err, "failed to create ssl uploader")
}
return &DeployerProvider{
config: config,
logger: logger.NewNilLogger(),
sdkClient: client,
sslUploader: uploader,
}, nil
}
func (d *DeployerProvider) WithLogger(logger logger.Logger) *DeployerProvider {
d.logger = logger
return d
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) {
// 获取网站 HTTPS 配置
getHttpsConfReq := &opsdk.GetHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
}
getHttpsConfResp, err := d.sdkClient.GetHttpsConf(getHttpsConfReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.GetHttpsConf'")
} else {
d.logger.Logt("已获取网站 HTTPS 配置", getHttpsConfResp)
}
// 上传证书到面板
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)
}
// 修改网站 HTTPS 配置
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
updateHttpsConfReq := &opsdk.UpdateHttpsConfRequest{
WebsiteID: d.config.WebsiteId,
Type: "existed",
WebsiteSSLID: certId,
Enable: getHttpsConfResp.Data.Enable,
HttpConfig: getHttpsConfResp.Data.HttpConfig,
SSLProtocol: getHttpsConfResp.Data.SSLProtocol,
Algorithm: getHttpsConfResp.Data.Algorithm,
Hsts: getHttpsConfResp.Data.Hsts,
}
updateHttpsConfResp, err := d.sdkClient.UpdateHttpsConf(updateHttpsConfReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UpdateHttpsConf'")
} else {
d.logger.Logt("已获取网站 HTTPS 配置", updateHttpsConfResp)
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
return client, nil
}

View File

@ -0,0 +1,75 @@
package onepanelsite_test
import (
"context"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/1panel-site"
)
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fApiKey string
fWebsiteId int64
)
func init() {
argsPrefix := "CERTIMATE_DEPLOYER_1PANELCONSOLE_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
flag.Int64Var(&fWebsiteId, argsPrefix+"WEBSITEID", 0, "")
}
/*
Shell command to run this test:
go test -v ./1panel_console_test.go -args \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_APIKEY="your-api-key" \
--CERTIMATE_DEPLOYER_1PANELCONSOLE_WEBSITEID="your-website-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("APIKEY: %v", fApiKey),
fmt.Sprintf("WEBSITEID: %v", fWebsiteId),
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ApiKey: fApiKey,
WebsiteId: fWebsiteId,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := deployer.Deploy(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
t.Logf("ok: %v", res)
})
}

View File

@ -49,8 +49,9 @@ func TestDeploy(t *testing.T) {
}, "\n"))
deployer, err := provider.NewDeployer(&provider.DeployerConfig{
ApiUrl: fApiUrl,
ApiKey: fApiKey,
ApiUrl: fApiUrl,
ApiKey: fApiKey,
AutoRestart: true,
})
if err != nil {
t.Errorf("err: %+v", err)

View File

@ -0,0 +1,125 @@
package onepanelssl
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
xerrors "github.com/pkg/errors"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
opsdk "github.com/usual2970/certimate/internal/pkg/vendors/1panel-sdk"
)
type UploaderConfig struct {
// 1Panel 地址。
ApiUrl string `json:"apiUrl"`
// 1Panel 接口密钥。
ApiKey string `json:"apiKey"`
}
type UploaderProvider struct {
config *UploaderConfig
sdkClient *opsdk.Client
}
var _ uploader.Uploader = (*UploaderProvider)(nil)
func NewUploader(config *UploaderConfig) (*UploaderProvider, error) {
if config == nil {
panic("config is nil")
}
client, err := createSdkClient(config.ApiUrl, config.ApiKey)
if err != nil {
return nil, xerrors.Wrap(err, "failed to create sdk client")
}
return &UploaderProvider{
config: config,
sdkClient: client,
}, nil
}
func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
// 遍历证书列表,避免重复上传
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
return nil, err
} else if res != nil {
return res, nil
}
// 生成新证书名(需符合 1Panel 命名规则)
certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli())
// 上传证书
uploadWebsiteSSLReq := &opsdk.UploadWebsiteSSLRequest{
Type: "paste",
Description: certName,
Certificate: certPem,
PrivateKey: privkeyPem,
}
uploadWebsiteSSLResp, err := u.sdkClient.UploadWebsiteSSL(uploadWebsiteSSLReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.UploadWebsiteSSL'")
}
// 遍历证书列表,获取刚刚上传证书 ID
if res, err := u.getExistCert(ctx, certPem, privkeyPem); err != nil {
return nil, err
} else if res == nil {
return nil, fmt.Errorf("no ssl certificate found, may be upload failed (code: %d, message: %s)", uploadWebsiteSSLResp.GetCode(), uploadWebsiteSSLResp.GetMessage())
} else {
return res, nil
}
}
func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string, privkeyPem string) (res *uploader.UploadResult, err error) {
searchWebsiteSSLPageNumber := int32(1)
searchWebsiteSSLPageSize := int32(100)
for {
searchWebsiteSSLReq := &opsdk.SearchWebsiteSSLRequest{
Page: searchWebsiteSSLPageNumber,
PageSize: searchWebsiteSSLPageSize,
}
searchWebsiteSSLResp, err := u.sdkClient.SearchWebsiteSSL(searchWebsiteSSLReq)
if err != nil {
return nil, xerrors.Wrap(err, "failed to execute sdk request '1panel.SearchWebsiteSSL'")
}
for _, sslItem := range searchWebsiteSSLResp.Data.Items {
if strings.TrimSpace(sslItem.PEM) == strings.TrimSpace(certPem) &&
strings.TrimSpace(sslItem.PrivateKey) == strings.TrimSpace(privkeyPem) {
// 如果已存在相同证书,直接返回已有的证书信息
return &uploader.UploadResult{
CertId: fmt.Sprintf("%d", sslItem.ID),
CertName: sslItem.Description,
}, nil
}
}
if len(searchWebsiteSSLResp.Data.Items) < int(searchWebsiteSSLPageSize) {
break
} else {
searchWebsiteSSLPageNumber++
}
}
return nil, nil
}
func createSdkClient(apiUrl, apiKey string) (*opsdk.Client, error) {
if _, err := url.Parse(apiUrl); err != nil {
return nil, errors.New("invalid 1panel api url")
}
if apiKey == "" {
return nil, errors.New("invalid 1panel api key")
}
client := opsdk.NewClient(apiUrl, apiKey)
return client, nil
}

View File

@ -0,0 +1,72 @@
package onepanelssl_test
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"testing"
provider "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/1panel-ssl"
)
var (
fInputCertPath string
fInputKeyPath string
fApiUrl string
fApiKey string
)
func init() {
argsPrefix := "CERTIMATE_UPLOADER_1PANELSSL_"
flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "")
flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "")
flag.StringVar(&fApiUrl, argsPrefix+"APIURL", "", "")
flag.StringVar(&fApiKey, argsPrefix+"APIKEY", "", "")
}
/*
Shell command to run this test:
go test -v ./1panel_ssl_test.go -args \
--CERTIMATE_UPLOADER_1PANELSSL_INPUTCERTPATH="/path/to/your-input-cert.pem" \
--CERTIMATE_UPLOADER_1PANELSSL_INPUTKEYPATH="/path/to/your-input-key.pem" \
--CERTIMATE_UPLOADER_1PANELSSL_APIURL="http://127.0.0.1:20410" \
--CERTIMATE_UPLOADER_1PANELSSL_APIKEY="your-api-key"
*/
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("APIKEY: %v", fApiKey),
}, "\n"))
uploader, err := provider.NewUploader(&provider.UploaderConfig{
ApiUrl: fApiUrl,
ApiKey: fApiKey,
})
if err != nil {
t.Errorf("err: %+v", err)
return
}
fInputCertData, _ := os.ReadFile(fInputCertPath)
fInputKeyData, _ := os.ReadFile(fInputKeyPath)
res, err := uploader.Upload(context.Background(), string(fInputCertData), string(fInputKeyData))
if err != nil {
t.Errorf("err: %+v", err)
return
}
sres, _ := json.Marshal(res)
t.Logf("ok: %s", string(sres))
})
}

View File

@ -76,7 +76,13 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPem string, privkeyPe
uploadNormalCertificateResp, err := u.sdkClient.UploadNormalCertificate(uploadNormalCertificateReq)
if err != nil {
if uploadNormalCertificateResp != nil && uploadNormalCertificateResp.GetRetCode() == 80035 {
return u.getExistCert(ctx, certPem)
if res, err := u.getExistCert(ctx, certPem); err != nil {
return nil, err
} else if res == nil {
return nil, errors.New("no certificate found")
} else {
return res, nil
}
}
return nil, xerrors.Wrap(err, "failed to execute sdk request 'ussl.UploadNormalCertificate'")
@ -205,7 +211,7 @@ func (u *UploaderProvider) getExistCert(ctx context.Context, certPem string) (re
}
}
return nil, errors.New("no certificate found")
return nil, nil
}
func createSdkClient(privateKey, publicKey string) (*usdkSsl.USSLClient, error) {

51
internal/pkg/vendors/1panel-sdk/api.go vendored Normal file
View File

@ -0,0 +1,51 @@
package onepanelsdk
import (
"fmt"
"net/http"
)
func (c *Client) UpdateSystemSSL(req *UpdateSystemSSLRequest) (*UpdateSystemSSLResponse, error) {
resp := &UpdateSystemSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/settings/ssl/update", req, resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) SearchWebsiteSSL(req *SearchWebsiteSSLRequest) (*SearchWebsiteSSLResponse, error) {
resp := &SearchWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/search", req, resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) UploadWebsiteSSL(req *UploadWebsiteSSLRequest) (*UploadWebsiteSSLResponse, error) {
resp := &UploadWebsiteSSLResponse{}
err := c.sendRequestWithResult(http.MethodPost, "/websites/ssl/upload", req, resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) GetHttpsConf(req *GetHttpsConfRequest) (*GetHttpsConfResponse, error) {
resp := &GetHttpsConfResponse{}
err := c.sendRequestWithResult(http.MethodGet, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (c *Client) UpdateHttpsConf(req *UpdateHttpsConfRequest) (*UpdateHttpsConfResponse, error) {
resp := &UpdateHttpsConfResponse{}
err := c.sendRequestWithResult(http.MethodPost, fmt.Sprintf("/websites/%d/https", req.WebsiteID), req, resp)
if err != nil {
return nil, err
}
return resp, nil
}

View File

@ -0,0 +1,101 @@
package onepanelsdk
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/go-resty/resty/v2"
)
type Client struct {
apiHost string
apiKey string
client *resty.Client
}
func NewClient(apiHost, apiKey string) *Client {
client := resty.New()
return &Client{
apiHost: strings.TrimRight(apiHost, "/"),
apiKey: apiKey,
client: client,
}
}
func (c *Client) WithTimeout(timeout time.Duration) *Client {
c.client.SetTimeout(timeout)
return c
}
func (c *Client) WithTlsConfig(config *tls.Config) *Client {
c.client.SetTLSClientConfig(config)
return c
}
func (c *Client) generateToken(timestamp string) string {
tokenMd5 := md5.Sum([]byte("1panel" + c.apiKey + timestamp))
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
return tokenMd5Hex
}
func (c *Client) sendRequest(method string, path string, params interface{}) (*resty.Response, error) {
req := c.client.R()
req.Method = method
req.URL = c.apiHost + "/api/v1" + path
if strings.EqualFold(method, http.MethodGet) {
qs := make(map[string]string)
if params != nil {
temp := make(map[string]any)
jsonb, _ := json.Marshal(params)
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(params)
}
timestamp := fmt.Sprintf("%d", time.Now().Unix())
token := c.generateToken(timestamp)
req.SetHeader("1Panel-Timestamp", timestamp)
req.SetHeader("1Panel-Token", token)
resp, err := req.Send()
if err != nil {
return nil, fmt.Errorf("1panel api error: failed to send request: %w", err)
} else if resp.IsError() {
return nil, fmt.Errorf("1panel api error: unexpected status code: %d, %s", resp.StatusCode(), resp.Body())
}
return resp, nil
}
func (c *Client) sendRequestWithResult(method string, path string, params interface{}, result BaseResponse) error {
resp, err := c.sendRequest(method, path, params)
if err != nil {
return err
}
if err := json.Unmarshal(resp.Body(), &result); err != nil {
return fmt.Errorf("1panel api error: failed to parse response: %w", err)
} else if errcode := result.GetCode(); errcode/100 != 2 {
return fmt.Errorf("1panel api error: %d - %s", errcode, result.GetMessage())
}
return nil
}

View File

@ -0,0 +1,103 @@
package onepanelsdk
type BaseResponse interface {
GetCode() int32
GetMessage() string
}
type baseResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
}
func (r *baseResponse) GetCode() int32 {
return r.Code
}
func (r *baseResponse) GetMessage() string {
return r.Message
}
type UpdateSystemSSLRequest struct {
Cert string `json:"cert"`
Key string `json:"key"`
SSLType string `json:"sslType"`
SSL string `json:"ssl"`
SSLID int64 `json:"sslID"`
AutoRestart string `json:"autoRestart"`
}
type UpdateSystemSSLResponse struct {
baseResponse
}
type SearchWebsiteSSLRequest struct {
Page int32 `json:"page"`
PageSize int32 `json:"pageSize"`
}
type SearchWebsiteSSLResponse struct {
baseResponse
Data struct {
Items []*struct {
ID int64 `json:"id"`
PEM string `json:"pem"`
PrivateKey string `json:"privateKey"`
Domains string `json:"domains"`
Description string `json:"description"`
Status string `json:"status"`
UpdatedAt string `json:"updatedAt"`
CreatedAt string `json:"createdAt"`
} `json:"items"`
Total int32 `json:"total"`
} `json:"data"`
}
type UploadWebsiteSSLRequest struct {
Type string `json:"type"`
SSLID int64 `json:"sslID"`
Certificate string `json:"certificate"`
CertificatePath string `json:"certificatePath"`
PrivateKey string `json:"privateKey"`
PrivateKeyPath string `json:"privateKeyPath"`
Description string `json:"description"`
}
type UploadWebsiteSSLResponse struct {
baseResponse
}
type GetHttpsConfRequest struct {
WebsiteID int64 `json:"-"`
}
type GetHttpsConfResponse struct {
baseResponse
Data struct {
Enable bool `json:"enable"`
HttpConfig string `json:"httpConfig"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
} `json:"data"`
}
type UpdateHttpsConfRequest struct {
WebsiteID int64 `json:"websiteId"`
Enable bool `json:"enable"`
Type string `json:"type"`
WebsiteSSLID int64 `json:"websiteSSLId"`
PrivateKey string `json:"privateKey"`
Certificate string `json:"certificate"`
PrivateKeyPath string `json:"privateKeyPath"`
CertificatePath string `json:"certificatePath"`
ImportType string `json:"importType"`
HttpConfig string `json:"httpConfig"`
SSLProtocol []string `json:"SSLProtocol"`
Algorithm string `json:"algorithm"`
Hsts bool `json:"hsts"`
}
type UpdateHttpsConfResponse struct {
baseResponse
}

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5156" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M69.530864 0h884.938272v1024H69.530864z" fill="#E0E0E0" fill-opacity="0"></path><path d="M79.560165 259.855802v503.572017l435.09465 250.73251 434.041152-250.73251-1.053498-502.518519-432.987654-250.73251-435.09465 249.679012z" fill="#005EFD"></path><path d="M93.255638 268.283786v486.716049l422.452675 241.251029 419.292181-242.304527v-485.662551l-421.399177-242.304527-420.345679 242.304527z" fill="#FFFFFF"></path><path d="M139.609547 294.621235l95.868313 56.888888 371.884774-218.074074-93.761317-53.728395-373.99177 214.913581z" fill="#005EFD"></path><path d="M139.609547 293.567737v435.09465l243.358025 139.061728 92.707819-55.835391-241.251029-140.115226 1.053498-320.263375-95.868313-57.942386zM791.269663 350.096329v321.780412l95.269925 55.732148V294.364181L645.749992 154.506008 551.527243 210.238156l239.74242 139.858173z" fill="#0854C1"></path><path d="M420.893498 886.470058l93.339918 56.052411 372.306172-214.692346-94.387094-57.110123L420.893498 886.470058zM385.074568 416.826996h49.88102v280.230453l93.3947 55.835391v-474.074075l-143.27572 81.119342v56.888889z" fill="#005EFD"></path><path d="M528.350288 752.89284l31.604938-16.855968v-440.362139l-31.604938-16.855968v474.074075z" fill="#0854C1"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -9,6 +9,7 @@ import { type AccessModel } from "@/domain/access";
import { ACCESS_PROVIDERS } from "@/domain/provider";
import { useAntdForm, useAntdFormName } from "@/hooks";
import AccessForm1PanelConfig from "./AccessForm1PanelConfig";
import AccessFormACMEHttpReqConfig from "./AccessFormACMEHttpReqConfig";
import AccessFormAliyunConfig from "./AccessFormAliyunConfig";
import AccessFormAWSConfig from "./AccessFormAWSConfig";
@ -99,6 +100,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className,
NOTICE: If you add new child component, please keep ASCII order.
*/
switch (fieldProvider) {
case ACCESS_PROVIDERS["1PANEL"]:
return <AccessForm1PanelConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.ACMEHTTPREQ:
return <AccessFormACMEHttpReqConfig {...nestedFormProps} />;
case ACCESS_PROVIDERS.ALIYUN:

View File

@ -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 AccessConfigFor1Panel } from "@/domain/access";
type AccessForm1PanelConfigFieldValues = Nullish<AccessConfigFor1Panel>;
export type AccessForm1PanelConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: AccessForm1PanelConfigFieldValues;
onValuesChange?: (values: AccessForm1PanelConfigFieldValues) => void;
};
const initFormModel = (): AccessForm1PanelConfigFieldValues => {
return {
apiUrl: "http://<your-host-addr>:20410/",
apiKey: "",
};
};
const AccessForm1PanelConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessForm1PanelConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
apiUrl: z.string().url(t("common.errmsg.url_invalid")),
apiKey: z
.string()
.min(1, t("access.form.1panel_api_key.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.1panel_api_url.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.1panel_api_url.tooltip") }}></span>}
>
<Input placeholder={t("access.form.1panel_api_url.placeholder")} />
</Form.Item>
<Form.Item
name="apiKey"
label={t("access.form.1panel_api_key.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.1panel_api_key.tooltip") }}></span>}
>
<Input.Password autoComplete="new-password" placeholder={t("access.form.1panel_api_key.placeholder")} />
</Form.Item>
</Form>
);
};
export default AccessForm1PanelConfig;

View File

@ -17,7 +17,7 @@ export type AccessFormBaotaPanelConfigProps = {
const initFormModel = (): AccessFormBaotaPanelConfigFieldValues => {
return {
apiUrl: "http://<your-ipaddr>:8888/",
apiUrl: "http://<your-host-addr>:8888/",
apiKey: "",
};
};

View File

@ -17,7 +17,7 @@ export type AccessFormCdnflyConfigProps = {
const initFormModel = (): AccessFormCdnflyConfigFieldValues => {
return {
apiUrl: "http://<your-ipaddr>:88/",
apiUrl: "http://<your-host-addr>:88/",
apiKey: "",
apiSecret: "",
};

View File

@ -17,7 +17,7 @@ export type AccessFormPowerDNSConfigProps = {
const initFormModel = (): AccessFormPowerDNSConfigFieldValues => {
return {
apiUrl: "http://<your-ipaddr>:8082/",
apiUrl: "http://<your-host-addr>:8082/",
apiKey: "",
};
};

View File

@ -17,7 +17,7 @@ export type AccessFormSafeLineConfigProps = {
const initFormModel = (): AccessFormSafeLineConfigFieldValues => {
return {
apiUrl: "http://<your-ipaddr>:9443/",
apiUrl: "http://<your-host-addr>:9443/",
apiToken: "",
};
};

View File

@ -15,6 +15,8 @@ import { type WorkflowNode, type WorkflowNodeConfigForDeploy } from "@/domain/wo
import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks";
import { useWorkflowStore } from "@/stores/workflow";
import DeployNodeConfigForm1PanelConsoleConfig from "./DeployNodeConfigForm1PanelConsoleConfig";
import DeployNodeConfigForm1PanelSiteConfig from "./DeployNodeConfigForm1PanelSiteConfig";
import DeployNodeConfigFormAliyunALBConfig from "./DeployNodeConfigFormAliyunALBConfig";
import DeployNodeConfigFormAliyunCASDeployConfig from "./DeployNodeConfigFormAliyunCASDeployConfig";
import DeployNodeConfigFormAliyunCDNConfig from "./DeployNodeConfigFormAliyunCDNConfig";
@ -138,6 +140,10 @@ const DeployNodeConfigForm = forwardRef<DeployNodeConfigFormInstance, DeployNode
NOTICE: If you add new child component, please keep ASCII order.
*/
switch (fieldProvider) {
case DEPLOY_PROVIDERS["1PANEL_CONSOLE"]:
return <DeployNodeConfigForm1PanelConsoleConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS["1PANEL_SITE"]:
return <DeployNodeConfigForm1PanelSiteConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_ALB:
return <DeployNodeConfigFormAliyunALBConfig {...nestedFormProps} />;
case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY:

View File

@ -0,0 +1,56 @@
import { useTranslation } from "react-i18next";
import { Form, type FormInstance, Switch } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
type DeployNodeConfigForm1PanelConsoleConfigFieldValues = Nullish<{
autoRestart?: boolean;
}>;
export type DeployNodeConfigForm1PanelConsoleConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigForm1PanelConsoleConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigForm1PanelConsoleConfigFieldValues) => void;
};
const initFormModel = (): DeployNodeConfigForm1PanelConsoleConfigFieldValues => {
return {};
};
const DeployNodeConfigForm1PanelConsoleConfig = ({
form: formInst,
formName,
disabled,
initialValues,
onValuesChange,
}: DeployNodeConfigForm1PanelConsoleConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
autoRestart: z.boolean().nullish(),
});
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="autoRestart" label={t("workflow_node.deploy.form.1panel_console_auto_restart.label")} rules={[formRule]}>
<Switch />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigForm1PanelConsoleConfig;

View File

@ -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 DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{
websiteId: string | number;
}>;
export type DeployNodeConfigForm1PanelSiteConfigProps = {
form: FormInstance;
formName: string;
disabled?: boolean;
initialValues?: DeployNodeConfigForm1PanelSiteConfigFieldValues;
onValuesChange?: (values: DeployNodeConfigForm1PanelSiteConfigFieldValues) => void;
};
const initFormModel = (): DeployNodeConfigForm1PanelSiteConfigFieldValues => {
return {};
};
const DeployNodeConfigForm1PanelSiteConfig = ({
form: formInst,
formName,
disabled,
initialValues,
onValuesChange,
}: DeployNodeConfigForm1PanelSiteConfigProps) => {
const { t } = useTranslation();
const formSchema = z.object({
websiteId: z.union([z.string(), z.number()]).refine((v) => {
return /^\d+$/.test(v + "") && +v > 0;
}, t("workflow_node.deploy.form.1panel_site_website_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="websiteId"
label={t("workflow_node.deploy.form.1panel_site_website_id.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.1panel_site_website_id.tooltip") }}></span>}
>
<Input type="number" placeholder={t("workflow_node.deploy.form.1panel_site_website_id.placeholder")} />
</Form.Item>
</Form>
);
};
export default DeployNodeConfigForm1PanelSiteConfig;

View File

@ -6,6 +6,7 @@ export interface AccessModel extends BaseModel {
NOTICE: If you add new type, please keep ASCII order.
*/ Record<string, unknown> &
(
| AccessConfigFor1Panel
| AccessConfigForACMEHttpReq
| AccessConfigForAliyun
| AccessConfigForAWS
@ -46,6 +47,11 @@ export interface AccessModel extends BaseModel {
}
// #region AccessConfig
export type AccessConfigFor1Panel = {
apiUrl: string;
apiKey: string;
};
export type AccessConfigForACMEHttpReq = {
endpoint: string;
mode?: string;

View File

@ -4,6 +4,7 @@
NOTICE: If you add new constant, please keep ASCII order.
*/
export const ACCESS_PROVIDERS = Object.freeze({
["1PANEL"]: "1panel",
ACMEHTTPREQ: "acmehttpreq",
ALIYUN: "aliyun",
AWS: "aws",
@ -84,6 +85,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv
[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["1PANEL"], "provider.1panel", "/imgs/providers/1panel.svg", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.BAOTAPANEL, "provider.baotapanel", "/imgs/providers/baotapanel.svg", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.CACHEFLY, "provider.cachefly", "/imgs/providers/cachefly.png", [ACCESS_USAGES.DEPLOY]],
[ACCESS_PROVIDERS.CDNFLY, "provider.cdnfly", "/imgs/providers/cdnfly.png", [ACCESS_USAGES.DEPLOY]],
@ -211,6 +213,8 @@ export const applyDNSProvidersMap: Map<ApplyDNSProvider["type"] | string, ApplyD
NOTICE: If you add new constant, please keep ASCII order.
*/
export const DEPLOY_PROVIDERS = Object.freeze({
["1PANEL_CONSOLE"]: `${ACCESS_PROVIDERS["1PANEL"]}-console`,
["1PANEL_SITE"]: `${ACCESS_PROVIDERS["1PANEL"]}-site`,
ALIYUN_ALB: `${ACCESS_PROVIDERS.ALIYUN}-alb`,
ALIYUN_CAS_DEPLOY: `${ACCESS_PROVIDERS.ALIYUN}-casdeploy`,
ALIYUN_CDN: `${ACCESS_PROVIDERS.ALIYUN}-cdn`,
@ -345,6 +349,8 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv
[DEPLOY_PROVIDERS.CDNFLY, "provider.cdnfly", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS.EDGIO_APPLICATIONS, "provider.edgio.applications", DEPLOY_CATEGORIES.WEBSITE],
[DEPLOY_PROVIDERS.GCORE_CDN, "provider.gcore.cdn", DEPLOY_CATEGORIES.CDN],
[DEPLOY_PROVIDERS["1PANEL_SITE"], "provider.1panel.site", DEPLOY_CATEGORIES.WEBSITE],
[DEPLOY_PROVIDERS["1PANEL_CONSOLE"], "provider.1panel.console", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.BAOTAPANEL_SITE, "provider.baotapanel.site", DEPLOY_CATEGORIES.WEBSITE],
[DEPLOY_PROVIDERS.BAOTAPANEL_CONSOLE, "provider.baotapanel.console", DEPLOY_CATEGORIES.OTHER],
[DEPLOY_PROVIDERS.SAFELINE, "provider.safeline", DEPLOY_CATEGORIES.FIREWALL],

View File

@ -23,6 +23,12 @@
"access.form.provider.label": "Provider",
"access.form.provider.placeholder": "Please select a provider",
"access.form.provider.tooltip": "DNS provider: The provider that hosts your domain names and manages your DNS records.<br>Host provider: The provider that hosts your servers or cloud services for deploying certificates.<br><br><i>Cannot be edited after saving.</i>",
"access.form.1panel_api_url.label": "1Panel URL",
"access.form.1panel_api_url.placeholder": "Please enter 1Panel URL",
"access.form.1panel_api_url.tooltip": "For more information, see <a href=\"https://docs.1panel.pro/dev_manual/api_manual/\" target=\"_blank\">https://docs.1panel.pro/dev_manual/api_manual/</a>",
"access.form.1panel_api_key.label": "1Panel API key",
"access.form.1panel_api_key.placeholder": "Please enter 1Panel API key",
"access.form.1panel_api_key.tooltip": "For more information, see <a href=\"https://docs.1panel.pro/dev_manual/api_manual/\" target=\"_blank\">https://docs.1panel.pro/dev_manual/api_manual/</a>",
"access.form.acmehttpreq_endpoint.label": "Endpoint",
"access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint",
"access.form.acmehttpreq_endpoint.tooltip": "For more information, see <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",

View File

@ -1,5 +1,7 @@
{
"provider.1panel": "1Panel",
"provider.1panel.console": "1Panel - Console",
"provider.1panel.site": "1Panel - Website",
"provider.acmehttpreq": "Http Request (ACME Proxy)",
"provider.aliyun": "Alibaba Cloud",
"provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)",
@ -28,7 +30,7 @@
"provider.baishan.cdn": "Baishan - CDN (Content Delivery Network)",
"provider.baotapanel": "aaPanel (aka BaoTaPanel)",
"provider.baotapanel.console": "aaPanel (aka BaoTaPanel) - Console",
"provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Site",
"provider.baotapanel.site": "aaPanel (aka BaoTaPanel) - Website",
"provider.byteplus": "BytePlus",
"provider.byteplus.cdn": "BytePlus - CDN (Content Delivery Network)",
"provider.cachefly": "CacheFly",

View File

@ -90,6 +90,10 @@
"workflow_node.deploy.form.certificate.placeholder": "Please select certificate",
"workflow_node.deploy.form.certificate.tooltip": "The certificate to be deployed comes from the previous application stage node.",
"workflow_node.deploy.form.params_config.label": "Parameter settings",
"workflow_node.deploy.form.1panel_console_auto_restart.label": "Auto restart after deployment",
"workflow_node.deploy.form.1panel_site_website_id.label": "1Panel website ID",
"workflow_node.deploy.form.1panel_site_website_id.placeholder": "Please enter 1Panel website ID",
"workflow_node.deploy.form.1panel_site_website_id.tooltip": "You can find it on 1Panel WebUI.",
"workflow_node.deploy.form.aliyun_alb_resource_type.label": "Resource type",
"workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "Please select resource type",
"workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "ALB load balancer",

View File

@ -23,6 +23,12 @@
"access.form.provider.label": "提供商",
"access.form.provider.placeholder": "请选择提供商",
"access.form.provider.tooltip": "提供商分为两种类型:<br>【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。<br>【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。<br><br>该字段保存后不可修改。",
"access.form.1panel_api_url.label": "1Panel URL",
"access.form.1panel_api_url.placeholder": "请输入 1Panel URL",
"access.form.1panel_api_url.tooltip": "这是什么?请参阅 <a href=\"https://1panel.cn/docs/dev_manual/api_manual/\" target=\"_blank\">https://1panel.cn/docs/dev_manual/api_manual/</a>",
"access.form.1panel_api_key.label": "1Panel 接口密钥",
"access.form.1panel_api_key.placeholder": "请输入 1Panel 接口密钥",
"access.form.1panel_api_key.tooltip": "这是什么?请参阅 <a href=\"https://1panel.cn/docs/dev_manual/api_manual/\" target=\"_blank\">https://1panel.cn/docs/dev_manual/api_manual/</a>",
"access.form.acmehttpreq_endpoint.label": "服务端点",
"access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点",
"access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://go-acme.github.io/lego/dns/httpreq/\" target=\"_blank\">https://go-acme.github.io/lego/dns/httpreq/</a>",

View File

@ -1,5 +1,7 @@
{
"provider.1panel": "1Panel",
"provider.1panel.console": "1Panel - 面板",
"provider.1panel.site": "1Panel - 网站",
"provider.acmehttpreq": "Http Request (ACME Proxy)",
"provider.aliyun": "阿里云",
"provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB",

View File

@ -90,6 +90,10 @@
"workflow_node.deploy.form.certificate.placeholder": "请选择待部署证书",
"workflow_node.deploy.form.certificate.tooltip": "待部署证书来自之前的申请阶段。如果选项为空请先确保前序节点配置正确。",
"workflow_node.deploy.form.params_config.label": "参数设置",
"workflow_node.deploy.form.1panel_console_auto_restart.label": "部署后自动重启面板服务",
"workflow_node.deploy.form.1panel_site_website_id.label": "1Panel 网站 ID",
"workflow_node.deploy.form.1panel_site_website_id.placeholder": "请输入 1Panel 网站 ID",
"workflow_node.deploy.form.1panel_site_website_id.tooltip": "请在 1Panel 管理面板查看。",
"workflow_node.deploy.form.aliyun_alb_resource_type.label": "证书替换方式",
"workflow_node.deploy.form.aliyun_alb_resource_type.placeholder": "请选择证书替换方式",
"workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "替换指定负载均衡器下的全部 HTTPS/QUIC 监听的证书",