From ce5a8b29152a6b922244e8245f94621e8769880c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 10 Sep 2025 18:11:52 +0800 Subject: [PATCH] feat: support mustache syntax on notification --- internal/workflow/engine/engine.go | 20 ++++----- .../workflow/engine/executor_biznotify.go | 32 +++++++++++++- internal/workflow/engine/state.go | 42 ++++--------------- ...zDeployNodeConfigFieldsProviderWebhook.tsx | 5 --- ...zNotifyNodeConfigFieldsProviderWebhook.tsx | 5 --- .../forms/BizNotifyNodeConfigForm.tsx | 5 +++ .../BranchBlockNodeConfigExprInputBox.tsx | 8 +++- ui/src/i18n/locales/en/nls.access.json | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 4 +- ui/src/i18n/locales/en/nls.workflow.vars.json | 5 ++- .../i18n/locales/zh/nls.workflow.nodes.json | 5 +-- ui/src/i18n/locales/zh/nls.workflow.vars.json | 5 ++- ui/src/pages/workflows/WorkflowNew.tsx | 10 +++-- 13 files changed, 76 insertions(+), 72 deletions(-) diff --git a/internal/workflow/engine/engine.go b/internal/workflow/engine/engine.go index 3f92b6c31..29a7525eb 100644 --- a/internal/workflow/engine/engine.go +++ b/internal/workflow/engine/engine.go @@ -66,13 +66,13 @@ func (we *workflowEngine) Invoke(ctx context.Context, execution WorkflowExecutio wfIOs := newInOutManager() wfVars := newVariableManager() - wfVars.Set("workflow.id", execution.WorkflowId, "string") - wfVars.Set("workflow.name", execution.WorkflowName, "string") - wfVars.Set("run.id", execution.RunId, "string") - wfVars.Set("run.trigger", execution.RunTrigger, "string") - wfVars.Set("error.nodeId", "", "string") - wfVars.Set("error.nodeName", "", "string") - wfVars.Set("error.message", "", "string") + wfVars.Set(stateVarKeyWorkflowId, execution.WorkflowId, "string") + wfVars.Set(stateVarKeyWorkflowName, execution.WorkflowName, "string") + wfVars.Set(stateVarKeyRunId, execution.RunId, "string") + wfVars.Set(stateVarKeyRunTrigger, execution.RunTrigger, "string") + wfVars.Set(stateVarKeyErrorNodeId, "", "string") + wfVars.Set(stateVarKeyErrorNodeName, "", "string") + wfVars.Set(stateVarKeyErrorMessage, "", "string") wfCtx := (&WorkflowContext{}). SetExecutingWorkflow(execution.WorkflowId, execution.RunId, execution.Graph). @@ -164,9 +164,9 @@ func (we *workflowEngine) executeNode(wfCtx *WorkflowContext, node *Node) error execRes, err := executor.Execute(execCtx) if err != nil && !errors.Is(err, ErrTerminated) { if !errors.Is(err, ErrBlocksException) { - wfCtx.variables.Set("error.nodeId", node.Id, "string") - wfCtx.variables.Set("error.nodeName", node.Data.Name, "string") - wfCtx.variables.Set("error.message", err.Error(), "string") + wfCtx.variables.Set(stateVarKeyErrorNodeId, node.Id, "string") + wfCtx.variables.Set(stateVarKeyErrorNodeName, node.Data.Name, "string") + wfCtx.variables.Set(stateVarKeyErrorMessage, err.Error(), "string") } we.fireOnNodeErrorHooks(wfCtx.ctx, node, err) diff --git a/internal/workflow/engine/executor_biznotify.go b/internal/workflow/engine/executor_biznotify.go index 7795fcac1..d29a8c22d 100644 --- a/internal/workflow/engine/executor_biznotify.go +++ b/internal/workflow/engine/executor_biznotify.go @@ -3,6 +3,9 @@ import ( "fmt" "log/slog" + "regexp" + "strings" + "time" "github.com/certimate-go/certimate/internal/notify" "github.com/certimate-go/certimate/internal/repository" @@ -37,14 +40,39 @@ func (ne *bizNotifyNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeEx } } + // 渲染通知模板 + reMustache := regexp.MustCompile(`\{\{\s*(\$[^\s]+)\s*\}\}`) + reMustacheReplacer := func(match string) string { + mustache := strings.TrimSpace(match[2 : len(match)-2]) + if mustache == "" { + return match + } + + key := mustache[1:] + if key == "" { + return match + } else if key == "now" { + return time.Now().Format(time.RFC3339) + } + + // TODO: 支持作用域变量 + if state, ok := execCtx.variables.Get(key); ok { + return state.ValueString() + } + + return match + } + subject := reMustache.ReplaceAllStringFunc(nodeCfg.Subject, reMustacheReplacer) + message := reMustache.ReplaceAllStringFunc(nodeCfg.Message, reMustacheReplacer) + // 推送通知 notifier := notify.NewClient(notify.WithLogger(ne.logger)) notifyReq := ¬ify.SendNotificationRequest{ Provider: nodeCfg.Provider, ProviderAccessConfig: providerAccessConfig, ProviderExtendedConfig: nodeCfg.ProviderConfig, - Subject: nodeCfg.Subject, - Message: nodeCfg.Message, + Subject: subject, + Message: message, } if _, err := notifier.SendNotification(execCtx.ctx, notifyReq); err != nil { ne.logger.Warn("could not send notification") diff --git a/internal/workflow/engine/state.go b/internal/workflow/engine/state.go index 68577d56c..7005fefbc 100644 --- a/internal/workflow/engine/state.go +++ b/internal/workflow/engine/state.go @@ -2,10 +2,8 @@ import ( "fmt" - "regexp" "slices" "strconv" - "strings" "sync" "time" ) @@ -32,7 +30,7 @@ func (s VariableState) ValueString() string { } return valueAsTime.Format(time.RFC3339) default: - return fmt.Sprintf("%v", s.Value) + return fmt.Sprintf("[%s]%v", s.ValueType, s.Value) } } @@ -49,8 +47,6 @@ type VariableManager interface { TakeScoped(scope string, key string) (*VariableState, bool) Remove(key string) bool RemoveScoped(scope string, key string) bool - - RenderTemplateString(template string) string } type variableManager struct { @@ -153,35 +149,6 @@ func (m *variableManager) RemoveScoped(scope string, key string) bool { return ok } -func (m *variableManager) RenderTemplateString(template string) string { - m.statesMtx.RLock() - defer m.statesMtx.RUnlock() - - replaceFunc := func(match string) string { - mustache := strings.TrimSpace(match[2 : len(match)-2]) - if mustache == "" { - return match - } - - key := mustache[1:] - if key == "" { - return match - } else if key == "$now" { - return time.Now().Format(time.RFC3339) - } - - // TODO: 支持作用域变量 - if state, ok := m.Get(key); ok { - return state.ValueString() - } - - return match - } - - re := regexp.MustCompile(`\{\{\s*(\$[^\s]+)\s*\}\}`) - return re.ReplaceAllStringFunc(template, replaceFunc) -} - func newVariableManager() VariableManager { return &variableManager{ states: make([]VariableState, 0), @@ -316,9 +283,16 @@ const ( ) const ( + stateVarKeyWorkflowId = "workflow.id" // ValueType: "string" + stateVarKeyWorkflowName = "workflow.name" // ValueType: "string" + stateVarKeyRunId = "run.id" // ValueType: "string" + stateVarKeyRunTrigger = "run.trigger" // ValueType: "string" stateVarKeyNodeId = "node.id" // ValueType: "string" stateVarKeyNodeName = "node.name" // ValueType: "string" stateVarKeyNodeSkipped = "node.skipped" // ValueType: "boolean" + stateVarKeyErrorNodeId = "error.nodeId" // ValueType: "string" + stateVarKeyErrorNodeName = "error.nodeName" // ValueType: "string" + stateVarKeyErrorMessage = "error.message" // ValueType: "string" stateVarKeyCertificateDomain = "certificate.domain" // ValueType: "string" stateVarKeyCertificateDomains = "certificate.domains" // ValueType: "string" stateVarKeyCertificateNotBefore = "certificate.notBefore" // ValueType: "datetime" diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx index 7c288dcab..58578dae4 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx @@ -4,7 +4,6 @@ import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; import CodeInput from "@/components/CodeInput"; -import Tips from "@/components/Tips"; import { useFormNestedFieldsContext } from "./_context"; @@ -48,10 +47,6 @@ const BizDeployNodeConfigFieldsProviderWebhook = () => { /> - - } /> - - { /> - - } /> - - + + + } /> +
diff --git a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigExprInputBox.tsx b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigExprInputBox.tsx index 0fa1799b4..ee55e3f52 100644 --- a/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigExprInputBox.tsx +++ b/ui/src/components/workflow/designer/forms/BranchBlockNodeConfigExprInputBox.tsx @@ -189,13 +189,17 @@ const BranchBlockNodeConfigExprInputBox = forwardRef
The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
  1. ${DOMAIN}: The primary domain of the certificate (CommonName).
  2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
  3. ${CERTIFICATE}: The PEM format content of the certificate file.
  4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
  5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
  6. ${PRIVATE_KEY}: The PEM format content of the private key file.

When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
  1. application/json (default).
  2. application/x-www-form-urlencoded: Nested data is not supported.
  3. multipart/form-data: Nested data is not supported.
  4. ", + "access.form.webhook_data.guide_for_deployment": "The Webhook data should be in JSON format.

    The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
    1. ${DOMAIN}: The primary domain of the certificate (a.k.a. CommonName).
    2. ${DOMAINS}: The domains list of the certificate (a.k.a. SubjectAltNames).
    3. ${CERTIFICATE}: The PEM format content of the certificate file.
    4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
    5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
    6. ${PRIVATE_KEY}: The PEM format content of the private key file.

    When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
    1. application/json (default).
    2. application/x-www-form-urlencoded: Nested data is not supported.
    3. multipart/form-data: Nested data is not supported.
    4. ", "access.form.webhook_data.guide_for_notification": "The Webhook data should be in JSON format.

      The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables:
      1. ${SUBJECT}: The subject of notification.
      2. ${MESSAGE}: The message of notification.

      When the request method is GET, the data will be passed as query string. Otherwise, the data will be encoded in the format indicated by the Content-Type in the request headers. Supported formats:
      1. application/json (default).
      2. application/x-www-form-urlencoded: Nested data is not supported.
      3. multipart/form-data: Nested data is not supported.
      4. ", "access.form.webhook_preset_data.button": "Use preset template", "access.form.webhook_preset_data.option.bark.label": "Bark", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 422a55d40..4046b3129 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -1036,7 +1036,6 @@ "workflow_node.deploy.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", "workflow_node.deploy.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.", - "workflow_node.deploy.form.webhook_data.guide": "
        Supported variables:
        1. ${DOMAIN}: The primary domain of the certificate (CommonName).
        2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
        3. ${CERTIFICATE}: The PEM format content of the certificate file.
        4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
        5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
        6. ${PRIVATE_KEY}: The PEM format content of the private key file.

        Please visit the credentials page for addtional notes.", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string", "workflow_node.deploy.form.webhook_timeout.label": "Webhook timeout (Optional)", "workflow_node.deploy.form.webhook_timeout.placeholder": "Please enter Webhook timeout", @@ -1046,6 +1045,7 @@ "workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.", "workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "skip", "workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "not skip", + "workflow_node.notify.label": "Send notification", "workflow_node.notify.help": "Invoke the APIs of the service provider to push message notifications.", "workflow_node.notify.default_name": "Notification", @@ -1058,6 +1058,7 @@ "workflow_node.notify.form.subject.placeholder": "Please enter subject", "workflow_node.notify.form.message.label": "Message", "workflow_node.notify.form.message.placeholder": "Please enter message", + "workflow_node.notify.form.template.guide": "
        The content using the \"Mustache\" syntax (double curly braces) and preceded by \"$\" in the subject or message are text interpolations. They will be replaced by the actual values.
        Supported text interpolations:
        1. workflow.id: The ID of the workflow.
        2. workflow.name: The name of the workflow.
        3. run.id: The ID of the workflow run.
        4. error.nodeId: The node ID that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.
        5. error.nodeName: The node name that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.
        6. error.message: The error message that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.
        7. certificate.domain: The primary domain of the certificate (a.k.a. CommonName). If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        8. certificate.domains: The domains list of the certificate (a.k.a. SubjectAltNames). If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        9. certificate.notBefore: The effect time of the certificate, formatted in RFC3339. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        10. certificate.notAfter: The effect time of the certificate, formatted in RFC3339. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        11. certificate.hoursLeft: The left hours of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        12. certificate.daysLeft: The left days of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        13. certificate.validity: The validity of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.
        14. now: The current time on the server, formatted in RFC3339.

        Example:
        Your workflow {{ $workflow.name }} has failed on node {{ $error.nodeName }} at {{ $now }}.

        Please visit the documentation for more details.
        ", "workflow_node.notify.form.provider.label": "Notification channel", "workflow_node.notify.form.provider.placeholder": "Please select notification channel", "workflow_node.notify.form.provider.search.placeholder": "Search notification channel ...", @@ -1083,7 +1084,6 @@ "workflow_node.notify.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data", "workflow_node.notify.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.", - "workflow_node.notify.form.webhook_data.guide": "
        Supported variables:
        1. ${SUBJECT}: The subject of notification.
        2. ${MESSAGE}: The message of notification.

        Please visit the credentials page for addtional notes.", "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string", "workflow_node.notify.form.webhook_timeout.label": "Webhook timeout (Optional)", "workflow_node.notify.form.webhook_timeout.placeholder": "Please enter Webhook timeout", diff --git a/ui/src/i18n/locales/en/nls.workflow.vars.json b/ui/src/i18n/locales/en/nls.workflow.vars.json index a96d8ba54..b25d7a0d2 100644 --- a/ui/src/i18n/locales/en/nls.workflow.vars.json +++ b/ui/src/i18n/locales/en/nls.workflow.vars.json @@ -1,6 +1,7 @@ { "workflow.variables.type.certificate.label": "Certificate", - "workflow.variables.selector.validity.label": "Validity", - "workflow.variables.selector.days_left.label": "Days left" + "workflow.variables.selector.hours_left.label": "Hours left", + "workflow.variables.selector.days_left.label": "Days left", + "workflow.variables.selector.validity.label": "Validity" } diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index fd3b1bf57..91b6f22cd 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -1034,7 +1034,6 @@ "workflow_node.deploy.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.deploy.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.deploy.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", - "workflow_node.deploy.form.webhook_data.guide": "
        支持的变量:
        1. ${DOMAIN}:证书的主域名(即 CommonName)。
        2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
        3. ${CERTIFICATE}:证书文件 PEM 格式内容。
        4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
        5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
        6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

        其他注意事项请前往授权凭据页面查看。", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "workflow_node.deploy.form.webhook_timeout.label": "Webhook 超时时间(可选)", "workflow_node.deploy.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间", @@ -1044,6 +1043,7 @@ "workflow_node.deploy.form.skip_on_last_succeeded.suffix": "此部署节点。", "workflow_node.deploy.form.skip_on_last_succeeded.switch.on": "跳过", "workflow_node.deploy.form.skip_on_last_succeeded.switch.off": "不跳过", + "workflow_node.notify.label": "推送通知", "workflow_node.notify.help": "调用服务提供商相关 API,将消息通知推送到指定的目标。", "workflow_node.notify.default_name": "通知", @@ -1055,7 +1055,7 @@ "workflow_node.notify.form.subject.label": "通知主题", "workflow_node.notify.form.subject.placeholder": "请输入通知主题", "workflow_node.notify.form.message.label": "通知内容", - "workflow_node.notify.form.message.placeholder": "请输入通知内容", + "workflow_node.notify.form.template.guide": "
        通知主题或内容中使用「Mustache」语法(即双大括号)包裹、并以「$」符号开头的文本会被视为模板插值,将在推送时被替换为实际值。
        支持的模板插值:
        1. workflow.id:工作流 ID。
        2. workflow.name:工作流名称。
        3. run.id:运行 ID。
        4. error.nodeId:执行失败时的节点 ID。如果在此之前有多个执行失败的节点,始终表示最近的一个。
        5. error.nodeName:执行失败时的节点名称。如果在此之前有多个执行失败的节点,始终表示最近的一个。
        6. error.message:执行失败时的错误信息。如果在此之前有多个执行失败的节点,始终表示最近的一个。
        7. certificate.domain:证书主域名(即 CommonName)。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        8. certificate.domains:证书多域名列表(即 SubjectAltNames)。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        9. certificate.notBefore:证书生效时间,以 RFC3339 格式化。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        10. certificate.notAfter:证书过期时间,以 RFC3339 格式化。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        11. certificate.hoursLeft:证书剩余小时数。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        12. certificate.daysLeft:证书剩余天数。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        13. certificate.validity:证书是否有效。如果在此之前有多个输出证书的节点,始终表示最近的一个。
        14. now:服务器当前时间,以 RFC3339 格式化。

        示例:
        Your workflow {{ $workflow.name }} has failed on node {{ $error.nodeName }} at {{ $now }}.

        更多内容请查看文档。
        ", "workflow_node.notify.form.provider.label": "通知渠道", "workflow_node.notify.form.provider.placeholder": "请选择通知渠道", "workflow_node.notify.form.provider.search.placeholder": "搜索通知渠道……", @@ -1081,7 +1081,6 @@ "workflow_node.notify.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.notify.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.notify.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", - "workflow_node.notify.form.webhook_data.guide": "
        支持的变量:
        1. ${SUBJECT}:通知主题。
        2. ${MESSAGE}:通知内容。

        其他注意事项请前往授权凭据页面查看。", "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", "workflow_node.notify.form.webhook_timeout.label": "Webhook 超时时间(可选)", "workflow_node.notify.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间", diff --git a/ui/src/i18n/locales/zh/nls.workflow.vars.json b/ui/src/i18n/locales/zh/nls.workflow.vars.json index eddfc5858..b6d08a064 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.vars.json +++ b/ui/src/i18n/locales/zh/nls.workflow.vars.json @@ -1,6 +1,7 @@ { "workflow.variables.type.certificate.label": "证书", - "workflow.variables.selector.validity.label": "有效性", - "workflow.variables.selector.days_left.label": "剩余天数" + "workflow.variables.selector.hours_left.label": "剩余小时数", + "workflow.variables.selector.days_left.label": "剩余天数", + "workflow.variables.selector.validity.label": "有效性" } diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index 72ba65bb7..c0f403dcf 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -116,7 +116,7 @@ const WorkflowNew = () => { notifyOnFailureNode.data.config = { ...notifyOnFailureNode.data.config, subject: "[Certimate] Workflow Failure Alert!", - message: "Your workflow run has failed. Please check the details.", + message: 'Your workflow "{{ $workflow.name }}" run has failed. Please check the details.', } as WorkflowNodeConfigForBizNotify; tryCatchNode.blocks!.at(0)!.blocks ??= []; @@ -142,19 +142,21 @@ const WorkflowNew = () => { notifyOnExpiringSoonNode.data.config = { ...notifyOnExpiringSoonNode.data.config, subject: "[Certimate] Certificate Expiry Alert!", - message: "The certificate which you are monitoring will be expiring soon. Please pay attention to your website.", + message: + "The certificate which you are monitoring will be expiring soon. Please pay attention to your website. \r\nDomains: {{ $certificate.domains }} \r\nExpiration: {{ $certificate.notAfter }}({{ $certificate.daysLeft }} days left)", } as WorkflowNodeConfigForBizNotify; notifyOnExpiredNode.data.config = { ...notifyOnExpiredNode.data.config, subject: "[Certimate] Certificate Expiry Alert!", - message: "The certificate which you are monitoring has already expired. Please pay attention to your website.", + message: + "The certificate which you are monitoring has already expired. Please pay attention to your website. \r\nDomains: {{ $certificate.domains }} \r\nExpiration: {{ $certificate.notAfter }}", } as WorkflowNodeConfigForBizNotify; notifyOnFailureNode.data.config = { ...notifyOnFailureNode.data.config, subject: "[Certimate] Workflow Failure Alert!", - message: "Your workflow run has failed. Please check the details.", + message: 'Your workflow "{{ $workflow.name }}" run has failed. Please check the details.', } as WorkflowNodeConfigForBizNotify; tryCatchNode.blocks!.at(0)!.blocks ??= [];