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)",