feat: support mustache syntax on notification

This commit is contained in:
Fu Diwei
2025-09-10 18:12:06 +08:00
parent a1ed763aa0
commit ce5a8b2915
13 changed files with 76 additions and 72 deletions
+10 -10
View File
@@ -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)
+30 -2
View File
@@ -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 := &notify.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")
+8 -34
View File
@@ -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"
@@ -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 = () => {
/>
</Form.Item>
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.webhook_data.guide") }}></span>} />
</Form.Item>
<Form.Item name={[parentNamePath, "timeout"]} label={t("workflow_node.deploy.form.webhook_timeout.label")} rules={[formRule]}>
<Input
type="number"
@@ -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 BizNotifyNodeConfigFieldsProviderWebhook = () => {
/>
</Form.Item>
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.webhook_data.guide") }}></span>} />
</Form.Item>
<Form.Item name={[parentNamePath, "timeout"]} label={t("workflow_node.notify.form.webhook_timeout.label")} rules={[formRule]}>
<Input
type="number"
@@ -11,6 +11,7 @@ import AccessSelect from "@/components/access/AccessSelect";
import NotificationProviderPicker from "@/components/provider/NotificationProviderPicker";
import NotificationProviderSelect from "@/components/provider/NotificationProviderSelect";
import Show from "@/components/Show";
import Tips from "@/components/Tips";
import { type AccessModel } from "@/domain/access";
import { NOTIFICATION_PROVIDERS, notificationProvidersMap } from "@/domain/provider";
import { type WorkflowNodeConfigForBizNotify, defaultNodeConfigForBizNotify } from "@/domain/workflow";
@@ -143,6 +144,10 @@ const BizNotifyNodeConfigForm = ({ node, ...props }: BizNotifyNodeConfigFormProp
<Form.Item name="message" label={t("workflow_node.notify.form.message.label")} rules={[formRule]}>
<Input.TextArea autoSize={{ minRows: 3, maxRows: 10 }} placeholder={t("workflow_node.notify.form.message.placeholder")} />
</Form.Item>
<Form.Item>
<Tips message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.notify.form.template.guide") }}></span>} />
</Form.Item>
</div>
<div id="channel" data-anchor="channel">
@@ -189,13 +189,17 @@ const BranchBlockNodeConfigExprInputBox = forwardRef<BranchBlockNodeConfigExprIn
};
group.options.push({
label: `${t("workflow.variables.type.certificate.label")} - ${t("workflow.variables.selector.validity.label")}`,
value: `${node.id}#certificate.validity#boolean`,
label: `${t("workflow.variables.type.certificate.label")} - ${t("workflow.variables.selector.hours_left.label")}`,
value: `${node.id}#certificate.hoursLeft#number`,
});
group.options.push({
label: `${t("workflow.variables.type.certificate.label")} - ${t("workflow.variables.selector.days_left.label")}`,
value: `${node.id}#certificate.daysLeft#number`,
});
group.options.push({
label: `${t("workflow.variables.type.certificate.label")} - ${t("workflow.variables.selector.validity.label")}`,
value: `${node.id}#certificate.validity#boolean`,
});
return group;
})
+1 -1
View File
@@ -518,7 +518,7 @@
"access.form.webhook_data.label": "Webhook data (Optional)",
"access.form.webhook_data.placeholder": "Please enter the default Webhook data",
"access.form.webhook_data.help": "Notes: It can be overrided in the workflows.",
"access.form.webhook_data.guide_for_deployment": "The Webhook data should be in JSON format. <br><br>The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables: <br><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>: The primary domain of the certificate (<i>CommonName</i>).</li><li><strong>${DOMAINS}</strong>: The domain list of the certificate (<i>SubjectAltNames</i>).</li><li><strong>${CERTIFICATE}</strong>: The PEM format content of the certificate file.</li><li><strong>${SERVER_CERTIFICATE}</strong>: The PEM format content of the server certificate file.</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>: The PEM format content of the intermediate CA certificate file.</li><li><strong>${PRIVATE_KEY}</strong>: The PEM format content of the private key file.</li></ol><br>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: <br><ol style=\"list-style: disc;\"><li>application/json (default).</li><li>application/x-www-form-urlencoded: Nested data is not supported.</li><li>multipart/form-data: Nested data is not supported.</li>",
"access.form.webhook_data.guide_for_deployment": "The Webhook data should be in JSON format. <br><br>The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables: <br><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>: The primary domain of the certificate (a.k.a. <i>CommonName</i>).</li><li><strong>${DOMAINS}</strong>: The domains list of the certificate (a.k.a. <i>SubjectAltNames</i>).</li><li><strong>${CERTIFICATE}</strong>: The PEM format content of the certificate file.</li><li><strong>${SERVER_CERTIFICATE}</strong>: The PEM format content of the server certificate file.</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>: The PEM format content of the intermediate CA certificate file.</li><li><strong>${PRIVATE_KEY}</strong>: The PEM format content of the private key file.</li></ol><br>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: <br><ol style=\"list-style: disc;\"><li>application/json (default).</li><li>application/x-www-form-urlencoded: Nested data is not supported.</li><li>multipart/form-data: Nested data is not supported.</li>",
"access.form.webhook_data.guide_for_notification": "The Webhook data should be in JSON format. <br><br>The values in JSON support template variables, which will be replaced by actual values when sent to the Webhook URL. Supported variables: <br><ol style=\"list-style: disc;\"><li><strong>${SUBJECT}</strong>: The subject of notification.</li><li><strong>${MESSAGE}</strong>: The message of notification.</li></ol><br>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: <br><ol style=\"list-style: disc;\"><li>application/json (default).</li><li>application/x-www-form-urlencoded: Nested data is not supported.</li><li>multipart/form-data: Nested data is not supported.</li>",
"access.form.webhook_preset_data.button": "Use preset template",
"access.form.webhook_preset_data.option.bark.label": "Bark",
@@ -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": "<details><summary>Supported variables: </summary><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>: The primary domain of the certificate (<i>CommonName</i>).</li><li><strong>${DOMAINS}</strong>: The domain list of the certificate (<i>SubjectAltNames</i>).</li><li><strong>${CERTIFICATE}</strong>: The PEM format content of the certificate file.</li><li><strong>${SERVER_CERTIFICATE}</strong>: The PEM format content of the server certificate file.</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>: The PEM format content of the intermediate CA certificate file.</li><li><strong>${PRIVATE_KEY}</strong>: The PEM format content of the private key file.</li></ol></details><br>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": "<details><summary>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. </summary><br>Supported text interpolations: <ol style=\"list-style: disc;\"><li><em>workflow.id</em>: The ID of the workflow.</li><li><em>workflow.name</em>: The name of the workflow.</li><li><em>run.id</em>: The ID of the workflow run.</li><li><em>error.nodeId</em>: The node ID that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.</li><li><em>error.nodeName</em>: The node name that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.</li><li><em>error.message</em>: The error message that execution failed. If there are multiple nodes that have failed before this, it always indicate the nearest one.</li><li><em>certificate.domain</em>: The primary domain of the certificate (a.k.a. <i>CommonName</i>). If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.</li><li><em>certificate.domains</em>: The domains list of the certificate (a.k.a. <i>SubjectAltNames</i>). If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.</li><li><em>certificate.notBefore</em>: 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.</li><li><em>certificate.notAfter</em>: 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.</li><li><em>certificate.hoursLeft</em>: The left hours of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.</li><li><em>certificate.daysLeft</em>: The left days of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.</li><li><em>certificate.validity</em>: The validity of the certificate. If there are multiple nodes outputting a certificate before this, it always indicate the nearest one.</li><li><em>now</em>: The current time on the server, formatted in RFC3339. </li></ol><br>Example: <br><em>Your workflow {{ $workflow.name }} has failed on node {{ $error.nodeName }} at {{ $now }}.</em><br><br>Please visit the documentation for more details.</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": "<details><summary>Supported variables: </summary><ol style=\"list-style: disc;\"><li><strong>${SUBJECT}</strong>: The subject of notification.</li><li><strong>${MESSAGE}</strong>: The message of notification.</li></ol></details><br>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",
@@ -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"
}
@@ -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": "<details><summary>支持的变量:</summary><ol style=\"list-style: disc;\"><li><strong>${DOMAIN}</strong>:证书的主域名(即 <i>CommonName</i>)。</li><li><strong>${DOMAINS}</strong>:证书的多域名列表(即 <i>SubjectAltNames</i>)。</li><li><strong>${CERTIFICATE}</strong>:证书文件 PEM 格式内容。</li><li><strong>${SERVER_CERTIFICATE}</strong>:证书文件(仅含服务器证书)PEM 格式内容。</li><li><strong>${INTERMEDIA_CERTIFICATE}</strong>:证书文件(仅含中间证书)PEM 格式内容。</li><li><strong>${PRIVATE_KEY}</strong>:私钥文件 PEM 格式内容。</li></ol></details><br>其他注意事项请前往授权凭据页面查看。",
"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": "<details><summary>通知主题或内容中使用「Mustache」语法(即双大括号)包裹、并以「$」符号开头的文本会被视为模板插值,将在推送时被替换为实际值。</summary><br>支持的模板插值:<ol style=\"list-style: disc;\"><li><em>workflow.id</em>:工作流 ID。</li><li><em>workflow.name</em>:工作流名称。</li><li><em>run.id</em>:运行 ID。</li><li><em>error.nodeId</em>:执行失败时的节点 ID。如果在此之前有多个执行失败的节点,始终表示最近的一个。</li><li><em>error.nodeName</em>:执行失败时的节点名称。如果在此之前有多个执行失败的节点,始终表示最近的一个。</li><li><em>error.message</em>:执行失败时的错误信息。如果在此之前有多个执行失败的节点,始终表示最近的一个。</li><li><em>certificate.domain</em>:证书主域名(即 <i>CommonName</i>)。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.domains</em>:证书多域名列表(即 <i>SubjectAltNames</i>)。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.notBefore</em>:证书生效时间,以 RFC3339 格式化。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.notAfter</em>:证书过期时间,以 RFC3339 格式化。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.hoursLeft</em>:证书剩余小时数。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.daysLeft</em>:证书剩余天数。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>certificate.validity</em>:证书是否有效。如果在此之前有多个输出证书的节点,始终表示最近的一个。</li><li><em>now</em>:服务器当前时间,以 RFC3339 格式化。</li></ol><br>示例:<br><em>Your workflow {{ $workflow.name }} has failed on node {{ $error.nodeName }} at {{ $now }}.</em><br><br>更多内容请查看文档。</details>",
"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": "<details><summary>支持的变量:</summary><ol style=\"list-style: disc;\"><li><strong>${SUBJECT}</strong>:通知主题。</li><li><strong>${MESSAGE}</strong>:通知内容。</ol></details><br>其他注意事项请前往授权凭据页面查看。",
"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 超时时间",
@@ -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": "有效性"
}
+6 -4
View File
@@ -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 ??= [];