feat: add cronjob for send https certificate expired notification

This commit is contained in:
moonrailgun 2024-05-03 00:30:23 +08:00
parent 18f3073e94
commit 7b95c55a70
12 changed files with 109 additions and 9 deletions

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { TipIcon } from '../TipIcon';
interface MonitorStatsBlockProps { interface MonitorStatsBlockProps {
title: string; title: string;
tooltip?: string;
desc: string; desc: string;
text: string; text: string;
} }
@ -9,7 +11,12 @@ export const MonitorStatsBlock: React.FC<MonitorStatsBlockProps> = React.memo(
(props) => { (props) => {
return ( return (
<div> <div>
<div className="mb-0.5 font-bold">{props.title}</div> <div className="mb-0.5 font-bold">
{props.title}
{props.tooltip && (
<TipIcon className="ml-1" content={props.tooltip} />
)}
</div>
<div className="text-gray-500">{props.desc}</div> <div className="text-gray-500">{props.desc}</div>
<div>{props.text}</div> <div>{props.text}</div>
</div> </div>

View File

@ -30,9 +30,13 @@ const MonitorHttp: React.FC = React.memo(() => {
}, },
}, },
]} ]}
tooltip={t(
'For HTTPS monitoring, if any notification method is assigned, notifications will be sent at 1, 3, 7 and 14 days before expiration.'
)}
> >
<Input placeholder="https://example.com" /> <Input placeholder="https://example.com" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="Method" label="Method"
name={['payload', 'method']} name={['payload', 'method']}
@ -161,6 +165,9 @@ export const MonitorHttpOverview: MonitorOverviewComponent = React.memo(
return ( return (
<MonitorStatsBlock <MonitorStatsBlock
title={t('Cert Exp.')} title={t('Cert Exp.')}
tooltip={t(
'For HTTPS monitoring, if any notification method is assigned, notifications will be sent at 1, 3, 7 and 14 days before expiration.'
)}
desc={dayjs(payload.certInfo?.validTo).format('YYYY-MM-DD')} desc={dayjs(payload.certInfo?.validTo).format('YYYY-MM-DD')}
text={t('{{num}} days', { text={t('{{num}} days', {
num: payload.certInfo?.daysRemaining, num: payload.certInfo?.daysRemaining,

View File

@ -5,7 +5,9 @@ const config = {
namespaces: ['translation'], namespaces: ['translation'],
translator: { translator: {
type: 'openai', type: 'openai',
model: 'gpt-4', openai: {
modelName: 'gpt-4',
},
}, },
scanner: { scanner: {
autoImport: false, autoImport: false,

View File

@ -87,6 +87,7 @@
"k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?", "k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?",
"k5a839f71": "Betriebszeit", "k5a839f71": "Betriebszeit",
"k5eb87a8b": "Start", "k5eb87a8b": "Start",
"k5ec0de4": "Für die HTTPS-Überwachung werden bei Zuweisung einer Benachrichtigungsmethode Benachrichtigungen 1, 3, 7 und 14 Tage vor Ablauf gesendet.",
"k5ecf04b0": "Ansicht", "k5ecf04b0": "Ansicht",
"k6067f0ff": "TLS/SSL-Fehler ignorieren", "k6067f0ff": "TLS/SSL-Fehler ignorieren",
"k621317b5": "Neue Seite", "k621317b5": "Neue Seite",

View File

@ -87,6 +87,7 @@
"k593cf342": "Did you sure delete this monitor?", "k593cf342": "Did you sure delete this monitor?",
"k5a839f71": "Uptime", "k5a839f71": "Uptime",
"k5eb87a8b": "Start", "k5eb87a8b": "Start",
"k5ec0de4": "For HTTPS monitoring, if any notification method is assigned, notifications will be sent at 1, 3, 7 and 14 days before expiration.",
"k5ecf04b0": "View", "k5ecf04b0": "View",
"k6067f0ff": "Ignore TLS/SSL error", "k6067f0ff": "Ignore TLS/SSL error",
"k621317b5": "New page", "k621317b5": "New page",

View File

@ -87,6 +87,7 @@
"k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?", "k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?",
"k5a839f71": "Disponibilité", "k5a839f71": "Disponibilité",
"k5eb87a8b": "Démarrer", "k5eb87a8b": "Démarrer",
"k5ec0de4": "Pour la surveillance HTTPS, si une méthode de notification est assignée, des notifications seront envoyées à 1, 3, 7 et 14 jours avant l'expiration.",
"k5ecf04b0": "Vue", "k5ecf04b0": "Vue",
"k6067f0ff": "Ignorer l'erreur TLS/SSL", "k6067f0ff": "Ignorer l'erreur TLS/SSL",
"k621317b5": "Nouvelle page", "k621317b5": "Nouvelle page",

View File

@ -87,12 +87,13 @@
"k593cf342": "このモニターを削除してもよろしいですか?", "k593cf342": "このモニターを削除してもよろしいですか?",
"k5a839f71": "アップタイム", "k5a839f71": "アップタイム",
"k5eb87a8b": "開始", "k5eb87a8b": "開始",
"k5ec0de4": "HTTPSモニタリングの場合、通知方法が割り当てられている場合、有効期限の1、3、7、14日前に通知が送信されます。",
"k5ecf04b0": "ビュー", "k5ecf04b0": "ビュー",
"k6067f0ff": "TLS/SSLエラーを無視", "k6067f0ff": "TLS/SSLエラーを無視",
"k621317b5": "新しいページ", "k621317b5": "新しいページ",
"k62e19375": "最終更新:{{date}}", "k62e19375": "最終更新:{{date}}",
"k646a3a80": "{{monitorName}}のメトリック", "k646a3a80": "{{monitorName}}のメトリック",
"k659b065": "For example: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000", "k659b065": "例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
"k67c5a895": "昨日", "k67c5a895": "昨日",
"k683be220": "実行", "k683be220": "実行",
"k691b7170": "停止済み", "k691b7170": "停止済み",

View File

@ -87,6 +87,7 @@
"k593cf342": "Czy na pewno chcesz usunąć ten monitor?", "k593cf342": "Czy na pewno chcesz usunąć ten monitor?",
"k5a839f71": "Czas działania", "k5a839f71": "Czas działania",
"k5eb87a8b": "Wznów", "k5eb87a8b": "Wznów",
"k5ec0de4": "Dla monitorowania HTTPS, jeśli przypisana jest jakakolwiek metoda powiadamiania, powiadomienia zostaną wysłane 1, 3, 7 i 14 dni przed wygaśnięciem.",
"k5ecf04b0": "Widok", "k5ecf04b0": "Widok",
"k6067f0ff": "Ignoruj błąd TLS/SSL", "k6067f0ff": "Ignoruj błąd TLS/SSL",
"k621317b5": "Nowa strona", "k621317b5": "Nowa strona",

View File

@ -87,6 +87,7 @@
"k593cf342": "De certeza que eliminou este monitor?", "k593cf342": "De certeza que eliminou este monitor?",
"k5a839f71": "Tempo de atividade", "k5a839f71": "Tempo de atividade",
"k5eb87a8b": "Início", "k5eb87a8b": "Início",
"k5ec0de4": "Para monitoramento HTTPS, se algum método de notificação estiver atribuído, notificações serão enviadas com 1, 3, 7 e 14 dias antes do vencimento.",
"k5ecf04b0": "Ver", "k5ecf04b0": "Ver",
"k6067f0ff": "Ignorar erro TLS/SSL", "k6067f0ff": "Ignorar erro TLS/SSL",
"k621317b5": "Nova página", "k621317b5": "Nova página",

View File

@ -87,6 +87,7 @@
"k593cf342": "Вы уверены, что хотите удалить этот монитор?", "k593cf342": "Вы уверены, что хотите удалить этот монитор?",
"k5a839f71": "Время работы", "k5a839f71": "Время работы",
"k5eb87a8b": "Старт", "k5eb87a8b": "Старт",
"k5ec0de4": "Для мониторинга HTTPS, если назначен любой метод уведомления, уведомления будут отправлены за 1, 3, 7 и 14 дней до истечения срока действия.",
"k5ecf04b0": "Просмотр", "k5ecf04b0": "Просмотр",
"k6067f0ff": "Игнорировать ошибку TLS/SSL", "k6067f0ff": "Игнорировать ошибку TLS/SSL",
"k621317b5": "Новая страница", "k621317b5": "Новая страница",

View File

@ -87,6 +87,7 @@
"k593cf342": "您确定要删除这个监控器吗?", "k593cf342": "您确定要删除这个监控器吗?",
"k5a839f71": "正常运行时间", "k5a839f71": "正常运行时间",
"k5eb87a8b": "开始", "k5eb87a8b": "开始",
"k5ec0de4": "对于 HTTPS 监控,如果分配了任何通知方法,则将在到期前 1、3、7 和 14 天发送通知。",
"k5ecf04b0": "查看", "k5ecf04b0": "查看",
"k6067f0ff": "忽略 TLS/SSL 错误", "k6067f0ff": "忽略 TLS/SSL 错误",
"k621317b5": "新页面", "k621317b5": "新页面",

View File

@ -4,6 +4,9 @@ import { prisma } from '../model/_client';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { env } from '../utils/env'; import { env } from '../utils/env';
import { sendNotification } from '../model/notification';
import { token } from '../model/notification/token';
import _ from 'lodash';
type WebsiteEventCountSqlReturn = { type WebsiteEventCountSqlReturn = {
workspace_id: string; workspace_id: string;
@ -15,8 +18,11 @@ export function initCronjob() {
logger.info('Start daily cronjob'); logger.info('Start daily cronjob');
try { try {
await statDailyUsage(); await Promise.all([
await clearMonitorDataDaily(); statDailyUsage().catch(logger.error),
clearMonitorDataDaily().catch(logger.error),
dailyHTTPCertCheckNotify().catch(logger.error),
]);
logger.info('Daily cronjob completed'); logger.info('Daily cronjob completed');
} catch (err) { } catch (err) {
@ -30,7 +36,7 @@ export function initCronjob() {
} }
async function statDailyUsage() { async function statDailyUsage() {
logger.info('Statistics Workspace Daily Usage Start'); logger.info('[statDailyUsage] Statistics Workspace Daily Usage Start');
const start = dayjs().subtract(1, 'day').startOf('day').toDate(); const start = dayjs().subtract(1, 'day').startOf('day').toDate();
const end = dayjs().startOf('day').toDate(); const end = dayjs().startOf('day').toDate();
const date = dayjs().subtract(1, 'day').toDate(); const date = dayjs().subtract(1, 'day').toDate();
@ -133,7 +139,7 @@ async function statDailyUsage() {
skipDuplicates: true, skipDuplicates: true,
}); });
logger.info('Statistics Workspace Daily Usage Completed'); logger.info('[statDailyUsage] Statistics Workspace Daily Usage Completed');
} }
/** /**
@ -145,7 +151,10 @@ async function clearMonitorDataDaily() {
} }
const date = dayjs().subtract(2, 'weeks').toDate(); const date = dayjs().subtract(2, 'weeks').toDate();
logger.info('Start clear monitor data before:', date.toISOString()); logger.info(
'[clearMonitorDataDaily] Start clear monitor data before:',
date.toISOString()
);
const res = await prisma.monitorData.deleteMany({ const res = await prisma.monitorData.deleteMany({
where: { where: {
createdAt: { createdAt: {
@ -154,5 +163,72 @@ async function clearMonitorDataDaily() {
}, },
}); });
logger.info('Clear monitor completed, delete record:', res.count); logger.info(
'[clearMonitorDataDaily] Clear monitor completed, delete record:',
res.count
);
}
/**
* Https notify
*/
async function dailyHTTPCertCheckNotify() {
logger.info('[dailyHTTPCertCheckNotify] Start run dailyHTTPCertCheckNotify');
const start = Date.now();
const res = await prisma.$queryRaw<
{ monitorId: string; daysRemaining: number }[]
>`
SELECT "monitorId", (payload -> 'certInfo' ->> 'daysRemaining')::int as "daysRemaining"
FROM "MonitorStatus"
WHERE "statusName" = 'tls'
AND "updatedAt" >= now() - interval '1 day'
AND (payload -> 'certInfo' ->> 'daysRemaining')::int in (1, 3, 7, 14);
`;
logger.info(`[dailyHTTPCertCheckNotify] find ${res.length} records`);
const monitors = await prisma.monitor.findMany({
where: {
id: {
in: res.map((r) => r.monitorId),
},
},
include: {
notifications: true,
},
});
let sendCount = 0;
for (const m of monitors) {
if (m.active === false) {
continue;
}
for (const n of m.notifications) {
const daysRemaining = res.find(
(item) => item.monitorId === m.id
)?.daysRemaining;
if (!daysRemaining) {
continue;
}
const content = `[${m.name}][${_.get(m.payload, 'url')}] Certificate will be expired in ${daysRemaining} days`;
try {
await sendNotification(n, content, [token.text(content)]).catch(
logger.error
);
sendCount++;
} catch (err) {
logger.error(err);
}
}
}
logger.info(
`[dailyHTTPCertCheckNotify] run completed, send ${sendCount} notifications, time usage: ${Date.now() - start}ms`
);
} }