From 7bc245eb455671de9dc5b49b6fba2e3741d1f696 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Fri, 13 Oct 2023 00:55:41 +0800 Subject: [PATCH] feat: add monitor data metrics in monitor info --- .../components/monitor/MonitorHealthBar.tsx | 40 +++++---- src/client/components/monitor/MonitorInfo.tsx | 78 +++++++++++++++- src/client/hooks/useWatch.ts | 12 +++ src/server/trpc/routers/monitor.ts | 90 ++++++++++++++++++- 4 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 src/client/hooks/useWatch.ts diff --git a/src/client/components/monitor/MonitorHealthBar.tsx b/src/client/components/monitor/MonitorHealthBar.tsx index 4b5b92f..84e18e2 100644 --- a/src/client/components/monitor/MonitorHealthBar.tsx +++ b/src/client/components/monitor/MonitorHealthBar.tsx @@ -5,12 +5,14 @@ import { HealthBar, HealthBarBeat, HealthBarProps } from '../HealthBar'; import dayjs from 'dayjs'; import { trpc } from '../../api/trpc'; import { useCurrentWorkspaceId } from '../../store/user'; +import { useWatch } from '../../hooks/useWatch'; interface MonitorHealthBarProps { monitorId: string; count?: number; size?: HealthBarProps['size']; showCurrentStatus?: boolean; + onBeatsItemUpdate?: (items: { value: number; createdAt: string }[]) => void; } export const MonitorHealthBar: React.FC = React.memo( (props) => { @@ -38,28 +40,34 @@ export const MonitorHealthBar: React.FC = React.memo( ); }, [newDataList, recent, count]); - const beats = items.map((item): HealthBarBeat => { - if (!item) { - return { - status: 'none', - }; - } + const beats = useMemo(() => { + return items.map((item): HealthBarBeat => { + if (!item) { + return { + status: 'none', + }; + } - const title = `${dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} | ${ - item.value - }ms`; + const title = `${dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} | ${ + item.value + }ms`; + + if (item.value < 0) { + return { + title, + status: 'error', + }; + } - if (item.value < 0) { return { title, - status: 'error', + status: 'health', }; - } + }); + }, [items]); - return { - title, - status: 'health', - }; + useWatch([items], () => { + props.onBeatsItemUpdate?.(items); }); return ( diff --git a/src/client/components/monitor/MonitorInfo.tsx b/src/client/components/monitor/MonitorInfo.tsx index 4f23e92..5094dd3 100644 --- a/src/client/components/monitor/MonitorInfo.tsx +++ b/src/client/components/monitor/MonitorInfo.tsx @@ -10,7 +10,8 @@ import { MonitorInfo as MonitorInfoType } from '../../../types'; import { Area, AreaConfig } from '@ant-design/charts'; import { MonitorHealthBar } from './MonitorHealthBar'; import { useSocketSubscribeList } from '../../api/socketio'; -import { uniqBy } from 'lodash'; +import { last, uniqBy } from 'lodash-es'; +import { ErrorTip } from '../ErrorTip'; interface MonitorInfoProps { monitorId: string; @@ -18,6 +19,7 @@ interface MonitorInfoProps { export const MonitorInfo: React.FC = React.memo((props) => { const workspaceId = useCurrentWorkspaceId(); const { monitorId } = props; + const [currectResponse, setCurrentResponse] = useState(0); const { data: monitorInfo, isLoading } = trpc.monitor.get.useQuery({ workspaceId, @@ -59,6 +61,16 @@ export const MonitorInfo: React.FC = React.memo((props) => { count={40} size="large" showCurrentStatus={true} + onBeatsItemUpdate={(items) => { + setCurrentResponse(last(items)?.value ?? 0); + }} + /> + + + + @@ -71,6 +83,70 @@ export const MonitorInfo: React.FC = React.memo((props) => { }); MonitorInfo.displayName = 'MonitorInfo'; +export const MonitorDataMetrics: React.FC<{ + monitorId: string; + currectResponse: number; +}> = React.memo((props) => { + const workspaceId = useCurrentWorkspaceId(); + const { monitorId, currectResponse } = props; + const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({ + workspaceId, + monitorId, + }); + + if (isLoading) { + return ; + } + + if (!data) { + return ; + } + + return ( +
+
+
Response
+
(Current)
+
{currectResponse} ms
+
+
+
Avg. Response
+
(24 hour)
+
{parseFloat(data.recent1DayAvg.toFixed(0))} ms
+
+
+
Uptime
+
(24 hour)
+
+ {parseFloat( + ( + (data.recent1DayOnlineCount / + (data.recent1DayOnlineCount + data.recent1DayOfflineCount)) * + 100 + ).toFixed(2) + )} + % +
+
+
+
Uptime
+
(30 days)
+
+ {parseFloat( + ( + (data.recent30DayOnlineCount / + (data.recent30DayOnlineCount + data.recent30DayOfflineCount)) * + 100 + ).toFixed(2) + )} + % +
+
+
+ ); +}); +MonitorDataMetrics.displayName = 'MonitorDataMetrics'; + const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo( (props) => { const workspaceId = useCurrentWorkspaceId(); diff --git a/src/client/hooks/useWatch.ts b/src/client/hooks/useWatch.ts new file mode 100644 index 0000000..0cadf90 --- /dev/null +++ b/src/client/hooks/useWatch.ts @@ -0,0 +1,12 @@ +import { DependencyList, useLayoutEffect } from 'react'; +import { useEvent } from './useEvent'; + +/** + * 监听变更并触发回调 + */ +export function useWatch(deps: DependencyList, cb: () => void) { + const memoizedFn = useEvent(cb); + useLayoutEffect(() => { + memoizedFn(); + }, deps); +} diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts index 64553b0..3cc3b53 100644 --- a/src/server/trpc/routers/monitor.ts +++ b/src/server/trpc/routers/monitor.ts @@ -3,6 +3,7 @@ import { prisma } from '../../model/_client'; import { z } from 'zod'; import { monitorManager } from '../../model/monitor'; import { MonitorInfo } from '../../../types'; +import dayjs from 'dayjs'; export const monitorRouter = router({ all: workspaceProcedure.query(async ({ input }) => { @@ -58,7 +59,7 @@ export const monitorRouter = router({ return monitor; }), - data: workspaceOwnerProcedure + data: workspaceProcedure .input( z.object({ monitorId: z.string().cuid2(), @@ -86,7 +87,7 @@ export const monitorRouter = router({ }, }); }), - recentData: workspaceOwnerProcedure + recentData: workspaceProcedure .input( z.object({ monitorId: z.string().cuid2(), @@ -107,4 +108,89 @@ export const monitorRouter = router({ }, }); }), + dataMetrics: workspaceProcedure + .input( + z.object({ + monitorId: z.string().cuid2(), + }) + ) + .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, + }; + }), });