From f53fba35f43487a8be755adbdce160b808be1b89 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Tue, 10 Oct 2023 19:47:05 +0800 Subject: [PATCH] feat: add MonitorHealthBar and data subscribe --- src/client/components/HealthBar.tsx | 25 +++++- .../components/monitor/MonitorHealthBar.tsx | 90 +++++++++++++++++++ src/client/components/monitor/MonitorInfo.tsx | 32 +++++-- src/client/components/monitor/MonitorList.tsx | 7 +- src/server/model/monitor/index.ts | 12 ++- src/server/trpc/routers/monitor.ts | 23 ++++- src/server/ws/shared.ts | 15 ++-- src/types/index.ts | 2 + 8 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 src/client/components/monitor/MonitorHealthBar.tsx diff --git a/src/client/components/HealthBar.tsx b/src/client/components/HealthBar.tsx index 91ab7d5..48a2422 100644 --- a/src/client/components/HealthBar.tsx +++ b/src/client/components/HealthBar.tsx @@ -3,18 +3,35 @@ import React from 'react'; type HealthStatus = 'health' | 'error' | 'warning' | 'none'; -interface HealthBarProps { - beats: { title?: string; status: HealthStatus }[]; +export interface HealthBarBeat { + title?: string; + status: HealthStatus; +} + +export interface HealthBarProps { + size?: 'small' | 'large'; + beats: HealthBarBeat[]; } export const HealthBar: React.FC = React.memo((props) => { + const size = props.size ?? 'small'; + return ( -
+
{props.beats.map((beat, i) => (
= React.memo( + (props) => { + const { monitorId, size, count = 20, showCurrentStatus = false } = props; + const workspaceId = useCurrentWorkspaceId(); + const { data: recent = [] } = trpc.monitor.recentData.useQuery({ + workspaceId, + monitorId, + take: count, + }); + const newDataList = useSocketSubscribeList('onMonitorReceiveNewData', { + filter: (data) => { + return data.monitorId === props.monitorId; + }, + }); + + const items = useMemo(() => { + return takeRight( + [ + ...Array.from({ length: count }).map(() => null), + ...recent, + ...takeRight(newDataList, count), + ], + count + ); + }, [newDataList, recent, count]); + + const beats = items.map((item): HealthBarBeat => { + if (!item) { + return { + status: 'none', + }; + } + + const title = `${dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} | ${ + item.value + }ms`; + + if (item.value < 0) { + return { + title, + status: 'error', + }; + } + + return { + title, + status: 'health', + }; + }); + + return ( +
+ + + {showCurrentStatus && ( + <> + {last(beats)?.status === 'health' ? ( +
+ UP +
+ ) : last(beats)?.status === 'error' ? ( +
+ DOWN +
+ ) : ( +
+ NONE +
+ )} + + )} +
+ ); + } +); +MonitorHealthBar.displayName = 'MonitorHealthBar'; diff --git a/src/client/components/monitor/MonitorInfo.tsx b/src/client/components/monitor/MonitorInfo.tsx index d49b7f9..a1db6d4 100644 --- a/src/client/components/monitor/MonitorInfo.tsx +++ b/src/client/components/monitor/MonitorInfo.tsx @@ -8,6 +8,7 @@ import { getMonitorLink } from '../modals/monitor/provider'; import { NotFoundTip } from '../NotFoundTip'; import { MonitorInfo as MonitorInfoType } from '../../../types'; import { Area, AreaConfig } from '@ant-design/charts'; +import { MonitorHealthBar } from './MonitorHealthBar'; interface MonitorInfoProps { monitorId: string; @@ -32,17 +33,32 @@ export const MonitorInfo: React.FC = React.memo((props) => { return (
-
{monitorInfo.name}
+
+ +
{monitorInfo.name}
-
- {monitorInfo.type} - {getMonitorLink(monitorInfo as any as MonitorInfoType)} +
+ {monitorInfo.type} + + {getMonitorLink(monitorInfo as any as MonitorInfoType)} + +
+ + +
+ Monitored for {dayjs().diff(dayjs(monitorInfo.createdAt), 'days')}{' '} + days +
-
- Monitored for {dayjs().diff(dayjs(monitorInfo.createdAt), 'days')}{' '} - days -
+ + + diff --git a/src/client/components/monitor/MonitorList.tsx b/src/client/components/monitor/MonitorList.tsx index 77b2feb..6127fe7 100644 --- a/src/client/components/monitor/MonitorList.tsx +++ b/src/client/components/monitor/MonitorList.tsx @@ -6,6 +6,7 @@ import { trpc } from '../../api/trpc'; import { useCurrentWorkspaceId } from '../../store/user'; import { HealthBar } from '../HealthBar'; import { NoWorkspaceTip } from '../NoWorkspaceTip'; +import { MonitorHealthBar } from './MonitorHealthBar'; export const MonitorList: React.FC = React.memo(() => { const currentWorkspaceId = useCurrentWorkspaceId()!; @@ -76,11 +77,7 @@ export const MonitorList: React.FC = React.memo(() => {
- ({ - status: 'health', - }))} - /> +
))} diff --git a/src/server/model/monitor/index.ts b/src/server/model/monitor/index.ts index 6fcc8b0..1414f80 100644 --- a/src/server/model/monitor/index.ts +++ b/src/server/model/monitor/index.ts @@ -1,4 +1,5 @@ import { Monitor } from '@prisma/client'; +import { createSubscribeInitializer, subscribeEventBus } from '../../ws/shared'; import { prisma } from '../_client'; import { monitorProviders } from './provider'; @@ -80,6 +81,9 @@ class MonitorManager { } } +/** + * Class which actually run monitor data collect + */ class MonitorRunner { isStopped = false; timer: NodeJS.Timeout | null = null; @@ -91,7 +95,7 @@ class MonitorRunner { */ async startMonitor() { const monitor = this.monitor; - const { type, interval } = monitor; + const { type, interval, workspaceId } = monitor; const provider = monitorProviders[type]; if (!provider) { @@ -139,18 +143,20 @@ class MonitorRunner { } // insert into data - await prisma.monitorData.create({ + const data = await prisma.monitorData.create({ data: { monitorId: monitor.id, value, }, }); + subscribeEventBus.emit('onMonitorReceiveNewData', workspaceId, data); + // Run next loop nextAction(); } - nextAction(); + run(); console.log(`Start monitor ${monitor.name}(${monitor.id})`); } diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts index 55769f2..910089d 100644 --- a/src/server/trpc/routers/monitor.ts +++ b/src/server/trpc/routers/monitor.ts @@ -18,7 +18,7 @@ export const monitorRouter = router({ get: workspaceProcedure .input( z.object({ - id: z.string(), + id: z.string().cuid2(), }) ) .query(async ({ input }) => { @@ -35,7 +35,7 @@ export const monitorRouter = router({ upsert: workspaceOwnerProcedure .input( z.object({ - id: z.string().optional(), + id: z.string().cuid2().optional(), name: z.string(), type: z.string(), active: z.boolean().default(true), @@ -61,7 +61,7 @@ export const monitorRouter = router({ data: workspaceOwnerProcedure .input( z.object({ - monitorId: z.string(), + monitorId: z.string().cuid2(), startAt: z.number(), endAt: z.number(), }) @@ -86,4 +86,21 @@ export const monitorRouter = router({ }, }); }), + recentData: workspaceOwnerProcedure + .input( + z.object({ + monitorId: z.string().cuid2(), + take: z.number(), + }) + ) + .query(async ({ input }) => { + const { monitorId, take } = input; + + return prisma.monitorData.findMany({ + where: { + monitorId, + }, + take: -take, + }); + }), }); diff --git a/src/server/ws/shared.ts b/src/server/ws/shared.ts index 3e4e632..077fec4 100644 --- a/src/server/ws/shared.ts +++ b/src/server/ws/shared.ts @@ -1,11 +1,13 @@ +import { MonitorData } from '@prisma/client'; import { EventEmitter } from 'eventemitter-strict'; import { Socket } from 'socket.io'; -import { ServerStatusInfo } from '../../types'; +import { MaybePromise, ServerStatusInfo } from '../../types'; type SubscribeEventFn = (workspaceId: string, eventData: T) => void; export interface SubscribeEventMap { onServerStatusUpdate: SubscribeEventFn>; + onMonitorReceiveNewData: SubscribeEventFn; } type SocketEventFn = ( @@ -31,9 +33,7 @@ type SubscribeInitializerFn< > = ( workspaceId: string, socket: Socket -) => - | Parameters[1] - | Promise[1]>; +) => MaybePromise[1]> | MaybePromise; const subscribeInitializerList: [ keyof SubscribeEventMap, SubscribeInitializerFn @@ -46,7 +46,7 @@ socketEventBus.on('$subscribe', (eventData, socket, callback) => { const { name } = eventData; const cursor = i++; - const fn: SubscribeEventMap[typeof name] = (workspaceId, data) => { + const fn: SubscribeEventFn = (workspaceId, data) => { if (workspaceId === '*' || _workspaceId === workspaceId) { socket.emit(`${name}#${cursor}`, data); } @@ -58,7 +58,10 @@ socketEventBus.on('$subscribe', (eventData, socket, callback) => { subscribeInitializerList.forEach(async ([_name, initializer]) => { if (_name === name) { - socket.emit(`${name}#${cursor}`, await initializer(_workspaceId, socket)); + const res = await initializer(_workspaceId, socket); + if (res) { + socket.emit(`${name}#${cursor}`, res); + } } }); diff --git a/src/types/index.ts b/src/types/index.ts index 6438088..2d783e7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,5 @@ export * from './server'; export * from './monitor'; export * from './utils'; + +export type { MaybePromise } from '@trpc/server';