feat: add monitor data metrics in monitor info

This commit is contained in:
moonrailgun 2023-10-13 00:55:41 +08:00
parent b4f2170e51
commit 7bc245eb45
4 changed files with 201 additions and 19 deletions

View File

@ -5,12 +5,14 @@ import { HealthBar, HealthBarBeat, HealthBarProps } from '../HealthBar';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { trpc } from '../../api/trpc'; import { trpc } from '../../api/trpc';
import { useCurrentWorkspaceId } from '../../store/user'; import { useCurrentWorkspaceId } from '../../store/user';
import { useWatch } from '../../hooks/useWatch';
interface MonitorHealthBarProps { interface MonitorHealthBarProps {
monitorId: string; monitorId: string;
count?: number; count?: number;
size?: HealthBarProps['size']; size?: HealthBarProps['size'];
showCurrentStatus?: boolean; showCurrentStatus?: boolean;
onBeatsItemUpdate?: (items: { value: number; createdAt: string }[]) => void;
} }
export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo( export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
(props) => { (props) => {
@ -38,28 +40,34 @@ export const MonitorHealthBar: React.FC<MonitorHealthBarProps> = React.memo(
); );
}, [newDataList, recent, count]); }, [newDataList, recent, count]);
const beats = items.map((item): HealthBarBeat => { const beats = useMemo(() => {
if (!item) { return items.map((item): HealthBarBeat => {
return { if (!item) {
status: 'none', return {
}; status: 'none',
} };
}
const title = `${dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} | ${ const title = `${dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')} | ${
item.value item.value
}ms`; }ms`;
if (item.value < 0) {
return {
title,
status: 'error',
};
}
if (item.value < 0) {
return { return {
title, title,
status: 'error', status: 'health',
}; };
} });
}, [items]);
return { useWatch([items], () => {
title, props.onBeatsItemUpdate?.(items);
status: 'health',
};
}); });
return ( return (

View File

@ -10,7 +10,8 @@ import { MonitorInfo as MonitorInfoType } from '../../../types';
import { Area, AreaConfig } from '@ant-design/charts'; import { Area, AreaConfig } from '@ant-design/charts';
import { MonitorHealthBar } from './MonitorHealthBar'; import { MonitorHealthBar } from './MonitorHealthBar';
import { useSocketSubscribeList } from '../../api/socketio'; import { useSocketSubscribeList } from '../../api/socketio';
import { uniqBy } from 'lodash'; import { last, uniqBy } from 'lodash-es';
import { ErrorTip } from '../ErrorTip';
interface MonitorInfoProps { interface MonitorInfoProps {
monitorId: string; monitorId: string;
@ -18,6 +19,7 @@ interface MonitorInfoProps {
export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => { export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
const workspaceId = useCurrentWorkspaceId(); const workspaceId = useCurrentWorkspaceId();
const { monitorId } = props; const { monitorId } = props;
const [currectResponse, setCurrentResponse] = useState(0);
const { data: monitorInfo, isLoading } = trpc.monitor.get.useQuery({ const { data: monitorInfo, isLoading } = trpc.monitor.get.useQuery({
workspaceId, workspaceId,
@ -59,6 +61,16 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
count={40} count={40}
size="large" size="large"
showCurrentStatus={true} showCurrentStatus={true}
onBeatsItemUpdate={(items) => {
setCurrentResponse(last(items)?.value ?? 0);
}}
/>
</Card>
<Card>
<MonitorDataMetrics
monitorId={monitorId}
currectResponse={currectResponse}
/> />
</Card> </Card>
@ -71,6 +83,70 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
}); });
MonitorInfo.displayName = 'MonitorInfo'; 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 <Loading />;
}
if (!data) {
return <ErrorTip />;
}
return (
<div className="flex justify-between text-center">
<div>
<div className="font-bold mb-0.5">Response</div>
<div className="text-gray-500">(Current)</div>
<div>{currectResponse} ms</div>
</div>
<div>
<div className="font-bold mb-0.5">Avg. Response</div>
<div className="text-gray-500">(24 hour)</div>
<div>{parseFloat(data.recent1DayAvg.toFixed(0))} ms</div>
</div>
<div>
<div className="font-bold mb-0.5">Uptime</div>
<div className="text-gray-500 mb-2 text-xs">(24 hour)</div>
<div>
{parseFloat(
(
(data.recent1DayOnlineCount /
(data.recent1DayOnlineCount + data.recent1DayOfflineCount)) *
100
).toFixed(2)
)}
%
</div>
</div>
<div>
<div className="font-bold mb-0.5">Uptime</div>
<div className="text-gray-500">(30 days)</div>
<div>
{parseFloat(
(
(data.recent30DayOnlineCount /
(data.recent30DayOnlineCount + data.recent30DayOfflineCount)) *
100
).toFixed(2)
)}
%
</div>
</div>
</div>
);
});
MonitorDataMetrics.displayName = 'MonitorDataMetrics';
const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo( const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
(props) => { (props) => {
const workspaceId = useCurrentWorkspaceId(); const workspaceId = useCurrentWorkspaceId();

View File

@ -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);
}

View File

@ -3,6 +3,7 @@ import { prisma } from '../../model/_client';
import { z } from 'zod'; import { z } from 'zod';
import { monitorManager } from '../../model/monitor'; import { monitorManager } from '../../model/monitor';
import { MonitorInfo } from '../../../types'; import { MonitorInfo } from '../../../types';
import dayjs from 'dayjs';
export const monitorRouter = router({ export const monitorRouter = router({
all: workspaceProcedure.query(async ({ input }) => { all: workspaceProcedure.query(async ({ input }) => {
@ -58,7 +59,7 @@ export const monitorRouter = router({
return monitor; return monitor;
}), }),
data: workspaceOwnerProcedure data: workspaceProcedure
.input( .input(
z.object({ z.object({
monitorId: z.string().cuid2(), monitorId: z.string().cuid2(),
@ -86,7 +87,7 @@ export const monitorRouter = router({
}, },
}); });
}), }),
recentData: workspaceOwnerProcedure recentData: workspaceProcedure
.input( .input(
z.object({ z.object({
monitorId: z.string().cuid2(), 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,
};
}),
}); });