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 监听的证书",