From b5cc794061c11b7200b669473c25c4bbfc944b61 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 6 Apr 2026 01:17:02 +0800 Subject: [PATCH] =?UTF-8?q?perf(monitor):=20=E6=94=AF=E6=8C=81=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E7=9B=91=E6=8E=A7=E6=89=A7=E8=A1=8C=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增监控任务执行记录页面及相关API - 添加数据库表结构及多数据库支持 - 完善国际化翻译 - 实现批量删除功能 - 优化站点监控服务逻辑 --- .../src/locales/langs/en-US/monitor.ts | 19 ++ .../src/locales/langs/zh-CN/certd.ts | 1 + .../src/locales/langs/zh-CN/monitor.ts | 21 +- .../src/router/source/modules/certd.ts | 13 +- .../src/views/certd/monitor/history/api.ts | 37 +++ .../src/views/certd/monitor/history/crud.tsx | 257 ++++++++++++++++++ .../src/views/certd/monitor/history/index.vue | 48 ++++ .../migration-mysql/v10042__job_history.sql | 24 ++ .../db/migration-pg/v10042__job_history.sql | 22 ++ .../db/migration/v10042__job_history.sql | 2 +- .../user/monitor/job-history-controller.ts | 11 +- .../monitor/service/site-info-service.ts | 11 +- 12 files changed, 454 insertions(+), 12 deletions(-) create mode 100644 packages/ui/certd-client/src/views/certd/monitor/history/api.ts create mode 100644 packages/ui/certd-client/src/views/certd/monitor/history/crud.tsx create mode 100644 packages/ui/certd-client/src/views/certd/monitor/history/index.vue create mode 100644 packages/ui/certd-server/db/migration-mysql/v10042__job_history.sql create mode 100644 packages/ui/certd-server/db/migration-pg/v10042__job_history.sql diff --git a/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts b/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts index 1fad8644c..bbcca3754 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts @@ -82,4 +82,23 @@ export default { expiring: "Expiring", noExpired: "Not Expired", }, + history: { + title: "Monitoring Execution Records", + description: "Monitoring execution records", + resultTitle: "Status", + contentTitle: "Content", + titleTitle: "Title", + jobTypeTitle: "Job Type", + startAtTitle: "Start Time", + endAtTitle: "End Time", + jobResultTitle: "Result", + jobResult: { + done: "Done", + start: "Start", + }, + jobType: { + domainExpirationCheck: "Domain Expiration Check", + siteCertMonitor: "Site Certificate Monitor", + }, + }, }; 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 5364ea306..bcfc5cac7 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 @@ -227,6 +227,7 @@ export default { currentProject: "当前项目", projectMemberManager: "项目成员管理", domainMonitorSetting: "域名监控设置", + jobHistory: "监控执行记录", }, certificateRepo: { title: "证书仓库", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts index 9d0769458..e31b2bc22 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts @@ -71,7 +71,7 @@ export default { domain: { monitorSettings: "域名监控设置", enabled: "启用域名监控", - enabledHelper: "启用后,将监控域名管理中的域名的过期时间", + enabledHelper: "启用后,监控“域名管理”中域名的过期时间,到期前通知提醒", notificationChannel: "通知渠道", setNotificationChannel: "设置通知渠道", willExpireDays: "到期前天数", @@ -85,4 +85,23 @@ export default { expiring: "即将过期", noExpired: "未过期", }, + history: { + title: "监控执行记录", + description: "站点证书、域名等监控任务的执行记录", + resultTitle: "状态", + contentTitle: "内容", + titleTitle: "标题", + jobTypeTitle: "任务类型", + startAtTitle: "开始时间", + endAtTitle: "结束时间", + jobResultTitle: "任务结果", + jobResult: { + done: "完成", + start: "开始", + }, + jobType: { + domainExpirationCheck: "域名到期检查", + siteCertMonitor: "站点证书监控", + }, + }, }; diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index fce00a0c7..90deb6767 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -247,7 +247,18 @@ export const certdResources = [ path: "/certd/cert/domain/setting", component: "/certd/cert/domain/setting/index.vue", meta: { - icon: "ion:videocam-outline", + icon: "ion:stopwatch-outline", + auth: true, + isMenu: true, + }, + }, + { + title: "certd.sysResources.jobHistory", + name: "JobHistory", + path: "/certd/monitor/history", + component: "/certd/monitor/history/index.vue", + meta: { + icon: "ion:barcode-outline", auth: true, isMenu: true, }, diff --git a/packages/ui/certd-client/src/views/certd/monitor/history/api.ts b/packages/ui/certd-client/src/views/certd/monitor/history/api.ts new file mode 100644 index 000000000..f7e3ada28 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/monitor/history/api.ts @@ -0,0 +1,37 @@ +import { request } from "/src/api/service"; + +const apiPrefix = "/monitor/job-history"; + +export const jobHistoryApi = { + async GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query, + }); + }, + + async DelObj(id: number) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id }, + }); + }, + + async BatchDelObj(ids: number[]) { + return await request({ + url: apiPrefix + "/batchDelete", + method: "post", + data: { ids }, + }); + }, + + async GetObj(id: number) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id }, + }); + }, +}; diff --git a/packages/ui/certd-client/src/views/certd/monitor/history/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/history/crud.tsx new file mode 100644 index 000000000..bb8c9c767 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/monitor/history/crud.tsx @@ -0,0 +1,257 @@ +// @ts-ignore +import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { message, Modal } from "ant-design-vue"; +import { ref } from "vue"; +import { createGroupDictRef } from "../../basic/group/api"; +import { useDicts } from "../../dicts"; +import { jobHistoryApi } from "./api"; +import { useCrudPermission } from "/@/plugin/permission"; +import { useProjectStore } from "/@/store/project"; +import { useI18n } from "/src/locales"; +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const { t } = useI18n(); + const api = jobHistoryApi; + const { crudBinding } = crudExpose; + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async (req: EditReq) => {}; + const delRequest = async (req: DelReq) => { + const { row } = req; + return await api.DelObj(row.id); + }; + + const addRequest = async (req: AddReq) => {}; + const { myProjectDict } = useDicts(); + + const historyResultDict = dict({ + data: [ + { label: t("monitor.history.jobResult.done"), value: "done", color: "green" }, + { label: t("monitor.history.jobResult.start"), value: "start", color: "blue" }, + ], + }); + + const jobTypeDict = dict({ + data: [ + { label: t("monitor.history.jobType.domainExpirationCheck"), value: "domainExpirationCheck", color: "green" }, + { label: t("monitor.history.jobType.siteCertMonitor"), value: "siteCertMonitor", color: "blue" }, + ], + }); + + const selectedRowKeys = ref([]); + + const handleBatchDelete = () => { + if (selectedRowKeys.value?.length > 0) { + Modal.confirm({ + title: "确认", + content: `确定要批量删除这${selectedRowKeys.value.length}条记录吗`, + async onOk() { + await api.BatchDelObj(selectedRowKeys.value); + message.info("删除成功"); + crudExpose.doRefresh(); + selectedRowKeys.value = []; + }, + }); + } else { + message.error("请先勾选记录"); + } + }; + + context.handleBatchDelete = handleBatchDelete; + + const GroupTypeSite = "site"; + const groupDictRef = createGroupDictRef(GroupTypeSite); + + function getDefaultGroupId() { + const searchFrom = crudExpose.getSearchValidatedFormData(); + if (searchFrom.groupId) { + return searchFrom.groupId; + } + } + + const projectStore = useProjectStore(); + const { hasActionPermission } = useCrudPermission({ permission: context.permission }); + return { + id: "jobHistoryCrud", + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + // tabs: { + // name: "groupId", + // show: true, + // }, + toolbar: { + buttons: { + export: { + show: true, + }, + }, + }, + pagination: { + pageSizeOptions: ["10", "20", "50", "100", "200"], + }, + settings: { + plugins: { + //这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并 + rowSelection: { + enabled: true, + props: { + multiple: true, + crossPage: false, + selectedRowKeys: () => { + return selectedRowKeys; + }, + }, + }, + }, + }, + form: { + labelCol: { + //固定label宽度 + span: null, + style: { + width: "100px", + }, + }, + col: { + span: 22, + }, + wrapper: { + width: 600, + }, + }, + actionbar: { + buttons: { + add: { + show: false, + }, + }, + }, + rowHandle: { + fixed: "right", + width: 280, + buttons: { + edit: { + show: false, + }, + }, + }, + // tabs: { + // name: "disabled", + // show: true, + // }, + search: { + initialForm: { + ...projectStore.getSearchForm(), + }, + }, + columns: { + id: { + title: "ID", + key: "id", + type: "number", + search: { + show: false, + }, + column: { + width: 80, + align: "center", + }, + form: { + show: false, + }, + }, + type: { + title: t("monitor.history.jobTypeTitle"), + search: { + show: true, + }, + type: "dict-select", + dict: jobTypeDict, + form: { + show: false, + }, + column: { + width: 120, + }, + }, + title: { + title: t("monitor.history.titleTitle"), + search: { + show: true, + }, + type: "text", + column: { + width: 200, + }, + }, + content: { + title: t("monitor.history.contentTitle"), + search: { + show: true, + }, + type: "text", + column: { + width: 460, + ellipsis: true, + }, + }, + result: { + title: t("monitor.history.resultTitle"), + search: { + show: false, + }, + type: "dict-select", + dict: historyResultDict, + form: { + show: false, + }, + column: { + width: 100, + align: "center", + sorter: true, + cellRender({ value, row }) { + return ( + + + + ); + }, + }, + }, + startAt: { + title: t("monitor.history.startAtTitle"), + search: { + show: true, + }, + type: "datetime", + column: { + width: 160, + }, + }, + endAt: { + title: t("monitor.history.endAtTitle"), + search: { + show: true, + }, + type: "datetime", + column: { + width: 160, + }, + }, + projectId: { + title: t("certd.fields.projectName"), + type: "dict-select", + dict: myProjectDict, + form: { + show: false, + }, + }, + }, + }, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/monitor/history/index.vue b/packages/ui/certd-client/src/views/certd/monitor/history/index.vue new file mode 100644 index 000000000..b7e8f2b9f --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/monitor/history/index.vue @@ -0,0 +1,48 @@ + + + diff --git a/packages/ui/certd-server/db/migration-mysql/v10042__job_history.sql b/packages/ui/certd-server/db/migration-mysql/v10042__job_history.sql new file mode 100644 index 000000000..be02463ee --- /dev/null +++ b/packages/ui/certd-server/db/migration-mysql/v10042__job_history.sql @@ -0,0 +1,24 @@ + +CREATE TABLE `cd_job_history` +( + `id` bigint PRIMARY KEY AUTO_INCREMENT NOT NULL, + `user_id` bigint NOT NULL, + `project_id` bigint , + `type` varchar(100) NOT NULL, + `title` varchar(512) NOT NULL, + `related_id` varchar(100), + `result` varchar(100) NOT NULL, + `content` longtext , + `start_at` bigint NOT NULL, + `end_at` bigint , + `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + + + +CREATE INDEX `index_job_history_user_id` ON `cd_job_history` (`user_id`); +CREATE INDEX `index_job_history_project_id` ON `cd_job_history` (`project_id`); +CREATE INDEX `index_job_history_type` ON `cd_job_history` (`type`); + +ALTER TABLE `cd_job_history` ENGINE = InnoDB; \ No newline at end of file diff --git a/packages/ui/certd-server/db/migration-pg/v10042__job_history.sql b/packages/ui/certd-server/db/migration-pg/v10042__job_history.sql new file mode 100644 index 000000000..f21d44dde --- /dev/null +++ b/packages/ui/certd-server/db/migration-pg/v10042__job_history.sql @@ -0,0 +1,22 @@ + +CREATE TABLE "cd_job_history" +( + "id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY NOT NULL, + "user_id" bigint NOT NULL, + "project_id" bigint , + "type" varchar(100) NOT NULL, + "title" varchar(512) NOT NULL, + "related_id" varchar(100), + "result" varchar(100) NOT NULL, + "content" text , + "start_at" bigint NOT NULL, + "end_at" bigint , + "create_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" timestamp NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + + +CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id"); +CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id"); +CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type"); diff --git a/packages/ui/certd-server/db/migration/v10042__job_history.sql b/packages/ui/certd-server/db/migration/v10042__job_history.sql index df683b4c5..1e07cd5dd 100644 --- a/packages/ui/certd-server/db/migration/v10042__job_history.sql +++ b/packages/ui/certd-server/db/migration/v10042__job_history.sql @@ -3,7 +3,7 @@ CREATE TABLE "cd_job_history" ( "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "user_id" integer NOT NULL, - "project_id" integer NOT NULL, + "project_id" integer , "type" varchar(100) NOT NULL, "title" varchar(512) NOT NULL, "related_id" varchar(100), diff --git a/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts b/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts index 6b63c4797..e06727c71 100644 --- a/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts +++ b/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts @@ -45,7 +45,6 @@ export class JobHistoryController extends CrudController { return await super.list(body); } - @Post('/info', { description: Constants.per.authOnly, summary: "查询监控运行历史详情" }) async info(@Query('id') id: number) { await this.checkOwner(this.service,id,"read"); @@ -55,8 +54,12 @@ export class JobHistoryController extends CrudController { @Post('/delete', { description: Constants.per.authOnly, summary: "删除监控运行历史" }) async delete(@Query('id') id: number) { await this.checkOwner(this.service,id,"write"); - const res = await super.delete(id); - return res + return await super.delete(id); + } + @Post('/batchDelete', { description: Constants.per.authOnly, summary: "批量删除监控运行历史" }) + async batchDelete(@Body('ids') ids: number[]) { + const { projectId, userId } = await this.getProjectUserIdWrite() + await this.service.batchDelete(ids,userId,projectId); + return this.ok(); } - } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts index b7fdc8a4c..e3e60e3cd 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts @@ -357,10 +357,11 @@ export class SiteInfoService extends BaseService { if (userId==null) { throw new Error("userId is required"); } - const sites = await this.repository.find({ - where: {userId,projectId} - }); - this.checkList(sites,false); + // const sites = await this.repository.find({ + // where: {userId,projectId} + // }); + // this.checkList(sites,false); + await this.triggerJobOnce(userId,projectId); } async checkList(sites: SiteInfoEntity[],isCommon: boolean) { @@ -529,7 +530,7 @@ export class SiteInfoService extends BaseService { } //判断是否已关闭 const setting = await this.userSettingsService.getSetting(userId,projectId, UserSiteMonitorSetting); - if (!setting.cron) { + if (setting && !setting.cron) { return; } jobEntity = {