From c2427697def4f7dba76367aa6761988d04198d5f Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 4 Dec 2025 17:03:40 +0800 Subject: [PATCH] feat: preset notify templates in workflow notification nodes --- .../notifier/providers/webhook/webhook.go | 4 + ui/eslint.config.mjs | 2 +- .../AccessConfigFieldsProviderWebhook.tsx | 32 +++---- .../preset/PresetNotifyTemplatesPopselect.tsx | 83 +++++++++++++++++++ .../forms/BizNotifyNodeConfigForm.tsx | 25 +++++- ui/src/i18n/locales/en/nls.access.json | 2 +- ui/src/i18n/locales/en/nls.preset.json | 7 +- .../i18n/locales/en/nls.workflow.nodes.json | 2 +- ui/src/i18n/locales/zh/nls.access.json | 4 +- ui/src/i18n/locales/zh/nls.preset.json | 7 +- .../i18n/locales/zh/nls.workflow.nodes.json | 3 +- ui/src/pages/workflows/WorkflowNew.tsx | 4 +- 12 files changed, 146 insertions(+), 29 deletions(-) create mode 100644 ui/src/components/preset/PresetNotifyTemplatesPopselect.tsx diff --git a/pkg/core/notifier/providers/webhook/webhook.go b/pkg/core/notifier/providers/webhook/webhook.go index 4e065e310..9c182273c 100644 --- a/pkg/core/notifier/providers/webhook/webhook.go +++ b/pkg/core/notifier/providers/webhook/webhook.go @@ -127,6 +127,10 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) } + replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_SUBJECT}", subject) + replaceJsonValueRecursively(webhookData, "${CERTIMATE_NOTIFIER_MESSAGE}", message) + + // 兼容旧版变量 replaceJsonValueRecursively(webhookData, "${SUBJECT}", subject) replaceJsonValueRecursively(webhookData, "${MESSAGE}", message) diff --git a/ui/eslint.config.mjs b/ui/eslint.config.mjs index 39721b4f2..f7a96e166 100644 --- a/ui/eslint.config.mjs +++ b/ui/eslint.config.mjs @@ -125,7 +125,7 @@ export default defineConfig( "react-hooks/exhaustive-deps": ["warn"], "react-hooks/immutability": ["warn"], "react-hooks/refs": ["warn"], - "react-hooks/preserve-manual-memoization": ["warn"], + "react-hooks/preserve-manual-memoization": ["off"], "react-hooks/set-state-in-effect": ["warn"], "react-hooks/set-state-in-render": ["warn"], "react-refresh/only-export-components": [ diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderWebhook.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderWebhook.tsx index eeaf62981..de3c671ad 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProviderWebhook.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderWebhook.tsx @@ -68,8 +68,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF [parentNamePath, "data"], JSON.stringify( { - title: "${SUBJECT}", - body: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + body: "${CERTIMATE_NOTIFIER_MESSAGE}", device_key: "", }, null, @@ -86,8 +86,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF [parentNamePath, "data"], JSON.stringify( { - title: "${SUBJECT}", - message: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + message: "${CERTIMATE_NOTIFIER_MESSAGE}", priority: 1, }, null, @@ -105,8 +105,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF JSON.stringify( { topic: "", - title: "${SUBJECT}", - message: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + message: "${CERTIMATE_NOTIFIER_MESSAGE}", priority: 1, }, null, @@ -125,8 +125,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF { token: "", user: "", - title: "${SUBJECT}", - message: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + message: "${CERTIMATE_NOTIFIER_MESSAGE}", }, null, 2 @@ -143,8 +143,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF JSON.stringify( { token: "", - title: "${SUBJECT}", - content: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + content: "${CERTIMATE_NOTIFIER_MESSAGE}", }, null, 2 @@ -160,8 +160,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF [parentNamePath, "data"], JSON.stringify( { - title: "${SUBJECT}", - desp: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + desp: "${CERTIMATE_NOTIFIER_MESSAGE}", }, null, 2 @@ -177,8 +177,8 @@ const AccessConfigFormFieldsProviderWebhook = ({ usage = "none" }: AccessConfigF [parentNamePath, "data"], JSON.stringify( { - title: "${SUBJECT}", - desp: "${MESSAGE}", + title: "${CERTIMATE_NOTIFIER_SUBJECT}", + desp: "${CERTIMATE_NOTIFIER_MESSAGE}", }, null, 2 @@ -324,8 +324,8 @@ const getInitialValues = ({ usage = "none" }: { usage?: "deployment" | "notifica } : usage === "notification" ? { - subject: "${SUBJECT}", - message: "${MESSAGE}", + subject: "${CERTIMATE_NOTIFIER_SUBJECT}", + message: "${CERTIMATE_NOTIFIER_MESSAGE}", } : {}, null, diff --git a/ui/src/components/preset/PresetNotifyTemplatesPopselect.tsx b/ui/src/components/preset/PresetNotifyTemplatesPopselect.tsx new file mode 100644 index 000000000..bf171a517 --- /dev/null +++ b/ui/src/components/preset/PresetNotifyTemplatesPopselect.tsx @@ -0,0 +1,83 @@ +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { IconMoodEmpty } from "@tabler/icons-react"; +import { useMount } from "ahooks"; +import { Dropdown, type DropdownProps } from "antd"; + +import { useZustandShallowSelector } from "@/hooks"; +import { useNotifyTemplatesStore } from "@/stores/settings"; + +type PresetTemplate = { + subject: string; + message: string; +}; + +export interface PresetNotifyTemplatesPopselectProps extends Omit { + options?: NonNullable["items"]; + onSelect?: (value: string, template?: PresetTemplate | undefined) => void; +} + +const PresetNotifyTemplatesPopselect = ({ className, options, onSelect, ...props }: PresetNotifyTemplatesPopselectProps) => { + const { t } = useTranslation(); + + const { templates, fetchTemplates } = useNotifyTemplatesStore(useZustandShallowSelector(["templates", "fetchTemplates"])); + useMount(() => { + fetchTemplates(false); + }); + + const menuItems = useMemo(() => { + type MenuItem = NonNullable["items"]>[number]; + const temp: MenuItem[] = []; + + if (!options?.length && !templates?.length) { + temp.push({ + key: "nodata", + label: t("common.text.nodata"), + icon: ( + + + + ), + disabled: true, + }); + return temp; + } + + if (options?.length) { + temp.push( + ...options.map((option) => { + return { + ...option!, + onClick: (e: any) => { + if ("onClick" in option!) { + option.onClick?.(e); + } + + onSelect?.(String(option!.key!)); + }, + }; + }) + ); + } + + if (templates?.length) { + temp.push({ + key: "custom", + label: t("preset.dropdown.option_group.custom"), + children: templates.map((template) => ({ + key: `custom/${template.name}`, + label: template.name, + onClick: () => { + onSelect?.(template.name, template); + }, + })), + }); + } + + return temp; + }, [options, templates, onSelect]); + + return ; +}; + +export default PresetNotifyTemplatesPopselect; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx index a88736526..bc2ad189c 100644 --- a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigForm.tsx @@ -1,13 +1,14 @@ import { useEffect, useMemo } from "react"; import { getI18n, useTranslation } from "react-i18next"; import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; -import { IconPlus } from "@tabler/icons-react"; +import { IconChevronDown, IconPlus } from "@tabler/icons-react"; import { type AnchorProps, Button, Divider, Form, type FormInstance, Input, Switch, Typography } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; import AccessEditDrawer from "@/components/access/AccessEditDrawer"; import AccessSelect from "@/components/access/AccessSelect"; +import PresetNotifyTemplatesPopselect from "@/components/preset/PresetNotifyTemplatesPopselect"; import NotificationProviderPicker from "@/components/provider/NotificationProviderPicker"; import NotificationProviderSelect from "@/components/provider/NotificationProviderSelect"; import Show from "@/components/Show"; @@ -111,8 +112,26 @@ const BizNotifyNodeConfigForm = ({ node, ...props }: BizNotifyNodeConfigFormProp - - + +
+ { + if (template) { + formInst.setFieldValue("subject", template.subject); + formInst.setFieldValue("message", template.message); + } + }} + > + + +
+ + +
diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index e118a7cc7..cb43649dd 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -627,7 +627,7 @@ "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.

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_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. ${CERTIMATE_NOTIFIER_SUBJECT}: The subject of notification.
      2. ${CERTIMATE_NOTIFIER_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 data", "access.form.webhook_preset_data.option.bark.label": "Bark", "access.form.webhook_preset_data.option.gotify.label": "Gotify", diff --git a/ui/src/i18n/locales/en/nls.preset.json b/ui/src/i18n/locales/en/nls.preset.json index aa9ef623a..b04991051 100644 --- a/ui/src/i18n/locales/en/nls.preset.json +++ b/ui/src/i18n/locales/en/nls.preset.json @@ -25,5 +25,10 @@ "preset.form.notification_message.label": "Notification message", "preset.form.notification_message.placeholder": "Please enter notification message", "preset.form.script_command.label": "Script command", - "preset.form.script_command.placeholder": "Please enter script command" + "preset.form.script_command.placeholder": "Please enter script command", + + "preset.dropdown.notification.button": "Use preset notification", + "preset.dropdown.script.button": "Use preset script", + "preset.dropdown.option_group.builtin": "Built-in templates", + "preset.dropdown.option_group.custom": "Customized templates" } diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index c7c8bb040..857041486 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -1025,7 +1025,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. (Expand to see more)
        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.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 ...", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 7c6244916..43ff809bf 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -627,8 +627,8 @@ "access.form.webhook_data.placeholder": "请输入默认的 Webhook 回调数据", "access.form.webhook_data.help": "提示:可在工作流中覆盖此设置。", "access.form.webhook_data.guide_for_deployment": "回调数据是一个 JSON 格式的数据。

        其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
        1. ${DOMAIN}:证书的主域名(即 CommonName)。
        2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
        3. ${CERTIFICATE}:证书文件 PEM 格式内容。
        4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
        5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
        6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

        当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
        1. application/json(默认)。
        2. application/x-www-form-urlencoded:不支持嵌套数据。
        3. multipart/form-data:不支持嵌套数据。
        4. ", - "access.form.webhook_data.guide_for_notification": "回调数据是一个 JSON 格式的数据。

          其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
          1. ${SUBJECT}:通知主题。
          2. ${MESSAGE}:通知内容。

          当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
          1. application/json(默认)。
          2. application/x-www-form-urlencoded:不支持嵌套数据。
          3. multipart/form-data:不支持嵌套数据。
          4. ", - "access.form.webhook_preset_data.button": "使用预设内容", + "access.form.webhook_data.guide_for_notification": "回调数据是一个 JSON 格式的数据。

            其中值支持模板变量,将在被发送到指定的 Webhook URL 时被替换为实际值;其他内容将保持原样。支持的变量:
            1. ${CERTIMATE_NOTIFIER_SUBJECT}:通知主题。
            2. ${CERTIMATE_NOTIFIER_MESSAGE}:通知内容。

            当请求谓词为 GET 时,回调数据将作为查询参数;否则,回调数据将按照请求标头中 Content-Type 所指示的格式进行编码。支持的格式:
            1. application/json(默认)。
            2. application/x-www-form-urlencoded:不支持嵌套数据。
            3. multipart/form-data:不支持嵌套数据。
            4. ", + "access.form.webhook_preset_data.button": "使用预设数据", "access.form.webhook_preset_data.option.bark.label": "Bark", "access.form.webhook_preset_data.option.gotify.label": "Gotify", "access.form.webhook_preset_data.option.ntfy.label": "ntfy", diff --git a/ui/src/i18n/locales/zh/nls.preset.json b/ui/src/i18n/locales/zh/nls.preset.json index 0c5f22177..19f87ca22 100644 --- a/ui/src/i18n/locales/zh/nls.preset.json +++ b/ui/src/i18n/locales/zh/nls.preset.json @@ -25,5 +25,10 @@ "preset.form.notification_message.label": "通知内容", "preset.form.notification_message.placeholder": "请输入通知内容", "preset.form.script_command.label": "脚本命令", - "preset.form.script_command.placeholder": "请输入脚本命令" + "preset.form.script_command.placeholder": "请输入脚本命令", + + "preset.dropdown.notification.button": "使用预设通知", + "preset.dropdown.script.button": "使用预设脚本", + "preset.dropdown.option_group.builtin": "内置模板", + "preset.dropdown.option_group.custom": "自定义模板" } diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 29cfc0134..481e4bcf5 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -1022,7 +1022,8 @@ "workflow_node.notify.form.subject.label": "通知主题", "workflow_node.notify.form.subject.placeholder": "请输入通知主题", "workflow_node.notify.form.message.label": "通知内容", - "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.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": "搜索通知渠道……", diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index 6245ffe5a..3b3f9505b 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { IconArrowRight, IconCode, IconSquarePlus2 } from "@tabler/icons-react"; +import { IconArrowRight, IconSquarePlus2, IconUpload } from "@tabler/icons-react"; import { App, Button, Card, Spin, Typography } from "antd"; import Show from "@/components/Show"; @@ -290,7 +290,7 @@ const WorkflowNew = () => { -