From dcff57fe69273c7f9b3dd9c28e8acc9cb6e430a9 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 14 Oct 2024 00:24:16 +0800 Subject: [PATCH] feat: add daily monitor data display for public --- .../monitor/CustomizedErrorArea.tsx | 49 ++++++ .../components/monitor/MonitorDataChart.tsx | 44 ------ .../monitor/MonitorPublicDataChart.tsx | 149 ++++++++++++++++++ .../components/monitor/StatusPage/Body.tsx | 115 ++++++++------ src/client/tailwind.config.ts | 1 + src/server/model/_schema/monitor.ts | 1 + src/server/trpc/routers/monitor.ts | 32 ++++ 7 files changed, 300 insertions(+), 91 deletions(-) create mode 100644 src/client/components/monitor/CustomizedErrorArea.tsx create mode 100644 src/client/components/monitor/MonitorPublicDataChart.tsx diff --git a/src/client/components/monitor/CustomizedErrorArea.tsx b/src/client/components/monitor/CustomizedErrorArea.tsx new file mode 100644 index 0000000..023a553 --- /dev/null +++ b/src/client/components/monitor/CustomizedErrorArea.tsx @@ -0,0 +1,49 @@ +import { useTheme } from '@/hooks/useTheme'; +import { get } from 'lodash-es'; +import React from 'react'; +import { useMemo } from 'react'; +import { Rectangle } from 'recharts'; + +export const CustomizedErrorArea: React.FC = (props) => { + const { colors } = useTheme(); + const y = get(props, 'offset.top', 10); + const height = get(props, 'offset.height', 160); + const points = get(props, 'formattedGraphicalItems.0.props.points', []) as { + x: number; + y: number | null; + }[]; + + const errorArea = useMemo(() => { + const _errorArea: { x: number; width: number }[] = []; + let prevX: number | null = null; + points.forEach((item, i, arr) => { + if (i === 0 && !item.y) { + prevX = 0; + } else if (!item.y && prevX === null && arr[i - 1].y) { + prevX = arr[i - 1].x; + } else if (item.y && prevX !== null) { + _errorArea.push({ + x: prevX, + width: item.x - prevX, + }); + prevX = null; + } + }); + + return _errorArea; + }, [points]); + + return errorArea.map((area, i) => { + return ( + + ); + }); +}; +CustomizedErrorArea.displayName = 'CustomizedErrorArea'; diff --git a/src/client/components/monitor/MonitorDataChart.tsx b/src/client/components/monitor/MonitorDataChart.tsx index e744445..f99ae1c 100644 --- a/src/client/components/monitor/MonitorDataChart.tsx +++ b/src/client/components/monitor/MonitorDataChart.tsx @@ -195,47 +195,3 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo( } ); MonitorDataChart.displayName = 'MonitorDataChart'; - -const CustomizedErrorArea: React.FC = (props) => { - const { colors } = useTheme(); - const y = get(props, 'offset.top', 10); - const height = get(props, 'offset.height', 160); - const points = get(props, 'formattedGraphicalItems.0.props.points', []) as { - x: number; - y: number | null; - }[]; - - const errorArea = useMemo(() => { - const _errorArea: { x: number; width: number }[] = []; - let prevX: number | null = null; - points.forEach((item, i, arr) => { - if (i === 0 && !item.y) { - prevX = 0; - } else if (!item.y && prevX === null && arr[i - 1].y) { - prevX = arr[i - 1].x; - } else if (item.y && prevX !== null) { - _errorArea.push({ - x: prevX, - width: item.x - prevX, - }); - prevX = null; - } - }); - - return _errorArea; - }, [points]); - - return errorArea.map((area, i) => { - return ( - - ); - }); -}; -CustomizedErrorArea.displayName = 'CustomizedErrorArea'; diff --git a/src/client/components/monitor/MonitorPublicDataChart.tsx b/src/client/components/monitor/MonitorPublicDataChart.tsx new file mode 100644 index 0000000..c01ecf0 --- /dev/null +++ b/src/client/components/monitor/MonitorPublicDataChart.tsx @@ -0,0 +1,149 @@ +import dayjs from 'dayjs'; +import { get } from 'lodash-es'; +import React, { useMemo } from 'react'; +import { trpc } from '../../api/trpc'; +import { getMonitorProvider, getProviderDisplay } from './provider'; +import { useTranslation } from '@i18next-toolkit/react'; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from '../ui/chart'; +import { + Area, + AreaChart, + CartesianGrid, + Customized, + XAxis, + YAxis, +} from 'recharts'; +import { useTheme } from '@/hooks/useTheme'; +import { CustomizedErrorArea } from './CustomizedErrorArea'; + +const chartConfig = { + value: { + label: Result, + }, +} satisfies ChartConfig; + +interface MonitorPublicDataChartProps { + workspaceId: string; + monitorId: string; + className?: string; +} + +export const MonitorPublicDataChart: React.FC = + React.memo((props) => { + const { t } = useTranslation(); + const { workspaceId, monitorId } = props; + const { colors } = useTheme(); + + const { data: monitorInfo } = trpc.monitor.getPublicInfo.useQuery( + { + monitorIds: [monitorId], + }, + { + select(data) { + return data[0]; + }, + } + ); + + const { data: _data = [] } = trpc.monitor.publicData.useQuery({ + workspaceId, + monitorId, + }); + + const providerInfo = getMonitorProvider(monitorInfo?.type ?? ''); + + const { data } = useMemo(() => { + const data = _data.map((d, i, arr) => { + const value = d.value > 0 ? d.value : null; + const time = dayjs(d.createdAt).valueOf(); + + return { + value, + time, + }; + }); + + return { data }; + }, [_data]); + + const isTrendingMode = monitorInfo?.trendingMode ?? false; // if true, y axis not start from 0 + + return ( +
+ + + + + + + + + dayjs(date).format('HH:mm')} + /> + + + + dayjs(get(payload, [0, 'payload', 'time'])).format( + 'YYYY-MM-DD HH:mm:ss' + ) + } + formatter={(value, defaultText, item, index, payload) => { + if (typeof value !== 'number') { + return defaultText; + } + const { name, text } = getProviderDisplay( + Number(value), + providerInfo + ); + + return ( +
+ {name}: + {text} +
+ ); + }} + content={} + /> + + + + +
+
+
+ ); + }); +MonitorPublicDataChart.displayName = 'MonitorPublicDataChart'; diff --git a/src/client/components/monitor/StatusPage/Body.tsx b/src/client/components/monitor/StatusPage/Body.tsx index 8c1276b..9d0e31a 100644 --- a/src/client/components/monitor/StatusPage/Body.tsx +++ b/src/client/components/monitor/StatusPage/Body.tsx @@ -15,6 +15,7 @@ import { getStatusBgColorClassName, parseHealthStatusByPercent, } from '@/utils/health'; +import { MonitorPublicDataChart } from '../MonitorPublicDataChart'; interface StatusPageBodyProps { workspaceId: string; @@ -51,7 +52,7 @@ export const StatusPageBody: React.FC = React.memo( ); @@ -69,13 +70,13 @@ export const StatusPageBody: React.FC = React.memo( StatusPageBody.displayName = 'StatusPageBody'; export const StatusItemMonitor: React.FC<{ - id: string; + monitorId: string; showCurrent: boolean; workspaceId: string; }> = React.memo((props) => { const { data: info } = trpc.monitor.getPublicInfo.useQuery( { - monitorIds: [props.id], + monitorIds: [props.monitorId], }, { select: (data) => data[0], @@ -84,7 +85,7 @@ export const StatusItemMonitor: React.FC<{ const { data: list = [], isLoading } = trpc.monitor.publicSummary.useQuery({ workspaceId: props.workspaceId, - monitorId: props.id, + monitorId: props.monitorId, }); const { summaryStatus, summaryPercent } = useMemo(() => { @@ -108,51 +109,58 @@ export const StatusItemMonitor: React.FC<{ } return ( -
-
- - {summaryPercent}% - +
+
+
+ + {summaryPercent}% + +
+ +
+
{info?.name}
+
+ + {props.showCurrent && info && ( + + )} + +
+ { + const status = parseHealthStatusByPercent( + item.upRate, + item.totalCount + ); + + return { + status, + title: `${item.day} | (${item.upCount}/${item.totalCount}) ${item.upRate}%`, + }; + })} + /> +
-
-
{info?.name}
-
- - {props.showCurrent && info && ( - - )} - -
- { - const status = parseHealthStatusByPercent( - item.upRate, - item.totalCount - ); - - return { - status, - title: `${item.day} | (${item.upCount}/${item.totalCount}) ${item.upRate}%`, - }; - })} - /> -
+
); }); @@ -199,3 +207,16 @@ const MonitorLatestResponse: React.FC<{ ); }); MonitorLatestResponse.displayName = 'MonitorLatestResponse'; + +export const MonitorRecentChart: React.FC<{ + workspaceId: string; + monitorId: string; +}> = React.memo((props) => { + return ( + + ); +}); +MonitorRecentChart.displayName = 'MonitorRecentChart'; diff --git a/src/client/tailwind.config.ts b/src/client/tailwind.config.ts index aeea1e3..64c177a 100644 --- a/src/client/tailwind.config.ts +++ b/src/client/tailwind.config.ts @@ -13,6 +13,7 @@ module.exports = { './components/**/*.{js,jsx,ts,tsx}', './pages/**/*.{js,jsx,ts,tsx}', './routes/**/*.{js,jsx,ts,tsx}', + './utils/health.ts', ], }, theme: { diff --git a/src/server/model/_schema/monitor.ts b/src/server/model/_schema/monitor.ts index 03780aa..712695a 100644 --- a/src/server/model/_schema/monitor.ts +++ b/src/server/model/_schema/monitor.ts @@ -9,4 +9,5 @@ export const monitorPublicInfoSchema = MonitorModelSchema.pick({ id: true, name: true, type: true, + trendingMode: true, }); diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts index fab6bdf..c869436 100644 --- a/src/server/trpc/routers/monitor.ts +++ b/src/server/trpc/routers/monitor.ts @@ -361,6 +361,38 @@ export const monitorRouter = router({ 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({