diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index cb6f4d335..23651f8b2 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -423,31 +423,46 @@ export class Executor { let subject = ""; let content = ""; const errorMessage = error?.message; + const templateData: any = { + pipelineId: this.pipeline.id, + historyId: this.runtime.id, + pipelineTitle: this.pipeline.title, + }; + let pipelineResult = ""; + let errors = ""; if (when === "start") { - subject = `开始执行,${this.pipeline.title}【${this.pipeline.id}】`; + pipelineResult = "开始执行"; + subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`; content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`; } else if (when === "success") { - subject = `执行成功,${this.pipeline.title}【${this.pipeline.id}】`; + pipelineResult = "执行成功"; + subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`; content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`; } else if (when === "turnToSuccess") { - subject = `执行成功(失败转成功),${this.pipeline.title}【${this.pipeline.id}】`; + pipelineResult = "执行成功(失败转成功)"; + subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`; content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}`; } else if (when === "error") { - subject = `执行失败,${this.pipeline.title}【${this.pipeline.id}】`; - + pipelineResult = "执行失败"; + subject = `${pipelineResult},${this.pipeline.title}【${this.pipeline.id}】`; if (error instanceof RunnableError) { const runnableError = error as RunnableError; content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n`; for (const re of runnableError.errors) { - content += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`; + errors += ` - ${re.runnable.title} 执行失败,错误详情:${re.e?.message || re.e?.error?.message}\n\n`; } + content += errors; } else { + errors = error.message; content = `流水线ID:${this.pipeline.id},运行ID:${this.runtime.id}\n\n${this.currentStatusMap?.currentStep?.title} 执行失败\n\n错误详情:${error.message}`; } } else { return; } + templateData.errors = errors; + templateData.pipelineResult = pipelineResult; + for (const notification of this.pipeline.notifications) { if (!notification.when.includes(when)) { continue; @@ -455,10 +470,12 @@ export class Executor { if (notification.type === "email" && notification.options?.receivers) { try { - await this.options.emailService?.send({ - subject, - content, - receivers: notification.options?.receivers, + await this.options.emailService?.sendByTemplate({ + type: "pipelineResult", + data: templateData, + email: { + receivers: notification.options?.receivers, + }, }); } catch (e) { logger.error("send email error", e); @@ -472,15 +489,15 @@ export class Executor { useEmail: false, logger: this.logger, body: { + notificationType: "pipelineResult", title: subject, content, userId: this.pipeline.userId, pipeline: this.pipeline, result: this.lastRuntime?.pipeline?.status, - pipelineId: this.pipeline.id, - historyId: this.runtime.id, errorMessage, url, + ...templateData, }, }); } catch (e) { diff --git a/packages/core/pipeline/src/notification/api.ts b/packages/core/pipeline/src/notification/api.ts index 0d4b62db3..e5e5acf57 100644 --- a/packages/core/pipeline/src/notification/api.ts +++ b/packages/core/pipeline/src/notification/api.ts @@ -15,6 +15,11 @@ export type NotificationBody = { historyId?: number; errorMessage?: string; url?: string; + notificationType?: string; + attachments?: any[]; + pipelineResult?: string; + pipelineTitle?: string; + errors?: string; }; export type NotificationRequestHandleReqInput = { diff --git a/packages/core/pipeline/src/service/email.ts b/packages/core/pipeline/src/service/email.ts index dd3b65616..a427ef718 100644 --- a/packages/core/pipeline/src/service/email.ts +++ b/packages/core/pipeline/src/service/email.ts @@ -6,6 +6,13 @@ export type EmailSend = { html?: string; }; +export type EmailSendByTemplateReq = { + type: string; + data: any; + email: { receivers: string[]; attachments?: any[] }; +}; + export interface IEmailService { send(email: EmailSend): Promise; + sendByTemplate(req: EmailSendByTemplateReq): Promise; } diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index 3312679d0..17a0fd54c 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -108,6 +108,11 @@ export class SysLicenseInfo extends BaseSettings { license?: string; } + +export type EmailTemplate = { + addonId?: number; +} + export class SysEmailConf extends BaseSettings { static __title__ = '邮箱配置'; static __key__ = 'sys.email'; @@ -126,6 +131,16 @@ export class SysEmailConf extends BaseSettings { }; sender: string; usePlus?: boolean; + + templates:{ + registerCode?: EmailTemplate, + forgotPasswordCode?: EmailTemplate, + certSuccessNotify?: EmailTemplate, + certSend?: EmailTemplate, + pipelineNotify?: EmailTemplate, + test?: EmailTemplate, + siteMonitorNotify?: EmailTemplate, + } } export class SysSiteInfo extends BaseSettings { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts index 8180fb829..0547cc56b 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -154,6 +154,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin { title: `证书申请成功【${this.pipeline.title}】`, content: `域名:${this.domains.join(",")}`, url: url, + notificationType: "certApplySuccess", }; try { await this.ctx.notificationService.send({ diff --git a/packages/ui/certd-client/src/components/plugins/common/params-show.vue b/packages/ui/certd-client/src/components/plugins/common/params-show.vue new file mode 100644 index 000000000..4a7e1eab5 --- /dev/null +++ b/packages/ui/certd-client/src/components/plugins/common/params-show.vue @@ -0,0 +1,40 @@ + + + + diff --git a/packages/ui/certd-client/src/components/plugins/index.ts b/packages/ui/certd-client/src/components/plugins/index.ts index be4697212..5335d5c09 100644 --- a/packages/ui/certd-client/src/components/plugins/index.ts +++ b/packages/ui/certd-client/src/components/plugins/index.ts @@ -11,6 +11,7 @@ import AccessSelector from "/@/views/certd/access/access-selector/index.vue"; import InputPassword from "./common/input-password.vue"; import CertInfoUpdater from "/@/views/certd/pipeline/cert-upload/index.vue"; import ApiTest from "./common/api-test.vue"; +import ParamsShow from "./common/params-show.vue"; export * from "./cert/index.js"; export default { install(app: any) { @@ -29,5 +30,6 @@ export default { app.component("RemoteInput", RemoteInput); app.component("CertDomainsGetter", CertDomainsGetter); app.component("InputPassword", InputPassword); + app.component("ParamsShow", ParamsShow); }, }; diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index e81f32735..b57069f92 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -780,6 +780,18 @@ export default { oauthAutoRedirectHelper: "Whether to auto redirect to OAuth2 login when login (using the first enabled OAuth2 login type)", oauthOnly: "OAuth2 Login Only", oauthOnlyHelper: "Whether to only allow OAuth2 login, disable password login", + + email: { + templates: "Email Templates", + templateType: "Template Type", + templateProvider: "Template Config", + + templateSetting: "Email Template Setting", + serverSetting: "Email Server Setting", + sendTest: "Send Test", + + templateProviderSelectorPlaceholder: "Not Configured", + }, }, }, modal: { diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index d2b7f8ad9..6f39a9159 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -780,6 +780,18 @@ export default { oauthAutoRedirectHelper: "是否自动跳转第三方登录(使用第一个已启用的第三方登录类型)", oauthOnly: "仅使用第三方登录", oauthOnlyHelper: "是否仅使用第三方登录,关闭密码登录(注意:请务必在测试第三方登录功能正常后再开启)", + + email: { + templates: "邮件模板", + templateType: "模板类型", + templateProvider: "模板配置", + + templateSetting: "邮件模板设置", + serverSetting: "邮件服务器设置", + sendTest: "发送测试", + + templateProviderSelectorPlaceholder: "未配置", + }, }, }, modal: { diff --git a/packages/ui/certd-client/src/views/sys/settings/email/api.email.ts b/packages/ui/certd-client/src/views/sys/settings/email/api.email.ts index 43c70939d..1bdc6e50e 100644 --- a/packages/ui/certd-client/src/views/sys/settings/email/api.email.ts +++ b/packages/ui/certd-client/src/views/sys/settings/email/api.email.ts @@ -1,5 +1,6 @@ import { request } from "/src/api/service"; const apiPrefix = "/mine/email"; +const apiSettingPrefix = "/sys/settings"; export async function TestSend(receiver: string) { await request({ @@ -10,3 +11,10 @@ export async function TestSend(receiver: string) { }, }); } + +export async function GetEmailTemplates() { + return await request({ + url: apiSettingPrefix + "/getEmailTemplates", + method: "post", + }); +} diff --git a/packages/ui/certd-client/src/views/sys/settings/email/index.vue b/packages/ui/certd-client/src/views/sys/settings/email/index.vue index 16df2be72..8d94f2a27 100644 --- a/packages/ui/certd-client/src/views/sys/settings/email/index.vue +++ b/packages/ui/certd-client/src/views/sys/settings/email/index.vue @@ -7,53 +7,93 @@ -
-