feat: allow display current response value in monitor list

useful for monitor page to display current value
This commit is contained in:
moonrailgun 2024-01-13 02:16:57 +08:00
parent e24d82224c
commit ec591f0c54
11 changed files with 133 additions and 47 deletions

View File

@ -1,6 +1,6 @@
{ {
"verbose": true, "verbose": true,
"watch": ["./src/server"], "watch": ["./src/server", "./prisma"],
"ext": "ts", "ext": "ts",
"delay": 1000, "delay": 1000,
"exec": "ts-node --transpileOnly ./src/server/main.ts" "exec": "ts-node --transpileOnly ./src/server/main.ts"

View File

@ -3,5 +3,6 @@ import { z } from 'zod';
export const MonitorStatusPageListSchema = z.array( export const MonitorStatusPageListSchema = z.array(
z.object({ z.object({
id: z.string(), id: z.string(),
showCurrent: z.boolean().default(false).optional(),
}) })
); );

View File

@ -6,7 +6,7 @@ import React, { useState, useMemo } from 'react';
import { useSocketSubscribeList } from '../../api/socketio'; import { useSocketSubscribeList } from '../../api/socketio';
import { trpc } from '../../api/trpc'; import { trpc } from '../../api/trpc';
import { useCurrentWorkspaceId } from '../../store/user'; import { useCurrentWorkspaceId } from '../../store/user';
import { getMonitorProvider } from './provider'; import { getMonitorProvider, getProviderDisplay } from './provider';
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo( export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
(props) => { (props) => {
@ -122,16 +122,14 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
return dayjs(datum.time).format('YYYY-MM-DD HH:mm'); return dayjs(datum.time).format('YYYY-MM-DD HH:mm');
}, },
formatter(datum) { formatter(datum) {
const name = providerInfo?.valueLabel const { name, text } = getProviderDisplay(
? providerInfo?.valueLabel datum.value,
: 'usage'; providerInfo
const formatterFn = providerInfo?.valueFormatter );
? providerInfo?.valueFormatter
: (value: number) => `${value}ms`;
return { return {
name, name,
value: datum.value ? formatterFn(datum.value) : 'null', value: datum.value ? text : 'null',
}; };
}, },
}, },

View File

@ -4,7 +4,6 @@ import { takeRight, last } from 'lodash-es';
import { HealthBar, HealthBarBeat, HealthBarProps } from '../HealthBar'; 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 { useWatch } from '../../hooks/useWatch'; import { useWatch } from '../../hooks/useWatch';
interface MonitorHealthBarProps { interface MonitorHealthBarProps {

View File

@ -44,6 +44,7 @@ export const MonitorList: React.FC = React.memo(() => {
workspaceId={workspaceId} workspaceId={workspaceId}
monitorId={monitor.id} monitorId={monitor.id}
monitorName={monitor.name} monitorName={monitor.name}
monitorType={monitor.type}
onClick={() => { onClick={() => {
navigate(`/monitor/${monitor.id}`); navigate(`/monitor/${monitor.id}`);
}} }}

View File

@ -1,15 +1,28 @@
import clsx from 'clsx'; import clsx from 'clsx';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { MonitorHealthBar } from './MonitorHealthBar'; import { MonitorHealthBar } from './MonitorHealthBar';
import { last } from 'lodash-es';
import { getMonitorProvider, getProviderDisplay } from './provider';
import { Tooltip } from 'antd';
export const MonitorListItem: React.FC<{ export const MonitorListItem: React.FC<{
className?: string; className?: string;
workspaceId: string; workspaceId: string;
monitorId: string; monitorId: string;
monitorName: string; monitorName: string;
monitorType: string;
showCurrentResponse?: boolean;
onClick?: () => void; onClick?: () => void;
}> = React.memo((props) => { }> = React.memo((props) => {
const { className, workspaceId, monitorId, monitorName, onClick } = props; const {
className,
workspaceId,
monitorId,
monitorName,
monitorType,
showCurrentResponse = false,
onClick,
} = props;
const [beats, setBeats] = useState< const [beats, setBeats] = useState<
({ ({
@ -33,11 +46,29 @@ export const MonitorListItem: React.FC<{
return parseFloat(((up / beats.length) * 100).toFixed(1)); return parseFloat(((up / beats.length) * 100).toFixed(1));
}, [beats]); }, [beats]);
const provider = getMonitorProvider(monitorType);
const latestResponse = useMemo((): string | false => {
const val = last(beats)?.value;
if (!val) {
return false;
}
if (!provider) {
return String(val);
}
const { text } = getProviderDisplay(val, provider);
return text;
}, [beats, provider]);
return ( return (
<div <div
className={clsx( className={clsx(
className, className,
'flex rounded-lg py-3 px-4 mb-1 bg-green-500 bg-opacity-0 hover:bg-opacity-10', 'flex rounded-lg py-3 px-4 mb-1 bg-green-500 bg-opacity-0 hover:bg-opacity-10 items-center',
onClick && 'cursor-pointer' onClick && 'cursor-pointer'
)} )}
onClick={onClick} onClick={onClick}
@ -66,6 +97,14 @@ export const MonitorListItem: React.FC<{
</div> */} </div> */}
</div> </div>
{showCurrentResponse && latestResponse && (
<Tooltip title="Current">
<div className="px-2 text-sm text-gray-800 dark:text-gray-400">
{latestResponse}
</div>
</Tooltip>
)}
<div className="flex items-center"> <div className="flex items-center">
<MonitorHealthBar <MonitorHealthBar
workspaceId={workspaceId} workspaceId={workspaceId}

View File

@ -1,4 +1,4 @@
import { Button, Form, Input, Typography } from 'antd'; import { Button, Divider, Form, Input, Switch, Typography } from 'antd';
import React from 'react'; import React from 'react';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { MonitorPicker } from '../MonitorPicker'; import { MonitorPicker } from '../MonitorPicker';
@ -72,27 +72,50 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
return ( return (
<> <>
<Form.Item label="Monitors"> <Form.Item label="Monitors">
<div className="flex flex-col gap-2 mb-2">
{fields.map((field, index) => ( {fields.map((field, index) => (
<div key={field.key} className="flex gap-2 items-start"> // monitor item
<>
{index !== 0 && <Divider className="my-0.5" />}
<div key={field.key} className="flex flex-col gap-1">
<Form.Item <Form.Item
name={[field.name, 'id']} name={[field.name, 'id']}
className="flex-1"
rules={[ rules={[
{ {
required: true, required: true,
message: 'Please select monitor', message: 'Please select monitor',
}, },
]} ]}
noStyle={true}
> >
<MonitorPicker /> <MonitorPicker />
</Form.Item> </Form.Item>
<div className="flex item-center">
<div className="flex-1">
<Form.Item
name={[field.name, 'showCurrent']}
valuePropName="checked"
noStyle={true}
>
<Switch size="small" />
</Form.Item>
<span className="text-sm align-middle ml-1">
Show Current Response
</span>
</div>
<MinusCircleOutlined <MinusCircleOutlined
className="text-lg mt-1.5" className="text-lg mt-1.5"
onClick={() => remove(field.name)} onClick={() => remove(field.name)}
/> />
</div> </div>
</div>
</>
))} ))}
</div>
<Button <Button
type="dashed" type="dashed"

View File

@ -1,8 +1,9 @@
import { Empty } from 'antd'; import { Empty } from 'antd';
import React from 'react'; import React, { useMemo } from 'react';
import { trpc } from '../../../api/trpc'; import { trpc } from '../../../api/trpc';
import { Loading } from '../../Loading'; import { Loading } from '../../Loading';
import { MonitorListItem } from '../MonitorListItem'; import { MonitorListItem } from '../MonitorListItem';
import { keyBy } from 'lodash-es';
interface StatusPageServicesProps { interface StatusPageServicesProps {
workspaceId: string; workspaceId: string;
@ -16,12 +17,14 @@ export const StatusPageServices: React.FC<StatusPageServicesProps> = React.memo(
monitorIds: monitorList.map((item) => item.id), monitorIds: monitorList.map((item) => item.id),
}); });
const monitorProps = useMemo(() => keyBy(monitorList, 'id'), [monitorList]);
if (isLoading) { if (isLoading) {
return <Loading />; return <Loading />;
} }
return ( return (
<div className="shadow-2xl p-2.5 flex flex-col gap-4"> <div className="p-2.5 flex flex-col gap-4 rounded-md border border-gray-200 dark:border-gray-700">
{list.length > 0 ? ( {list.length > 0 ? (
list.map((item) => ( list.map((item) => (
<MonitorListItem <MonitorListItem
@ -29,6 +32,8 @@ export const StatusPageServices: React.FC<StatusPageServicesProps> = React.memo(
workspaceId={workspaceId} workspaceId={workspaceId}
monitorId={item.id} monitorId={item.id}
monitorName={item.name} monitorName={item.name}
monitorType={item.type}
showCurrentResponse={monitorProps[item.id].showCurrent ?? false}
/> />
)) ))
) : ( ) : (

View File

@ -32,3 +32,21 @@ export function getMonitorLink(info: MonitorInfo): React.ReactNode {
return provider.link(info); return provider.link(info);
} }
export function getProviderDisplay(
value: number,
provider:
| Pick<MonitorProvider, 'valueFormatter' | 'valueLabel'>
| undefined
| null
) {
const name = provider?.valueLabel ? provider?.valueLabel : 'usage';
const formatterFn = provider?.valueFormatter
? provider?.valueFormatter
: (value: number) => `${value}ms`;
return {
name,
text: formatterFn(value),
};
}

View File

@ -1,3 +1,6 @@
import { z } from 'zod';
import { MonitorStatusPageListSchema } from '../../prisma/zod/schemas';
export * as schemas from '../../prisma/zod/index'; export * as schemas from '../../prisma/zod/index';
export * from './server'; export * from './server';
export * from './monitor'; export * from './monitor';
@ -10,8 +13,6 @@ declare global {
layouts: Record<string, any[]>; layouts: Record<string, any[]>;
items: any[]; items: any[];
} | null; } | null;
type MonitorStatusPageList = { type MonitorStatusPageList = z.infer<typeof MonitorStatusPageListSchema>;
id: string;
}[];
} }
} }

View File

@ -16,4 +16,5 @@ export type MonitorInfoWithNotificationIds = MonitorInfo & {
export const MonitorPublicInfoSchema = schemas.MonitorModelSchema.pick({ export const MonitorPublicInfoSchema = schemas.MonitorModelSchema.pick({
id: true, id: true,
name: true, name: true,
type: true,
}); });