mirror of
https://github.com/certd/certd.git
synced 2026-04-14 12:30:54 +08:00
perf: 优化站点监控,支持设置忽略主站证书一致性,支持开启和关闭自动同步ip
This commit is contained in:
@@ -282,6 +282,9 @@ export default {
|
||||
disabled: "Enable/Disable",
|
||||
ipCheck: "Enable IP Check",
|
||||
ipSyncAuto: "Enable IP Sync Auto",
|
||||
ipSyncMode: "IP Sync Mode",
|
||||
ipIgnoreCoherence: "Ignore Certificate Coherence",
|
||||
ipIgnoreCoherenceHelper: "Enable to ignore certificate coherence check, only check certificate expiration time",
|
||||
selectRequired: "Please select",
|
||||
ipCheckConfirm: "Are you sure to {status} IP check?",
|
||||
ipCount: "IP Count",
|
||||
|
||||
@@ -286,9 +286,16 @@ export default {
|
||||
disabled: "禁用启用",
|
||||
ipCheck: "开启IP检查",
|
||||
ipSyncAuto: "自动同步IP",
|
||||
ipSyncMode: "IP同步模式",
|
||||
ipSyncModeHelper: "选择仅检查IPv4或IPv6,或检查所有IP",
|
||||
ipSyncModeAll: "检查所有IP",
|
||||
ipSyncModeIPV4Only: "仅检查IPv4",
|
||||
ipSyncModeIPV6Only: "仅检查IPv6",
|
||||
selectRequired: "请选择",
|
||||
ipCheckConfirm: "确定{status}IP检查?",
|
||||
ipCount: "IP数量",
|
||||
ipIgnoreCoherence: "忽略证书一致性",
|
||||
ipIgnoreCoherenceHelper: "开启后,即使IP上的证书与站点证书不一致,也会被认为是正常,仅校验证书过期时间",
|
||||
checkStatus: "检查状态",
|
||||
pipelineId: "关联流水线ID",
|
||||
certInfoId: "证书ID",
|
||||
|
||||
@@ -148,7 +148,6 @@ async function refreshTarget(value: any) {
|
||||
type: "",
|
||||
};
|
||||
}
|
||||
debugger
|
||||
}
|
||||
|
||||
watch(
|
||||
|
||||
@@ -610,6 +610,46 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
},
|
||||
},
|
||||
checkStatus: {
|
||||
title: t("certd.monitor.checkStatus"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
type: "dict-select",
|
||||
dict: checkStatusDict,
|
||||
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={checkStatusDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
// error: {
|
||||
// title: "错误信息",
|
||||
// search: {
|
||||
// show: false
|
||||
// },
|
||||
// type: "text",
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// column: {
|
||||
// width: 200,
|
||||
// sorter: true,
|
||||
// cellRender({ value }) {
|
||||
// return <a-tooltip title={value}>{value}</a-tooltip>;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
ipCheck: {
|
||||
title: t("certd.monitor.ipCheck"),
|
||||
type: "dict-switch",
|
||||
@@ -672,46 +712,51 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
checkStatus: {
|
||||
title: t("certd.monitor.checkStatus"),
|
||||
search: {
|
||||
show: false,
|
||||
},
|
||||
ipSyncMode: {
|
||||
title: t("certd.monitor.ipSyncMode"),
|
||||
type: "dict-select",
|
||||
dict: checkStatusDict,
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("certd.monitor.ipSyncModeAll"), value: "all" },
|
||||
{ label: t("certd.monitor.ipSyncModeIPV4Only"), value: "ipv4" },
|
||||
{ label: t("certd.monitor.ipSyncModeIPV6Only"), value: "ipv6" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
value: "all",
|
||||
show: compute(({ form }) => {
|
||||
return form.ipSyncAuto;
|
||||
}),
|
||||
helper: t("certd.monitor.ipSyncModeHelper"),
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
align: "center",
|
||||
sorter: true,
|
||||
cellRender({ value, row }) {
|
||||
return (
|
||||
<a-tooltip title={row.error}>
|
||||
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
|
||||
</a-tooltip>
|
||||
);
|
||||
},
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
ipIgnoreCoherence: {
|
||||
title: t("certd.monitor.ipIgnoreCoherence"),
|
||||
type: "dict-switch",
|
||||
dict: dict({
|
||||
data: [
|
||||
{ label: t("common.enabled"), value: true, color: "green" },
|
||||
{ label: t("common.disabled"), value: false, color: "gray" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
value: false,
|
||||
show: compute(({ form }) => {
|
||||
return form.ipCheck;
|
||||
}),
|
||||
helper: t("certd.monitor.ipIgnoreCoherenceHelper"),
|
||||
},
|
||||
column: {
|
||||
width: 100,
|
||||
sorter: true,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
// error: {
|
||||
// title: "错误信息",
|
||||
// search: {
|
||||
// show: false
|
||||
// },
|
||||
// type: "text",
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// column: {
|
||||
// width: 200,
|
||||
// sorter: true,
|
||||
// cellRender({ value }) {
|
||||
// return <a-tooltip title={value}>{value}</a-tooltip>;
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
pipelineId: {
|
||||
title: t("certd.monitor.pipelineId"),
|
||||
search: {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
ALTER TABLE cd_site_info ADD COLUMN ip_sync_auto boolean DEFAULT (1);
|
||||
ALTER TABLE cd_site_info ADD COLUMN ip_sync_auto boolean;
|
||||
ALTER TABLE cd_site_info ADD COLUMN ip_sync_mode varchar(20);
|
||||
ALTER TABLE cd_site_info ADD COLUMN ip_ignore_coherence boolean;
|
||||
|
||||
ALTER TABLE pi_pipeline ADD COLUMN webhook_key varchar(100);
|
||||
ALTER TABLE pi_pipeline ADD COLUMN trigger_count integer DEFAULT (0);
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SiteInfoService } from "../../../modules/monitor/service/site-info-serv
|
||||
import { UserSiteMonitorSetting } from "../../../modules/mine/service/models.js";
|
||||
import { merge } from "lodash-es";
|
||||
import {SiteIpService} from "../../../modules/monitor/service/site-ip-service.js";
|
||||
import { utils } from "@certd/basic";
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -104,6 +105,7 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
|
||||
async check(@Body('id') id: number) {
|
||||
await this.service.checkUserId(id, this.getUserId());
|
||||
await this.service.check(id, true, 0);
|
||||
await utils.sleep(1000);
|
||||
return this.ok();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ export class SiteInfoEntity {
|
||||
@Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' })
|
||||
ipSyncAuto: boolean;
|
||||
|
||||
@Column({ name: 'ip_sync_mode', comment: 'IP同步模式' })
|
||||
ipSyncMode: string;
|
||||
|
||||
@Column({ name: 'ip_ignore_coherence', comment: '忽略证书一致性' })
|
||||
ipIgnoreCoherence: boolean;
|
||||
|
||||
@Column({ name: 'ip_count', comment: 'ip数量' })
|
||||
ipCount: number
|
||||
|
||||
|
||||
@@ -162,16 +162,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
await this.update(updateData);
|
||||
|
||||
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
|
||||
//检查ip
|
||||
await this.checkAllIp(site,retryTimes);
|
||||
await this.checkAllIp(site,retryTimes,setting);
|
||||
|
||||
if (!notify) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sendExpiresNotify(site.id);
|
||||
await this.sendExpiresNotify(site.id,setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
@@ -187,18 +187,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.sendCheckErrorNotify(site.id);
|
||||
await this.sendCheckErrorNotify(site.id,false,setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkAllIp(site: SiteInfoEntity,retryTimes = null) {
|
||||
async checkAllIp(site: SiteInfoEntity,retryTimes = null,setting: UserSiteMonitorSetting) {
|
||||
if (!site.ipCheck) {
|
||||
return;
|
||||
}
|
||||
const certExpiresTime = site.certExpiresTime;
|
||||
const tipDays = setting?.certValidDays || 10;
|
||||
const onFinished = async (list: SiteIpEntity[]) => {
|
||||
let errorCount = 0;
|
||||
let errorMessage = "";
|
||||
@@ -207,11 +208,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
continue;
|
||||
}
|
||||
errorCount++;
|
||||
|
||||
const isExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).valueOf();
|
||||
const isWillExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).subtract(tipDays, "day").valueOf();
|
||||
|
||||
if (item.error) {
|
||||
errorMessage += `${item.ipAddress}:${item.error}; \n`;
|
||||
} else if (item.certExpiresTime !== certExpiresTime) {
|
||||
} else if (item.certExpiresTime !== certExpiresTime && !site.ipIgnoreCoherence) {
|
||||
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致(主站:${dayjs(certExpiresTime).format("YYYY-MM-DD")},IP:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
} else {
|
||||
} else if (isExpired){
|
||||
errorMessage += `${item.ipAddress}:证书已过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
}else if (isWillExpired){
|
||||
errorMessage += `${item.ipAddress}:证书将过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}); \n`;
|
||||
}else {
|
||||
errorCount--;
|
||||
}
|
||||
}
|
||||
@@ -232,12 +241,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
ipErrorCount: errorCount
|
||||
});
|
||||
try {
|
||||
await this.sendCheckErrorNotify(site.id, true);
|
||||
await this.sendCheckErrorNotify(site.id, true,setting);
|
||||
} catch (e) {
|
||||
logger.error("send notify error", e);
|
||||
}
|
||||
};
|
||||
if (!site.ipSyncAuto) {
|
||||
if (site.ipSyncAuto === false) {
|
||||
await this.siteIpService.checkAll(site, retryTimes,onFinished);
|
||||
}else{
|
||||
await this.siteIpService.syncAndCheck(site, retryTimes,onFinished);
|
||||
@@ -246,7 +255,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查
|
||||
* 检查,不等待返回
|
||||
* @param id
|
||||
* @param notify
|
||||
* @param retryTimes
|
||||
@@ -256,13 +265,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
if (!site) {
|
||||
throw new Error("站点不存在");
|
||||
}
|
||||
return await this.doCheck(site, notify, retryTimes);
|
||||
|
||||
this.doCheck(site, notify, retryTimes).catch((err) => {
|
||||
logger.error("check site error", err);
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
async sendCheckErrorNotify(siteId: number, fromIpCheck = false) {
|
||||
async sendCheckErrorNotify(siteId: number, fromIpCheck = false,setting: UserSiteMonitorSetting) {
|
||||
const site = await this.info(siteId);
|
||||
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
|
||||
// 发邮件
|
||||
await this.notificationService.send(
|
||||
{
|
||||
@@ -281,11 +293,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
);
|
||||
}
|
||||
|
||||
async sendExpiresNotify(siteId: number) {
|
||||
const site = await this.info(siteId);
|
||||
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
|
||||
async sendExpiresNotify(siteId: number,setting: UserSiteMonitorSetting) {
|
||||
const tipDays = setting?.certValidDays || 10;
|
||||
|
||||
const site = await this.info(siteId);
|
||||
const expires = site.certExpiresTime;
|
||||
const validDays = dayjs(expires).diff(dayjs(), "day");
|
||||
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||
|
||||
@@ -76,7 +76,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
}
|
||||
|
||||
//从域名解析中获取所有ip
|
||||
const ips = await this.getAllIpsFromDomain(domain,resolver);
|
||||
const ips = await this.getAllIpsFromDomain(domain,resolver,entity.ipSyncMode);
|
||||
if (ips.length === 0 ) {
|
||||
logger.warn(`没有发现${domain}的IP`)
|
||||
return
|
||||
@@ -126,7 +126,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
async check(ipId: number, domain: string, port: number,retryTimes = null) {
|
||||
async check(ipId: number, domain: string, port: number,retryTimes = null ,tipDays = 10) {
|
||||
if(!ipId){
|
||||
return
|
||||
}
|
||||
@@ -231,7 +231,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
})
|
||||
}
|
||||
|
||||
async getAllIpsFromDomain(domain: string,resolver:any = dns):Promise<string[]> {
|
||||
async getAllIpsFromDomain(domain: string,resolver:any = dns,ipSyncMode:string = "all"):Promise<string[]> {
|
||||
const getFromV4 = async ():Promise<string[]> => {
|
||||
try{
|
||||
return await resolver.resolve4(domain);
|
||||
@@ -249,6 +249,12 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||
}
|
||||
}
|
||||
|
||||
if (ipSyncMode === "ipv4") {
|
||||
return await getFromV4();
|
||||
}
|
||||
if (ipSyncMode === "ipv6") {
|
||||
return await getFromV6();
|
||||
}
|
||||
|
||||
return Promise.all([getFromV4(), getFromV6()]).then(res => {
|
||||
return [...res[0], ...res[1]];
|
||||
|
||||
Reference in New Issue
Block a user