diff --git a/internal/certmgmt/deployers/sp_proxmoxbs.go b/internal/certmgmt/deployers/sp_proxmoxbs.go
new file mode 100644
index 000000000..0d3e99044
--- /dev/null
+++ b/internal/certmgmt/deployers/sp_proxmoxbs.go
@@ -0,0 +1,29 @@
+package deployers
+
+import (
+ "fmt"
+
+ "github.com/certimate-go/certimate/internal/domain"
+ "github.com/certimate-go/certimate/pkg/core"
+ dplyimpl "github.com/certimate-go/certimate/pkg/core/deployer/providers/proxmoxbs"
+ xmaps "github.com/certimate-go/certimate/pkg/utils/maps"
+)
+
+func init() {
+ Registries.MustRegister(domain.DeploymentProviderTypeProxmoxBS, func(options *ProviderFactoryOptions) (core.Deployer, error) {
+ credentials := domain.AccessConfigForProxmoxBS{}
+ if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil {
+ return nil, fmt.Errorf("failed to populate provider access config: %w", err)
+ }
+
+ provider, err := dplyimpl.NewDeployer(&dplyimpl.DeployerConfig{
+ ServerUrl: credentials.ServerUrl,
+ ApiToken: credentials.ApiToken,
+ ApiTokenSecret: credentials.ApiTokenSecret,
+ AllowInsecureConnections: credentials.AllowInsecureConnections,
+ NodeName: xmaps.GetString(options.ProviderExtendedConfig, "nodeName"),
+ AutoRestart: xmaps.GetBool(options.ProviderExtendedConfig, "autoRestart"),
+ })
+ return provider, err
+ })
+}
diff --git a/internal/domain/access.go b/internal/domain/access.go
index a2ddf4487..682277f19 100644
--- a/internal/domain/access.go
+++ b/internal/domain/access.go
@@ -491,6 +491,13 @@ type AccessConfigForPowerDNS struct {
AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
}
+type AccessConfigForProxmoxBS struct {
+ ServerUrl string `json:"serverUrl"`
+ ApiToken string `json:"apiToken"`
+ ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
+ AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
+}
+
type AccessConfigForProxmoxVE struct {
ServerUrl string `json:"serverUrl"`
ApiToken string `json:"apiToken"`
diff --git a/internal/domain/provider.go b/internal/domain/provider.go
index 51931f0b6..ac6aba95f 100644
--- a/internal/domain/provider.go
+++ b/internal/domain/provider.go
@@ -104,6 +104,7 @@ const (
AccessProviderTypeOVHcloud = AccessProviderType("ovhcloud")
AccessProviderTypePorkbun = AccessProviderType("porkbun")
AccessProviderTypePowerDNS = AccessProviderType("powerdns")
+ AccessProviderTypeProxmoxBS = AccessProviderType("proxmoxbs")
AccessProviderTypeProxmoxVE = AccessProviderType("proxmoxve")
AccessProviderTypeQiniu = AccessProviderType("qiniu")
AccessProviderTypeQingCloud = AccessProviderType("qingcloud")
@@ -413,6 +414,7 @@ const (
DeploymentProviderTypeNetlify = DeploymentProviderType(AccessProviderTypeNetlify)
DeploymentProviderTypeNginxProxyManager = DeploymentProviderType(AccessProviderTypeNginxProxyManager)
DeploymentProviderTypeOracleCloudCertificatesMgmt = DeploymentProviderType(AccessProviderTypeOracleCloud + "-certificatesmgmt")
+ DeploymentProviderTypeProxmoxBS = DeploymentProviderType(AccessProviderTypeProxmoxBS)
DeploymentProviderTypeProxmoxVE = DeploymentProviderType(AccessProviderTypeProxmoxVE)
DeploymentProviderTypeQingCloudLB = DeploymentProviderType(AccessProviderTypeQingCloud + "-lb")
DeploymentProviderTypeQiniuCDN = DeploymentProviderType(AccessProviderTypeQiniu + "-cdn")
diff --git a/pkg/core/deployer/providers/proxmoxbs/proxmoxbs.go b/pkg/core/deployer/providers/proxmoxbs/proxmoxbs.go
new file mode 100644
index 000000000..a64f9ebce
--- /dev/null
+++ b/pkg/core/deployer/providers/proxmoxbs/proxmoxbs.go
@@ -0,0 +1,102 @@
+package proxmoxbs
+
+import (
+ "context"
+ "crypto/tls"
+ "fmt"
+ "log/slog"
+
+ "github.com/certimate-go/certimate/pkg/core"
+ pbssdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxbs"
+)
+
+type (
+ Provider = core.Deployer
+ DeployResult = core.DeployerDeployResult
+)
+
+type DeployerConfig struct {
+ // Proxmox BS 服务地址。
+ ServerUrl string `json:"serverUrl"`
+ // Proxmox BS API Token。
+ ApiToken string `json:"apiToken"`
+ // Proxmox BS API Token Secret。
+ ApiTokenSecret string `json:"apiTokenSecret,omitempty"`
+ // 是否允许不安全的连接。
+ AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"`
+ // 节点名称。
+ NodeName string `json:"nodeName"`
+ // 是否自动重启。
+ AutoRestart bool `json:"autoRestart"`
+}
+
+type Deployer struct {
+ config *DeployerConfig
+ logger *slog.Logger
+ sdkClient *pbssdk.Client
+}
+
+var _ Provider = (*Deployer)(nil)
+
+func NewDeployer(config *DeployerConfig) (*Deployer, error) {
+ if config == nil {
+ return nil, fmt.Errorf("the configuration of the deployer provider is nil")
+ }
+
+ client, err := createSDKClient(config.ServerUrl, config.ApiToken, config.ApiTokenSecret, config.AllowInsecureConnections)
+ if err != nil {
+ return nil, fmt.Errorf("could not create client: %w", err)
+ }
+
+ return &Deployer{
+ config: config,
+ logger: slog.Default(),
+ sdkClient: client,
+ }, nil
+}
+
+func (d *Deployer) SetLogger(logger *slog.Logger) {
+ if logger == nil {
+ d.logger = slog.New(slog.DiscardHandler)
+ } else {
+ d.logger = logger
+ }
+}
+
+func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*DeployResult, error) {
+ if d.config.NodeName == "" {
+ return nil, fmt.Errorf("config `nodeName` is required")
+ }
+
+ // 上传自定义证书
+ // REF: https://pbs.proxmox.com/docs/api-viewer/index.html#/nodes/{node}/certificates/custom
+ nodeUploadCustomCertificateReq := &pbssdk.NodeUploadCustomCertificateRequest{
+ Certificates: certPEM,
+ Key: privkeyPEM,
+ Force: true,
+ Restart: d.config.AutoRestart,
+ }
+ nodeUploadCustomCertificateResp, err := d.sdkClient.NodeUploadCustomCertificateWithContext(ctx, d.config.NodeName, nodeUploadCustomCertificateReq)
+ d.logger.Debug("sdk request 'node.UploadCustomCertificate'", slog.String("params.node", d.config.NodeName), slog.Any("request", nodeUploadCustomCertificateReq), slog.Any("response", nodeUploadCustomCertificateResp))
+ if err != nil {
+ return nil, fmt.Errorf("failed to execute sdk request 'node.UploadCustomCertificate': %w", err)
+ }
+
+ return &DeployResult{}, nil
+}
+
+func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*pbssdk.Client, error) {
+ client, err := pbssdk.NewClient(
+ serverUrl,
+ pbssdk.WithApiToken(apiToken, apiTokenSecret),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ if skipTlsVerify {
+ client.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})
+ }
+
+ return client, nil
+}
diff --git a/pkg/core/deployer/providers/proxmoxbs/proxmoxbs_test.go b/pkg/core/deployer/providers/proxmoxbs/proxmoxbs_test.go
new file mode 100644
index 000000000..87ba5ca9e
--- /dev/null
+++ b/pkg/core/deployer/providers/proxmoxbs/proxmoxbs_test.go
@@ -0,0 +1,58 @@
+package proxmoxbs_test
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ impl "github.com/certimate-go/certimate/pkg/core/deployer/providers/proxmoxbs"
+ it "github.com/certimate-go/certimate/pkg/core/deployer/testing"
+)
+
+var (
+ fp = it.Args("PROXMOXBS_")
+ fTestCertPath string
+ fTestKeyPath string
+ fServerUrl string
+ fApiToken string
+ fApiTokenSecret string
+ fNodeName string
+)
+
+func init() {
+ fp.DefineString(&fTestCertPath, "TESTCERTPATH")
+ fp.DefineString(&fTestKeyPath, "TESTKEYPATH")
+ fp.DefineString(&fServerUrl, "SERVERURL")
+ fp.DefineString(&fApiToken, "APITOKEN")
+ fp.DefineString(&fApiTokenSecret, "APITOKENSECRET")
+ fp.DefineString(&fNodeName, "NODENAME")
+}
+
+/*
+Shell command to run this test:
+
+ go test -v ./proxmoxbs_test.go -args \
+ --PROXMOXBS_TESTCERTPATH="/path/to/your-test-cert.pem" \
+ --PROXMOXBS_TESTKEYPATH="/path/to/your-test-key.pem" \
+ --PROXMOXBS_SERVERURL="http://127.0.0.1:8007" \
+ --PROXMOXBS_APITOKEN="your-api-token" \
+ --PROXMOXBS_APITOKENSECRET="your-api-token-secret" \
+ --PROXMOXBS_NODENAME="your-node-name"
+*/
+func TestProvider(t *testing.T) {
+ fp.Parse()
+
+ t.Run("Deploy", func(t *testing.T) {
+ provider, err := impl.NewDeployer(&impl.DeployerConfig{
+ ServerUrl: fServerUrl,
+ ApiToken: fApiToken,
+ ApiTokenSecret: fApiTokenSecret,
+ AllowInsecureConnections: true,
+ NodeName: fNodeName,
+ AutoRestart: true,
+ })
+ require.NoError(t, err)
+
+ it.TestDeploy(t, provider, it.TestDeployArgs{CertPath: fTestCertPath, KeyPath: fTestKeyPath})
+ })
+}
diff --git a/pkg/core/deployer/providers/proxmoxve/proxmoxve.go b/pkg/core/deployer/providers/proxmoxve/proxmoxve.go
index fdc5987ec..1210a390b 100644
--- a/pkg/core/deployer/providers/proxmoxve/proxmoxve.go
+++ b/pkg/core/deployer/providers/proxmoxve/proxmoxve.go
@@ -7,7 +7,7 @@ import (
"log/slog"
"github.com/certimate-go/certimate/pkg/core"
- proxmoxvesdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxve"
+ pvesdk "github.com/certimate-go/certimate/pkg/sdk3rd/proxmoxve"
)
type (
@@ -33,7 +33,7 @@ type DeployerConfig struct {
type Deployer struct {
config *DeployerConfig
logger *slog.Logger
- sdkClient *proxmoxvesdk.Client
+ sdkClient *pvesdk.Client
}
var _ Provider = (*Deployer)(nil)
@@ -70,7 +70,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
// 上传自定义证书
// REF: https://pve.proxmox.com/pve-docs/api-viewer/index.html#/nodes/{node}/certificates/custom
- nodeUploadCustomCertificateReq := &proxmoxvesdk.NodeUploadCustomCertificateRequest{
+ nodeUploadCustomCertificateReq := &pvesdk.NodeUploadCustomCertificateRequest{
Certificates: certPEM,
Key: privkeyPEM,
Force: true,
@@ -85,9 +85,10 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep
return &DeployResult{}, nil
}
-func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*proxmoxvesdk.Client, error) {
- client, err := proxmoxvesdk.NewClient(serverUrl,
- proxmoxvesdk.WithApiToken(apiToken, apiTokenSecret),
+func createSDKClient(serverUrl, apiToken, apiTokenSecret string, skipTlsVerify bool) (*pvesdk.Client, error) {
+ client, err := pvesdk.NewClient(
+ serverUrl,
+ pvesdk.WithApiToken(apiToken, apiTokenSecret),
)
if err != nil {
return nil, err
diff --git a/pkg/sdk3rd/proxmoxbs/api_node_upload_custom_certificate.go b/pkg/sdk3rd/proxmoxbs/api_node_upload_custom_certificate.go
new file mode 100644
index 000000000..abb43e1de
--- /dev/null
+++ b/pkg/sdk3rd/proxmoxbs/api_node_upload_custom_certificate.go
@@ -0,0 +1,47 @@
+package proxmoxbs
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+)
+
+type NodeUploadCustomCertificateRequest CustomCertificate
+
+type NodeUploadCustomCertificateResponse struct {
+ Data []struct {
+ FileName string `json:"filename,omitempty"`
+ Fingerprint string `json:"fingerprint,omitempty"`
+ Subject string `json:"subject,omitempty"`
+ Issuer string `json:"issuer,omitempty"`
+ SAN []string `json:"san,omitempty"`
+ NotAfter int64 `json:"notafter,omitempty"`
+ NotBefore int64 `json:"notbefore,omitempty"`
+ } `json:"data,omitempty"`
+}
+
+func (c *Client) NodeUploadCustomCertificate(node string, req *NodeUploadCustomCertificateRequest) (*NodeUploadCustomCertificateResponse, error) {
+ return c.NodeUploadCustomCertificateWithContext(context.Background(), node, req)
+}
+
+func (c *Client) NodeUploadCustomCertificateWithContext(ctx context.Context, node string, req *NodeUploadCustomCertificateRequest) (*NodeUploadCustomCertificateResponse, error) {
+ if node == "" {
+ return nil, fmt.Errorf("sdkerr: bad request: unset node")
+ }
+
+ path := fmt.Sprintf("/nodes/%s/certificates/custom", node)
+ httpreq, err := c.newRequest(http.MethodPost, path)
+ if err != nil {
+ return nil, err
+ } else {
+ httpreq.SetBody(req)
+ httpreq.SetContext(ctx)
+ }
+
+ result := &NodeUploadCustomCertificateResponse{}
+ if _, err := c.doRequestWithResult(httpreq, result); err != nil {
+ return result, err
+ }
+
+ return result, nil
+}
diff --git a/pkg/sdk3rd/proxmoxbs/client.go b/pkg/sdk3rd/proxmoxbs/client.go
new file mode 100644
index 000000000..0c4740e1c
--- /dev/null
+++ b/pkg/sdk3rd/proxmoxbs/client.go
@@ -0,0 +1,113 @@
+// A simple SDK client for Proxmox Backup Server.
+// API documentation: https://pbs.proxmox.com/docs/api-viewer/
+package proxmoxbs
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/go-resty/resty/v2"
+
+ "github.com/certimate-go/certimate/internal/app"
+)
+
+type Client struct {
+ rc *resty.Client
+}
+
+func NewClient(serverUrl string, optFns ...OptionsFunc) (*Client, error) {
+ opts := &Options{}
+ for _, fn := range optFns {
+ fn(opts)
+ }
+
+ if serverUrl == "" {
+ return nil, fmt.Errorf("sdkerr: unset serverUrl")
+ }
+ if _, err := url.Parse(serverUrl); err != nil {
+ return nil, fmt.Errorf("sdkerr: invalid serverUrl: %w", err)
+ }
+ if opts.TokenId == "" {
+ return nil, fmt.Errorf("sdkerr: unset token")
+ }
+ if opts.TokenSecret == "" {
+ return nil, fmt.Errorf("sdkerr: unset tokenSecret")
+ }
+
+ httper := resty.New().
+ SetBaseURL(strings.TrimSuffix(serverUrl, "/")+"/api2/json").
+ SetHeader("Accept", "application/json").
+ SetHeader("Authorization", fmt.Sprintf("PBSAPIToken=%s:%s", opts.TokenId, opts.TokenSecret)).
+ SetHeader("Content-Type", "application/json").
+ SetHeader("User-Agent", app.AppUserAgent)
+
+ return &Client{rc: httper}, nil
+}
+
+func (c *Client) SetTimeout(timeout time.Duration) *Client {
+ c.rc.SetTimeout(timeout)
+ return c
+}
+
+func (c *Client) SetTLSConfig(config *tls.Config) *Client {
+ c.rc.SetTLSClientConfig(config)
+ return c
+}
+
+func (c *Client) newRequest(method string, path string) (*resty.Request, error) {
+ if method == "" {
+ return nil, fmt.Errorf("sdkerr: unset method")
+ }
+ if path == "" {
+ return nil, fmt.Errorf("sdkerr: unset path")
+ }
+
+ req := c.rc.R()
+ req.Method = method
+ req.URL = path
+
+ // WARN:
+ // DO NOT CALL `req.SetResult` or `req.SetError` AGAIN! USE `doRequestWithResult` INSTEAD.
+ return req, nil
+}
+
+func (c *Client) doRequest(req *resty.Request) (*resty.Response, error) {
+ if req == nil {
+ return nil, fmt.Errorf("sdkerr: nil request")
+ }
+
+ resp, err := req.Send()
+ if err != nil {
+ return resp, fmt.Errorf("sdkerr: failed to send request: %w", err)
+ } else if resp.IsError() {
+ return resp, fmt.Errorf("sdkerr: unexpected status code: %d (resp: %s)", resp.StatusCode(), resp.String())
+ }
+
+ return resp, nil
+}
+
+func (c *Client) doRequestWithResult(req *resty.Request, res any) (*resty.Response, error) {
+ if req == nil {
+ return nil, fmt.Errorf("sdkerr: nil request")
+ }
+
+ resp, err := c.doRequest(req)
+ if err != nil {
+ if resp != nil {
+ json.Unmarshal(resp.Body(), &res)
+ }
+ return resp, err
+ }
+
+ if len(resp.Body()) != 0 {
+ if err := json.Unmarshal(resp.Body(), &res); err != nil {
+ return resp, fmt.Errorf("sdkerr: failed to unmarshal response: %w (resp: %s)", err, resp.String())
+ }
+ }
+
+ return resp, nil
+}
diff --git a/pkg/sdk3rd/proxmoxbs/models.go b/pkg/sdk3rd/proxmoxbs/models.go
new file mode 100644
index 000000000..acef210c1
--- /dev/null
+++ b/pkg/sdk3rd/proxmoxbs/models.go
@@ -0,0 +1,8 @@
+package proxmoxbs
+
+type CustomCertificate struct {
+ Certificates string `json:"certificates,omitempty"`
+ Force bool `json:"force,omitempty"`
+ Key string `json:"key,omitempty"`
+ Restart bool `json:"restart,omitempty"`
+}
diff --git a/pkg/sdk3rd/proxmoxbs/options.go b/pkg/sdk3rd/proxmoxbs/options.go
new file mode 100644
index 000000000..532bc4e40
--- /dev/null
+++ b/pkg/sdk3rd/proxmoxbs/options.go
@@ -0,0 +1,15 @@
+package proxmoxbs
+
+type Options struct {
+ TokenId string
+ TokenSecret string
+}
+
+type OptionsFunc func(*Options)
+
+func WithApiToken(tokenId string, tokenSecret string) OptionsFunc {
+ return func(o *Options) {
+ o.TokenId = tokenId
+ o.TokenSecret = tokenSecret
+ }
+}
diff --git a/pkg/sdk3rd/proxmoxve/client.go b/pkg/sdk3rd/proxmoxve/client.go
index d0c8618d1..da9a8bb1f 100644
--- a/pkg/sdk3rd/proxmoxve/client.go
+++ b/pkg/sdk3rd/proxmoxve/client.go
@@ -1,4 +1,4 @@
-// A simple SDK client for Proxmox VE.
+// A simple SDK client for Proxmox Virtual Environment.
// API documentation: https://pve.proxmox.com/pve-docs/api-viewer/index.html
package proxmoxve
diff --git a/ui/public/imgs/providers/proxmoxbs.svg b/ui/public/imgs/providers/proxmoxbs.svg
new file mode 100644
index 000000000..f99099155
--- /dev/null
+++ b/ui/public/imgs/providers/proxmoxbs.svg
@@ -0,0 +1 @@
+
diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
index 376ebe3db..2506154d4 100644
--- a/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
+++ b/ui/src/components/access/forms/AccessConfigFieldsProvider.tsx
@@ -88,6 +88,7 @@ import AccessConfigFieldsProviderOracleCloud from "./AccessConfigFieldsProviderO
import AccessConfigFieldsProviderOVHcloud from "./AccessConfigFieldsProviderOVHcloud";
import AccessConfigFieldsProviderPorkbun from "./AccessConfigFieldsProviderPorkbun";
import AccessConfigFieldsProviderPowerDNS from "./AccessConfigFieldsProviderPowerDNS";
+import AccessConfigFieldsProviderProxmoxBS from "./AccessConfigFieldsProviderProxmoxBS";
import AccessConfigFieldsProviderProxmoxVE from "./AccessConfigFieldsProviderProxmoxVE";
import AccessConfigFieldsProviderQingCloud from "./AccessConfigFieldsProviderQingCloud";
import AccessConfigFieldsProviderQiniu from "./AccessConfigFieldsProviderQiniu";
@@ -216,6 +217,7 @@ const providerComponentMap: Partial {
+ const { i18n, t } = useTranslation();
+
+ const { parentNamePath } = useFormNestedFieldsContext();
+ const formSchema = z.object({
+ [parentNamePath]: getSchema({ i18n }),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+ const initialValues = getInitialValues();
+
+ return (
+ <>
+
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+
+
+
+ >
+ );
+};
+
+const getInitialValues = (): Nullish>> => {
+ return {
+ serverUrl: "http://:8007/",
+ apiToken: "",
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => {
+ const { t: _ } = i18n;
+
+ return z.object({
+ serverUrl: z.url({ protocol: z.core.regexes.httpProtocol }),
+ apiToken: z.string().nonempty(),
+ apiTokenSecret: z.string().nullish(),
+ allowInsecureConnections: z.boolean().nullish(),
+ });
+};
+
+const _default = Object.assign(AccessConfigFormFieldsProviderProxmoxBS, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx
index bc8fc8273..9c1d4e157 100644
--- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx
+++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProvider.tsx
@@ -90,6 +90,7 @@ import BizDeployNodeConfigFieldsProviderMohuaMVH from "./BizDeployNodeConfigFiel
import BizDeployNodeConfigFieldsProviderNetlify from "./BizDeployNodeConfigFieldsProviderNetlify";
import BizDeployNodeConfigFieldsProviderNginxProxyManager from "./BizDeployNodeConfigFieldsProviderNginxProxyManager";
import BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt from "./BizDeployNodeConfigFieldsProviderOracleCloudCertificatesMgmt";
+import BizDeployNodeConfigFieldsProviderProxmoxBS from "./BizDeployNodeConfigFieldsProviderProxmoxBS";
import BizDeployNodeConfigFieldsProviderProxmoxVE from "./BizDeployNodeConfigFieldsProviderProxmoxVE";
import BizDeployNodeConfigFieldsProviderQingCloudLB from "./BizDeployNodeConfigFieldsProviderQingCloudLB";
import BizDeployNodeConfigFieldsProviderQiniuCDN from "./BizDeployNodeConfigFieldsProviderQiniuCDN";
@@ -241,6 +242,7 @@ const providerComponentMap: Partial {
+ const { i18n, t } = useTranslation();
+
+ const { parentNamePath } = useFormNestedFieldsContext();
+ const formSchema = z.object({
+ [parentNamePath]: getSchema({ i18n }),
+ });
+ const formRule = createSchemaFieldRule(formSchema);
+ const initialValues = getInitialValues();
+
+ return (
+ <>
+
+ } />
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const getInitialValues = (): Nullish>> => {
+ return {
+ nodeName: "",
+ autoRestart: true,
+ };
+};
+
+const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) => {
+ const { t: _ } = i18n;
+
+ return z.object({
+ nodeName: z.string().nonempty(),
+ autoRestart: z.boolean().nullish(),
+ });
+};
+
+const _default = Object.assign(BizDeployNodeConfigFieldsProviderProxmoxBS, {
+ getInitialValues,
+ getSchema,
+});
+
+export default _default;
diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts
index ee113aab6..1b0861d25 100644
--- a/ui/src/domain/provider.ts
+++ b/ui/src/domain/provider.ts
@@ -106,6 +106,7 @@ export const ACCESS_PROVIDERS = Object.freeze({
OVHCLOUD: "ovhcloud",
PORKBUN: "porkbun",
POWERDNS: "powerdns",
+ PROXMOXBS: "proxmoxbs",
PROXMOXVE: "proxmoxve",
QINGCLOUD: "qingcloud",
QINIU: "qiniu",
@@ -223,6 +224,7 @@ export const accessProvidersMap: Map
diff --git a/ui/src/i18n/resources/en/nls.access.json b/ui/src/i18n/resources/en/nls.access.json
index 8dbb343a5..a14aafeee 100644
--- a/ui/src/i18n/resources/en/nls.access.json
+++ b/ui/src/i18n/resources/en/nls.access.json
@@ -1136,6 +1136,20 @@
"placeholder": "Please enter Proxmox VE API token secret",
"tooltip": "For more information, see https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens"
},
+ "proxmoxbs_server_url": {
+ "label": "Proxmox BS server URL",
+ "placeholder": "Please enter Proxmox BS server URL"
+ },
+ "proxmoxbs_api_token": {
+ "label": "Proxmox BS API token",
+ "placeholder": "Please enter Proxmox BS API token",
+ "tooltip": "For more information, see https://pbs.proxmox.com/docs/user-management.html#api-tokens"
+ },
+ "proxmoxbs_api_token_secret": {
+ "label": "Proxmox BS API token secret (Optional)",
+ "placeholder": "Please enter Proxmox BS API token secret",
+ "tooltip": "For more information, see https://pbs.proxmox.com/docs/user-management.html#api-tokens"
+ },
"qingcloud_access_key_id": {
"label": "QingCloud AccessKeyID",
"placeholder": "Please enter QingCloud AccessKeyID",
diff --git a/ui/src/i18n/resources/en/nls.provider.json b/ui/src/i18n/resources/en/nls.provider.json
index a12c53bae..6bde52d08 100644
--- a/ui/src/i18n/resources/en/nls.provider.json
+++ b/ui/src/i18n/resources/en/nls.provider.json
@@ -213,7 +213,8 @@
"ovhcloud": "OVHcloud",
"porkbun": "Porkbun",
"powerdns": "PowerDNS",
- "proxmoxve": "Proxmox VE",
+ "proxmoxve": "PVE (Proxmox Virtual Environment)",
+ "proxmoxbs": "PBS (Proxmox Backup Server)",
"qingcloud": "QingCloud",
"qingcloud_dns": "QingCloud - DNS",
"qingcloud_lb": "QingCloud - LBC (Load Balancer Cluster)",
diff --git a/ui/src/i18n/resources/en/nls.workflow.nodes.json b/ui/src/i18n/resources/en/nls.workflow.nodes.json
index 23e7b79e3..f6799ec9d 100644
--- a/ui/src/i18n/resources/en/nls.workflow.nodes.json
+++ b/ui/src/i18n/resources/en/nls.workflow.nodes.json
@@ -2020,7 +2020,7 @@
"tooltip": "For more information, see https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm"
},
"proxmoxve": {
- "guide": "Requires Proxmox VE v5.2 or higher."
+ "guide": "Requires Proxmox Virtual Environment v5.2 or higher."
},
"proxmoxve_node_name": {
"label": "Proxmox VE cluster node name",
@@ -2029,6 +2029,16 @@
"proxmoxve_auto_restart": {
"label": "Auto restart Proxmox VE after deployment"
},
+ "proxmoxbs": {
+ "guide": "Requires Proxmox Backup Server v2.1 or higher."
+ },
+ "proxmoxbs_node_name": {
+ "label": "Proxmox BS node name",
+ "placeholder": "Please enter Proxmox BS node name"
+ },
+ "proxmoxbs_auto_restart": {
+ "label": "Auto restart Proxmox BS after deployment"
+ },
"qingcloud_lb_zone_id": {
"label": "QingCloud zone ID",
"placeholder": "Please enter QingCloud LBC zone ID (e.g. pek3a)",
diff --git a/ui/src/i18n/resources/zh/nls.access.json b/ui/src/i18n/resources/zh/nls.access.json
index ad045dee6..090557e15 100644
--- a/ui/src/i18n/resources/zh/nls.access.json
+++ b/ui/src/i18n/resources/zh/nls.access.json
@@ -1136,6 +1136,20 @@
"placeholder": "请输入 Proxmox VE API Token Secret",
"tooltip": "这是什么?请参阅 https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_tokens"
},
+ "proxmoxbs_server_url": {
+ "label": "Proxmox BS 服务地址",
+ "placeholder": "请输入 Proxmox BS 服务地址"
+ },
+ "proxmoxbs_api_token": {
+ "label": "Proxmox BS API Token",
+ "placeholder": "请输入 Proxmox BS API Token",
+ "tooltip": "这是什么?请参阅 https://pbs.proxmox.com/docs/user-management.html#api-tokens"
+ },
+ "proxmoxbs_api_token_secret": {
+ "label": "Proxmox BS API Token Secret(可选)",
+ "placeholder": "请输入 Proxmox BS API Token Secret",
+ "tooltip": "这是什么?请参阅 https://pbs.proxmox.com/docs/user-management.html#api-tokens"
+ },
"qingcloud_access_key_id": {
"label": "青云 AccessKeyID",
"placeholder": "请输入青云 AccessKeyID",
diff --git a/ui/src/i18n/resources/zh/nls.provider.json b/ui/src/i18n/resources/zh/nls.provider.json
index 11166a60c..517c82329 100644
--- a/ui/src/i18n/resources/zh/nls.provider.json
+++ b/ui/src/i18n/resources/zh/nls.provider.json
@@ -213,7 +213,8 @@
"ovhcloud": "OVHcloud",
"porkbun": "Porkbun",
"powerdns": "PowerDNS",
- "proxmoxve": "Proxmox VE",
+ "proxmoxve": "PVE(Proxmox Virtual Environment)",
+ "proxmoxbs": "PBS(Proxmox Backup Server)",
"qingcloud": "青云",
"qingcloud_dns": "青云 - 云解析 DNS",
"qingcloud_lb": "青云 - 负载均衡 LB",
diff --git a/ui/src/i18n/resources/zh/nls.workflow.nodes.json b/ui/src/i18n/resources/zh/nls.workflow.nodes.json
index ca6d16c87..a181213c9 100644
--- a/ui/src/i18n/resources/zh/nls.workflow.nodes.json
+++ b/ui/src/i18n/resources/zh/nls.workflow.nodes.json
@@ -2019,7 +2019,7 @@
"tooltip": "这是什么?请参阅 https://docs.oracle.com/en-us/iaas/Content/Identity/compartments/managingcompartments.htm"
},
"proxmoxve": {
- "guide": "需要 Proxmox VE v5.2 或更高版本。"
+ "guide": "需要 Proxmox Virtual Environment v5.2 或更高版本。"
},
"proxmoxve_node_name": {
"label": "Proxmox VE 集群节点名称",
@@ -2028,6 +2028,16 @@
"proxmoxve_auto_restart": {
"label": "部署后自动重启 Proxmox VE 服务"
},
+ "proxmoxbs": {
+ "guide": "需要 Proxmox Backup Server v2.1 或更高版本。"
+ },
+ "proxmoxbs_node_name": {
+ "label": "Proxmox BS 节点名称",
+ "placeholder": "请输入 Proxmox BS 节点名称"
+ },
+ "proxmoxbs_auto_restart": {
+ "label": "部署后自动重启 Proxmox BS 服务"
+ },
"qingcloud_lb_zone_id": {
"label": "青云服务区域 ID",
"placeholder": "请输入青云 ELB 服务区域 ID(例如:pek3a)",