836 lines
18 KiB
TypeScript
Raw Normal View History

import {
OpenApiMetaInfo,
2023-12-16 00:33:36 +08:00
publicProcedure,
router,
workspaceAdminProcedure,
workspaceProcedure,
} from '../trpc.js';
import { prisma } from '../../model/_client.js';
2023-09-29 17:12:06 +08:00
import { z } from 'zod';
import {
getMonitorData,
getMonitorPublicInfos,
getMonitorRecentData,
getMonitorSummaryWithDay,
monitorManager,
} from '../../model/monitor/index.js';
import dayjs from 'dayjs';
import {
monitorEventSchema,
monitorInfoWithNotificationIdSchema,
monitorStatusSchema,
} from '../../model/_schema/index.js';
import { OPENAPI_TAG } from '../../utils/const.js';
import { OpenApiMeta } from 'trpc-openapi';
import {
MonitorModelSchema,
MonitorStatusPageModelSchema,
} from '../../prisma/zod/index.js';
import { runCodeInVM } from '../../model/monitor/provider/custom.js';
import { createAuditLog } from '../../model/auditLog.js';
import {
MonitorInfoWithNotificationIds,
2024-07-24 00:34:47 +08:00
monitorPublicInfoSchema,
} from '../../model/_schema/monitor.js';
import { monitorPageManager } from '../../model/monitor/page/manager.js';
2023-09-29 17:12:06 +08:00
export const monitorRouter = router({
all: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/all',
})
)
.output(z.array(monitorInfoWithNotificationIdSchema))
.query(async ({ input }) => {
const workspaceId = input.workspaceId;
const monitors = await prisma.monitor.findMany({
where: {
workspaceId,
},
include: {
notifications: {
select: {
id: true,
},
2023-10-20 00:48:56 +08:00
},
},
2023-12-29 21:15:32 +08:00
orderBy: {
updatedAt: 'desc',
},
});
2023-09-29 17:12:06 +08:00
return monitors as MonitorInfoWithNotificationIds[];
}),
2023-09-29 17:42:36 +08:00
get: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/{monitorId}',
})
)
2023-09-29 17:12:06 +08:00
.input(
z.object({
monitorId: z.string().cuid2(),
2023-09-29 17:42:36 +08:00
})
)
.output(monitorInfoWithNotificationIdSchema.nullable())
2023-09-29 17:42:36 +08:00
.query(async ({ input }) => {
const { monitorId, workspaceId } = input;
2023-09-29 17:42:36 +08:00
const monitor = await prisma.monitor.findUnique({
where: {
id: monitorId,
2023-09-29 17:42:36 +08:00
workspaceId,
},
2023-10-20 00:48:56 +08:00
include: {
notifications: {
select: {
id: true,
},
},
},
2023-09-29 17:42:36 +08:00
});
return monitor;
2023-09-29 17:42:36 +08:00
}),
getPublicInfo: publicProcedure
.meta({
openapi: {
tags: [OPENAPI_TAG.MONITOR],
protect: false,
method: 'POST',
path: '/monitor/getPublicInfo',
},
})
.input(
z.object({
monitorIds: z.array(z.string()),
})
)
2024-07-24 00:34:47 +08:00
.output(z.array(monitorPublicInfoSchema))
.query(async ({ input }) => {
const { monitorIds } = input;
return getMonitorPublicInfos(monitorIds);
}),
upsert: workspaceAdminProcedure
.meta(
buildMonitorOpenapi({
method: 'POST',
path: '/upsert',
})
)
2023-09-29 17:42:36 +08:00
.input(
z.object({
id: z.string().cuid2().optional(),
2023-09-29 17:12:06 +08:00
name: z.string(),
type: z.string(),
active: z.boolean().default(true),
interval: z.number().int().min(5).max(10000).default(20),
maxRetries: z.number().int().min(0).max(10).default(0),
2024-05-02 16:31:38 +08:00
trendingMode: z.boolean().default(false),
2023-10-20 00:48:56 +08:00
notificationIds: z.array(z.string()).default([]),
2023-09-29 17:12:06 +08:00
payload: z.object({}).passthrough(),
})
)
.output(MonitorModelSchema)
2023-09-29 17:12:06 +08:00
.mutation(async ({ input }) => {
2023-10-20 00:48:56 +08:00
const {
id,
workspaceId,
name,
type,
active,
interval,
maxRetries,
2024-05-02 16:31:38 +08:00
trendingMode,
2023-10-20 00:48:56 +08:00
notificationIds,
payload,
} = input;
2023-10-05 01:56:33 +08:00
const monitor = await monitorManager.upsert({
2023-09-29 17:42:36 +08:00
id,
2023-09-29 17:12:06 +08:00
workspaceId,
name,
type,
active,
interval,
maxRetries,
2024-05-02 16:31:38 +08:00
trendingMode,
2023-10-20 00:48:56 +08:00
notificationIds,
2023-09-29 17:12:06 +08:00
payload,
2023-10-05 01:56:33 +08:00
});
2023-09-29 17:12:06 +08:00
2023-10-05 01:56:33 +08:00
return monitor;
2023-09-29 17:12:06 +08:00
}),
delete: workspaceAdminProcedure
2023-12-09 00:39:36 +08:00
.meta(
buildMonitorOpenapi({
method: 'DELETE',
path: '/{monitorId}',
})
)
.input(
z.object({
monitorId: z.string().cuid2(),
})
)
.output(MonitorModelSchema)
2023-12-09 00:39:36 +08:00
.mutation(async ({ input }) => {
const { workspaceId, monitorId } = input;
return monitorManager.delete(workspaceId, monitorId);
}),
testCustomScript: workspaceAdminProcedure
2024-01-01 17:27:29 +08:00
.input(
z.object({
code: z.string(),
})
)
.output(
z.object({
logger: z.array(z.array(z.any())),
result: z.number(),
usage: z.number(),
})
)
.mutation(async ({ input }) => {
const res = await runCodeInVM(input.code);
return {
logger: res.logger,
result: res.result ?? -1,
usage: res.usage,
};
2024-01-01 17:27:29 +08:00
}),
data: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/{monitorId}/data',
})
)
2023-10-10 00:09:39 +08:00
.input(
z.object({
monitorId: z.string().cuid2(),
2023-10-10 00:09:39 +08:00
startAt: z.number(),
endAt: z.number(),
})
)
.output(
z.array(
z.object({
value: z.number(),
createdAt: z.date(),
})
)
)
2023-10-10 00:09:39 +08:00
.query(async ({ input }) => {
const { monitorId, workspaceId, startAt, endAt } = input;
return getMonitorData(
workspaceId,
monitorId,
new Date(startAt),
new Date(endAt)
);
2023-10-10 00:09:39 +08:00
}),
changeActive: workspaceAdminProcedure
2023-10-28 16:01:11 +08:00
.meta(
buildMonitorOpenapi({
method: 'PATCH',
path: '/{monitorId}/changeActive',
})
)
.input(
z.object({
monitorId: z.string(),
active: z.boolean(),
})
)
.output(MonitorModelSchema)
2024-01-22 13:23:30 +08:00
.mutation(async ({ input, ctx }) => {
2023-10-28 16:01:11 +08:00
const { workspaceId, monitorId, active } = input;
2024-01-22 13:23:30 +08:00
const user = ctx.user;
2023-10-28 16:01:11 +08:00
const monitor = await prisma.monitor.update({
where: {
workspaceId,
id: monitorId,
},
data: {
active,
},
include: {
notifications: true,
},
2023-10-28 16:01:11 +08:00
});
let runner = monitorManager.getRunner(monitorId);
if (!runner) {
runner = monitorManager.createRunner(monitor);
}
if (active === true) {
runner.startMonitor();
runner.createEvent(
'UP',
`Monitor [${monitor.name}] has been manual start`
);
2024-01-22 13:23:30 +08:00
createAuditLog({
workspaceId: workspaceId,
relatedId: monitorId,
relatedType: 'Monitor',
content: `Monitor(id: ${monitor.id}) manual start by ${String(
user.username
)}(${String(user.id)})`,
});
} else {
runner.stopMonitor();
runner.createEvent(
'DOWN',
`Monitor [${monitor.name}] has been manual stop`
);
2024-01-22 13:23:30 +08:00
createAuditLog({
workspaceId: workspaceId,
relatedId: monitorId,
relatedType: 'Monitor',
content: `Monitor(id: ${monitor.id}) manual stop by ${String(
user.username
)}(${String(user.id)})`,
});
2023-10-28 16:01:11 +08:00
}
return monitor;
}),
recentData: publicProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
protect: false,
path: '/{monitorId}/recentData',
})
)
.input(
z.object({
workspaceId: z.string().cuid2(),
monitorId: z.string().cuid2(),
take: z.number(),
})
)
.output(
z.array(
z.object({
value: z.number(),
createdAt: z.date(),
})
)
)
.query(async ({ input }) => {
const { workspaceId, monitorId, take } = input;
return getMonitorRecentData(workspaceId, monitorId, take);
}),
publicSummary: publicProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
protect: false,
path: '/{monitorId}/publicSummary',
})
)
.input(
z.object({
workspaceId: z.string().cuid2(),
monitorId: z.string().cuid2(),
})
)
.output(
z.array(
z.object({
day: z.string(),
totalCount: z.number(),
upCount: z.number(),
upRate: z.number(),
})
)
)
.query(async ({ input }) => {
const { monitorId } = input;
const summary = await getMonitorSummaryWithDay(monitorId, 30);
return summary;
}),
publicData: publicProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
protect: false,
path: '/{monitorId}/publicData',
})
)
.input(
z.object({
workspaceId: z.string().cuid2(),
monitorId: z.string().cuid2(),
})
)
.output(
z.array(
z.object({
value: z.number(),
createdAt: z.date(),
})
)
)
.query(async ({ input }) => {
const { workspaceId, monitorId } = input;
return getMonitorData(
workspaceId,
monitorId,
dayjs().subtract(1, 'days').toDate(),
dayjs().toDate()
);
}),
dataMetrics: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/{monitorId}/dataMetrics',
})
)
.input(
z.object({
monitorId: z.string().cuid2(),
})
)
.output(
z.object({
recent1DayAvg: z.number(),
recent1DayOnlineCount: z.number(),
recent1DayOfflineCount: z.number(),
recent30DayOnlineCount: z.number(),
recent30DayOfflineCount: z.number(),
})
)
.query(async ({ input }) => {
const { monitorId } = input;
const now = dayjs();
const [
recent1DayAvg,
recent1DayOnlineCount,
recent1DayOfflineCount,
recent30DayOnlineCount,
recent30DayOfflineCount,
] = await Promise.all([
prisma.monitorData
.aggregate({
_avg: {
value: true,
},
where: {
monitorId,
createdAt: {
lte: now.toDate(),
gte: now.subtract(1, 'day').toDate(),
},
},
})
.then((res) => res._avg.value ?? -1),
prisma.monitorData.count({
where: {
monitorId,
createdAt: {
lte: now.toDate(),
gte: now.subtract(1, 'day').toDate(),
},
value: {
gte: 0,
},
},
}),
prisma.monitorData.count({
where: {
monitorId,
createdAt: {
lte: now.toDate(),
gte: now.subtract(1, 'day').toDate(),
},
value: -1,
},
}),
prisma.monitorData.count({
where: {
monitorId,
createdAt: {
lte: now.toDate(),
gte: now.subtract(30, 'day').toDate(),
},
value: {
gte: 0,
},
},
}),
prisma.monitorData.count({
where: {
monitorId,
createdAt: {
lte: now.toDate(),
gte: now.subtract(30, 'day').toDate(),
},
value: -1,
},
}),
]);
return {
recent1DayAvg,
recent1DayOnlineCount,
recent1DayOfflineCount,
recent30DayOnlineCount,
recent30DayOfflineCount,
};
}),
2023-10-17 20:26:28 +08:00
events: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/events',
})
)
2023-10-17 20:26:28 +08:00
.input(
z.object({
monitorId: z.string().cuid2().optional(),
limit: z.number().default(20),
2023-10-17 20:26:28 +08:00
})
)
.output(z.array(monitorEventSchema))
2023-10-17 20:26:28 +08:00
.query(async ({ input }) => {
const { workspaceId, monitorId, limit } = input;
2023-10-17 20:26:28 +08:00
const list = await prisma.monitorEvent.findMany({
where: {
monitorId,
monitor: {
workspaceId: workspaceId,
},
2023-10-17 20:26:28 +08:00
},
orderBy: {
createdAt: 'desc',
},
take: limit,
2023-10-17 20:26:28 +08:00
});
return list;
}),
clearEvents: workspaceAdminProcedure
2023-12-29 21:15:32 +08:00
.meta(
buildMonitorOpenapi({
method: 'DELETE',
path: '/clearEvents',
})
)
.input(
z.object({
monitorId: z.string().cuid2(),
})
)
.output(z.number())
.mutation(async ({ input }) => {
const { workspaceId, monitorId } = input;
const { count } = await prisma.monitorEvent.deleteMany({
where: {
monitor: {
id: monitorId,
workspaceId,
},
},
});
return count;
}),
clearData: workspaceAdminProcedure
2023-12-29 21:15:32 +08:00
.meta(
buildMonitorOpenapi({
method: 'DELETE',
path: '/clearData',
})
)
.input(
z.object({
monitorId: z.string().cuid2(),
})
)
.output(z.number())
.mutation(async ({ input }) => {
const { workspaceId, monitorId } = input;
const { count } = await prisma.monitorData.deleteMany({
where: {
monitor: {
id: monitorId,
workspaceId,
},
},
});
return count;
}),
getStatus: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/{monitorId}/status',
})
)
.input(
z.object({
monitorId: z.string().cuid2(),
statusName: z.string(),
})
)
.output(monitorStatusSchema.nullable())
.query(async ({ input }) => {
const { monitorId, statusName } = input;
return prisma.monitorStatus.findUnique({
where: {
monitorId_statusName: {
monitorId,
statusName,
},
},
});
}),
2023-12-13 19:46:32 +08:00
getAllPages: workspaceProcedure
.meta(
buildMonitorOpenapi({
method: 'GET',
path: '/getAllPages',
})
)
.output(z.array(MonitorStatusPageModelSchema))
.query(({ input }) => {
const { workspaceId } = input;
return prisma.monitorStatusPage.findMany({
where: {
workspaceId,
},
2024-03-30 00:08:02 +08:00
orderBy: {
updatedAt: 'desc',
},
2023-12-13 19:46:32 +08:00
});
}),
2023-12-16 00:33:36 +08:00
getPageInfo: publicProcedure
.meta({
openapi: {
tags: [OPENAPI_TAG.MONITOR],
method: 'GET',
path: '/monitor/getPageInfo',
},
})
.input(
z.object({
slug: z.string(),
})
)
.output(MonitorStatusPageModelSchema.nullable())
.query(({ input }) => {
const { slug } = input;
return prisma.monitorStatusPage.findUnique({
where: {
slug,
},
});
}),
createPage: workspaceAdminProcedure
2023-12-13 18:21:04 +08:00
.meta(
buildMonitorOpenapi({
method: 'POST',
path: '/createStatusPage',
})
)
.input(
z
.object({
slug: z.string(),
title: z.string(),
})
.merge(
MonitorStatusPageModelSchema.pick({
description: true,
2024-09-17 19:08:58 +08:00
body: true,
monitorList: true,
domain: true,
}).partial()
)
2023-12-13 18:21:04 +08:00
)
.output(MonitorStatusPageModelSchema)
.mutation(async ({ input }) => {
2024-09-17 19:08:58 +08:00
const {
workspaceId,
slug,
title,
description,
body,
monitorList,
domain,
} = input;
2023-12-13 18:21:04 +08:00
const existSlugCount = await prisma.monitorStatusPage.count({
where: {
slug,
},
});
if (existSlugCount > 0) {
throw new Error('This slug has been existed');
}
if (domain && !(await monitorPageManager.checkDomain(domain))) {
throw new Error('This domain has been used');
}
const page = await prisma.monitorStatusPage.create({
2023-12-13 18:21:04 +08:00
data: {
workspaceId,
slug,
title,
description,
2024-09-17 19:08:58 +08:00
body,
monitorList,
domain: domain || null, // make sure not ''
2023-12-13 18:21:04 +08:00
},
});
if (page.domain) {
monitorPageManager.updatePageDomain(page.domain, {
workspaceId: page.workspaceId,
pageId: page.id,
slug: page.slug,
});
}
return page;
2023-12-13 18:21:04 +08:00
}),
editPage: workspaceAdminProcedure
2023-12-13 18:21:04 +08:00
.meta(
buildMonitorOpenapi({
method: 'PATCH',
path: '/updateStatusPage',
})
)
.input(
MonitorStatusPageModelSchema.pick({
id: true,
}).merge(
MonitorStatusPageModelSchema.pick({
slug: true,
title: true,
description: true,
2024-09-17 19:08:58 +08:00
body: true,
2023-12-13 18:21:04 +08:00
monitorList: true,
domain: true,
2023-12-13 18:21:04 +08:00
}).partial()
)
)
.output(MonitorStatusPageModelSchema)
.mutation(async ({ input }) => {
2024-09-17 19:08:58 +08:00
const {
id,
workspaceId,
slug,
title,
description,
body,
monitorList,
domain,
} = input;
2023-12-13 18:21:04 +08:00
if (slug) {
const existSlugCount = await prisma.monitorStatusPage.count({
where: {
slug,
id: {
not: id,
},
2023-12-13 18:21:04 +08:00
},
});
if (existSlugCount > 0) {
throw new Error('This slug has been existed');
}
}
if (domain && !(await monitorPageManager.checkDomain(domain, id))) {
throw new Error('This domain has been used by others');
}
const page = await prisma.monitorStatusPage.update({
2023-12-13 18:21:04 +08:00
where: {
id,
workspaceId,
},
data: {
slug,
title,
description,
2024-09-17 19:08:58 +08:00
body,
2023-12-13 18:21:04 +08:00
monitorList,
domain: domain || null,
2023-12-13 18:21:04 +08:00
},
});
if (page.domain) {
monitorPageManager.updatePageDomain(page.domain, {
workspaceId: page.workspaceId,
pageId: page.id,
slug: page.slug,
});
}
return page;
2023-12-13 18:21:04 +08:00
}),
deletePage: workspaceAdminProcedure
2024-01-13 01:10:12 +08:00
.meta(
buildMonitorOpenapi({
method: 'DELETE',
path: '/deleteStatusPage',
})
)
.input(
MonitorStatusPageModelSchema.pick({
id: true,
})
)
.output(MonitorStatusPageModelSchema)
.mutation(async ({ input }) => {
const { id, workspaceId } = input;
const res = await prisma.monitorStatusPage.delete({
where: {
id,
workspaceId,
},
});
return res;
}),
2023-09-29 17:12:06 +08:00
});
function buildMonitorOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
return {
openapi: {
tags: [OPENAPI_TAG.MONITOR],
protect: true,
...meta,
path: `/workspace/{workspaceId}/monitor${meta.path}`,
},
};
}