feat: add certtest workflow template

This commit is contained in:
Fu Diwei 2025-06-01 22:59:24 +08:00
parent 6731c465e7
commit f885b49daf
10 changed files with 184 additions and 23 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -55,7 +55,6 @@ const workflowNodeTypeDefaultNames: Map<WorkflowNodeType, string> = new Map([
[WorkflowNodeType.ExecuteResultBranch, i18n.t("workflow_node.execute_result_branch.default_name")],
[WorkflowNodeType.ExecuteSuccess, i18n.t("workflow_node.execute_success.default_name")],
[WorkflowNodeType.ExecuteFailure, i18n.t("workflow_node.execute_failure.default_name")],
[WorkflowNodeType.Custom, i18n.t("workflow_node.custom.default_name")],
]);
const workflowNodeTypeDefaultInputs: Map<WorkflowNodeType, WorkflowNodeIO[]> = new Map([
@ -240,25 +239,153 @@ const isBranchLike = (node: WorkflowNode) => {
};
type InitWorkflowOptions = {
template?: "standard";
template?: "standard" | "certtest";
};
export const initWorkflow = (options: InitWorkflowOptions = {}): WorkflowModel => {
const root = newNode(WorkflowNodeType.Start, {}) as WorkflowNode;
root.config = { trigger: WORKFLOW_TRIGGERS.MANUAL };
if (options.template === "standard") {
switch (options.template) {
case "standard":
{
let current = root;
current.next = newNode(WorkflowNodeType.Apply, {});
const applyNode = newNode(WorkflowNodeType.Apply);
current.next = applyNode;
current = current.next;
current.next = newNode(WorkflowNodeType.Deploy, {});
current = current.next;
current.next = newNode(WorkflowNodeType.ExecuteResultBranch, {});
current.next = newNode(WorkflowNodeType.ExecuteResultBranch);
current = current.next!.branches![1];
current.next = newNode(WorkflowNodeType.Notify, {});
current.next = newNode(WorkflowNodeType.Notify, {
nodeConfig: {
subject: "[Certimate] Workflow Failure Alert!",
message: "Your workflow run for the certificate application has failed. Please check the details.",
} as WorkflowNodeConfigForNotify,
});
current = applyNode.next!.branches![0];
current.next = newNode(WorkflowNodeType.Deploy, {
nodeConfig: {
certificate: `${applyNode.id}#certificate`,
skipOnLastSucceeded: true,
} as WorkflowNodeConfigForDeploy,
});
current = current.next;
current.next = newNode(WorkflowNodeType.ExecuteResultBranch);
current = current.next!.branches![1];
current.next = newNode(WorkflowNodeType.Notify, {
nodeConfig: {
subject: "[Certimate] Workflow Failure Alert!",
message: "Your workflow run for the certificate deployment has failed. Please check the details.",
} as WorkflowNodeConfigForNotify,
});
}
break;
case "certtest":
{
let current = root;
const monitorNode = newNode(WorkflowNodeType.Monitor);
current.next = monitorNode;
current = current.next;
current.next = newNode(WorkflowNodeType.ExecuteResultBranch);
current = current.next!.branches![1];
current.next = newNode(WorkflowNodeType.Notify, {
nodeConfig: {
subject: "[Certimate] Workflow Failure Alert!",
message: "Your workflow run for the certificate monitoring has failed. Please check the details.",
} as WorkflowNodeConfigForNotify,
});
current = monitorNode.next!.branches![0];
const branchNode = newNode(WorkflowNodeType.Branch);
current.next = branchNode;
current = branchNode.branches![0];
current.name = i18n.t("workflow_node.condition.default_name.template_certtest_on_expire_soon");
current.config = {
expression: {
left: {
left: {
selector: {
id: monitorNode.id,
name: "certificate.validity",
type: "boolean",
},
type: "var",
},
operator: "eq",
right: {
type: "const",
value: "true",
valueType: "boolean",
},
type: "comparison",
},
operator: "and",
right: {
left: {
selector: {
id: monitorNode.id,
name: "certificate.daysLeft",
type: "number",
},
type: "var",
},
operator: "lte",
right: {
type: "const",
value: "30",
valueType: "number",
},
type: "comparison",
},
type: "logical",
},
} as WorkflowNodeConfigForCondition;
current.next = newNode(WorkflowNodeType.Notify, {
nodeConfig: {
subject: "[Certimate] Certificate Expiry Alert!",
message: "The certificate will expire soon. Please pay attention to your website.",
} as WorkflowNodeConfigForNotify,
});
current = branchNode.branches![1];
current.name = i18n.t("workflow_node.condition.default_name.template_certtest_on_expired");
current.config = {
expression: {
left: {
selector: {
id: monitorNode.id,
name: "certificate.validity",
type: "boolean",
},
type: "var",
},
operator: "eq",
right: {
type: "const",
value: "false",
valueType: "boolean",
},
type: "comparison",
},
} as WorkflowNodeConfigForCondition;
current.next = newNode(WorkflowNodeType.Notify, {
nodeConfig: {
subject: "[Certimate] Certificate Expiry Alert!",
message: "The certificate has already expired. Please pay attention to your website.",
} as WorkflowNodeConfigForNotify,
});
}
break;
}
return {
@ -275,6 +402,8 @@ export const initWorkflow = (options: InitWorkflowOptions = {}): WorkflowModel =
};
type NewNodeOptions = {
nodeName?: string;
nodeConfig?: Record<string, unknown>;
branchIndex?: number;
};
@ -284,8 +413,9 @@ export const newNode = (nodeType: WorkflowNodeType, options: NewNodeOptions = {}
const node: WorkflowNode = {
id: nanoid(),
name: nodeName,
name: options.nodeName ?? nodeName,
type: nodeType,
config: options.nodeConfig,
};
switch (nodeType) {

View File

@ -1 +0,0 @@


View File

@ -30,6 +30,8 @@
"workflow.new.templates.title": "Choose a Workflow Template",
"workflow.new.templates.template.standard.title": "Standard template",
"workflow.new.templates.template.standard.description": "A standard operating procedure that includes application, deployment, and notification steps.",
"workflow.new.templates.template.certtest.title": "Monitoring template",
"workflow.new.templates.template.certtest.description": "A monitoring operating procedure that includes monitoring, and notification steps.",
"workflow.new.templates.template.blank.title": "Blank template",
"workflow.new.templates.template.blank.description": "Customize all the contents of the workflow from the beginning.",
"workflow.new.modal.title": "Create workflow",

View File

@ -876,6 +876,8 @@
"workflow_node.condition.label": "Branch",
"workflow_node.condition.default_name": "Branch",
"workflow_node.condition.default_name.template_certtest_on_expire_soon": "If the certificate will expire soon ...",
"workflow_node.condition.default_name.template_certtest_on_expired": "If the certificate has expired ...",
"workflow_node.condition.form.expression.label": "Conditions to enter the branch",
"workflow_node.condition.form.expression.logical_operator.errmsg": "Please select logical operator of conditions",
"workflow_node.condition.form.expression.logical_operator.option.and.label": "Meeting all of the conditions (AND)",

View File

@ -29,7 +29,9 @@
"workflow.new.subtitle": "使用工作流来申请证书、部署上传和发送通知",
"workflow.new.templates.title": "选择工作流模板",
"workflow.new.templates.template.standard.title": "标准模板",
"workflow.new.templates.template.standard.description": "一个包含申请 + 部署 + 通知步骤的标准工作流程。",
"workflow.new.templates.template.standard.description": "一个包含证书申请 + 证书部署 + 消息通知步骤的工作流程。",
"workflow.new.templates.template.certtest.title": "监控模板",
"workflow.new.templates.template.certtest.description": "一个包含证书监控 + 消息通知步骤的工作流程。",
"workflow.new.templates.template.blank.title": "空白模板",
"workflow.new.templates.template.blank.description": "从零开始自定义工作流的任务内容。",
"workflow.new.modal.title": "新建工作流",

View File

@ -875,6 +875,8 @@
"workflow_node.condition.label": "分支",
"workflow_node.condition.default_name": "分支",
"workflow_node.condition.default_name.template_certtest_on_expire_soon": "若网站证书即将到期 ...",
"workflow_node.condition.default_name.template_certtest_on_expired": "若网站证书已到期 ...",
"workflow_node.condition.form.expression.label": "分支进入条件",
"workflow_node.condition.form.expression.logical_operator.errmsg": "请选择条件组合方式",
"workflow_node.condition.form.expression.logical_operator.option.and.label": "满足以下所有条件 (AND)",
@ -900,9 +902,9 @@
"workflow_node.execute_result_branch.label": "执行结果分支",
"workflow_node.execute_result_branch.default_name": "执行结果分支",
"workflow_node.execute_success.label": "若前序节点执行成功…",
"workflow_node.execute_success.default_name": "若前序节点执行成功…",
"workflow_node.execute_success.label": "若上一节点执行成功…",
"workflow_node.execute_success.default_name": "若上一节点执行成功…",
"workflow_node.execute_failure.label": "若前序节点执行失败…",
"workflow_node.execute_failure.default_name": "若前序节点执行失败…"
"workflow_node.execute_failure.label": "若上一节点执行失败…",
"workflow_node.execute_failure.default_name": "若上一节点执行失败…"
}

View File

@ -12,9 +12,10 @@ import { useAntdForm } from "@/hooks";
import { save as saveWorkflow } from "@/repository/workflow";
import { getErrMsg } from "@/utils/error";
const TEMPLATE_KEY_BLANK = "blank" as const;
const TEMPLATE_KEY_STANDARD = "standard" as const;
type TemplateKeys = typeof TEMPLATE_KEY_BLANK | typeof TEMPLATE_KEY_STANDARD;
const TEMPLATE_KEY_CERTTEST = "monitor" as const;
const TEMPLATE_KEY_BLANK = "blank" as const;
type TemplateKeys = typeof TEMPLATE_KEY_BLANK | typeof TEMPLATE_KEY_CERTTEST | typeof TEMPLATE_KEY_STANDARD;
const WorkflowNew = () => {
const navigate = useNavigate();
@ -27,8 +28,8 @@ const WorkflowNew = () => {
xs: { flex: "100%" },
md: { flex: "100%" },
lg: { flex: "50%" },
xl: { flex: "50%" },
xxl: { flex: "50%" },
xl: { flex: "33.3333%" },
xxl: { flex: "33.3333%" },
};
const [templateSelectKey, setTemplateSelectKey] = useState<TemplateKeys>();
@ -64,6 +65,10 @@ const WorkflowNew = () => {
workflow = initWorkflow({ template: "standard" });
break;
case TEMPLATE_KEY_CERTTEST:
workflow = initWorkflow({ template: "certtest" });
break;
default:
throw "Invalid state: `templateSelectKey`";
}
@ -116,7 +121,7 @@ const WorkflowNew = () => {
</Card>
<div className="p-4">
<div className="mx-auto max-w-[960px] px-2">
<div className="mx-auto max-w-[1600px] px-2">
<Typography.Text type="secondary">
<div className="mb-8 mt-4 text-xl">{t("workflow.new.templates.title")}</div>
</Typography.Text>
@ -139,6 +144,25 @@ const WorkflowNew = () => {
</div>
</Card>
</Col>
<Col {...templateGridSpans}>
<Card
className="size-full"
cover={<img className="min-h-[120px] object-contain" src="/imgs/workflow/tpl-certtest.png" />}
hoverable
onClick={() => handleTemplateClick(TEMPLATE_KEY_CERTTEST)}
>
<div className="flex w-full items-center gap-4">
<Card.Meta
className="grow"
title={t("workflow.new.templates.template.certtest.title")}
description={t("workflow.new.templates.template.certtest.description")}
/>
<Spin spinning={templateSelectKey === TEMPLATE_KEY_CERTTEST} />
</div>
</Card>
</Col>
<Col {...templateGridSpans}>
<Card
className="size-full"