feat: add monitor data metrics in monitor info
This commit is contained in:
parent
b4f2170e51
commit
7bc245eb45
@ -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 (
|
||||||
|
@ -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();
|
||||||
|
12
src/client/hooks/useWatch.ts
Normal file
12
src/client/hooks/useWatch.ts
Normal 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);
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user