diff --git a/internal/app/scheduler.go b/internal/app/scheduler.go index 1b16ac32..c5e93c9f 100644 --- a/internal/app/scheduler.go +++ b/internal/app/scheduler.go @@ -3,6 +3,7 @@ package app import ( "sync" "time" + _ "time/tzdata" "github.com/pocketbase/pocketbase/tools/cron" ) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 4e03ad2e..7d62ac41 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -56,6 +56,7 @@ import ( pTencentCloudWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/tencentcloud-waf" pUCloudUCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-ucdn" pUCloudUS3 "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/ucloud-us3" + pUpyunCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/upyun-cdn" pVolcEngineCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-cdn" pVolcEngineCLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-clb" pVolcEngineDCDN "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/volcengine-dcdn" @@ -225,6 +226,7 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { AccessKeyId: access.AccessKeyId, AccessKeySecret: access.AccessKeySecret, Region: maputil.GetString(options.ProviderDeployConfig, "region"), + ServiceVersion: maputil.GetOrDefaultString(options.ProviderDeployConfig, "serviceVersion", "3.0"), InstanceId: maputil.GetString(options.ProviderDeployConfig, "instanceId"), Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), }) @@ -775,6 +777,27 @@ func createDeployer(options *deployerOptions) (deployer.Deployer, error) { } } + case domain.DeployProviderTypeUpyunCDN: + { + access := domain.AccessConfigForUpyun{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + switch options.Provider { + case domain.DeployProviderTypeUpyunCDN: + deployer, err := pUpyunCDN.NewDeployer(&pUpyunCDN.DeployerConfig{ + Username: access.Username, + Password: access.Password, + Domain: maputil.GetString(options.ProviderDeployConfig, "domain"), + }) + return deployer, err + + default: + break + } + } + case domain.DeployProviderTypeVolcEngineCDN, domain.DeployProviderTypeVolcEngineCLB, domain.DeployProviderTypeVolcEngineDCDN, domain.DeployProviderTypeVolcEngineImageX, domain.DeployProviderTypeVolcEngineLive, domain.DeployProviderTypeVolcEngineTOS: { access := domain.AccessConfigForVolcEngine{} diff --git a/internal/domain/access.go b/internal/domain/access.go index fc6a7eb1..963d4083 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -201,6 +201,11 @@ type AccessConfigForUCloud struct { ProjectId string `json:"projectId,omitempty"` } +type AccessConfigForUpyun struct { + Username string `json:"username"` + Password string `json:"password"` +} + type AccessConfigForVolcEngine struct { AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 78c79d4a..cbc034fa 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -28,6 +28,7 @@ const ( AccessProviderTypeCUCCCloud = AccessProviderType("cucccloud") // 天翼云(预留) AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") + AccessProviderTypeDynv6 = AccessProviderType("dynv6") // dynv6(预留) AccessProviderTypeEdgio = AccessProviderType("edgio") AccessProviderTypeFastly = AccessProviderType("fastly") // Fastly(预留) AccessProviderTypeGname = AccessProviderType("gname") @@ -50,6 +51,7 @@ const ( AccessProviderTypeSSH = AccessProviderType("ssh") AccessProviderTypeTencentCloud = AccessProviderType("tencentcloud") AccessProviderTypeUCloud = AccessProviderType("ucloud") + AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWestcn = AccessProviderType("westcn") @@ -158,6 +160,7 @@ const ( DeployProviderTypeTencentCloudWAF = DeployProviderType("tencentcloud-waf") DeployProviderTypeUCloudUCDN = DeployProviderType("ucloud-ucdn") DeployProviderTypeUCloudUS3 = DeployProviderType("ucloud-us3") + DeployProviderTypeUpyunCDN = DeployProviderType("upyun-cdn") DeployProviderTypeVolcEngineCDN = DeployProviderType("volcengine-cdn") DeployProviderTypeVolcEngineCLB = DeployProviderType("volcengine-clb") DeployProviderTypeVolcEngineDCDN = DeployProviderType("volcengine-dcdn") diff --git a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go index 02dac427..e8166afd 100644 --- a/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go +++ b/internal/pkg/core/deployer/providers/qiniu-cdn/qiniu_cdn.go @@ -87,18 +87,18 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPe // 判断域名是否已启用 HTTPS。如果已启用,修改域名证书;否则,启用 HTTPS // REF: https://developer.qiniu.com/fusion/4246/the-domain-name - if getDomainInfoResp.Https != nil && getDomainInfoResp.Https.CertID != "" { - modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) - d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) - if err != nil { - return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") - } - } else { + if getDomainInfoResp.Https == nil || getDomainInfoResp.Https.CertID == "" { enableDomainHttpsResp, err := d.sdkClient.EnableDomainHttps(context.TODO(), domain, upres.CertId, true, true) d.logger.Debug("sdk request 'cdn.EnableDomainHttps'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", enableDomainHttpsResp)) if err != nil { return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.EnableDomainHttps'") } + } else if getDomainInfoResp.Https.CertID != upres.CertId { + modifyDomainHttpsConfResp, err := d.sdkClient.ModifyDomainHttpsConf(context.TODO(), domain, upres.CertId, getDomainInfoResp.Https.ForceHttps, getDomainInfoResp.Https.Http2Enable) + d.logger.Debug("sdk request 'cdn.ModifyDomainHttpsConf'", slog.String("request.domain", domain), slog.String("request.certId", upres.CertId), slog.Any("response", modifyDomainHttpsConfResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'cdn.ModifyDomainHttpsConf'") + } } return &deployer.DeployResult{}, nil diff --git a/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go new file mode 100644 index 00000000..84d6cafb --- /dev/null +++ b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn.go @@ -0,0 +1,129 @@ +package upyuncdn + +import ( + "context" + "errors" + "log/slog" + + xerrors "github.com/pkg/errors" + "golang.org/x/exp/slices" + + "github.com/usual2970/certimate/internal/pkg/core/deployer" + "github.com/usual2970/certimate/internal/pkg/core/uploader" + uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/upyun-ssl" + upyunsdk "github.com/usual2970/certimate/internal/pkg/vendors/upyun-sdk/console" +) + +type DeployerConfig struct { + // 又拍云账号用户名。 + Username string `json:"username"` + // 又拍云账号密码。 + Password string `json:"password"` + // 加速域名(支持泛域名)。 + Domain string `json:"domain"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sdkClient *upyunsdk.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.Username, config.Password) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create sdk client") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + Username: config.Username, + Password: config.Password, + }) + if err != nil { + return nil, xerrors.Wrap(err, "failed to create ssl uploader") + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.Default() + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // 上传证书到 SSL + upres, err := d.sslUploader.Upload(ctx, certPem, privkeyPem) + if err != nil { + return nil, xerrors.Wrap(err, "failed to upload certificate file") + } else { + d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) + } + + // 获取域名证书配置 + getHttpsServiceManagerResp, err := d.sdkClient.GetHttpsServiceManager(d.config.Domain) + d.logger.Debug("sdk request 'console.GetHttpsServiceManager'", slog.String("request.domain", d.config.Domain), slog.Any("response", getHttpsServiceManagerResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.GetHttpsServiceManager'") + } + + // 判断域名是否已启用 HTTPS。如果已启用,迁移域名证书;否则,设置新证书 + lastCertIndex := slices.IndexFunc(getHttpsServiceManagerResp.Data.Domains, func(item upyunsdk.HttpsServiceManagerDomain) bool { + return item.Https + }) + if lastCertIndex == -1 { + updateHttpsCertificateManagerReq := &upyunsdk.UpdateHttpsCertificateManagerRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + Https: true, + ForceHttps: true, + } + updateHttpsCertificateManagerResp, err := d.sdkClient.UpdateHttpsCertificateManager(updateHttpsCertificateManagerReq) + d.logger.Debug("sdk request 'console.EnableDomainHttps'", slog.Any("request", updateHttpsCertificateManagerReq), slog.Any("response", updateHttpsCertificateManagerResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.UpdateHttpsCertificateManager'") + } + } else if getHttpsServiceManagerResp.Data.Domains[lastCertIndex].CertificateId != upres.CertId { + migrateHttpsDomainReq := &upyunsdk.MigrateHttpsDomainRequest{ + CertificateId: upres.CertId, + Domain: d.config.Domain, + } + migrateHttpsDomainResp, err := d.sdkClient.MigrateHttpsDomain(migrateHttpsDomainReq) + d.logger.Debug("sdk request 'console.MigrateHttpsDomain'", slog.Any("request", migrateHttpsDomainReq), slog.Any("response", migrateHttpsDomainResp)) + if err != nil { + return nil, xerrors.Wrap(err, "failed to execute sdk request 'console.MigrateHttpsDomain'") + } + } + + return &deployer.DeployResult{}, nil +} + +func createSdkClient(username, password string) (*upyunsdk.Client, error) { + if username == "" { + return nil, errors.New("invalid upyun username") + } + + if password == "" { + return nil, errors.New("invalid upyun password") + } + + client := upyunsdk.NewClient(username, password) + return client, nil +} diff --git a/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go new file mode 100644 index 00000000..8a7b4485 --- /dev/null +++ b/internal/pkg/core/deployer/providers/upyun-cdn/upyun_cdn_test.go @@ -0,0 +1,75 @@ +package upyuncdn_test + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/upyun-cdn" +) + +var ( + fInputCertPath string + fInputKeyPath string + fUsername string + fPassword string + fDomain string +) + +func init() { + argsPrefix := "CERTIMATE_DEPLOYER_UPYUNCDN_" + + flag.StringVar(&fInputCertPath, argsPrefix+"INPUTCERTPATH", "", "") + flag.StringVar(&fInputKeyPath, argsPrefix+"INPUTKEYPATH", "", "") + flag.StringVar(&fUsername, argsPrefix+"USERNAME", "", "") + flag.StringVar(&fPassword, argsPrefix+"PASSWORD", "", "") + flag.StringVar(&fDomain, argsPrefix+"DOMAIN", "", "") +} + +/* +Shell command to run this test: + + go test -v ./upyun_cdn_test.go -args \ + --CERTIMATE_DEPLOYER_UPYUNCDN_INPUTCERTPATH="/path/to/your-input-cert.pem" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_INPUTKEYPATH="/path/to/your-input-key.pem" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_USERNAME="your-username" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_PASSWORD="your-password" \ + --CERTIMATE_DEPLOYER_UPYUNCDN_DOMAIN="example.com" \ +*/ +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("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("DOMAIN: %v", fDomain), + }, "\n")) + + deployer, err := provider.NewDeployer(&provider.DeployerConfig{ + Username: fUsername, + Password: fPassword, + Domain: fDomain, + }) + 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/migrations/1742392800_upgrade.go b/migrations/1742392800_upgrade.go new file mode 100644 index 00000000..a06bdc75 --- /dev/null +++ b/migrations/1742392800_upgrade.go @@ -0,0 +1,81 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("4yzbv8urny5ja1e") + if err != nil { + return err + } + + // update field + if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{ + "hidden": false, + "id": "hwy7m03o", + "maxSelect": 1, + "name": "provider", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "1panel", + "acmehttpreq", + "akamai", + "aliyun", + "aws", + "azure", + "baiducloud", + "baishan", + "baotapanel", + "byteplus", + "cachefly", + "cdnfly", + "cloudflare", + "cloudns", + "cmcccloud", + "ctcccloud", + "cucccloud", + "dnsla", + "dogecloud", + "dynv6", + "edgio", + "fastly", + "gname", + "gcore", + "godaddy", + "goedge", + "huaweicloud", + "jdcloud", + "k8s", + "local", + "namecheap", + "namedotcom", + "namesilo", + "ns1", + "powerdns", + "qiniu", + "qingcloud", + "rainyun", + "safeline", + "ssh", + "tencentcloud", + "ucloud", + "upyun", + "volcengine", + "webhook", + "westcn" + ] + }`)); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + return nil + }) +} diff --git a/ui/public/imgs/providers/upyun.svg b/ui/public/imgs/providers/upyun.svg new file mode 100644 index 00000000..cc13793d --- /dev/null +++ b/ui/public/imgs/providers/upyun.svg @@ -0,0 +1 @@ +<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M819.733081 102.739233a479.288216 479.288216 0 0 0-59.60814-38.284903 33.923332 33.923332 0 0 0-43.131093 8.723143L589.054425 242.309513l-7.269285 13.084713a70.269759 70.269759 0 0 1-59.123521 27.623285h-25.20019a228.255561 228.255561 0 0 0-213.71699 239.886417 25.200189 25.200189 0 0 0 14.538571 21.807856 60.092759 60.092759 0 1 1-82.385234 71.723616v-3.876952a60.577378 60.577378 0 0 1 11.630856-48.461903 36.831046 36.831046 0 0 0 7.753905-26.654046 279.140558 279.140558 0 0 1 276.717463-303.37151 51.854236 51.854236 0 0 0 41.192617-20.838618l117.762423-156.531945a21.323237 21.323237 0 0 0-11.630856-33.923332A511.75769 511.75769 0 0 0 263.39044 959.54567a33.923332 33.923332 0 0 0 43.615712-9.207762L436.399432 780.721249l8.723143-12.115475a70.754378 70.754378 0 0 1 59.123521-27.623285 218.56318 218.56318 0 0 0 25.200189 0 230.194037 230.194037 0 0 0 168.162802-90.623757 225.832466 225.832466 0 0 0 45.554188-149.26266 25.200189 25.200189 0 0 0-14.538571-21.807856 60.092759 60.092759 0 0 1-35.861808-43.131093 60.092759 60.092759 0 0 1 117.277804-27.138666 14.053952 14.053952 0 0 0 0 4.361571 61.061997 61.061997 0 0 1-11.630856 48.461903 36.831046 36.831046 0 0 0-7.753905 26.654046 279.140558 279.140558 0 0 1-276.717463 303.37151 51.854236 51.854236 0 0 0-41.192617 20.838618L355.952674 969.23805a21.807856 21.807856 0 0 0 11.146238 33.923332 512.24231 512.24231 0 0 0 452.634169-900.422149z" fill="#009FFF"></path></svg> \ No newline at end of file diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 7f2143ac..caf5c605 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -44,6 +44,7 @@ import AccessFormSafeLineConfig from "./AccessFormSafeLineConfig"; import AccessFormSSHConfig from "./AccessFormSSHConfig"; import AccessFormTencentCloudConfig from "./AccessFormTencentCloudConfig"; import AccessFormUCloudConfig from "./AccessFormUCloudConfig"; +import AccessFormUpyunConfig from "./AccessFormUpyunConfig"; import AccessFormVolcEngineConfig from "./AccessFormVolcEngineConfig"; import AccessFormWebhookConfig from "./AccessFormWebhookConfig"; import AccessFormWestcnConfig from "./AccessFormWestcnConfig"; @@ -170,6 +171,8 @@ const AccessForm = forwardRef<AccessFormInstance, AccessFormProps>(({ className, return <AccessFormTencentCloudConfig {...nestedFormProps} />; case ACCESS_PROVIDERS.UCLOUD: return <AccessFormUCloudConfig {...nestedFormProps} />; + case ACCESS_PROVIDERS.UPYUN: + return <AccessFormUpyunConfig {...nestedFormProps} />; case ACCESS_PROVIDERS.VOLCENGINE: return <AccessFormVolcEngineConfig {...nestedFormProps} />; case ACCESS_PROVIDERS.WEBHOOK: diff --git a/ui/src/components/access/AccessFormUpyunConfig.tsx b/ui/src/components/access/AccessFormUpyunConfig.tsx new file mode 100644 index 00000000..8cc06d97 --- /dev/null +++ b/ui/src/components/access/AccessFormUpyunConfig.tsx @@ -0,0 +1,76 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForUpyun } from "@/domain/access"; + +type AccessFormUpyunConfigFieldValues = Nullish<AccessConfigForUpyun>; + +export type AccessFormUpyunConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormUpyunConfigFieldValues; + onValuesChange?: (values: AccessFormUpyunConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormUpyunConfigFieldValues => { + return { + username: "", + password: "", + }; +}; + +const AccessFormUpyunConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormUpyunConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + username: z + .string() + .trim() + .min(1, t("access.form.upyun_username.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })), + password: z + .string() + .min(1, t("access.form.upyun_password.placeholder")) + .max(64, t("common.errmsg.string_max", { max: 64 })) + .trim(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => { + onValuesChange?.(values); + }; + + return ( + <Form + form={formInst} + disabled={disabled} + initialValues={initialValues ?? initFormModel()} + layout="vertical" + name={formName} + onValuesChange={handleFormChange} + > + <Form.Item + name="username" + label={t("access.form.upyun_username.label")} + rules={[formRule]} + tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.upyun_username.tooltip") }}></span>} + > + <Input autoComplete="new-password" placeholder={t("access.form.upyun_username.placeholder")} /> + </Form.Item> + + <Form.Item + name="password" + label={t("access.form.upyun_password.label")} + rules={[formRule]} + tooltip={<span dangerouslySetInnerHTML={{ __html: t("access.form.upyun_password.tooltip") }}></span>} + > + <Input.Password autoComplete="new-password" placeholder={t("access.form.upyun_password.placeholder")} /> + </Form.Item> + </Form> + ); +}; + +export default AccessFormUpyunConfig; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx new file mode 100644 index 00000000..e09f5266 --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormUpyunCDNConfig.tsx @@ -0,0 +1,59 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { validDomainName } from "@/utils/validators"; + +type DeployNodeConfigFormUpyunCDNConfigFieldValues = Nullish<{ + domain: string; +}>; + +export type DeployNodeConfigFormUpyunCDNConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormUpyunCDNConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormUpyunCDNConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormUpyunCDNConfigFieldValues => { + return {}; +}; + +const DeployNodeConfigFormUpyunCDNConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormUpyunCDNConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + domain: z + .string({ message: t("workflow_node.deploy.form.upyun_cdn_domain.placeholder") }) + .refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => { + onValuesChange?.(values); + }; + + return ( + <Form + form={formInst} + disabled={disabled} + initialValues={initialValues ?? initFormModel()} + layout="vertical" + name={formName} + onValuesChange={handleFormChange} + > + <Form.Item + name="domain" + label={t("workflow_node.deploy.form.upyun_cdn_domain.label")} + rules={[formRule]} + tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.upyun_cdn_domain.tooltip") }}></span>} + > + <Input placeholder={t("workflow_node.deploy.form.upyun_cdn_domain.placeholder")} /> + </Form.Item> + </Form> + ); +}; + +export default DeployNodeConfigFormUpyunCDNConfig; diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 1b5adf45..59a41cc6 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -40,6 +40,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForSSH | AccessConfigForTencentCloud | AccessConfigForUCloud + | AccessConfigForUpyun | AccessConfigForVolcEngine | AccessConfigForWebhook | AccessConfigForWestcn @@ -224,6 +225,11 @@ export type AccessConfigForUCloud = { projectId?: string; }; +export type AccessConfigForUpyun = { + username: string; + password: string; +}; + export type AccessConfigForVolcEngine = { accessKeyId: string; secretAccessKey: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index f3d6deb3..998ec718 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -39,6 +39,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ SSH: "ssh", TENCENTCLOUD: "tencentcloud", UCLOUD: "ucloud", + UPYUN: "upyun", VOLCENGINE: "volcengine", WEBHOOK: "webhook", WESTCN: "westcn", @@ -80,6 +81,7 @@ export const accessProvidersMap: Map<AccessProvider["type"] | string, AccessProv [ACCESS_PROVIDERS.GCORE, "provider.gcore", "/imgs/providers/gcore.png", [ACCESS_USAGES.APPLY, ACCESS_USAGES.DEPLOY]], [ACCESS_PROVIDERS.QINIU, "provider.qiniu", "/imgs/providers/qiniu.svg", [ACCESS_USAGES.DEPLOY]], + [ACCESS_PROVIDERS.UPYUN, "provider.upyun", "/imgs/providers/upyun.svg", [ACCESS_USAGES.DEPLOY]], [ACCESS_PROVIDERS.BAISHAN, "provider.baishan", "/imgs/providers/baishan.png", [ACCESS_USAGES.DEPLOY]], [ACCESS_PROVIDERS.DOGECLOUD, "provider.dogecloud", "/imgs/providers/dogecloud.png", [ACCESS_USAGES.DEPLOY]], [ACCESS_PROVIDERS.BYTEPLUS, "provider.byteplus", "/imgs/providers/byteplus.svg", [ACCESS_USAGES.DEPLOY]], @@ -263,6 +265,7 @@ export const DEPLOY_PROVIDERS = Object.freeze({ TENCENTCLOUD_WAF: `${ACCESS_PROVIDERS.TENCENTCLOUD}-waf`, UCLOUD_UCDN: `${ACCESS_PROVIDERS.UCLOUD}-ucdn`, UCLOUD_US3: `${ACCESS_PROVIDERS.UCLOUD}-us3`, + UPYUN_CDN: `${ACCESS_PROVIDERS.UPYUN}-cdn`, VOLCENGINE_CDN: `${ACCESS_PROVIDERS.VOLCENGINE}-cdn`, VOLCENGINE_CLB: `${ACCESS_PROVIDERS.VOLCENGINE}-clb`, VOLCENGINE_DCDN: `${ACCESS_PROVIDERS.VOLCENGINE}-dcdn`, @@ -344,6 +347,7 @@ export const deployProvidersMap: Map<DeployProvider["type"] | string, DeployProv [DEPLOY_PROVIDERS.JDCLOUD_VOD, "provider.jdcloud.vod", DEPLOY_CATEGORIES.AV], [DEPLOY_PROVIDERS.QINIU_CDN, "provider.qiniu.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.QINIU_PILI, "provider.qiniu.pili", DEPLOY_CATEGORIES.AV], + [DEPLOY_PROVIDERS.UPYUN_CDN, "provider.upyun.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.BAISHAN_CDN, "provider.baishan.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.DOGECLOUD_CDN, "provider.dogecloud.cdn", DEPLOY_CATEGORIES.CDN], [DEPLOY_PROVIDERS.BYTEPLUS_CDN, "provider.byteplus.cdn", DEPLOY_CATEGORIES.CDN], diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index b3884dd4..caaf1dce 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -75,6 +75,12 @@ "access.form.baiducloud_secret_access_key.label": "Baidu Cloud SecretAccessKey", "access.form.baiducloud_secret_access_key.placeholder": "Please enter Baidu Cloud SecretAccessKey", "access.form.baiducloud_secret_access_key.tooltip": "For more information, see <a href=\"https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en\" target=\"_blank\">https://intl.cloud.baidu.com/doc/Reference/s/jjwvz2e3p-en</a>", + "access.form.upyun_username.label": "UPYUN subaccount username", + "access.form.upyun_username.placeholder": "Please enter UPYUN subaccount username", + "access.form.upyun_username.tooltip": "For more information, see <a href=\"https://console.upyun.com/account/subaccount/\" target=\"_blank\">https://console.upyun.com/account/subaccount/</a>", + "access.form.upyun_password.label": "UPYUN subaccount password", + "access.form.upyun_password.placeholder": "Please enter UPYUN subaccount password", + "access.form.upyun_password.tooltip": "For more information, see <a href=\"https://console.upyun.com/account/subaccount/\" target=\"_blank\">https://console.upyun.com/account/subaccount/</a>", "access.form.baishan_api_token.label": "Baishan Cloud API token", "access.form.baishan_api_token.placeholder": "Please enter Baishan Cloud API token", "access.form.baotapanel_api_url.label": "aaPanel URL", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 4c6091e1..9034aeea 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -93,6 +93,8 @@ "provider.ucloud": "UCloud", "provider.ucloud.ucdn": "UCloud - UCDN (UCloud Content Delivery Network)", "provider.ucloud.us3": "UCloud - US3 (UCloud Object-based Storage)", + "provider.upyun": "UPYUN", + "provider.upyun.cdn": "UPYUN - CDN (Content Delivery Network)", "provider.volcengine": "Volcengine", "provider.volcengine.cdn": "Volcengine - CDN (Content Delivery Network)", "provider.volcengine.clb": "Volcengine - CLB (Cloud Load Balancer)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 6d4b0f97..666ef424 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -508,6 +508,9 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "UCloud US3 domain", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "Please enter UCloud US3 domain name", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "For more information, see <a href=\"https://console.ucloud-global.com/ufile\" target=\"_blank\">https://console.ucloud-global.com/ufile</a>", + "workflow_node.deploy.form.upyun_cdn_domain.label": "UPYUN CDN domain", + "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "Please enter UPYUN CDN domain name", + "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "For more information, see <a href=\"https://console.upyun.com/services/cdn/\" target=\"_blank\">https://console.upyun.com/services/cdn/</a>", "workflow_node.deploy.form.volcengine_cdn_domain.label": "VolcEngine CDN domain", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "Please enter VolcEngine CDN domain name", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "For more information, see <a href=\"https://console.volcengine.com/cdn/homepage\" target=\"_blank\">https://console.volcengine.com/cdn/homepage</a>", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index d72cd259..bb2f829a 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -243,6 +243,12 @@ "access.form.ucloud_project_id.label": "优刻得项目 ID(可选)", "access.form.ucloud_project_id.placeholder": "请输入优刻得项目 ID", "access.form.ucloud_project_id.tooltip": "这是什么?请参阅 <a href=\"https://console.ucloud.cn/uaccount/iam/project_manage\" target=\"_blank\">https://console.ucloud.cn/uaccount/iam/project_manage</a>", + "access.form.upyun_username.label": "又拍云子账号用户名", + "access.form.upyun_username.placeholder": "请输入又拍云子账号用户名", + "access.form.upyun_username.tooltip": "这是什么?请参阅 <a href=\"https://console.upyun.com/account/subaccount/\" target=\"_blank\">https://console.upyun.com/account/subaccount/</a><br><br>请关闭该账号的二次登录验证。", + "access.form.upyun_password.label": "又拍云子账号密码", + "access.form.upyun_password.placeholder": "请输入又拍云子账号密码", + "access.form.upyun_password.tooltip": "这是什么?请参阅 <a href=\"https://console.upyun.com/account/subaccount/\" target=\"_blank\">https://console.upyun.com/account/subaccount/</a><br><br>请关闭该账号的二次登录验证。", "access.form.volcengine_access_key_id.label": "火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.placeholder": "请输入火山引擎 AccessKeyId", "access.form.volcengine_access_key_id.tooltip": "这是什么?请参阅 <a href=\"https://www.volcengine.com/docs/6291/216571\" target=\"_blank\">https://www.volcengine.com/docs/6291/216571</a>", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index e8580e41..2e830cf3 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -93,6 +93,8 @@ "provider.ucloud": "优刻得", "provider.ucloud.ucdn": "优刻得 - 内容分发 UCDN", "provider.ucloud.us3": "优刻得 - 对象存储 US3", + "provider.upyun": "又拍云", + "provider.upyun.cdn": "又拍云 - 云分发 CDN", "provider.volcengine": "火山引擎", "provider.volcengine.cdn": "火山引擎 - 内容分发网络 CDN", "provider.volcengine.clb": "火山引擎 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 3144bfa8..63d881f8 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -508,6 +508,9 @@ "workflow_node.deploy.form.ucloud_us3_domain.label": "优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.placeholder": "请输入优刻得 US3 自定义域名", "workflow_node.deploy.form.ucloud_us3_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.ucloud.cn/ufile\" target=\"_blank\">https://console.ucloud.cn/ufile</a>", + "workflow_node.deploy.form.upyun_cdn_domain.label": "又拍云 CDN 加速域名", + "workflow_node.deploy.form.upyun_cdn_domain.placeholder": "请输入又拍云 CDN 加速域名(支持泛域名)", + "workflow_node.deploy.form.upyun_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.upyun.com/services/cdn/\" target=\"_blank\">https://console.upyun.com/services/cdn/</a>", "workflow_node.deploy.form.volcengine_cdn_domain.label": "火山引擎 CDN 加速域名", "workflow_node.deploy.form.volcengine_cdn_domain.placeholder": "请输入火山引擎 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.volcengine_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://console.volcengine.com/cdn/homepage\" target=\"_blank\">https://console.volcengine.com/cdn/homepage</a>",