diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go
index 6a8a0b6c..c616e899 100644
--- a/internal/deployer/providers.go
+++ b/internal/deployer/providers.go
@@ -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{}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index 25df3210..47dd5132 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -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"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 950abc08..fcdffdd6 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -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")
diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go
new file mode 100644
index 00000000..d6b03b8c
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console.go
@@ -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
+}
diff --git a/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go
new file mode 100644
index 00000000..b2d2e788
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/1panel-console/1panel_console_test.go
@@ -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)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go
new file mode 100644
index 00000000..85fc78c9
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site.go
@@ -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
+}
diff --git a/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go
new file mode 100644
index 00000000..82c3874d
--- /dev/null
+++ b/internal/pkg/core/deployer/providers/1panel-site/1panel_site_test.go
@@ -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)
+ })
+}
diff --git a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go
index 06a2f096..2f6ccb18 100644
--- a/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go
+++ b/internal/pkg/core/deployer/providers/baotapanel-console/baotapanel_console_test.go
@@ -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)
diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go
new file mode 100644
index 00000000..bb612bec
--- /dev/null
+++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl.go
@@ -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
+}
diff --git a/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go
new file mode 100644
index 00000000..5f146dd1
--- /dev/null
+++ b/internal/pkg/core/uploader/providers/1panel-ssl/1panel_ssl_test.go
@@ -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))
+ })
+}
diff --git a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
index e5d2fc1c..67506bf4 100644
--- a/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
+++ b/internal/pkg/core/uploader/providers/ucloud-ussl/ucloud_ussl.go
@@ -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) {
diff --git a/internal/pkg/vendors/1panel-sdk/api.go b/internal/pkg/vendors/1panel-sdk/api.go
new file mode 100644
index 00000000..a6390408
--- /dev/null
+++ b/internal/pkg/vendors/1panel-sdk/api.go
@@ -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
+}
diff --git a/internal/pkg/vendors/1panel-sdk/client.go b/internal/pkg/vendors/1panel-sdk/client.go
new file mode 100644
index 00000000..629fad01
--- /dev/null
+++ b/internal/pkg/vendors/1panel-sdk/client.go
@@ -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
+}
diff --git a/internal/pkg/vendors/1panel-sdk/models.go b/internal/pkg/vendors/1panel-sdk/models.go
new file mode 100644
index 00000000..8510aefe
--- /dev/null
+++ b/internal/pkg/vendors/1panel-sdk/models.go
@@ -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
+}
diff --git a/ui/public/imgs/providers/1panel.svg b/ui/public/imgs/providers/1panel.svg
new file mode 100644
index 00000000..561199ce
--- /dev/null
+++ b/ui/public/imgs/providers/1panel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx
index 47c6d8c6..7f2143ac 100644
--- a/ui/src/components/access/AccessForm.tsx
+++ b/ui/src/components/access/AccessForm.tsx
@@ -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(({ className,
NOTICE: If you add new child component, please keep ASCII order.
*/
switch (fieldProvider) {
+ case ACCESS_PROVIDERS["1PANEL"]:
+ return ;
case ACCESS_PROVIDERS.ACMEHTTPREQ:
return ;
case ACCESS_PROVIDERS.ALIYUN:
diff --git a/ui/src/components/access/AccessForm1PanelConfig.tsx b/ui/src/components/access/AccessForm1PanelConfig.tsx
new file mode 100644
index 00000000..3b765b3e
--- /dev/null
+++ b/ui/src/components/access/AccessForm1PanelConfig.tsx
@@ -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;
+
+export type AccessForm1PanelConfigProps = {
+ form: FormInstance;
+ formName: string;
+ disabled?: boolean;
+ initialValues?: AccessForm1PanelConfigFieldValues;
+ onValuesChange?: (values: AccessForm1PanelConfigFieldValues) => void;
+};
+
+const initFormModel = (): AccessForm1PanelConfigFieldValues => {
+ return {
+ apiUrl: "http://: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) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AccessForm1PanelConfig;
diff --git a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
index accd80d0..9a47674d 100644
--- a/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
+++ b/ui/src/components/access/AccessFormBaotaPanelConfig.tsx
@@ -17,7 +17,7 @@ export type AccessFormBaotaPanelConfigProps = {
const initFormModel = (): AccessFormBaotaPanelConfigFieldValues => {
return {
- apiUrl: "http://:8888/",
+ apiUrl: "http://:8888/",
apiKey: "",
};
};
diff --git a/ui/src/components/access/AccessFormCdnflyConfig.tsx b/ui/src/components/access/AccessFormCdnflyConfig.tsx
index a9dd1139..ec0afaa2 100644
--- a/ui/src/components/access/AccessFormCdnflyConfig.tsx
+++ b/ui/src/components/access/AccessFormCdnflyConfig.tsx
@@ -17,7 +17,7 @@ export type AccessFormCdnflyConfigProps = {
const initFormModel = (): AccessFormCdnflyConfigFieldValues => {
return {
- apiUrl: "http://:88/",
+ apiUrl: "http://:88/",
apiKey: "",
apiSecret: "",
};
diff --git a/ui/src/components/access/AccessFormPowerDNSConfig.tsx b/ui/src/components/access/AccessFormPowerDNSConfig.tsx
index e93980c7..cada5b61 100644
--- a/ui/src/components/access/AccessFormPowerDNSConfig.tsx
+++ b/ui/src/components/access/AccessFormPowerDNSConfig.tsx
@@ -17,7 +17,7 @@ export type AccessFormPowerDNSConfigProps = {
const initFormModel = (): AccessFormPowerDNSConfigFieldValues => {
return {
- apiUrl: "http://:8082/",
+ apiUrl: "http://:8082/",
apiKey: "",
};
};
diff --git a/ui/src/components/access/AccessFormSafeLineConfig.tsx b/ui/src/components/access/AccessFormSafeLineConfig.tsx
index b9de115c..44dd77d1 100644
--- a/ui/src/components/access/AccessFormSafeLineConfig.tsx
+++ b/ui/src/components/access/AccessFormSafeLineConfig.tsx
@@ -17,7 +17,7 @@ export type AccessFormSafeLineConfigProps = {
const initFormModel = (): AccessFormSafeLineConfigFieldValues => {
return {
- apiUrl: "http://:9443/",
+ apiUrl: "http://:9443/",
apiToken: "",
};
};
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
index b9f65b08..9fe9943f 100644
--- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx
@@ -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;
+ case DEPLOY_PROVIDERS["1PANEL_SITE"]:
+ return ;
case DEPLOY_PROVIDERS.ALIYUN_ALB:
return ;
case DEPLOY_PROVIDERS.ALIYUN_CAS_DEPLOY:
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx
new file mode 100644
index 00000000..349b516e
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelConsoleConfig.tsx
@@ -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) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default DeployNodeConfigForm1PanelConsoleConfig;
diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx
new file mode 100644
index 00000000..f5a26450
--- /dev/null
+++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx
@@ -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) => {
+ onValuesChange?.(values);
+ };
+
+ return (
+ }
+ >
+
+
+
+ );
+};
+
+export default DeployNodeConfigForm1PanelSiteConfig;
diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts
index d32960ca..ea2586af 100644
--- a/ui/src/domain/access.ts
+++ b/ui/src/domain/access.ts
@@ -6,6 +6,7 @@ export interface AccessModel extends BaseModel {
NOTICE: If you add new type, please keep ASCII order.
*/ Record &
(
+ | 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;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index 0841f39d..70e0ba7a 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -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: MapHost provider: The provider that hosts your servers or cloud services for deploying certificates.
Cannot be edited after saving.",
+ "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 https://docs.1panel.pro/dev_manual/api_manual/",
+ "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 https://docs.1panel.pro/dev_manual/api_manual/",
"access.form.acmehttpreq_endpoint.label": "Endpoint",
"access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint",
"access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/",
diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json
index 9ce0bc5c..1f6be72a 100644
--- a/ui/src/i18n/locales/en/nls.provider.json
+++ b/ui/src/i18n/locales/en/nls.provider.json
@@ -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",
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index 66f43308..31b421a7 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -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",
diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json
index 64b034d5..a7b32ba8 100644
--- a/ui/src/i18n/locales/zh/nls.access.json
+++ b/ui/src/i18n/locales/zh/nls.access.json
@@ -23,6 +23,12 @@
"access.form.provider.label": "提供商",
"access.form.provider.placeholder": "请选择提供商",
"access.form.provider.tooltip": "提供商分为两种类型:
【DNS 提供商】你的 DNS 托管方,通常等同于域名注册商,用于在申请证书时管理您的域名解析记录。
【主机提供商】你的服务器或云服务的托管方,用于部署签发的证书。
该字段保存后不可修改。",
+ "access.form.1panel_api_url.label": "1Panel URL",
+ "access.form.1panel_api_url.placeholder": "请输入 1Panel URL",
+ "access.form.1panel_api_url.tooltip": "这是什么?请参阅 https://1panel.cn/docs/dev_manual/api_manual/",
+ "access.form.1panel_api_key.label": "1Panel 接口密钥",
+ "access.form.1panel_api_key.placeholder": "请输入 1Panel 接口密钥",
+ "access.form.1panel_api_key.tooltip": "这是什么?请参阅 https://1panel.cn/docs/dev_manual/api_manual/",
"access.form.acmehttpreq_endpoint.label": "服务端点",
"access.form.acmehttpreq_endpoint.placeholder": "请输入服务端点",
"access.form.acmehttpreq_endpoint.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/",
diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json
index f8d36830..8ff1bdbd 100644
--- a/ui/src/i18n/locales/zh/nls.provider.json
+++ b/ui/src/i18n/locales/zh/nls.provider.json
@@ -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",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 8778b6b5..8f060adf 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -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 监听的证书",