diff --git a/application/src/main/java/run/halo/app/notification/EmailNotifier.java b/application/src/main/java/run/halo/app/notification/EmailNotifier.java
index 0a49e2298..be6320adf 100644
--- a/application/src/main/java/run/halo/app/notification/EmailNotifier.java
+++ b/application/src/main/java/run/halo/app/notification/EmailNotifier.java
@@ -1,23 +1,21 @@
package run.halo.app.notification;
import com.fasterxml.jackson.databind.JsonNode;
-import java.nio.charset.StandardCharsets;
-import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
-import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.util.Pair;
+import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
-import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.infra.utils.JsonUtils;
+import run.halo.app.notification.EmailSenderHelper.EmailSenderConfig;
/**
*
A notifier that can send email.
@@ -34,7 +32,8 @@ public class EmailNotifier implements ReactiveNotifier {
private final SubscriberEmailResolver subscriberEmailResolver;
private final NotificationTemplateRender notificationTemplateRender;
- private final AtomicReference>
+ private final EmailSenderHelper emailSenderHelper;
+ private final AtomicReference>
emailSenderConfigPairRef = new AtomicReference<>();
@Override
@@ -48,7 +47,7 @@ public class EmailNotifier implements ReactiveNotifier {
return Mono.empty();
}
- JavaMailSenderImpl javaMailSender = getJavaMailSender(emailSenderConfig);
+ JavaMailSender javaMailSender = getJavaMailSender(emailSenderConfig);
String recipient = context.getMessage().getRecipient();
var subscriber = new Subscription.Subscriber();
@@ -83,55 +82,20 @@ public class EmailNotifier implements ReactiveNotifier {
}
@NonNull
- private static MimeMessagePreparator getMimeMessagePreparator(String toEmail,
+ private MimeMessagePreparator getMimeMessagePreparator(String toEmail,
EmailSenderConfig emailSenderConfig, NotificationContext.MessagePayload payload) {
- return mimeMessage -> {
- MimeMessageHelper helper =
- new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
- helper.setFrom(emailSenderConfig.getSender(), emailSenderConfig.getDisplayName());
-
- helper.setSubject(payload.getTitle());
- helper.setText(payload.getRawBody(), payload.getHtmlBody());
- helper.setTo(toEmail);
- };
+ return emailSenderHelper.createMimeMessagePreparator(emailSenderConfig, toEmail,
+ payload.getTitle(),
+ payload.getRawBody(), payload.getHtmlBody());
}
- @NonNull
- private static JavaMailSenderImpl createJavaMailSender(EmailSenderConfig emailSenderConfig) {
- JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
- javaMailSender.setHost(emailSenderConfig.getHost());
- javaMailSender.setPort(emailSenderConfig.getPort());
- javaMailSender.setUsername(emailSenderConfig.getUsername());
- javaMailSender.setPassword(emailSenderConfig.getPassword());
-
- Properties props = javaMailSender.getJavaMailProperties();
- props.put("mail.transport.protocol", "smtp");
- props.put("mail.smtp.auth", "true");
- if ("SSL".equals(emailSenderConfig.getEncryption())) {
- props.put("mail.smtp.ssl.enable", "true");
- }
-
- if ("TLS".equals(emailSenderConfig.getEncryption())) {
- props.put("mail.smtp.starttls.enable", "true");
- }
-
- if ("NONE".equals(emailSenderConfig.getEncryption())) {
- props.put("mail.smtp.ssl.enable", "false");
- props.put("mail.smtp.starttls.enable", "false");
- }
-
- if (log.isDebugEnabled()) {
- props.put("mail.debug", "true");
- }
- return javaMailSender;
- }
-
- JavaMailSenderImpl getJavaMailSender(EmailSenderConfig emailSenderConfig) {
+ JavaMailSender getJavaMailSender(EmailSenderConfig emailSenderConfig) {
return emailSenderConfigPairRef.updateAndGet(pair -> {
if (pair != null && pair.getFirst().equals(emailSenderConfig)) {
return pair;
}
- return Pair.of(emailSenderConfig, createJavaMailSender(emailSenderConfig));
+ return Pair.of(emailSenderConfig,
+ emailSenderHelper.createJavaMailSender(emailSenderConfig));
}).getSecond();
}
@@ -156,34 +120,4 @@ public class EmailNotifier implements ReactiveNotifier {
""", attributes);
}
-
- @Data
- static class EmailSenderConfig {
- private boolean enable;
- private String displayName;
- private String username;
- private String password;
- private String host;
- private Integer port;
- private String encryption;
- private String sender;
-
- /**
- * Gets email display name.
- *
- * @return display name if not blank, otherwise username.
- */
- public String getDisplayName() {
- return StringUtils.defaultIfBlank(displayName, username);
- }
-
- /**
- * Gets email sender address.
- *
- * @return sender if not blank, otherwise username.
- */
- public String getSender() {
- return StringUtils.defaultIfBlank(sender, username);
- }
- }
}
diff --git a/application/src/main/java/run/halo/app/notification/EmailSenderHelper.java b/application/src/main/java/run/halo/app/notification/EmailSenderHelper.java
new file mode 100644
index 000000000..48d0bd3a7
--- /dev/null
+++ b/application/src/main/java/run/halo/app/notification/EmailSenderHelper.java
@@ -0,0 +1,47 @@
+package run.halo.app.notification;
+
+import lombok.Data;
+import lombok.NonNull;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessagePreparator;
+
+public interface EmailSenderHelper {
+
+ @NonNull
+ JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig);
+
+ @NonNull
+ MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
+ String toEmail, String subject, String raw, String html);
+
+ @Data
+ class EmailSenderConfig {
+ private boolean enable;
+ private String displayName;
+ private String username;
+ private String sender;
+ private String password;
+ private String host;
+ private Integer port;
+ private String encryption;
+
+ /**
+ * Gets email display name.
+ *
+ * @return display name if not blank, otherwise username.
+ */
+ public String getDisplayName() {
+ return StringUtils.defaultIfBlank(displayName, username);
+ }
+
+ /**
+ * Gets email sender address.
+ *
+ * @return sender if not blank, otherwise username
+ */
+ public String getSender() {
+ return StringUtils.defaultIfBlank(sender, username);
+ }
+ }
+}
diff --git a/application/src/main/java/run/halo/app/notification/EmailSenderHelperImpl.java b/application/src/main/java/run/halo/app/notification/EmailSenderHelperImpl.java
new file mode 100644
index 000000000..d0186499d
--- /dev/null
+++ b/application/src/main/java/run/halo/app/notification/EmailSenderHelperImpl.java
@@ -0,0 +1,68 @@
+package run.halo.app.notification;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.mail.javamail.MimeMessagePreparator;
+import org.springframework.stereotype.Component;
+
+/**
+ * A default implementation of {@link EmailSenderHelper}.
+ *
+ * @author guqing
+ * @since 2.14.0
+ */
+@Slf4j
+@Component
+public class EmailSenderHelperImpl implements EmailSenderHelper {
+
+ @Override
+ @NonNull
+ public JavaMailSender createJavaMailSender(EmailSenderConfig senderConfig) {
+ JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
+ javaMailSender.setHost(senderConfig.getHost());
+ javaMailSender.setPort(senderConfig.getPort());
+ javaMailSender.setUsername(senderConfig.getUsername());
+ javaMailSender.setPassword(senderConfig.getPassword());
+
+ Properties props = javaMailSender.getJavaMailProperties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.auth", "true");
+ if ("SSL".equals(senderConfig.getEncryption())) {
+ props.put("mail.smtp.ssl.enable", "true");
+ }
+
+ if ("TLS".equals(senderConfig.getEncryption())) {
+ props.put("mail.smtp.starttls.enable", "true");
+ }
+
+ if ("NONE".equals(senderConfig.getEncryption())) {
+ props.put("mail.smtp.ssl.enable", "false");
+ props.put("mail.smtp.starttls.enable", "false");
+ }
+
+ if (log.isDebugEnabled()) {
+ props.put("mail.debug", "true");
+ }
+ return javaMailSender;
+ }
+
+ @Override
+ @NonNull
+ public MimeMessagePreparator createMimeMessagePreparator(EmailSenderConfig senderConfig,
+ String toEmail, String subject, String raw, String html) {
+ return mimeMessage -> {
+ MimeMessageHelper helper =
+ new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
+ helper.setFrom(senderConfig.getSender(), senderConfig.getDisplayName());
+
+ helper.setSubject(subject);
+ helper.setText(raw, html);
+ helper.setTo(toEmail);
+ };
+ }
+}
diff --git a/application/src/main/java/run/halo/app/notification/endpoint/EmailConfigValidationEndpoint.java b/application/src/main/java/run/halo/app/notification/endpoint/EmailConfigValidationEndpoint.java
new file mode 100644
index 000000000..8c219a2af
--- /dev/null
+++ b/application/src/main/java/run/halo/app/notification/endpoint/EmailConfigValidationEndpoint.java
@@ -0,0 +1,116 @@
+package run.halo.app.notification.endpoint;
+
+import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
+import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.security.Principal;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
+import org.springframework.mail.MailException;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import org.springframework.web.server.ServerWebInputException;
+import reactor.core.publisher.Mono;
+import run.halo.app.core.extension.User;
+import run.halo.app.core.extension.endpoint.CustomEndpoint;
+import run.halo.app.extension.GroupVersion;
+import run.halo.app.extension.ReactiveExtensionClient;
+import run.halo.app.notification.EmailSenderHelper;
+
+/**
+ * Validation endpoint for email config.
+ *
+ * @author guqing
+ * @since 2.14.0
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class EmailConfigValidationEndpoint implements CustomEndpoint {
+ private static final String EMAIL_SUBJECT = "测试邮件 - 验证邮箱连通性";
+ private static final String EMAIL_BODY = """
+ 你好!
+ 这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。
+ 此邮件由系统自动发送,请勿回复。
+ 祝好
+ """;
+
+ private final EmailSenderHelper emailSenderHelper;
+ private final ReactiveExtensionClient client;
+
+ @Override
+ public RouterFunction endpoint() {
+ var tag = "console.api.notification.halo.run/v1alpha1/Notifier";
+ return SpringdocRouteBuilder.route()
+ .POST("/notifiers/default-email-notifier/verify-connection",
+ this::verifyEmailSenderConfig,
+ builder -> builder.operationId("VerifyEmailSenderConfig")
+ .description("Verify email sender config.")
+ .tag(tag)
+ .requestBody(requestBodyBuilder()
+ .required(true)
+ .implementation(ValidationRequest.class)
+ )
+ .response(responseBuilder().implementation(Void.class))
+ )
+ .build();
+ }
+
+ private Mono verifyEmailSenderConfig(ServerRequest request) {
+ return request.bodyToMono(ValidationRequest.class)
+ .switchIfEmpty(
+ Mono.error(new ServerWebInputException("Required request body is missing."))
+ )
+ .flatMap(validationRequest -> getCurrentUserEmail()
+ .flatMap(recipient -> {
+ var mailSender = emailSenderHelper.createJavaMailSender(validationRequest);
+ var message = emailSenderHelper.createMimeMessagePreparator(validationRequest,
+ recipient, EMAIL_SUBJECT, EMAIL_BODY, EMAIL_BODY);
+ try {
+ mailSender.send(message);
+ } catch (MailException e) {
+ String errorMsg =
+ "Failed to send email, please check your email configuration.";
+ log.error(errorMsg, e);
+ throw new ServerWebInputException(errorMsg, null, e);
+ }
+ return ServerResponse.ok().build();
+ })
+ );
+ }
+
+ Mono getCurrentUserEmail() {
+ return ReactiveSecurityContextHolder.getContext()
+ .map(SecurityContext::getAuthentication)
+ .map(Principal::getName)
+ .flatMap(username -> client.fetch(User.class, username))
+ .flatMap(user -> {
+ var email = user.getSpec().getEmail();
+ if (StringUtils.isBlank(email)) {
+ return Mono.error(new ServerWebInputException(
+ "Your email is missing, please set it in your profile."));
+ }
+ return Mono.just(email);
+ });
+ }
+
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ @Schema(name = "EmailConfigValidationRequest")
+ static class ValidationRequest extends EmailSenderHelper.EmailSenderConfig {
+ }
+
+ @Override
+ public GroupVersion groupVersion() {
+ return GroupVersion.parseAPIVersion("console.api.notification.halo.run/v1alpha1");
+ }
+}
diff --git a/application/src/main/resources/extensions/notification.yaml b/application/src/main/resources/extensions/notification.yaml
index 15eaf1954..2e6e9dac9 100644
--- a/application/src/main/resources/extensions/notification.yaml
+++ b/application/src/main/resources/extensions/notification.yaml
@@ -23,47 +23,46 @@ spec:
label: "启用邮件通知器"
value: false
name: enable
- - $formkit: text
+ - $formkit: verificationForm
if: "$enable"
- label: "用户名"
- name: username
- validation: required
- - $formkit: text
- if: "$enable"
- label: "发信地址"
- name: "sender"
- help: "如果用户名为实际发信地址,可忽略"
- - $formkit: password
- if: "$enable"
- label: "密码"
- name: password
- validation: required
- - $formkit: text
- if: "$enable"
- label: "显示名称"
- name: displayName
- - $formkit: text
- if: "$enable"
- label: "SMTP 服务器地址"
- name: host
- validation: required
- - $formkit: text
- if: "$enable"
- label: "端口号"
- name: port
- validation: required
- - $formkit: select
- if: "$enable"
- label: "加密方式"
- name: encryption
- value: "SSL"
- options:
- - label: "SSL"
+ action: /apis/console.api.notification.halo.run/v1alpha1/notifiers/default-email-notifier/verify-connection
+ label: 测试邮箱
+ children:
+ - $formkit: text
+ label: "用户名"
+ name: username
+ validation: required
+ - $formkit: text
+ if: "$enable"
+ label: "发信地址"
+ name: "sender"
+ help: "如果用户名为实际发信地址,可忽略"
+ - $formkit: password
+ label: "密码"
+ name: password
+ validation: required
+ - $formkit: text
+ label: "显示名称"
+ name: displayName
+ - $formkit: text
+ label: "SMTP 服务器地址"
+ name: host
+ validation: required
+ - $formkit: text
+ label: "端口号"
+ name: port
+ validation: required
+ - $formkit: select
+ label: "加密方式"
+ name: encryption
value: "SSL"
- - label: "TLS"
- value: "TLS"
- - label: "不加密"
- value: "NONE"
+ options:
+ - label: "SSL"
+ value: "SSL"
+ - label: "TLS"
+ value: "TLS"
+ - label: "不加密"
+ value: "NONE"
---
apiVersion: notification.halo.run/v1alpha1
kind: ReasonType
diff --git a/application/src/main/resources/extensions/role-template-notification.yaml b/application/src/main/resources/extensions/role-template-notification.yaml
index 9ddfc8cab..fdce8c33f 100644
--- a/application/src/main/resources/extensions/role-template-notification.yaml
+++ b/application/src/main/resources/extensions/role-template-notification.yaml
@@ -15,3 +15,7 @@ rules:
- apiGroups: [ "api.console.halo.run" ]
resources: [ "notifiers/sender-config" ]
verbs: [ "get", "update" ]
+ - apiGroups: [ "console.api.notification.halo.run" ]
+ resources: [ "notifiers/verify-connection" ]
+ resourceNames: [ "default-email-notifier" ]
+ verbs: [ "create" ]
diff --git a/ui/docs/custom-formkit-input/README.md b/ui/docs/custom-formkit-input/README.md
index 1a697b73f..eb0f95199 100644
--- a/ui/docs/custom-formkit-input/README.md
+++ b/ui/docs/custom-formkit-input/README.md
@@ -38,6 +38,11 @@
- 参数
1. multiple: 是否多选,默认为 `false`
- `tagCheckbox`:选择多个标签
+- `verificationForm`: 远程验证一组数据是否符合某个规则
+ - 参数
+ 1. action: 对目标数据进行验证的接口地址
+ 2. label: 验证按钮文本
+ 3. buttonAttrs: 验证按钮的额外属性
在 Vue 单组件中使用:
diff --git a/ui/src/formkit/formkit.config.ts b/ui/src/formkit/formkit.config.ts
index 6f5907ae6..84861bf41 100644
--- a/ui/src/formkit/formkit.config.ts
+++ b/ui/src/formkit/formkit.config.ts
@@ -21,6 +21,7 @@ import { roleSelect } from "./inputs/role-select";
import { attachmentPolicySelect } from "./inputs/attachment-policy-select";
import { attachmentGroupSelect } from "./inputs/attachment-group-select";
import { password } from "./inputs/password";
+import { verificationForm } from "./inputs/verify-form";
import radioAlt from "./plugins/radio-alt";
import stopImplicitSubmission from "./plugins/stop-implicit-submission";
@@ -59,6 +60,7 @@ const config: DefaultConfigOptions = {
roleSelect,
attachmentPolicySelect,
attachmentGroupSelect,
+ verificationForm,
},
locales: { zh, en },
locale: "zh",
diff --git a/ui/src/formkit/inputs/verify-form/VerificationButton.vue b/ui/src/formkit/inputs/verify-form/VerificationButton.vue
new file mode 100644
index 000000000..298aa4369
--- /dev/null
+++ b/ui/src/formkit/inputs/verify-form/VerificationButton.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+ {{ context.node.props.label }}
+
+
+
+
+
+
+
diff --git a/ui/src/formkit/inputs/verify-form/features/index.ts b/ui/src/formkit/inputs/verify-form/features/index.ts
new file mode 100644
index 000000000..d00e652db
--- /dev/null
+++ b/ui/src/formkit/inputs/verify-form/features/index.ts
@@ -0,0 +1,58 @@
+import { i18n } from "@/locales";
+import { type FormKitNode } from "@formkit/core";
+
+function buildVerifyFormValue(node: FormKitNode) {
+ if (!node.parent) return {};
+ const parentValue = {
+ ...(node.parent.value as Record),
+ };
+ delete parentValue[node.name];
+ return parentValue;
+}
+
+/**
+ * A feature to add a submit handler and actions section.
+ *
+ * @param node - A {@link @formkit/core#FormKitNode | FormKitNode}.
+ *
+ * @public
+ */
+export default function verify(node: FormKitNode): void {
+ node.props.buttonAttrs ??= {
+ disabled: node.props.disabled,
+ };
+
+ node.props.label ??= i18n.global.t("core.common.buttons.verify");
+
+ node.on("prop:disabled", ({ payload: disabled }) => {
+ node.props.buttonAttrs = { ...node.props.buttonAttrs, disabled };
+ });
+
+ node.on("created", () => {
+ if (node.parent) {
+ node.parent.hook.commit((val) => {
+ const parentValue = {
+ ...val,
+ };
+ const verifyFormVal = (parentValue[node.name] || {}) as Record<
+ string,
+ unknown
+ >;
+ delete parentValue[node.name];
+ const mergeFormValue = {};
+ Object.keys(verifyFormVal).forEach((key) => {
+ if (node.children.find((child) => child.name === key)) {
+ mergeFormValue[key] = verifyFormVal[key];
+ }
+ });
+ return { ...parentValue, ...mergeFormValue };
+ });
+
+ node.hook.input(() => {
+ return buildVerifyFormValue(node);
+ });
+
+ node.input(buildVerifyFormValue(node));
+ }
+ });
+}
diff --git a/ui/src/formkit/inputs/verify-form/index.ts b/ui/src/formkit/inputs/verify-form/index.ts
new file mode 100644
index 000000000..8ddc4b26f
--- /dev/null
+++ b/ui/src/formkit/inputs/verify-form/index.ts
@@ -0,0 +1,64 @@
+import type { FormKitTypeDefinition } from "@formkit/core";
+import {
+ createSection,
+ disablesChildren,
+ message,
+ messages,
+} from "@formkit/inputs";
+import { default as verifyFeature } from "./features";
+import VerificationButton from "./VerificationButton.vue";
+
+export const verifyInput = createSection("verificationForm", () => ({
+ $el: "div",
+ bind: "$attrs",
+ attrs: {
+ id: "$id",
+ name: "$node.name",
+ "data-loading": "$state.loading || undefined",
+ },
+}));
+
+export const actions = createSection("actions", () => ({
+ $el: "div",
+}));
+
+export const verificationButton = createSection("verificationButton", () => ({
+ $cmp: "VerificationButton",
+ type: "button",
+ bind: "$buttonAttrs",
+ props: {
+ context: "$node.context",
+ },
+}));
+
+/**
+ * Input definition for a form.
+ * @public
+ */
+export const verificationForm: FormKitTypeDefinition = {
+ /**
+ * The actual schema of the input, or a function that returns the schema.
+ */
+ schema: verifyInput(
+ "$slots.default",
+ messages(message("$message.value")),
+ actions(verificationButton())
+ ),
+ /**
+ * The type of node, can be a list, group, or input.
+ */
+ type: "group",
+ /**
+ * An array of extra props to accept for this input.
+ */
+ props: ["action", "label", "buttonAttrs"],
+
+ /**
+ * Additional features that should be added to your input
+ */
+ features: [verifyFeature, disablesChildren],
+
+ library: {
+ VerificationButton,
+ },
+};
diff --git a/ui/src/formkit/theme.ts b/ui/src/formkit/theme.ts
index ef3b10a16..d0f315be1 100644
--- a/ui/src/formkit/theme.ts
+++ b/ui/src/formkit/theme.ts
@@ -33,6 +33,7 @@ const theme: Record> = {
help: "text-xs mt-2 text-gray-500",
messages: "list-none p-0 mt-1.5 mb-0 transition-all",
message: "text-red-500 mt-2 text-xs",
+ verificationForm: "pt-4 divide-y divide-gray-100",
},
button: buttonClassification,
color: {
diff --git a/ui/src/locales/en.yaml b/ui/src/locales/en.yaml
index e0dd6f2b1..f55a611fa 100644
--- a/ui/src/locales/en.yaml
+++ b/ui/src/locales/en.yaml
@@ -1482,6 +1482,10 @@ core:
creation_label: Create {text} tag
validation:
trim: Please remove the leading and trailing spaces
+ verification_form:
+ no_action_defined: "{label} interface not defined"
+ verify_success: "{label} successful"
+ verify_failed: "{label} failed"
common:
buttons:
save: Save
diff --git a/ui/src/locales/zh-CN.yaml b/ui/src/locales/zh-CN.yaml
index 35c9dd770..9844cb891 100644
--- a/ui/src/locales/zh-CN.yaml
+++ b/ui/src/locales/zh-CN.yaml
@@ -1428,6 +1428,10 @@ core:
creation_label: 创建 {text} 标签
validation:
trim: 不能以空格开头或结尾
+ verification_form:
+ no_action_defined: 未定义{label}接口
+ verify_success: "{label}成功"
+ verify_failed: "{label}失败"
common:
buttons:
save: 保存
diff --git a/ui/src/locales/zh-TW.yaml b/ui/src/locales/zh-TW.yaml
index acfe887d2..31517402c 100644
--- a/ui/src/locales/zh-TW.yaml
+++ b/ui/src/locales/zh-TW.yaml
@@ -1394,6 +1394,10 @@ core:
creation_label: 創建 {text} 標籤
validation:
trim: 不能以空格開頭或結尾
+ verification_form:
+ no_action_defined: 未定義{label}介面
+ verify_success: "{label}成功"
+ verify_failed: "{label}失敗"
common:
buttons:
save: 保存