diff --git a/ui/public/imgs/workflow/tpl-blank.png b/ui/public/imgs/workflow/tpl-blank.png index 8f683ce6..ee6568a9 100644 Binary files a/ui/public/imgs/workflow/tpl-blank.png and b/ui/public/imgs/workflow/tpl-blank.png differ diff --git a/ui/public/imgs/workflow/tpl-certtest.png b/ui/public/imgs/workflow/tpl-certtest.png new file mode 100644 index 00000000..da7bfada Binary files /dev/null and b/ui/public/imgs/workflow/tpl-certtest.png differ diff --git a/ui/public/imgs/workflow/tpl-standard.png b/ui/public/imgs/workflow/tpl-standard.png index 46698a87..c14238ff 100644 Binary files a/ui/public/imgs/workflow/tpl-standard.png and b/ui/public/imgs/workflow/tpl-standard.png differ diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index d3b9f822..9a41a393 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -55,7 +55,6 @@ const workflowNodeTypeDefaultNames: Map = 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 = 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") { - let current = root; - current.next = newNode(WorkflowNodeType.Apply, {}); + switch (options.template) { + case "standard": + { + let current = root; - current = current.next; - current.next = newNode(WorkflowNodeType.Deploy, {}); + const applyNode = newNode(WorkflowNodeType.Apply); + current.next = applyNode; - current = current.next; - current.next = newNode(WorkflowNodeType.ExecuteResultBranch, {}); + current = current.next; + current.next = newNode(WorkflowNodeType.ExecuteResultBranch); - current = current.next!.branches![1]; - current.next = newNode(WorkflowNodeType.Notify, {}); + current = current.next!.branches![1]; + 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; 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) { diff --git a/ui/src/domain/workflowExpr.ts b/ui/src/domain/workflowExpr.ts deleted file mode 100644 index 5f282702..00000000 --- a/ui/src/domain/workflowExpr.ts +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index b4f9d7e6..b086e25f 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -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", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 626e9b68..a22cd8c4 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -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)", diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index 46cdc228..9ff12aac 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.json +++ b/ui/src/i18n/locales/zh/nls.workflow.json @@ -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": "新建工作流", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 7710e386..b568d2ca 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -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": "若上一节点执行失败…" } diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index 5f6af27b..9877dcc2 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -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(); @@ -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 = () => {
-
+
{t("workflow.new.templates.title")}
@@ -139,6 +144,25 @@ const WorkflowNew = () => {
+ + + } + hoverable + onClick={() => handleTemplateClick(TEMPLATE_KEY_CERTTEST)} + > +
+ + +
+
+ +