diff --git a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go index dcb2be57..d2de5684 100644 --- a/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/internal/pkg/core/deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "time" aliyunCdn "github.com/alibabacloud-go/cdn-20180510/v5/client" @@ -20,7 +21,7 @@ type AliyunCDNDeployerConfig struct { AccessKeyId string `json:"accessKeyId"` // 阿里云 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` - // 加速域名(不支持泛域名)。 + // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -58,10 +59,13 @@ func NewWithLogger(config *AliyunCDNDeployerConfig, logger logger.Logger) (*Aliy } func (d *AliyunCDNDeployer) Deploy(ctx context.Context, certPem string, privkeyPem string) (*deployer.DeployResult, error) { + // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 + domain := strings.TrimPrefix(d.config.Domain, "*") + // 设置 CDN 域名域名证书 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate setCdnDomainSSLCertificateReq := &aliyunCdn.SetCdnDomainSSLCertificateRequest{ - DomainName: tea.String(d.config.Domain), + DomainName: tea.String(domain), CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), CertType: tea.String("upload"), SSLProtocol: tea.String("on"), diff --git a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go index 6db62251..ab932b42 100644 --- a/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/internal/pkg/core/deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -19,7 +19,7 @@ type BaiduCloudCDNDeployerConfig struct { AccessKeyId string `json:"accessKeyId"` // 百度智能云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` - // 加速域名(不支持泛域名)。 + // 加速域名(支持泛域名)。 Domain string `json:"domain"` } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 56fe9ace..b60b0e87 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -67,6 +67,7 @@ func (d *WebhookDeployer) Deploy(ctx context.Context, certPem string, privkeyPem return nil, xerrors.Wrap(err, "failed to parse x509") } + // TODO: 自定义回调数据 reqBody, _ := json.Marshal(&webhookData{ SubjectAltNames: strings.Join(certX509.DNSNames, ","), Certificate: certPem, diff --git a/ui/src/components/workflow/AccessSelect.tsx b/ui/src/components/workflow/AccessSelect.tsx index 827c82ad..467450e8 100644 --- a/ui/src/components/workflow/AccessSelect.tsx +++ b/ui/src/components/workflow/AccessSelect.tsx @@ -1,9 +1,8 @@ import React, { useEffect } from "react"; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "../ui/select"; -import { accessProvidersMap } from "@/domain/access"; +import { accessProvidersMap, deployProvidersMap } from "@/domain/provider"; import { useTranslation } from "react-i18next"; import { useAccessStore } from "@/stores/access"; -import { deployTargetsMap } from "@/domain/domain"; type AccessSelectProps = { providerType: string; @@ -24,8 +23,7 @@ const AccessSelect = ({ value, onValueChange, providerType }: AccessSelectProps) }, [value]); const targetAccesses = accesses.filter((item) => { - console.log(item, providerType); - return item.configType === deployTargetsMap.get(providerType)?.provider; + return item.configType === deployProvidersMap.get(providerType)?.provider; }); return ( diff --git a/ui/src/components/workflow/AddNode.tsx b/ui/src/components/workflow/AddNode.tsx index 66a7f612..ca8811ef 100644 --- a/ui/src/components/workflow/AddNode.tsx +++ b/ui/src/components/workflow/AddNode.tsx @@ -1,6 +1,6 @@ import { useTranslation } from "react-i18next"; import { Dropdown } from "antd"; -import { Plus as PlusIcon } from "lucide-react"; +import { PlusOutlined as PlusOutlinedIcon } from "@ant-design/icons"; import { useZustandShallowSelector } from "@/hooks"; import { newWorkflowNode, workflowNodeDropdownList, WorkflowNodeType } from "@/domain/workflow"; @@ -22,7 +22,7 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => { }; return ( -
+
{ @@ -56,8 +56,8 @@ const AddNode = ({ data }: NodeProps | BrandNodeProps) => { }} trigger={["click"]} > -
- +
+
diff --git a/ui/src/components/workflow/BranchNode.tsx b/ui/src/components/workflow/BranchNode.tsx index 5e1d5ccf..6071eb04 100644 --- a/ui/src/components/workflow/BranchNode.tsx +++ b/ui/src/components/workflow/BranchNode.tsx @@ -32,7 +32,7 @@ const BranchNode = memo(({ data }: BrandNodeProps) => { }} size={"sm"} variant={"outline"} - className="text-xs px-2 h-6 rounded-full absolute -top-3 left-[50%] -translate-x-1/2 z-10 dark:text-stone-200" + className="text-xs px-2 h-6 rounded-full absolute -top-3 left-[50%] -translate-x-1/2 z-[1] dark:text-stone-200" > {t("workflow.node.addBranch.label")} diff --git a/ui/src/components/workflow/ConditionNode.tsx b/ui/src/components/workflow/ConditionNode.tsx index 8335222f..bd600141 100644 --- a/ui/src/components/workflow/ConditionNode.tsx +++ b/ui/src/components/workflow/ConditionNode.tsx @@ -1,9 +1,10 @@ -import { useWorkflowStore } from "@/stores/workflow"; -import AddNode from "./AddNode"; -import { NodeProps } from "./types"; -import { useZustandShallowSelector } from "@/hooks"; import { Dropdown } from "antd"; -import { Ellipsis, Trash2 } from "lucide-react"; +import { DeleteOutlined as DeleteOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons"; + +import AddNode from "./AddNode"; +import { useZustandShallowSelector } from "@/hooks"; +import { useWorkflowStore } from "@/stores/workflow"; +import { type NodeProps } from "./types"; const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => { const { updateNode, removeBranch } = useWorkflowStore(useZustandShallowSelector(["updateNode", "removeBranch"])); @@ -12,14 +13,14 @@ const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => { }; return ( <> -
+
, + icon: , danger: true, onClick: () => { removeBranch(branchId ?? "", branchIndex ?? 0); @@ -30,7 +31,7 @@ const ConditionNode = ({ data, branchId, branchIndex }: NodeProps) => { trigger={["click"]} >
- +
diff --git a/ui/src/components/workflow/DeployForm.tsx b/ui/src/components/workflow/DeployForm.tsx index ca7846ee..32b5bf05 100644 --- a/ui/src/components/workflow/DeployForm.tsx +++ b/ui/src/components/workflow/DeployForm.tsx @@ -1,31 +1,33 @@ -import { WorkflowNode } from "@/domain/workflow"; import { memo } from "react"; -import DeployToAliyunOSS from "./DeployToAliyunOss"; + +import { type WorkflowNode } from "@/domain/workflow"; import DeployToAliyunALB from "./DeployToAliyunALB"; import DeployToAliyunCDN from "./DeployToAliyunCDN"; import DeployToAliyunCLB from "./DeployToAliyunCLB"; import DeployToAliyunNLB from "./DeployToAliyunNLB"; +import DeployToAliyunOSS from "./DeployToAliyunOss"; import DeployToBaiduCloudCDN from "./DeployToBaiduCloudCDN"; +import DeployToBytePlusCDN from "./DeployToByteplusCDN"; import DeployToDogeCloudCDN from "./DeployToDogeCloudCDN"; import DeployToHuaweiCloudCDN from "./DeployToHuaweiCloudCDN"; import DeployToHuaweiCloudELB from "./DeployToHuaweiCloudELB"; import DeployToKubernetesSecret from "./DeployToKubernetesSecret"; +import DeployToLocal from "./DeployToLocal"; import DeployToQiniuCDN from "./DeployToQiniuCDN"; -import DeployToWebhook from "./DeployToWebhook"; +import DeployToSSH from "./DeployToSSH"; import DeployToTencentCDN from "./DeployToTencentCDN"; import DeployToTencentCLB from "./DeployToTencentCLB"; import DeployToTencentCOS from "./DeployToTencentCOS"; -import DeployToTencentTEO from "./DeployToTencentTEO"; -import DeployToSSH from "./DeployToSSH"; -import DeployToLocal from "./DeployToLocal"; -import DeployToByteplusCDN from "./DeployToByteplusCDN"; -import DeployToVolcengineCDN from "./DeployToVolcengineCDN"; -import DeployToVolcengineLive from "./DeployToVolcengineLive"; +import DeployToTencentEO from "./DeployToTencentTEO"; +import DeployToVolcEngineCDN from "./DeployToVolcengineCDN"; +import DeployToVolcEngineLive from "./DeployToVolcengineLive"; +import DeployToWebhook from "./DeployToWebhook"; export type DeployFormProps = { data: WorkflowNode; defaultProivder?: string; }; + const DeployForm = ({ data, defaultProivder }: DeployFormProps) => { return
{getForm(data, defaultProivder)}
; }; @@ -68,17 +70,17 @@ const getForm = (data: WorkflowNode, defaultProivder?: string) => { case "tencentcloud-cos": return ; case "tencentcloud-eo": - return ; + return ; case "ssh": return ; case "local": return ; case "byteplus-cdn": - return ; + return ; case "volcengine-cdn": - return ; + return ; case "volcengine-live": - return ; + return ; default: return <>; } diff --git a/ui/src/components/workflow/DeployPanelBody.tsx b/ui/src/components/workflow/DeployPanelBody.tsx index e428c2c6..084302ec 100644 --- a/ui/src/components/workflow/DeployPanelBody.tsx +++ b/ui/src/components/workflow/DeployPanelBody.tsx @@ -1,13 +1,15 @@ -import { WorkflowNode } from "@/domain/workflow"; import { memo, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import Show from "../Show"; -import DeployForm from "./DeployForm"; -import { DeployTarget, deployTargets } from "@/domain/domain"; + +import Show from "@/components/Show"; +import DeployNodeForm from "./node/DeployNodeForm"; +import { deployProvidersMap } from "@/domain/provider"; +import { type WorkflowNode } from "@/domain/workflow"; type DeployPanelBodyProps = { data: WorkflowNode; }; + const DeployPanelBody = ({ data }: DeployPanelBodyProps) => { const { t } = useTranslation(); @@ -21,33 +23,22 @@ const DeployPanelBody = ({ data }: DeployPanelBodyProps) => { return ( <> {/* 默认展示服务商列表 */} - }> + }>
选择服务商
- {deployTargets - .reduce((acc: DeployTarget[][], provider, index) => { - if (index % 2 === 0) { - acc.push([provider]); - } else { - acc[acc.length - 1].push(provider); - } - return acc; - }, []) - .map((providerRow, rowIndex) => ( -
- {providerRow.map((provider, index) => ( -
{ - setProviderType(provider.type); - }} - > - {provider.type} -
{t(provider.name)}
-
- ))} + {Array.from(deployProvidersMap.values()).map((provider, index) => { + return ( +
{ + setProviderType(provider.type); + }} + > + {provider.type} +
{t(provider.name)}
- ))} + ); + })} ); diff --git a/ui/src/components/workflow/Node.tsx b/ui/src/components/workflow/Node.tsx index e749ab1d..7952a351 100644 --- a/ui/src/components/workflow/Node.tsx +++ b/ui/src/components/workflow/Node.tsx @@ -1,15 +1,16 @@ -import { WorkflowNode, WorkflowNodeType } from "@/domain/workflow"; -import AddNode from "./AddNode"; -import { useWorkflowStore } from "@/stores/workflow"; -import { useZustandShallowSelector } from "@/hooks"; +import { useTranslation } from "react-i18next"; import { Dropdown } from "antd"; -import { Ellipsis, Trash2 } from "lucide-react"; +import { DeleteOutlined as DeleteOutlinedIcon, EllipsisOutlined as EllipsisOutlinedIcon } from "@ant-design/icons"; + +import Show from "@/components/Show"; +import AddNode from "./AddNode"; import { usePanel } from "./PanelProvider"; import PanelBody from "./PanelBody"; -import { useTranslation } from "react-i18next"; -import Show from "../Show"; -import { deployTargetsMap } from "@/domain/domain"; +import { useZustandShallowSelector } from "@/hooks"; +import { deployProvidersMap } from "@/domain/provider"; import { notifyChannelsMap } from "@/domain/settings"; +import { type WorkflowNode, WorkflowNodeType } from "@/domain/workflow"; +import { useWorkflowStore } from "@/stores/workflow"; type NodeProps = { data: WorkflowNode; @@ -56,7 +57,7 @@ const Node = ({ data }: NodeProps) => { case WorkflowNodeType.Apply: return
{data.config?.domain as string}
; case WorkflowNodeType.Deploy: { - const provider = deployTargetsMap.get(data.config?.providerType as string); + const provider = deployProvidersMap.get(data.config?.providerType as string); return (
@@ -90,7 +91,7 @@ const Node = ({ data }: NodeProps) => { { key: "delete", label: t(`${i18nPrefix}.delete.label`), - icon: , + icon: , danger: true, onClick: () => { removeNode(data.id); @@ -101,7 +102,7 @@ const Node = ({ data }: NodeProps) => { trigger={["click"]} >
- +
diff --git a/ui/src/components/workflow/node/ApplyNodeForm.tsx b/ui/src/components/workflow/node/ApplyNodeForm.tsx index 88052c5c..bf357b93 100644 --- a/ui/src/components/workflow/node/ApplyNodeForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeForm.tsx @@ -4,6 +4,7 @@ import { useControllableValue } from "ahooks"; import { AutoComplete, Button, Divider, Form, Input, Select, Space, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { FormOutlined as FormOutlinedIcon, PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons"; +import { produce } from "immer"; import z from "zod"; import AccessEditModal from "@/components/access/AccessEditModal"; @@ -12,7 +13,8 @@ import ModalForm from "@/components/core/ModalForm"; import MultipleInput from "@/components/core/MultipleInput"; import { usePanel } from "../PanelProvider"; import { useAntdForm, useZustandShallowSelector } from "@/hooks"; -import { ACCESS_USAGES, accessProvidersMap } from "@/domain/access"; +import { ACCESS_USAGES } from "@/domain/access"; +import { accessProvidersMap } from "@/domain/provider"; import { type WorkflowNode, type WorkflowNodeConfig } from "@/domain/workflow"; import { useContactStore } from "@/stores/contact"; import { useWorkflowStore } from "@/stores/workflow"; @@ -42,33 +44,27 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => { const { hidePanel } = usePanel(); const formSchema = z.object({ - domain: z.string({ message: t("workflow.nodes.apply.form.domains.placeholder") }).refine( - (v) => { - return String(v) - .split(MULTIPLE_INPUT_DELIMITER) - .every((e) => validDomainName(e, true)); - }, - { message: t("common.errmsg.domain_invalid") } - ), - email: z.string({ message: t("workflow.nodes.apply.form.email.placeholder") }).email("common.errmsg.email_invalid"), - access: z.string({ message: t("workflow.nodes.apply.form.access.placeholder") }).min(1, t("workflow.nodes.apply.form.access.placeholder")), + domain: z.string({ message: t("workflow_node.apply.form.domains.placeholder") }).refine((v) => { + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => validDomainName(e, true)); + }, t("common.errmsg.domain_invalid")), + email: z.string({ message: t("workflow_node.apply.form.email.placeholder") }).email("common.errmsg.email_invalid"), + access: z.string({ message: t("workflow_node.apply.form.access.placeholder") }).min(1, t("workflow_node.apply.form.access.placeholder")), keyAlgorithm: z.string().nullish(), nameservers: z .string() - .refine( - (v) => { - if (!v) return true; - return String(v) - .split(MULTIPLE_INPUT_DELIMITER) - .every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e)); - }, - { message: t("common.errmsg.host_invalid") } - ) - .nullish(), + .nullish() + .refine((v) => { + if (!v) return true; + return String(v) + .split(MULTIPLE_INPUT_DELIMITER) + .every((e) => validIPv4Address(e) || validIPv6Address(e) || validDomainName(e)); + }, t("common.errmsg.host_invalid")), propagationTimeout: z .union([ - z.number().int().gte(1, t("workflow.nodes.apply.form.propagation_timeout.placeholder")), - z.string().refine((v) => !v || (parseInt(v) === +v && +v > 0), { message: t("workflow.nodes.apply.form.propagation_timeout.placeholder") }), + z.number().int().gte(1, t("workflow_node.apply.form.propagation_timeout.placeholder")), + z.string().refine((v) => !v || /^[1-9]\d*$/.test(v), t("workflow_node.apply.form.propagation_timeout.placeholder")), ]) .nullish(), disableFollowCNAME: z.boolean().nullish(), @@ -81,8 +77,14 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => { } = useAntdForm>({ initialValues: data?.config ?? initFormModel(), onSubmit: async (values) => { - await updateNode({ ...data, config: { ...values }, validated: true }); + await formInst.validateFields(); await addEmail(values.email); + await updateNode( + produce(data, (draft) => { + draft.config = { ...values }; + draft.validated = true; + }) + ); hidePanel(); }, }); @@ -106,15 +108,15 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
} + tooltip={} > { } + tooltip={} > - + - +