mirror of
https://github.com/certd/certd.git
synced 2026-04-14 12:30:54 +08:00
perf(monitor): 支持查看监控执行记录
- 新增监控任务执行记录页面及相关API - 添加数据库表结构及多数据库支持 - 完善国际化翻译 - 实现批量删除功能 - 优化站点监控服务逻辑
This commit is contained in:
@@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -227,6 +227,7 @@ export default {
|
||||
currentProject: "当前项目",
|
||||
projectMemberManager: "项目成员管理",
|
||||
domainMonitorSetting: "域名监控设置",
|
||||
jobHistory: "监控执行记录",
|
||||
},
|
||||
certificateRepo: {
|
||||
title: "证书仓库",
|
||||
|
||||
@@ -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: "站点证书监控",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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<UserPageRes> => {
|
||||
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 (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-values-format v-model={value} dict={historyResultDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<fs-page>
|
||||
<template #header>
|
||||
<div class="title flex items-center">
|
||||
{{ t("monitor.history.title") }}
|
||||
<div class="sub flex-1">
|
||||
<div>
|
||||
{{ t("monitor.history.description") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<fs-crud ref="crudRef" v-bind="crudBinding">
|
||||
<template #pagination-left>
|
||||
<a-tooltip title="批量删除">
|
||||
<fs-button icon="DeleteOutlined" @click="handleBatchDelete"></fs-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</fs-crud>
|
||||
</fs-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFs } from "@fast-crud/fast-crud";
|
||||
import { onActivated, onMounted } from "vue";
|
||||
import createCrudOptions from "./crud";
|
||||
import { useI18n } from "/src/locales";
|
||||
const { t } = useI18n();
|
||||
defineOptions({
|
||||
name: "JobHistory",
|
||||
});
|
||||
const context: any = {
|
||||
permission: {
|
||||
isProjectPermission: true,
|
||||
},
|
||||
};
|
||||
const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context });
|
||||
|
||||
const handleBatchDelete = context.handleBatchDelete;
|
||||
|
||||
// 页面打开后获取列表数据
|
||||
onMounted(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
onActivated(() => {
|
||||
crudExpose.doRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -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;
|
||||
@@ -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");
|
||||
@@ -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),
|
||||
|
||||
@@ -45,7 +45,6 @@ export class JobHistoryController extends CrudController<JobHistoryService> {
|
||||
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<JobHistoryService> {
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -357,10 +357,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
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<SiteInfoEntity> {
|
||||
}
|
||||
//判断是否已关闭
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
|
||||
if (!setting.cron) {
|
||||
if (setting && !setting.cron) {
|
||||
return;
|
||||
}
|
||||
jobEntity = {
|
||||
|
||||
Reference in New Issue
Block a user