mirror of https://github.com/certd/certd
perf: 站点监控支持批量导入域名和ip
parent
6467edb843
commit
2d7729dbe9
|
@ -168,7 +168,11 @@ https://afdian.com/a/greper
|
||||||
1. 可以调整开源协议以使其更严格或更宽松。
|
1. 可以调整开源协议以使其更严格或更宽松。
|
||||||
2. 可以用于商业用途。
|
2. 可以用于商业用途。
|
||||||
|
|
||||||
|
感谢以下贡献者做出的贡献。
|
||||||
|
|
||||||
|
<a href="https://github.com/certd/certd/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=certd/certd" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## 九、 开源许可
|
## 九、 开源许可
|
||||||
* 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
|
* 本项目遵循 GNU Affero General Public License(AGPL)开源协议。
|
||||||
|
|
|
@ -56,6 +56,14 @@ export const siteInfoApi = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async Import(form: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/import",
|
||||||
|
method: "post",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
async DisabledChange(id: number, disabled: boolean) {
|
async DisabledChange(id: number, disabled: boolean) {
|
||||||
return await request({
|
return await request({
|
||||||
url: apiPrefix + "/disabledChange",
|
url: apiPrefix + "/disabledChange",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useSettingStore } from "/@/store/settings";
|
||||||
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
import { mySuiteApi } from "/@/views/certd/suite/mine/api";
|
||||||
import { mitter } from "/@/utils/util.mitt";
|
import { mitter } from "/@/utils/util.mitt";
|
||||||
import { useSiteIpMonitor } from "./ip/use";
|
import { useSiteIpMonitor } from "./ip/use";
|
||||||
|
import { useSiteImport } from "/@/views/certd/monitor/site/use";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -44,6 +45,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
});
|
});
|
||||||
|
|
||||||
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
|
const { openSiteIpMonitorDialog } = useSiteIpMonitor();
|
||||||
|
const { openSiteImportDialog } = useSiteImport();
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
|
@ -97,6 +99,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
await crudExpose.openAdd({});
|
await crudExpose.openAdd({});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
//导入按钮
|
||||||
|
import: {
|
||||||
|
show: true,
|
||||||
|
text: "批量导入",
|
||||||
|
type: "primary",
|
||||||
|
async click() {
|
||||||
|
openSiteImportDialog({
|
||||||
|
afterSubmit() {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
|
@ -235,7 +250,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<a-popover placement="left" v-slots={slots} overlayStyle={{ maxWidth: "30%" }}>
|
<a-popover placement={"left"} v-slots={slots} overlayStyle={{ maxWidth: "30%" }}>
|
||||||
{row.certDomains}
|
{row.certDomains}
|
||||||
</a-popover>
|
</a-popover>
|
||||||
);
|
);
|
||||||
|
|
|
@ -68,4 +68,11 @@ export const siteIpApi = {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
async Import(form: any) {
|
||||||
|
return await request({
|
||||||
|
url: apiPrefix + "/import",
|
||||||
|
method: "post",
|
||||||
|
data: form,
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,13 +4,11 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
|
||||||
import { siteIpApi } from "./api";
|
import { siteIpApi } from "./api";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Modal, notification } from "ant-design-vue";
|
import { Modal, notification } from "ant-design-vue";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSiteIpMonitor } from "/@/views/certd/monitor/site/ip/use";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const { t } = useI18n();
|
|
||||||
const api = siteIpApi;
|
const api = siteIpApi;
|
||||||
|
|
||||||
const { crudBinding } = crudExpose;
|
|
||||||
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
if (!query.query) {
|
if (!query.query) {
|
||||||
query.query = {};
|
query.query = {};
|
||||||
|
@ -36,8 +34,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
const settingsStore = useSettingStore();
|
|
||||||
|
|
||||||
const checkStatusDict = dict({
|
const checkStatusDict = dict({
|
||||||
data: [
|
data: [
|
||||||
{ label: "成功", value: "ok", color: "green" },
|
{ label: "成功", value: "ok", color: "green" },
|
||||||
|
@ -45,6 +41,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
{ label: "异常", value: "error", color: "red" },
|
{ label: "异常", value: "error", color: "red" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
const { openSiteIpImportDialog } = useSiteIpMonitor();
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
request: {
|
request: {
|
||||||
|
@ -75,6 +72,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
await crudExpose.openAdd({});
|
await crudExpose.openAdd({});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
import: {
|
||||||
|
show: true,
|
||||||
|
text: "批量导入",
|
||||||
|
type: "primary",
|
||||||
|
async click() {
|
||||||
|
openSiteIpImportDialog({
|
||||||
|
siteId: context.props.siteId,
|
||||||
|
afterSubmit() {
|
||||||
|
crudExpose.doRefresh();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
load: {
|
load: {
|
||||||
text: "同步IP",
|
text: "同步IP",
|
||||||
type: "primary",
|
type: "primary",
|
||||||
|
@ -295,6 +305,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
data: [
|
data: [
|
||||||
{ label: "同步", value: "sync", color: "green" },
|
{ label: "同步", value: "sync", color: "green" },
|
||||||
{ label: "手动", value: "manual", color: "blue" },
|
{ label: "手动", value: "manual", color: "blue" },
|
||||||
|
{ label: "导入", value: "import", color: "blue" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
form: {
|
form: {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
import { useRouter } from "vue-router";
|
|
||||||
|
|
||||||
import SiteIpCertMonitor from "./index.vue";
|
import SiteIpCertMonitor from "./index.vue";
|
||||||
|
import { siteIpApi } from "/@/views/certd/monitor/site/ip/api";
|
||||||
|
|
||||||
export function useSiteIpMonitor() {
|
export function useSiteIpMonitor() {
|
||||||
const { openDialog } = useFormWrapper();
|
const { openDialog, openCrudFormDialog } = useFormWrapper();
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
async function openSiteIpMonitorDialog(opts: { siteId: number }) {
|
async function openSiteIpMonitorDialog(opts: { siteId: number }) {
|
||||||
await openDialog({
|
await openDialog({
|
||||||
|
@ -34,7 +33,42 @@ export function useSiteIpMonitor() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openSiteIpImportDialog(opts: { afterSubmit: any; siteId: any }) {
|
||||||
|
const { afterSubmit } = opts;
|
||||||
|
await openCrudFormDialog<any>({
|
||||||
|
crudOptions: {
|
||||||
|
columns: {
|
||||||
|
text: {
|
||||||
|
type: "textarea",
|
||||||
|
title: "IP列表",
|
||||||
|
form: {
|
||||||
|
helper: "IP或者CNAME域名,一行一个",
|
||||||
|
rules: [{ required: true, message: "请输入要导入的IP或域名" }],
|
||||||
|
component: {
|
||||||
|
placeholder: "192.168.1.2\ncname.foo.com",
|
||||||
|
rows: 8,
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
async doSubmit({ form }) {
|
||||||
|
return siteIpApi.Import({
|
||||||
|
...form,
|
||||||
|
siteId: opts.siteId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
afterSubmit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
openSiteIpMonitorDialog,
|
openSiteIpMonitorDialog,
|
||||||
|
openSiteIpImportDialog,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||||
|
import { siteInfoApi } from "./api";
|
||||||
|
|
||||||
|
export function useSiteImport() {
|
||||||
|
const { openCrudFormDialog } = useFormWrapper();
|
||||||
|
|
||||||
|
async function openSiteImportDialog(opts: { afterSubmit: any }) {
|
||||||
|
const { afterSubmit } = opts;
|
||||||
|
await openCrudFormDialog<any>({
|
||||||
|
crudOptions: {
|
||||||
|
columns: {
|
||||||
|
text: {
|
||||||
|
type: "textarea",
|
||||||
|
title: "域名列表",
|
||||||
|
form: {
|
||||||
|
helper: "格式【域名:端口:名称】,一行一个,其中端口、名称可以省略\n比如:\nwww.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com",
|
||||||
|
rules: [{ required: true, message: "请输入要导入的域名" }],
|
||||||
|
component: {
|
||||||
|
placeholder: "www.baidu.com:443:百度\nwww.taobao.com::淘宝\nwww.google.com\n",
|
||||||
|
rows: 8,
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
span: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
async doSubmit({ form }) {
|
||||||
|
return siteInfoApi.Import(form);
|
||||||
|
},
|
||||||
|
afterSubmit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
openSiteImportDialog,
|
||||||
|
};
|
||||||
|
}
|
|
@ -105,6 +105,17 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
|
||||||
await this.service.checkAllByUsers(userId);
|
await this.service.checkAllByUsers(userId);
|
||||||
return this.ok();
|
return this.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/import', { summary: Constants.per.authOnly })
|
||||||
|
async doImport(@Body(ALL) body: any) {
|
||||||
|
const userId = this.getUserId();
|
||||||
|
await this.service.doImport({
|
||||||
|
text:body.text,
|
||||||
|
userId
|
||||||
|
})
|
||||||
|
return this.ok();
|
||||||
|
}
|
||||||
|
|
||||||
@Post('/ipCheckChange', { summary: Constants.per.authOnly })
|
@Post('/ipCheckChange', { summary: Constants.per.authOnly })
|
||||||
async ipCheckChange(@Body(ALL) bean: any) {
|
async ipCheckChange(@Body(ALL) bean: any) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
|
|
|
@ -111,5 +111,16 @@ export class SiteInfoController extends CrudController<SiteIpService> {
|
||||||
return this.ok();
|
return this.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/import', { summary: Constants.per.authOnly })
|
||||||
|
async doImport(@Body(ALL) body: any) {
|
||||||
|
const userId = this.getUserId();
|
||||||
|
await this.service.doImport({
|
||||||
|
text:body.text,
|
||||||
|
userId,
|
||||||
|
siteId:body.siteId
|
||||||
|
})
|
||||||
|
return this.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
|
||||||
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from '@certd/lib-server';
|
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from "@midwayjs/typeorm";
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from "typeorm";
|
||||||
import { SiteInfoEntity } from '../entity/site-info.js';
|
import { SiteInfoEntity } from "../entity/site-info.js";
|
||||||
import { siteTester } from './site-tester.js';
|
import { siteTester } from "./site-tester.js";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
import { logger, utils } from '@certd/basic';
|
import { logger, utils } from "@certd/basic";
|
||||||
import { PeerCertificate } from 'tls';
|
import { PeerCertificate } from "tls";
|
||||||
import { NotificationService } from '../../pipeline/service/notification-service.js';
|
import { NotificationService } from "../../pipeline/service/notification-service.js";
|
||||||
import { isComm, isPlus } from '@certd/plus-core';
|
import { isComm, isPlus } from "@certd/plus-core";
|
||||||
import { UserSuiteService } from '@certd/commercial-core';
|
import { UserSuiteService } from "@certd/commercial-core";
|
||||||
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
|
||||||
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
import { UserSiteMonitorSetting } from "../../mine/service/models.js";
|
||||||
import {SiteIpService} from "./site-ip-service.js";
|
import { SiteIpService } from "./site-ip-service.js";
|
||||||
import {SiteIpEntity} from "../entity/site-ip.js";
|
import { SiteIpEntity } from "../entity/site-ip.js";
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||||
|
@ -43,7 +43,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
|
|
||||||
async add(data: SiteInfoEntity) {
|
async add(data: SiteInfoEntity) {
|
||||||
if (!data.userId) {
|
if (!data.userId) {
|
||||||
throw new Error('userId is required');
|
throw new Error("userId is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isComm()) {
|
if (isComm()) {
|
||||||
|
@ -51,25 +51,34 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
if (suiteSetting.enabled) {
|
if (suiteSetting.enabled) {
|
||||||
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
|
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
|
||||||
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
|
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
|
||||||
throw new NeedSuiteException('站点监控数量已达上限,请购买或升级套餐');
|
throw new NeedSuiteException("站点监控数量已达上限,请购买或升级套餐");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if (!isPlus()) {
|
} else if (!isPlus()) {
|
||||||
const count = await this.getUserMonitorCount(data.userId);
|
const count = await this.getUserMonitorCount(data.userId);
|
||||||
if (count >= 1) {
|
if (count >= 1) {
|
||||||
throw new NeedVIPException('站点监控数量已达上限,请升级专业版');
|
throw new NeedVIPException("站点监控数量已达上限,请升级专业版");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data.disabled = false;
|
data.disabled = false;
|
||||||
|
|
||||||
|
const found = await this.repository.findOne({
|
||||||
|
where: {
|
||||||
|
domain: data.domain,
|
||||||
|
userId: data.userId,
|
||||||
|
httpsPort: data.httpsPort || 443
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (found) {
|
||||||
|
return { id: found.id };
|
||||||
|
}
|
||||||
|
|
||||||
return await super.add(data);
|
return await super.add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(data: any) {
|
async update(data: any) {
|
||||||
if (!data.id) {
|
if (!data.id) {
|
||||||
throw new Error('id is required');
|
throw new Error("id is required");
|
||||||
}
|
}
|
||||||
delete data.userId;
|
delete data.userId;
|
||||||
await super.update(data);
|
await super.update(data);
|
||||||
|
@ -77,10 +86,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
|
|
||||||
async getUserMonitorCount(userId: number) {
|
async getUserMonitorCount(userId: number) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('userId is required');
|
throw new Error("userId is required");
|
||||||
}
|
}
|
||||||
return await this.repository.count({
|
return await this.repository.count({
|
||||||
where: { userId },
|
where: { userId }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,26 +101,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
*/
|
*/
|
||||||
async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) {
|
async doCheck(site: SiteInfoEntity, notify = true, retryTimes = 3) {
|
||||||
if (!site?.domain) {
|
if (!site?.domain) {
|
||||||
throw new Error('站点域名不能为空');
|
throw new Error("站点域名不能为空");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.update({
|
await this.update({
|
||||||
id: site.id,
|
id: site.id,
|
||||||
checkStatus: 'checking',
|
checkStatus: "checking",
|
||||||
lastCheckTime: dayjs().valueOf(),
|
lastCheckTime: dayjs().valueOf()
|
||||||
});
|
});
|
||||||
const res = await siteTester.test({
|
const res = await siteTester.test({
|
||||||
host: site.domain,
|
host: site.domain,
|
||||||
port: site.httpsPort,
|
port: site.httpsPort,
|
||||||
retryTimes,
|
retryTimes
|
||||||
});
|
});
|
||||||
|
|
||||||
const certi: PeerCertificate = res.certificate;
|
const certi: PeerCertificate = res.certificate;
|
||||||
if (!certi) {
|
if (!certi) {
|
||||||
throw new Error('没有发现证书');
|
throw new Error("没有发现证书");
|
||||||
}
|
}
|
||||||
const expires = certi.valid_to;
|
const expires = certi.valid_to;
|
||||||
const allDomains = certi.subjectaltname?.replaceAll('DNS:', '').split(',') ||[];
|
const allDomains = certi.subjectaltname?.replaceAll("DNS:", "").split(",") || [];
|
||||||
const mainDomain = certi.subject?.CN;
|
const mainDomain = certi.subject?.CN;
|
||||||
let domains = allDomains;
|
let domains = allDomains;
|
||||||
if (!allDomains.includes(mainDomain)) {
|
if (!allDomains.includes(mainDomain)) {
|
||||||
|
@ -119,23 +128,26 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
}
|
}
|
||||||
const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`;
|
const issuer = `${certi.issuer.O}<${certi.issuer.CN}>`;
|
||||||
const isExpired = dayjs().valueOf() > dayjs(expires).valueOf();
|
const isExpired = dayjs().valueOf() > dayjs(expires).valueOf();
|
||||||
const status = isExpired ? 'expired' : 'ok';
|
const status = isExpired ? "expired" : "ok";
|
||||||
const updateData = {
|
const updateData = {
|
||||||
id: site.id,
|
id: site.id,
|
||||||
certDomains: domains.join(','),
|
certDomains: domains.join(","),
|
||||||
certStatus: status,
|
certStatus: status,
|
||||||
certProvider: issuer,
|
certProvider: issuer,
|
||||||
certExpiresTime: dayjs(expires).valueOf(),
|
certExpiresTime: dayjs(expires).valueOf(),
|
||||||
lastCheckTime: dayjs().valueOf(),
|
lastCheckTime: dayjs().valueOf(),
|
||||||
error: null,
|
error: null,
|
||||||
checkStatus: 'ok',
|
checkStatus: "ok"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (site.ipCheck) {
|
||||||
|
delete updateData.checkStatus
|
||||||
|
}
|
||||||
await this.update(updateData);
|
await this.update(updateData);
|
||||||
|
|
||||||
|
|
||||||
//检查ip
|
//检查ip
|
||||||
await this.checkAllIp(site)
|
await this.checkAllIp(site);
|
||||||
|
|
||||||
if (!notify) {
|
if (!notify) {
|
||||||
return;
|
return;
|
||||||
|
@ -143,15 +155,15 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
try {
|
try {
|
||||||
await this.sendExpiresNotify(site);
|
await this.sendExpiresNotify(site);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('send notify error', e);
|
logger.error("send notify error", e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('check site error', e);
|
logger.error("check site error", e);
|
||||||
await this.update({
|
await this.update({
|
||||||
id: site.id,
|
id: site.id,
|
||||||
checkStatus: 'error',
|
checkStatus: "error",
|
||||||
lastCheckTime: dayjs().valueOf(),
|
lastCheckTime: dayjs().valueOf(),
|
||||||
error: e.message,
|
error: e.message
|
||||||
});
|
});
|
||||||
if (!notify) {
|
if (!notify) {
|
||||||
return;
|
return;
|
||||||
|
@ -159,49 +171,56 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
try {
|
try {
|
||||||
await this.sendCheckErrorNotify(site);
|
await this.sendCheckErrorNotify(site);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('send notify error', e);
|
logger.error("send notify error", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkAllIp(site:SiteInfoEntity){
|
async checkAllIp(site: SiteInfoEntity) {
|
||||||
if( !site.ipCheck){
|
if (!site.ipCheck) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const certExpiresTime = site.certExpiresTime;
|
const certExpiresTime = site.certExpiresTime;
|
||||||
const onFinished = async (list:SiteIpEntity[])=>{
|
const onFinished = async (list: SiteIpEntity[]) => {
|
||||||
let errorCount = 0
|
let errorCount = 0;
|
||||||
let errorMessage = ""
|
let errorMessage = "";
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
if (!item) {
|
if (!item) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
errorCount++
|
errorCount++;
|
||||||
if(item.error){
|
if (item.error) {
|
||||||
errorMessage += `${item.ipAddress}:${item.error}; \n`
|
errorMessage += `${item.ipAddress}:${item.error}; \n`;
|
||||||
}else if(item.certExpiresTime!==certExpiresTime){
|
} else if (item.certExpiresTime !== certExpiresTime) {
|
||||||
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`
|
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致; \n`;
|
||||||
}else{
|
} else {
|
||||||
errorCount--
|
errorCount--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (errorCount<=0){
|
if (errorCount <= 0) {
|
||||||
return
|
//检查正常
|
||||||
|
await this.update({
|
||||||
|
id: site.id,
|
||||||
|
checkStatus: "ok",
|
||||||
|
error: "",
|
||||||
|
ipErrorCount: 0
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
await this.update({
|
await this.update({
|
||||||
id: site.id,
|
id: site.id,
|
||||||
checkStatus: 'error',
|
checkStatus: "error",
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
ipErrorCount: errorCount,
|
ipErrorCount: errorCount
|
||||||
})
|
});
|
||||||
try {
|
try {
|
||||||
site = await this.info(site.id)
|
site = await this.info(site.id);
|
||||||
await this.sendCheckErrorNotify(site,true);
|
await this.sendCheckErrorNotify(site, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('send notify error', e);
|
logger.error("send notify error", e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
await this.siteIpService.checkAll(site,onFinished)
|
await this.siteIpService.checkAll(site, onFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -213,13 +232,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
async check(id: number, notify = false, retryTimes = 3) {
|
async check(id: number, notify = false, retryTimes = 3) {
|
||||||
const site = await this.info(id);
|
const site = await this.info(id);
|
||||||
if (!site) {
|
if (!site) {
|
||||||
throw new Error('站点不存在');
|
throw new Error("站点不存在");
|
||||||
}
|
}
|
||||||
return await this.doCheck(site, notify, retryTimes);
|
return await this.doCheck(site, notify, retryTimes);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendCheckErrorNotify(site: SiteInfoEntity,fromIpCheck=false) {
|
async sendCheckErrorNotify(site: SiteInfoEntity, fromIpCheck = false) {
|
||||||
const url = await this.notificationService.getBindUrl('#/certd/monitor/site');
|
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||||
// 发邮件
|
// 发邮件
|
||||||
await this.notificationService.send(
|
await this.notificationService.send(
|
||||||
{
|
{
|
||||||
|
@ -227,22 +246,23 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
logger: logger,
|
logger: logger,
|
||||||
body: {
|
body: {
|
||||||
url,
|
url,
|
||||||
title: `站点证书${fromIpCheck?"(IP)":""}检查出错<${site.name}>`,
|
title: `站点证书${fromIpCheck ? "(IP)" : ""}检查出错<${site.name}>`,
|
||||||
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
|
||||||
errorMessage: site.error,
|
errorMessage: site.error
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendExpiresNotify(site: SiteInfoEntity) {
|
async sendExpiresNotify(site: SiteInfoEntity) {
|
||||||
|
|
||||||
const tipDays = 10
|
const tipDays = 10;
|
||||||
|
|
||||||
const expires = site.certExpiresTime;
|
const expires = site.certExpiresTime;
|
||||||
const validDays = dayjs(expires).diff(dayjs(), 'day');
|
const validDays = dayjs(expires).diff(dayjs(), "day");
|
||||||
const url = await this.notificationService.getBindUrl('#/certd/monitor/site');
|
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
|
||||||
const content = `站点名称: ${site.name} \n站点域名: ${site.domain} \n证书域名: ${site.certDomains} \n颁发机构: ${site.certProvider} \n过期时间: ${dayjs(site.certExpiresTime).format('YYYY-MM-DD')} \n`;
|
const content = `站点名称: ${site.name} \n站点域名: ${site.domain} \n证书域名: ${site.certDomains} \n颁发机构: ${site.certProvider} \n过期时间: ${dayjs(site.certExpiresTime).format("YYYY-MM-DD")} \n`;
|
||||||
if (validDays >= 0 && validDays < tipDays) {
|
if (validDays >= 0 && validDays < tipDays) {
|
||||||
// 发通知
|
// 发通知
|
||||||
await this.notificationService.send(
|
await this.notificationService.send(
|
||||||
|
@ -252,8 +272,8 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
body: {
|
body: {
|
||||||
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
title: `站点证书即将过期,剩余${validDays}天,<${site.name}>`,
|
||||||
content,
|
content,
|
||||||
url,
|
url
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
);
|
);
|
||||||
|
@ -268,7 +288,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
content,
|
content,
|
||||||
url,
|
url,
|
||||||
errorMessage: "站点证书已过期"
|
errorMessage: "站点证书已过期"
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
site.userId
|
site.userId
|
||||||
);
|
);
|
||||||
|
@ -277,10 +297,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
|
|
||||||
async checkAllByUsers(userId: any) {
|
async checkAllByUsers(userId: any) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Error('userId is required');
|
throw new Error("userId is required");
|
||||||
}
|
}
|
||||||
const sites = await this.repository.find({
|
const sites = await this.repository.find({
|
||||||
where: { userId },
|
where: { userId }
|
||||||
});
|
});
|
||||||
this.checkList(sites);
|
this.checkList(sites);
|
||||||
}
|
}
|
||||||
|
@ -294,7 +314,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSetting(userId: number){
|
async getSetting(userId: number) {
|
||||||
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
|
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,26 +322,78 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||||
await this.userSettingsService.saveSetting(userId, bean);
|
await this.userSettingsService.saveSetting(userId, bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ipCheckChange(req: {id: any; ipCheck: any}) {
|
async ipCheckChange(req: { id: any; ipCheck: any }) {
|
||||||
|
|
||||||
await this.update({
|
await this.update({
|
||||||
id: req.id,
|
id: req.id,
|
||||||
ipCheck: req.ipCheck,
|
ipCheck: req.ipCheck
|
||||||
});
|
});
|
||||||
if(req.ipCheck){
|
if (req.ipCheck) {
|
||||||
const site = await this.info(req.id);
|
const site = await this.info(req.id);
|
||||||
await this.siteIpService.sync(site)
|
await this.siteIpService.sync(site);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async disabledChange(req: { disabled: any; id: any }) {
|
async disabledChange(req: { disabled: any; id: any }) {
|
||||||
await this.update({
|
await this.update({
|
||||||
id: req.id,
|
id: req.id,
|
||||||
disabled: req.disabled,
|
disabled: req.disabled
|
||||||
});
|
});
|
||||||
if(!req.disabled){
|
if (!req.disabled) {
|
||||||
const site = await this.info(req.id);
|
const site = await this.info(req.id);
|
||||||
await this.doCheck(site)
|
await this.doCheck(site);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async doImport(req: { text: string; userId: number }) {
|
||||||
|
if (!req.text) {
|
||||||
|
throw new Error("text is required");
|
||||||
|
}
|
||||||
|
if (!req.userId) {
|
||||||
|
throw new Error("userId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = req.text.split("\n");
|
||||||
|
|
||||||
|
const list = [];
|
||||||
|
for (const item of rows) {
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const arr = item.trim().split(":");
|
||||||
|
if (arr.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const domain = arr[0];
|
||||||
|
let port = 443;
|
||||||
|
let name = domain;
|
||||||
|
if (arr.length > 1) {
|
||||||
|
try {
|
||||||
|
port = parseInt(arr[1] || "443");
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`${item}格式错误`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (arr.length > 2) {
|
||||||
|
name = arr[2] || domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.push({
|
||||||
|
domain,
|
||||||
|
name,
|
||||||
|
httpsPort: port,
|
||||||
|
userId: req.userId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchAdd = async (list: any[]) => {
|
||||||
|
for (const item of list) {
|
||||||
|
await this.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// await this.checkAllByUsers(req.userId);
|
||||||
|
};
|
||||||
|
await batchAdd(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||||
const finished = res.filter(item=>{
|
const finished = res.filter(item=>{
|
||||||
return item!=null
|
return item!=null
|
||||||
})
|
})
|
||||||
if (finished.length > 0) {
|
if (onFinish) {
|
||||||
onFinish && onFinish(finished)
|
onFinish && onFinish(finished)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -232,4 +232,50 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
|
||||||
ipCount:count
|
ipCount:count
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async doImport(req: { text: string; userId:number, siteId:number }) {
|
||||||
|
if (!req.text) {
|
||||||
|
throw new Error("text is required");
|
||||||
|
}
|
||||||
|
if (!req.siteId) {
|
||||||
|
throw new Error("siteId is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteEntity = await this.siteInfoRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: req.siteId,
|
||||||
|
userId:req.userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!siteEntity) {
|
||||||
|
throw new Error(`站点${req.siteId}不存在`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = siteEntity.userId;
|
||||||
|
|
||||||
|
const rows = req.text.split("\n");
|
||||||
|
|
||||||
|
const list = [];
|
||||||
|
for (const item of rows) {
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.push({
|
||||||
|
ipAddress:item,
|
||||||
|
userId: userId,
|
||||||
|
siteId: req.siteId,
|
||||||
|
from: "import",
|
||||||
|
disabled:false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchAdd = async (list: any[]) => {
|
||||||
|
for (const item of list) {
|
||||||
|
await this.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// await this.checkAllByUsers(req.userId);
|
||||||
|
};
|
||||||
|
await batchAdd(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue