perf(monitor): 站点证书监控支持手动选中触发检查

This commit is contained in:
xiaojunnuo
2026-09-20 23:04:49 +08:00
parent 2735feb330
commit f60e63fbaf
9 changed files with 67 additions and 12 deletions
@@ -19,6 +19,7 @@ export default {
subdomainConfirmContent: "{domain} appears to be a subdomain. Only delegated subdomains and free second-level subdomains need to be maintained here. Otherwise certificate application may fail. Continue?",
importFromProvider: "Import from Domain Provider",
importFromResolveRecords: "Import from DNS Records",
importFromResolveRecordsTooltip: "Import DNS records as monitoring sites",
syncExpirationDate: "Sync Domain Expiration Time",
syncTaskSubmitted: "Sync task submitted",
syncExpirationProgress: "Sync Domain Expiration Progress",
@@ -7,6 +7,8 @@ export default {
settingLink: "Site Monitoring Settings",
limitInfo: "Basic edition limited to 1, professional and above unlimited, current",
checkAll: "Check All",
checkBatch: "Check Selected",
batchCheckConfirm: "Check the {count} selected sites now?",
checkNow: "Check Now",
batchDelete: "Batch Delete",
confirmTitle: "Confirm",
@@ -19,6 +19,7 @@ export default {
subdomainConfirmContent: "检测到{domain}为子域名,只有托管子域名和免费二级子域名才需要在此处维护,否则会导致申请证书失败,请确认是否继续?",
importFromProvider: "从域名提供商导入",
importFromResolveRecords: "从解析记录导入",
importFromResolveRecordsTooltip: "从域名的解析记录中导入并作为监控站点",
syncExpirationDate: "同步域名过期时间",
syncTaskSubmitted: "同步任务已提交",
syncExpirationProgress: "同步域名过期时间进度",
@@ -7,6 +7,8 @@ export default {
settingLink: "站点监控设置",
limitInfo: "基础版限制1条,专业版以上无限制,当前",
checkAll: "检查全部",
checkBatch: "检查选中",
batchCheckConfirm: "确认触发检查选中的{count}个站点吗?",
checkNow: "立即检查",
batchDelete: "批量删除",
confirmTitle: "确认",
@@ -63,6 +63,13 @@ export const siteInfoApi = {
method: "post",
});
},
async CheckBatch(ids: number[]) {
return await request({
url: apiPrefix + "/checkBatch",
method: "post",
data: { ids },
});
},
async Import(form: any) {
return await request({
@@ -100,6 +100,23 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
});
}
function checkBatch() {
if (selectedRowKeys.value.length === 0) {
notification.error({ message: t("monitor.selectRecordsFirst") });
return;
}
Modal.confirm({
title: t("monitor.confirmTitle"),
content: t("monitor.batchCheckConfirm", { count: selectedRowKeys.value.length }),
onOk: async () => {
await siteInfoApi.CheckBatch(selectedRowKeys.value);
notification.success({ message: t("monitor.checkSubmitted") });
selectedRowKeys.value = [];
await crudExpose.doRefresh();
},
});
}
const GroupTypeSite = "site";
const groupDictRef = createGroupDictRef(GroupTypeSite);
@@ -253,6 +270,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
show: hasActionPermission("write"),
title: t("certd.domain.importFromResolveRecords"),
text: t("certd.domain.importFromResolveRecords"),
tooltip: { title: t("certd.domain.importFromResolveRecordsTooltip") },
type: "primary",
// needPlus: true,
color: "gold",
@@ -274,6 +292,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
checkAll();
},
},
checkBatch: {
show: true,
text: t("monitor.checkBatch"),
type: "primary",
icon: "ion:play-forward-outline",
click() {
checkBatch();
},
},
},
},
rowHandle: {
@@ -134,6 +134,14 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
return this.ok();
}
@Post("/checkBatch", { description: Constants.per.authOnly, summary: "批量检查站点监控" })
async checkBatch(@Body(ALL) body: any) {
const { projectId, userId } = await this.getProjectUserIdRead();
const count = await this.service.checkBatch(body.ids, userId, projectId);
this.auditLog({ content: `批量检查了${count}条站点监控` });
return this.ok({ count });
}
@Post("/checkAll", { description: Constants.per.authOnly, summary: "检查所有站点监控" })
async checkAll() {
const { projectId, userId } = await this.getProjectUserIdWrite();
@@ -324,6 +324,23 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
return;
}
async checkBatch(ids: number[], userId: number, projectId?: number) {
const validIds = this.filterIds(ids);
if (validIds.length === 0) {
return 0;
}
const userProjectQuery = this.buildUserProjectQuery(userId, projectId);
const sites = await this.repository.find({
where: {
id: In(validIds),
...userProjectQuery,
disabled: false,
},
});
await this.checkList(sites);
return sites.length;
}
async sendCheckErrorNotify(siteId: number, fromIpCheck = false, setting: UserSiteMonitorSetting) {
const site = await this.info(siteId);
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
@@ -1,4 +1,5 @@
import { BaseNotification, IsNotification, NotificationBody, NotificationInput } from "@certd/pipeline";
import { stringUtils } from "@certd/basic";
import qs from "qs";
@IsNotification({
@@ -95,17 +96,6 @@ export class WebhookNotification extends BaseNotification {
})
skipSslVerify: boolean;
replaceTemplate(target: string, body: any, urlEncode = false) {
let bodyStr = target;
const keys = Object.keys(body);
for (const key of keys) {
let value = urlEncode ? encodeURIComponent(body[key]) : body[key];
value = value.replaceAll(`\n`, "\\n");
bodyStr = bodyStr.replaceAll(`{${key}}`, value);
}
return bodyStr;
}
async send(body: NotificationBody) {
if (!this.template) {
throw new Error("模版不能为空");
@@ -119,7 +109,7 @@ export class WebhookNotification extends BaseNotification {
content: body.content,
url: body.url,
};
const bodyStr = this.replaceTemplate(this.template, replaceBody);
const bodyStr = stringUtils.replaceTemplate(this.template, replaceBody);
let data = JSON.parse(bodyStr);
let url = this.webhook;