diff --git a/internal/applicant/acme_user.go b/internal/applicant/acme_user.go index daa7a4cf..1ab4c424 100644 --- a/internal/applicant/acme_user.go +++ b/internal/applicant/acme_user.go @@ -82,9 +82,9 @@ type acmeAccountRepository interface { var registerGroup singleflight.Group -func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) { +func registerAcmeUserWithSingleFlight(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) { resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", sslProviderConfig.Provider, user.GetEmail()), func() (interface{}, error) { - return register(client, sslProviderConfig, user) + return registerAcmeUser(client, sslProviderConfig, user) }) if err != nil { @@ -94,7 +94,7 @@ func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderCon return resp.(*registration.Resource), nil } -func register(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) { +func registerAcmeUser(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, user *acmeUser) (*registration.Resource, error) { var reg *registration.Resource var err error switch sslProviderConfig.Provider { @@ -123,7 +123,6 @@ func register(client *lego.Client, sslProviderConfig *acmeSSLProviderConfig, use } repo := repository.NewAcmeAccountRepository() - resp, err := repo.GetByCAAndEmail(sslProviderConfig.Provider, user.GetEmail()) if err == nil { user.privkey = resp.Key diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index abee203a..a612ebda 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -159,7 +159,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap // New users need to register first if !acmeUser.hasRegistration() { - reg, err := registerAcmeUser(client, sslProviderConfig, acmeUser) + reg, err := registerAcmeUserWithSingleFlight(client, sslProviderConfig, acmeUser) if err != nil { return nil, fmt.Errorf("failed to register: %w", err) } diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index ff675f08..10ca1c28 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -68,11 +68,11 @@ type WorkflowNodeConfigForApply struct { ProviderConfig map[string]any `json:"providerConfig"` // DNS 提供商额外配置 KeyAlgorithm string `json:"keyAlgorithm"` // 密钥算法 Nameservers string `json:"nameservers"` // DNS 服务器列表,以半角逗号分隔 - DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商) - DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商) - DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随 - DisableARI bool `json:"disableARI"` // 是否禁用 ARI - SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(默认值:30) + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(零值取决于提供商的默认值) + DnsTTL int32 `json:"dnsTTL"` // DNS TTL(零值取决于提供商的默认值) + DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否关闭 CNAME 跟随 + DisableARI bool `json:"disableARI"` // 是否关闭 ARI + SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(零值将使用默认值 30) } type WorkflowNodeConfigForDeploy struct { diff --git a/internal/pkg/core/logger/builtin.go b/internal/pkg/core/logger/builtin.go index d3209d70..9787817d 100644 --- a/internal/pkg/core/logger/builtin.go +++ b/internal/pkg/core/logger/builtin.go @@ -5,6 +5,8 @@ import ( "fmt" "reflect" "strings" + + "github.com/usual2970/certimate/internal/pkg/utils/types" ) // 表示默认的日志记录器类型。 @@ -21,7 +23,7 @@ func (l *DefaultLogger) Logt(tag string, data ...any) { temp[0] = tag for i, v := range data { s := "" - if v == nil { + if types.IsNil(v) { s = "" } else { switch reflect.ValueOf(v).Kind() { diff --git a/internal/pkg/utils/types/types.go b/internal/pkg/utils/types/types.go index b88467b1..cd2b1602 100644 --- a/internal/pkg/utils/types/types.go +++ b/internal/pkg/utils/types/types.go @@ -3,6 +3,7 @@ import "reflect" // 判断对象是否为 nil。 +// 与直接使用 `obj == nil` 不同,该函数会正确判断接口类型对象的真实值是否为空。 // // 入参: // - value:待判断的对象。 diff --git a/internal/workflow/processor/processor.go b/internal/workflow/processor/processor.go index ec6a8da4..f011c152 100644 --- a/internal/workflow/processor/processor.go +++ b/internal/workflow/processor/processor.go @@ -42,23 +42,23 @@ func (w *workflowProcessor) processNode(ctx context.Context, node *domain.Workfl var runErr error var processor nodes.NodeProcessor for { - if current.Type != domain.WorkflowNodeTypeBranch && current.Type != domain.WorkflowNodeTypeExecuteResultBranch { - processor, runErr = nodes.GetProcessor(current) - if runErr != nil { - break - } - - runErr = processor.Run(ctx) - - log := processor.Log(ctx) - if log != nil { - w.logs = append(w.logs, *log) - } - if runErr != nil { - break - } + if current.Type == domain.WorkflowNodeTypeBranch || current.Type == domain.WorkflowNodeTypeExecuteResultBranch { + break + } + + processor, runErr = nodes.GetProcessor(current) + if runErr != nil { + break + } + + runErr = processor.Run(ctx) + log := processor.Log(ctx) + if log != nil { + w.logs = append(w.logs, *log) + } + if runErr != nil { + break } - break } if runErr != nil && current.Next != nil && current.Next.Type != domain.WorkflowNodeTypeExecuteResultBranch { @@ -70,8 +70,8 @@ func (w *workflowProcessor) processNode(ctx context.Context, node *domain.Workfl } else { current = current.Next } - } + return nil } @@ -79,10 +79,6 @@ func setContextWorkflowId(ctx context.Context, id string) context.Context { return context.WithValue(ctx, "workflow_id", id) } -func GetWorkflowId(ctx context.Context) string { - return ctx.Value("workflow_id").(string) -} - func getBranchByType(branches []domain.WorkflowNode, nodeType domain.WorkflowNodeType) *domain.WorkflowNode { for _, branch := range branches { if branch.Type == nodeType { diff --git a/ui/src/components/workflow/node/AddNode.tsx b/ui/src/components/workflow/node/AddNode.tsx index a4fa451f..0c1d07b8 100644 --- a/ui/src/components/workflow/node/AddNode.tsx +++ b/ui/src/components/workflow/node/AddNode.tsx @@ -26,13 +26,13 @@ const AddNode = ({ node, disabled }: AddNodeProps) => { return [ [WorkflowNodeType.Apply, "workflow_node.apply.label", ], [WorkflowNodeType.Deploy, "workflow_node.deploy.label", ], + [WorkflowNodeType.Notify, "workflow_node.notify.label", ], [WorkflowNodeType.Branch, "workflow_node.branch.label", ], [WorkflowNodeType.ExecuteResultBranch, "workflow_node.execute_result_branch.label", ], - [WorkflowNodeType.Notify, "workflow_node.notify.label", ], ] .filter(([type]) => { - if (node.type !== WorkflowNodeType.Apply && node.type !== WorkflowNodeType.Deploy && type === WorkflowNodeType.ExecuteResultBranch) { - return false; + if (node.type !== WorkflowNodeType.Apply && node.type !== WorkflowNodeType.Deploy && node.type !== WorkflowNodeType.Notify) { + return type !== WorkflowNodeType.ExecuteResultBranch; } return true; diff --git a/ui/src/components/workflow/node/ConditionNode.tsx b/ui/src/components/workflow/node/ConditionNode.tsx index f33b9bb5..d84e8550 100644 --- a/ui/src/components/workflow/node/ConditionNode.tsx +++ b/ui/src/components/workflow/node/ConditionNode.tsx @@ -16,6 +16,8 @@ const ConditionNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeP return ( <> } variant="text" />} /> } - overlayClassName="shadow-md" - overlayInnerStyle={{ padding: 0 }} placement="rightTop" > diff --git a/ui/src/components/workflow/node/ExecuteResultNode.tsx b/ui/src/components/workflow/node/ExecuteResultNode.tsx index a90b7970..b53b2a8c 100644 --- a/ui/src/components/workflow/node/ExecuteResultNode.tsx +++ b/ui/src/components/workflow/node/ExecuteResultNode.tsx @@ -1,9 +1,12 @@ import { memo } from "react"; import { useTranslation } from "react-i18next"; -import { MoreOutlined as MoreOutlinedIcon } from "@ant-design/icons"; -import { Button, Card, Popover } from "antd"; +import { + CheckCircleOutlined as CheckCircleOutlinedIcon, + CloseCircleOutlined as CloseCircleOutlinedIcon, + MoreOutlined as MoreOutlinedIcon, +} from "@ant-design/icons"; +import { Button, Card, Popover, theme } from "antd"; -import { CheckCircleIcon, XCircleIcon } from "lucide-react"; import { WorkflowNodeType } from "@/domain/workflow"; import AddNode from "./AddNode"; import SharedNode, { type SharedNodeProps } from "./_SharedNode"; @@ -16,9 +19,13 @@ export type ConditionNodeProps = SharedNodeProps & { const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionNodeProps) => { const { t } = useTranslation(); + const { token: themeToken } = theme.useToken(); + return ( <> } variant="text" />} /> } - overlayClassName="shadow-md" - overlayInnerStyle={{ padding: 0 }} placement="rightTop" > @@ -38,12 +43,12 @@ const ExecuteResultNode = ({ node, disabled, branchId, branchIndex }: ConditionN
{node.type === WorkflowNodeType.ExecuteSuccess ? ( <> - +
{t("workflow_node.execute_success.label")}
) : ( <> - +
{t("workflow_node.execute_failure.label")}
)} diff --git a/ui/src/components/workflow/node/_SharedNode.tsx b/ui/src/components/workflow/node/_SharedNode.tsx index 868481ef..f2a4df60 100644 --- a/ui/src/components/workflow/node/_SharedNode.tsx +++ b/ui/src/components/workflow/node/_SharedNode.tsx @@ -64,6 +64,16 @@ type SharedNodeMenuProps = SharedNodeProps & { afterDelete?: () => void; }; +const isBranchingNode = (node: WorkflowNode) => { + return ( + node.type === WorkflowNodeType.Branch || + node.type === WorkflowNodeType.Condition || + node.type === WorkflowNodeType.ExecuteResultBranch || + node.type === WorkflowNodeType.ExecuteSuccess || + node.type === WorkflowNodeType.ExecuteFailure + ); +}; + const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterUpdate, afterDelete }: SharedNodeMenuProps) => { const { t } = useTranslation(); @@ -91,13 +101,7 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU }; const handleDeleteClick = async () => { - if ( - node.type === WorkflowNodeType.Branch || - node.type === WorkflowNodeType.Condition || - node.type === WorkflowNodeType.ExecuteResultBranch || - node.type === WorkflowNodeType.ExecuteSuccess || - node.type === WorkflowNodeType.ExecuteFailure - ) { + if (isBranchingNode(node)) { await removeBranch(branchId!, branchIndex!); } else { await removeNode(node.id); @@ -116,19 +120,13 @@ const SharedNodeMenu = ({ trigger, node, disabled, branchId, branchIndex, afterU { key: "rename", disabled: disabled, - label: - node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition - ? t("workflow_node.action.rename_branch") - : t("workflow_node.action.rename_node"), + label: isBranchingNode(node) ? t("workflow_node.action.rename_branch") : t("workflow_node.action.rename_node"), icon: , onClick: () => { nameRef.current = node.name; const dialog = modalApi.confirm({ - title: - node.type === WorkflowNodeType.Branch || node.type === WorkflowNodeType.Condition - ? t("workflow_node.action.rename_branch") - : t("workflow_node.action.rename_node"), + title: isBranchingNode(node) ? t("workflow_node.action.rename_branch") : t("workflow_node.action.rename_node"), content: (
, danger: true, onClick: handleDeleteClick, @@ -193,10 +184,10 @@ const SharedNodeBlock = ({ children, node, disabled, onClick }: SharedNodeBlockP return ( <> } variant="text" />} />} - overlayClassName="shadow-md" - overlayInnerStyle={{ padding: 0 }} placement="rightTop" > diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 6d35d578..8c8e1f79 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -29,28 +29,28 @@ export type WorkflowTriggerType = (typeof WORKFLOW_TRIGGERS)[keyof typeof WORKFL export enum WorkflowNodeType { Start = "start", End = "end", - Branch = "branch", - ExecuteResultBranch = "execute_result_branch", - ExecuteSuccess = "execute_success", - ExecuteFailure = "execute_failure", - Condition = "condition", Apply = "apply", Deploy = "deploy", Notify = "notify", + Branch = "branch", + Condition = "condition", + ExecuteResultBranch = "execute_result_branch", + ExecuteSuccess = "execute_success", + ExecuteFailure = "execute_failure", Custom = "custom", } const workflowNodeTypeDefaultNames: Map = new Map([ [WorkflowNodeType.Start, i18n.t("workflow_node.start.label")], [WorkflowNodeType.End, i18n.t("workflow_node.end.label")], - [WorkflowNodeType.Branch, i18n.t("workflow_node.branch.label")], - [WorkflowNodeType.ExecuteResultBranch, i18n.t("workflow_node.execute_result_branch.label")], - [WorkflowNodeType.ExecuteSuccess, i18n.t("workflow_node.execute_success.label")], - [WorkflowNodeType.ExecuteFailure, i18n.t("workflow_node.execute_failure.label")], - [WorkflowNodeType.Condition, i18n.t("workflow_node.condition.label")], [WorkflowNodeType.Apply, i18n.t("workflow_node.apply.label")], [WorkflowNodeType.Deploy, i18n.t("workflow_node.deploy.label")], [WorkflowNodeType.Notify, i18n.t("workflow_node.notify.label")], + [WorkflowNodeType.Branch, i18n.t("workflow_node.branch.label")], + [WorkflowNodeType.Condition, i18n.t("workflow_node.condition.label")], + [WorkflowNodeType.ExecuteResultBranch, i18n.t("workflow_node.execute_result_branch.label")], + [WorkflowNodeType.ExecuteSuccess, i18n.t("workflow_node.execute_success.label")], + [WorkflowNodeType.ExecuteFailure, i18n.t("workflow_node.execute_failure.label")], [WorkflowNodeType.Custom, i18n.t("workflow_node.custom.title")], ]); diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index f6def82c..c9c7c8ba 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -1,5 +1,5 @@ { - "workflow_node.action.configure_node": "Configure", + "workflow_node.action.configure_node": "Configure node", "workflow_node.action.add_node": "Add node", "workflow_node.action.rename_node": "Rename node", "workflow_node.action.remove_node": "Delete node", @@ -379,13 +379,13 @@ "workflow_node.end.label": "End", - "workflow_node.branch.label": "Branch", + "workflow_node.branch.label": "Parallel branch", - "workflow_node.execute_result_branch.label": "Execute result branch", + "workflow_node.condition.label": "Branch", - "workflow_node.execute_success.label": "Execute success", + "workflow_node.execute_result_branch.label": "Execution result branch", - "workflow_node.execute_failure.label": "Execute failure", + "workflow_node.execute_success.label": "If the previous node succeeded ...", - "workflow_node.condition.label": "Condition" + "workflow_node.execute_failure.label": "If the previous node failed ..." } diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index aa82404e..c54656c8 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -3,7 +3,7 @@ "workflow_node.branch.add_node": "添加节点", "workflow_node.action.rename_node": "重命名", "workflow_node.action.remove_node": "删除节点", - "workflow_node.action.add_branch": "添加分支", + "workflow_node.action.add_branch": "添加并行分支", "workflow_node.action.rename_branch": "重命名", "workflow_node.action.remove_branch": "删除分支", @@ -379,13 +379,13 @@ "workflow_node.end.label": "结束", - "workflow_node.branch.label": "分支", + "workflow_node.branch.label": "并行分支", + + "workflow_node.condition.label": "分支", "workflow_node.execute_result_branch.label": "执行结果分支", - "workflow_node.execute_success.label": "执行成功", + "workflow_node.execute_success.label": "若前一节点执行成功…", - "workflow_node.execute_failure.label": "执行失败", - - "workflow_node.condition.label": "条件" + "workflow_node.execute_failure.label": "若前一节点执行失败…" }