diff --git a/pkg/core/deployer/providers/1panel-site/1panel_site.go b/pkg/core/deployer/providers/1panel-site/1panel_site.go
index de1a1f49..b91bc670 100644
--- a/pkg/core/deployer/providers/1panel-site/1panel_site.go
+++ b/pkg/core/deployer/providers/1panel-site/1panel_site.go
@@ -13,6 +13,7 @@ import (
"github.com/certimate-go/certimate/pkg/core/deployer"
onepanelsdk "github.com/certimate-go/certimate/pkg/sdk3rd/1panel"
onepanelsdk2 "github.com/certimate-go/certimate/pkg/sdk3rd/1panel/v2"
+ xcert "github.com/certimate-go/certimate/pkg/utils/cert"
)
type DeployerConfig struct {
@@ -30,8 +31,11 @@ type DeployerConfig struct {
NodeName string `json:"nodeName,omitempty"`
// 部署资源类型。
ResourceType string `json:"resourceType"`
+ // 域名匹配模式。
+ // 零值时默认值 [WEBSITE_MATCH_PATTERN_EXACT]。
+ WebsiteMatchPattern string `json:"websiteMatchPattern,omitempty"`
// 网站 ID。
- // 部署资源类型为 [RESOURCE_TYPE_WEBSITE] 时必填。
+ // 部署资源类型为 [RESOURCE_TYPE_WEBSITE]、且匹配模式非 [WEBSITE_MATCH_PATTERN_CERTSAN] 时必填。
WebsiteId int64 `json:"websiteId,omitempty"`
// 证书 ID。
// 部署资源类型为 [RESOURCE_TYPE_CERTIFICATE] 时必填。
@@ -106,10 +110,6 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
}
func (d *Deployer) deployToWebsite(ctx context.Context, certPEM, privkeyPEM string) error {
- if d.config.WebsiteId == 0 {
- return errors.New("config `websiteId` is required")
- }
-
// 上传证书
upres, err := d.sdkCertmgr.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
@@ -118,65 +118,54 @@ func (d *Deployer) deployToWebsite(ctx context.Context, certPEM, privkeyPEM stri
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
- switch sdkClient := d.sdkClient.(type) {
- case *onepanelsdk.Client:
+ // 获取待部署的网站列表
+ var websiteIds []int64
+ switch d.config.WebsiteMatchPattern {
+ case "", WEBSITE_MATCH_PATTERN_SPECIFIED:
{
- // 获取网站 HTTPS 配置
- websiteHttpsGetResp, err := sdkClient.WebsiteHttpsGet(d.config.WebsiteId)
- d.logger.Debug("sdk request '1panel.WebsiteHttpsGet'", slog.Int64("websiteId", d.config.WebsiteId), slog.Any("response", websiteHttpsGetResp))
- if err != nil {
- return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsGet': %w", err)
+ if d.config.WebsiteId == 0 {
+ return errors.New("config `websiteId` is required")
}
- // 修改网站 HTTPS 配置
- sslId, _ := strconv.ParseInt(upres.CertId, 10, 64)
- websiteHttpsPostReq := &onepanelsdk.WebsiteHttpsPostRequest{
- WebsiteID: d.config.WebsiteId,
- Type: "existed",
- WebsiteSSLID: sslId,
- Enable: websiteHttpsGetResp.Data.Enable,
- HttpConfig: websiteHttpsGetResp.Data.HttpConfig,
- SSLProtocol: websiteHttpsGetResp.Data.SSLProtocol,
- Algorithm: websiteHttpsGetResp.Data.Algorithm,
- Hsts: websiteHttpsGetResp.Data.Hsts,
- }
- websiteHttpsPostResp, err := sdkClient.WebsiteHttpsPost(d.config.WebsiteId, websiteHttpsPostReq)
- d.logger.Debug("sdk request '1panel.WebsiteHttpsPost'", slog.Int64("websiteId", d.config.WebsiteId), slog.Any("request", websiteHttpsPostReq), slog.Any("response", websiteHttpsPostResp))
- if err != nil {
- return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsPost': %w", err)
- }
+ websiteIds = []int64{d.config.WebsiteId}
}
- case *onepanelsdk2.Client:
+ case WEBSITE_MATCH_PATTERN_CERTSAN:
{
- // 获取网站 HTTPS 配置
- websiteHttpsGetResp, err := sdkClient.WebsiteHttpsGet(d.config.WebsiteId)
- d.logger.Debug("sdk request '1panel.WebsiteHttpsGet'", slog.Int64("websiteId", d.config.WebsiteId), slog.Any("response", websiteHttpsGetResp))
+ websiteIdCandidates, err := d.getMatchedWebsiteIdsByCertificate(ctx, certPEM)
if err != nil {
- return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsGet': %w", err)
+ return err
}
- // 修改网站 HTTPS 配置
- sslId, _ := strconv.ParseInt(upres.CertId, 10, 64)
- websiteHttpsPostReq := &onepanelsdk2.WebsiteHttpsPostRequest{
- WebsiteID: d.config.WebsiteId,
- Type: "existed",
- WebsiteSSLID: sslId,
- Enable: websiteHttpsGetResp.Data.Enable,
- HttpConfig: websiteHttpsGetResp.Data.HttpConfig,
- SSLProtocol: websiteHttpsGetResp.Data.SSLProtocol,
- Algorithm: websiteHttpsGetResp.Data.Algorithm,
- Hsts: websiteHttpsGetResp.Data.Hsts,
- }
- websiteHttpsPostResp, err := sdkClient.WebsiteHttpsPost(d.config.WebsiteId, websiteHttpsPostReq)
- d.logger.Debug("sdk request '1panel.WebsiteHttpsPost'", slog.Int64("websiteId", d.config.WebsiteId), slog.Any("request", websiteHttpsPostReq), slog.Any("response", websiteHttpsPostResp))
- if err != nil {
- return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsPost': %w", err)
- }
+ websiteIds = websiteIdCandidates
}
default:
- panic("unreachable")
+ return fmt.Errorf("unsupported website match pattern: '%s'", d.config.WebsiteMatchPattern)
+ }
+
+ // 遍历更新网站证书
+ if len(websiteIds) == 0 {
+ d.logger.Info("no websites to deploy")
+ } else {
+ d.logger.Info("found websites to deploy", slog.Any("websiteIds", websiteIds))
+ var errs []error
+
+ websiteSSLId, _ := strconv.ParseInt(upres.CertId, 10, 64)
+ for _, websiteId := range websiteIds {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ if err := d.updateWebsiteCertificate(ctx, websiteId, websiteSSLId); err != nil {
+ errs = append(errs, err)
+ }
+ }
+ }
+
+ if len(errs) > 0 {
+ return errors.Join(errs...)
+ }
}
return nil
@@ -198,6 +187,211 @@ func (d *Deployer) deployToCertificate(ctx context.Context, certPEM, privkeyPEM
return nil
}
+func (d *Deployer) getMatchedWebsiteIdsByCertificate(ctx context.Context, certPEM string) ([]int64, error) {
+ var websiteIds []int64
+
+ certX509, err := xcert.ParseCertificateFromPEM(certPEM)
+ if err != nil {
+ return nil, err
+ }
+
+ switch sdkClient := d.sdkClient.(type) {
+ case *onepanelsdk.Client:
+ {
+ websiteSearchPage := 1
+ websiteSearchPageSize := 100
+ for {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+ websiteSearchReq := &onepanelsdk.WebsiteSearchRequest{
+ Order: "ascending",
+ OrderBy: "primary_domain",
+ Page: int32(websiteSearchPage),
+ PageSize: int32(websiteSearchPageSize),
+ }
+ websiteSearchResp, err := sdkClient.WebsiteSearch(websiteSearchReq)
+ d.logger.Debug("sdk request '1panel.WebsiteSearch'", slog.Any("request", websiteSearchReq), slog.Any("response", websiteSearchResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request '1panel.WebsiteSearch': %w", err)
+ }
+
+ if websiteSearchResp.Data == nil {
+ break
+ }
+
+ for _, websiteItem := range websiteSearchResp.Data.Items {
+ if certX509.VerifyHostname(websiteItem.PrimaryDomain) != nil {
+ continue
+ }
+
+ websiteGetResp, err := sdkClient.WebsiteGet(websiteItem.ID)
+ d.logger.Debug("sdk request '1panel.WebsiteGet'", slog.Int64("websiteId", websiteItem.ID), slog.Any("response", websiteGetResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request '1panel.WebsiteGet': %w", err)
+ }
+
+ for _, domainInfo := range websiteGetResp.Data.Domains {
+ if domainInfo.SSL {
+ websiteIds = append(websiteIds, websiteItem.ID)
+ break
+ }
+ }
+ }
+
+ if len(websiteSearchResp.Data.Items) < websiteSearchPageSize {
+ break
+ }
+
+ websiteSearchPage++
+ }
+ }
+
+ case *onepanelsdk2.Client:
+ {
+ websiteSearchPage := 1
+ websiteSearchPageSize := 100
+ for {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ websiteSearchReq := &onepanelsdk2.WebsiteSearchRequest{
+ Order: "ascending",
+ OrderBy: "primary_domain",
+ Page: int32(websiteSearchPage),
+ PageSize: int32(websiteSearchPageSize),
+ }
+ websiteSearchResp, err := sdkClient.WebsiteSearch(websiteSearchReq)
+ d.logger.Debug("sdk request '1panel.WebsiteSearch'", slog.Any("request", websiteSearchReq), slog.Any("response", websiteSearchResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request '1panel.WebsiteSearch': %w", err)
+ }
+
+ if websiteSearchResp.Data == nil {
+ break
+ }
+
+ for _, websiteItem := range websiteSearchResp.Data.Items {
+ if certX509.VerifyHostname(websiteItem.PrimaryDomain) != nil {
+ continue
+ }
+
+ websiteGetResp, err := sdkClient.WebsiteGet(websiteItem.ID)
+ d.logger.Debug("sdk request '1panel.WebsiteGet'", slog.Int64("websiteId", websiteItem.ID), slog.Any("response", websiteGetResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request '1panel.WebsiteGet': %w", err)
+ }
+
+ for _, domainInfo := range websiteGetResp.Data.Domains {
+ if domainInfo.SSL {
+ websiteIds = append(websiteIds, websiteItem.ID)
+ break
+ }
+ }
+ }
+
+ if len(websiteSearchResp.Data.Items) < websiteSearchPageSize {
+ break
+ }
+
+ websiteSearchPage++
+ }
+ }
+
+ default:
+ panic("unreachable")
+ }
+
+ if len(websiteIds) == 0 {
+ return nil, errors.New("could not find any websites matched by certificate")
+ }
+
+ return websiteIds, nil
+}
+
+func (d *Deployer) updateWebsiteCertificate(ctx context.Context, websiteId int64, websiteSSLId int64) error {
+ switch sdkClient := d.sdkClient.(type) {
+ case *onepanelsdk.Client:
+ {
+ // 获取网站 HTTPS 配置
+ websiteHttpsGetResp, err := sdkClient.WebsiteHttpsGet(websiteId)
+ d.logger.Debug("sdk request '1panel.WebsiteHttpsGet'", slog.Int64("websiteId", websiteId), slog.Any("response", websiteHttpsGetResp))
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsGet': %w", err)
+ } else {
+ if websiteHttpsGetResp.Data.Enable && websiteHttpsGetResp.Data.WebsiteSSLID == websiteSSLId {
+ return nil
+ }
+ }
+
+ // 修改网站 HTTPS 配置
+ websiteHttpsPostReq := &onepanelsdk.WebsiteHttpsPostRequest{
+ WebsiteID: websiteId,
+ Type: "existed",
+ WebsiteSSLID: websiteSSLId,
+ Enable: true,
+ HttpConfig: websiteHttpsGetResp.Data.HttpConfig,
+ SSLProtocol: websiteHttpsGetResp.Data.SSLProtocol,
+ Algorithm: websiteHttpsGetResp.Data.Algorithm,
+ Hsts: websiteHttpsGetResp.Data.Hsts,
+ }
+ if websiteHttpsPostReq.HttpConfig == "" {
+ websiteHttpsPostReq.HttpConfig = "HTTPToHTTPS"
+ }
+ websiteHttpsPostResp, err := sdkClient.WebsiteHttpsPost(websiteId, websiteHttpsPostReq)
+ d.logger.Debug("sdk request '1panel.WebsiteHttpsPost'", slog.Int64("websiteId", websiteId), slog.Any("request", websiteHttpsPostReq), slog.Any("response", websiteHttpsPostResp))
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsPost': %w", err)
+ }
+ }
+
+ case *onepanelsdk2.Client:
+ {
+ // 获取网站 HTTPS 配置
+ websiteHttpsGetResp, err := sdkClient.WebsiteHttpsGet(websiteId)
+ d.logger.Debug("sdk request '1panel.WebsiteHttpsGet'", slog.Int64("websiteId", websiteId), slog.Any("response", websiteHttpsGetResp))
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsGet': %w", err)
+ } else {
+ if websiteHttpsGetResp.Data.Enable && websiteHttpsGetResp.Data.WebsiteSSLID == websiteSSLId {
+ return nil
+ }
+ }
+
+ // 修改网站 HTTPS 配置
+ websiteHttpsPostReq := &onepanelsdk2.WebsiteHttpsPostRequest{
+ WebsiteID: websiteId,
+ Type: "existed",
+ WebsiteSSLID: websiteSSLId,
+ Enable: true,
+ HttpConfig: websiteHttpsGetResp.Data.HttpConfig,
+ SSLProtocol: websiteHttpsGetResp.Data.SSLProtocol,
+ Algorithm: websiteHttpsGetResp.Data.Algorithm,
+ Hsts: websiteHttpsGetResp.Data.Hsts,
+ Http3: websiteHttpsGetResp.Data.Http3,
+ }
+ if websiteHttpsPostReq.HttpConfig == "" {
+ websiteHttpsPostReq.HttpConfig = "HTTPToHTTPS"
+ }
+ websiteHttpsPostResp, err := sdkClient.WebsiteHttpsPost(websiteId, websiteHttpsPostReq)
+ d.logger.Debug("sdk request '1panel.WebsiteHttpsPost'", slog.Int64("websiteId", websiteId), slog.Any("request", websiteHttpsPostReq), slog.Any("response", websiteHttpsPostResp))
+ if err != nil {
+ return fmt.Errorf("failed to execute sdk request '1panel.WebsiteHttpsPost': %w", err)
+ }
+ }
+
+ default:
+ panic("unreachable")
+ }
+
+ return nil
+}
+
const (
sdkVersionV1 = "v1"
sdkVersionV2 = "v2"
diff --git a/pkg/core/deployer/providers/1panel-site/1panel_site_test.go b/pkg/core/deployer/providers/1panel-site/1panel_site_test.go
index ef17d61b..fe93f09d 100644
--- a/pkg/core/deployer/providers/1panel-site/1panel_site_test.go
+++ b/pkg/core/deployer/providers/1panel-site/1panel_site_test.go
@@ -62,6 +62,7 @@ func TestDeploy(t *testing.T) {
ApiKey: fApiKey,
AllowInsecureConnections: true,
ResourceType: provider.RESOURCE_TYPE_WEBSITE,
+ WebsiteMatchPattern: provider.WEBSITE_MATCH_PATTERN_SPECIFIED,
WebsiteId: fWebsiteId,
})
if err != nil {
diff --git a/pkg/core/deployer/providers/1panel-site/consts.go b/pkg/core/deployer/providers/1panel-site/consts.go
index ab403c50..91e1b430 100644
--- a/pkg/core/deployer/providers/1panel-site/consts.go
+++ b/pkg/core/deployer/providers/1panel-site/consts.go
@@ -6,3 +6,10 @@ const (
// 资源类型:替换指定证书。
RESOURCE_TYPE_CERTIFICATE = "certificate"
)
+
+const (
+ // 匹配模式:指定 ID。
+ WEBSITE_MATCH_PATTERN_SPECIFIED = "specified"
+ // 匹配模式:证书 SAN 匹配。
+ WEBSITE_MATCH_PATTERN_CERTSAN = "certsan"
+)
diff --git a/pkg/sdk3rd/1panel/api_website_get.go b/pkg/sdk3rd/1panel/api_website_get.go
new file mode 100644
index 00000000..6841c88e
--- /dev/null
+++ b/pkg/sdk3rd/1panel/api_website_get.go
@@ -0,0 +1,64 @@
+package onepanel
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+)
+
+type WebsiteGetRequest struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Page int32 `json:"page"`
+ PageSize int32 `json:"pageSize"`
+}
+
+type WebsiteGetResponse struct {
+ apiResponseBase
+
+ Data *struct {
+ ID int64 `json:"id"`
+ Alias string `json:"alias"`
+ PrimaryDomain string `json:"primaryDomain"`
+ Protocol string `json:"protocol"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ SitePath string `json:"sitePath"`
+ Remark string `json:"remark"`
+ Domains []*struct {
+ ID int64 `json:"id"`
+ Domain string `json:"domain"`
+ Port int32 `json:"port"`
+ SSL bool `json:"ssl"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"domains"`
+ WebsiteSSLId int64 `json:"webSiteSSLId"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"data,omitempty"`
+}
+
+func (c *Client) WebsiteGet(websiteId int64) (*WebsiteGetResponse, error) {
+ return c.WebsiteGetWithContext(context.Background(), websiteId)
+}
+
+func (c *Client) WebsiteGetWithContext(ctx context.Context, websiteId int64) (*WebsiteGetResponse, error) {
+ if websiteId == 0 {
+ return nil, fmt.Errorf("sdkerr: unset websiteId")
+ }
+
+ httpreq, err := c.newRequest(http.MethodGet, fmt.Sprintf("/websites/%d", websiteId))
+ if err != nil {
+ return nil, err
+ } else {
+ httpreq.SetContext(ctx)
+ }
+
+ result := &WebsiteGetResponse{}
+ if _, err := c.doRequestWithResult(httpreq, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/pkg/sdk3rd/1panel/api_website_https_get.go b/pkg/sdk3rd/1panel/api_website_https_get.go
index 25f00eed..d09b683a 100644
--- a/pkg/sdk3rd/1panel/api_website_https_get.go
+++ b/pkg/sdk3rd/1panel/api_website_https_get.go
@@ -10,11 +10,12 @@ type WebsiteHttpsGetResponse struct {
apiResponseBase
Data *struct {
- Enable bool `json:"enable"`
- HttpConfig string `json:"httpConfig"`
- SSLProtocol []string `json:"SSLProtocol"`
- Algorithm string `json:"algorithm"`
- Hsts bool `json:"hsts"`
+ Enable bool `json:"enable"`
+ WebsiteSSLID int64 `json:"websiteSSLId"`
+ HttpConfig string `json:"httpConfig"`
+ SSLProtocol []string `json:"SSLProtocol"`
+ Algorithm string `json:"algorithm"`
+ Hsts bool `json:"hsts"`
} `json:"data,omitempty"`
}
diff --git a/pkg/sdk3rd/1panel/api_website_https_post.go b/pkg/sdk3rd/1panel/api_website_https_post.go
index 998fbd4b..fa4de62f 100644
--- a/pkg/sdk3rd/1panel/api_website_https_post.go
+++ b/pkg/sdk3rd/1panel/api_website_https_post.go
@@ -7,19 +7,14 @@ import (
)
type WebsiteHttpsPostRequest 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"`
+ WebsiteID int64 `json:"websiteId"`
+ Enable bool `json:"enable"`
+ Type string `json:"type"`
+ WebsiteSSLID int64 `json:"websiteSSLId"`
+ HttpConfig string `json:"httpConfig"`
+ SSLProtocol []string `json:"SSLProtocol"`
+ Algorithm string `json:"algorithm"`
+ Hsts bool `json:"hsts"`
}
type WebsiteHttpsPostResponse struct {
diff --git a/pkg/sdk3rd/1panel/api_website_search.go b/pkg/sdk3rd/1panel/api_website_search.go
new file mode 100644
index 00000000..e2263d52
--- /dev/null
+++ b/pkg/sdk3rd/1panel/api_website_search.go
@@ -0,0 +1,58 @@
+package onepanel
+
+import (
+ "context"
+ "net/http"
+)
+
+type WebsiteSearchRequest struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Order string `json:"order"`
+ OrderBy string `json:"orderBy"`
+ Page int32 `json:"page"`
+ PageSize int32 `json:"pageSize"`
+}
+
+type WebsiteSearchResponse struct {
+ apiResponseBase
+
+ Data *struct {
+ Items []*struct {
+ ID int64 `json:"id"`
+ Alias string `json:"alias"`
+ PrimaryDomain string `json:"primaryDomain"`
+ Protocol string `json:"protocol"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ SitePath string `json:"sitePath"`
+ Remark string `json:"remark"`
+ SSLStatus string `json:"sslStatus"`
+ SSLExpireDate string `json:"sslExpireDate"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"items"`
+ Total int32 `json:"total"`
+ } `json:"data,omitempty"`
+}
+
+func (c *Client) WebsiteSearch(req *WebsiteSearchRequest) (*WebsiteSearchResponse, error) {
+ return c.WebsiteSearchWithContext(context.Background(), req)
+}
+
+func (c *Client) WebsiteSearchWithContext(ctx context.Context, req *WebsiteSearchRequest) (*WebsiteSearchResponse, error) {
+ httpreq, err := c.newRequest(http.MethodPost, "/websites/search")
+ if err != nil {
+ return nil, err
+ } else {
+ httpreq.SetBody(req)
+ httpreq.SetContext(ctx)
+ }
+
+ result := &WebsiteSearchResponse{}
+ if _, err := c.doRequestWithResult(httpreq, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/pkg/sdk3rd/1panel/v2/api_website_get.go b/pkg/sdk3rd/1panel/v2/api_website_get.go
new file mode 100644
index 00000000..9681533f
--- /dev/null
+++ b/pkg/sdk3rd/1panel/v2/api_website_get.go
@@ -0,0 +1,64 @@
+package v2
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+)
+
+type WebsiteGetRequest struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Page int32 `json:"page"`
+ PageSize int32 `json:"pageSize"`
+}
+
+type WebsiteGetResponse struct {
+ apiResponseBase
+
+ Data *struct {
+ ID int64 `json:"id"`
+ Alias string `json:"alias"`
+ PrimaryDomain string `json:"primaryDomain"`
+ Protocol string `json:"protocol"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ SitePath string `json:"sitePath"`
+ Remark string `json:"remark"`
+ Domains []*struct {
+ ID int64 `json:"id"`
+ Domain string `json:"domain"`
+ Port int32 `json:"port"`
+ SSL bool `json:"ssl"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"domains,omitempty"`
+ WebsiteSSLId int64 `json:"webSiteSSLId"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"data,omitempty"`
+}
+
+func (c *Client) WebsiteGet(websiteId int64) (*WebsiteGetResponse, error) {
+ return c.WebsiteGetWithContext(context.Background(), websiteId)
+}
+
+func (c *Client) WebsiteGetWithContext(ctx context.Context, websiteId int64) (*WebsiteGetResponse, error) {
+ if websiteId == 0 {
+ return nil, fmt.Errorf("sdkerr: unset websiteId")
+ }
+
+ httpreq, err := c.newRequest(http.MethodGet, fmt.Sprintf("/websites/%d", websiteId))
+ if err != nil {
+ return nil, err
+ } else {
+ httpreq.SetContext(ctx)
+ }
+
+ result := &WebsiteGetResponse{}
+ if _, err := c.doRequestWithResult(httpreq, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/pkg/sdk3rd/1panel/v2/api_website_https_get.go b/pkg/sdk3rd/1panel/v2/api_website_https_get.go
index 757077f9..a18a2c13 100644
--- a/pkg/sdk3rd/1panel/v2/api_website_https_get.go
+++ b/pkg/sdk3rd/1panel/v2/api_website_https_get.go
@@ -10,11 +10,13 @@ type WebsiteHttpsGetResponse struct {
apiResponseBase
Data *struct {
- Enable bool `json:"enable"`
- HttpConfig string `json:"httpConfig"`
- SSLProtocol []string `json:"SSLProtocol"`
- Algorithm string `json:"algorithm"`
- Hsts bool `json:"hsts"`
+ Enable bool `json:"enable"`
+ HttpConfig string `json:"httpConfig"`
+ WebsiteSSLID int64 `json:"websiteSSLId"`
+ SSLProtocol []string `json:"SSLProtocol"`
+ Algorithm string `json:"algorithm"`
+ Hsts bool `json:"hsts"`
+ Http3 bool `json:"http3"`
} `json:"data,omitempty"`
}
diff --git a/pkg/sdk3rd/1panel/v2/api_website_https_post.go b/pkg/sdk3rd/1panel/v2/api_website_https_post.go
index de05a08c..1b96e409 100644
--- a/pkg/sdk3rd/1panel/v2/api_website_https_post.go
+++ b/pkg/sdk3rd/1panel/v2/api_website_https_post.go
@@ -7,19 +7,15 @@ import (
)
type WebsiteHttpsPostRequest 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"`
+ WebsiteID int64 `json:"websiteId"`
+ Enable bool `json:"enable"`
+ Type string `json:"type"`
+ WebsiteSSLID int64 `json:"websiteSSLId"`
+ HttpConfig string `json:"httpConfig"`
+ SSLProtocol []string `json:"SSLProtocol"`
+ Algorithm string `json:"algorithm"`
+ Hsts bool `json:"hsts"`
+ Http3 bool `json:"http3"`
}
type WebsiteHttpsPostResponse struct {
diff --git a/pkg/sdk3rd/1panel/v2/api_website_search.go b/pkg/sdk3rd/1panel/v2/api_website_search.go
new file mode 100644
index 00000000..7bc5706b
--- /dev/null
+++ b/pkg/sdk3rd/1panel/v2/api_website_search.go
@@ -0,0 +1,58 @@
+package v2
+
+import (
+ "context"
+ "net/http"
+)
+
+type WebsiteSearchRequest struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Order string `json:"order"`
+ OrderBy string `json:"orderBy"`
+ Page int32 `json:"page"`
+ PageSize int32 `json:"pageSize"`
+}
+
+type WebsiteSearchResponse struct {
+ apiResponseBase
+
+ Data *struct {
+ Items []*struct {
+ ID int64 `json:"id"`
+ Alias string `json:"alias"`
+ PrimaryDomain string `json:"primaryDomain"`
+ Protocol string `json:"protocol"`
+ Type string `json:"type"`
+ Status string `json:"status"`
+ SitePath string `json:"sitePath"`
+ Remark string `json:"remark"`
+ SSLStatus string `json:"sslStatus"`
+ SSLExpireDate string `json:"sslExpireDate"`
+ UpdatedAt string `json:"updatedAt"`
+ CreatedAt string `json:"createdAt"`
+ } `json:"items"`
+ Total int32 `json:"total"`
+ } `json:"data,omitempty"`
+}
+
+func (c *Client) WebsiteSearch(req *WebsiteSearchRequest) (*WebsiteSearchResponse, error) {
+ return c.WebsiteSearchWithContext(context.Background(), req)
+}
+
+func (c *Client) WebsiteSearchWithContext(ctx context.Context, req *WebsiteSearchRequest) (*WebsiteSearchResponse, error) {
+ httpreq, err := c.newRequest(http.MethodPost, "/websites/search")
+ if err != nil {
+ return nil, err
+ } else {
+ httpreq.SetBody(req)
+ httpreq.SetContext(ctx)
+ }
+
+ result := &WebsiteSearchResponse{}
+ if _, err := c.doRequestWithResult(httpreq, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider1PanelSite.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider1PanelSite.tsx
index 76e2d9b7..7567d7ea 100644
--- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider1PanelSite.tsx
+++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider1PanelSite.tsx
@@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
-import { Form, Input, Select } from "antd";
+import { Form, Input, Radio, Select } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@@ -10,6 +10,9 @@ import { useFormNestedFieldsContext } from "./_context";
const RESOURCE_TYPE_WEBSITE = "website" as const;
const RESOURCE_TYPE_CERTIFICATE = "certificate" as const;
+const WEBSITE_MATCH_PATTERN_SPECIFIED = "specified" as const;
+const WEBSITE_MATCH_PATTERN_CERTSAN = "certsan" as const;
+
const BizDeployNodeConfigFieldsProvider1PanelSite = () => {
const { i18n, t } = useTranslation();
@@ -22,6 +25,7 @@ const BizDeployNodeConfigFieldsProvider1PanelSite = () => {
const initialValues = getInitialValues();
const fieldResourceType = Form.useWatch([parentNamePath, "resourceType"], formInst);
+ const fieldWebsiteMatchPattern = Form.useWatch([parentNamePath, "websiteMatchPattern"], { form: formInst, preserve: true });
return (
<>
@@ -53,14 +57,38 @@ const BizDeployNodeConfigFieldsProvider1PanelSite = () => {
+ ) : (
+ void 0
+ )
+ }
rules={[formRule]}
- tooltip={}
>
-
+ ({
+ key: s,
+ label: t(`workflow_node.deploy.form.1panel_site_website_match_pattern.option.${s}.label`),
+ value: s,
+ }))}
+ />
+
+
+ }
+ >
+
+
+
@@ -81,6 +109,8 @@ const BizDeployNodeConfigFieldsProvider1PanelSite = () => {
const getInitialValues = (): Nullish>> => {
return {
resourceType: RESOURCE_TYPE_WEBSITE,
+ websiteMatchPattern: WEBSITE_MATCH_PATTERN_SPECIFIED,
+ websiteId: "",
};
};
@@ -91,6 +121,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType })
.object({
nodeName: z.string().nullish(),
resourceType: z.literal([RESOURCE_TYPE_WEBSITE, RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.shared_resource_type.placeholder")),
+ websiteMatchPattern: z.string().nullish(),
websiteId: z.union([z.string(), z.number()]).nullish(),
certificateId: z.union([z.string(), z.number()]).nullish(),
})
@@ -98,12 +129,26 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType })
switch (values.resourceType) {
case RESOURCE_TYPE_WEBSITE:
{
- const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.websiteId);
- if (!res.success) {
+ if (values.websiteMatchPattern) {
+ switch (values.websiteMatchPattern) {
+ case WEBSITE_MATCH_PATTERN_SPECIFIED:
+ {
+ const res = z.preprocess((v) => Number(v), z.number().int().positive()).safeParse(values.websiteId);
+ if (!res.success) {
+ ctx.addIssue({
+ code: "custom",
+ message: t("workflow_node.deploy.form.1panel_site_website_id.placeholder"),
+ path: ["websiteId"],
+ });
+ }
+ }
+ break;
+ }
+ } else {
ctx.addIssue({
code: "custom",
- message: t("workflow_node.deploy.form.1panel_site_website_id.placeholder"),
- path: ["websiteId"],
+ message: t("workflow_node.deploy.form.1panel_site_website_match_pattern.placeholder"),
+ path: ["websiteMatchPattern"],
});
}
}
diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json
index 5d081d57..b0b93d1f 100644
--- a/ui/src/i18n/locales/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json
@@ -203,15 +203,20 @@
"workflow_node.deploy.form.1panel_site_node_name.placeholder": "Please enter 1Panel node name",
"workflow_node.deploy.form.1panel_site_node_name.help": "Notes: It is only used for 1Panel v2+.",
"workflow_node.deploy.form.1panel_site_node_name.tooltip": "You can find it on 1Panel dashboard.",
- "workflow_node.deploy.form.aliyun_alb_region.label": "Alibaba Cloud region",
"workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "Website",
"workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "Certificate",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.label": "Website match pattern",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.placeholder": "Please select website match pattern",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.option.specified.label": "Specified ID",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.option.certsan.label": "via Certificate",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.help_certsan": "Notes: The website name should be a domain name and include SSL configurations.",
"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 dashboard.",
"workflow_node.deploy.form.1panel_site_certificate_id.label": "1Panel certificate ID",
"workflow_node.deploy.form.1panel_site_certificate_id.placeholder": "Please enter 1Panel certificate ID",
"workflow_node.deploy.form.1panel_site_certificate_id.tooltip": "You can find it on 1Panel dashboard.",
+ "workflow_node.deploy.form.aliyun_alb_region.label": "Alibaba Cloud region",
"workflow_node.deploy.form.aliyun_alb_region.placeholder": "Please enter Alibaba Cloud ALB region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_alb_region.tooltip": "For more information, see https://www.alibabacloud.com/help/en/slb/application-load-balancer/product-overview/supported-regions-and-zones",
"workflow_node.deploy.form.aliyun_alb_resource_type.option.loadbalancer.label": "ALB load balancer",
diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
index 167ea5d2..5cc2f784 100644
--- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json
@@ -204,6 +204,11 @@
"workflow_node.deploy.form.1panel_site_node_name.tooltip": "请登录 1Panel 面板查看",
"workflow_node.deploy.form.1panel_site_resource_type.option.website.label": "部署到指定网站",
"workflow_node.deploy.form.1panel_site_resource_type.option.certificate.label": "替换指定证书",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.label": "网站匹配模式",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.placeholder": "请选择部署网站匹配模式",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.option.specified.label": "指定 ID",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.option.certsan.label": "根据证书自动匹配",
+ "workflow_node.deploy.form.1panel_site_website_match_pattern.help_certsan": "注意:网站名称需要为域名、且包含开启了 SSL 的域名配置。",
"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 面板查看",