feat: allow display current response value in monitor list
useful for monitor page to display current value
This commit is contained in:
parent
e24d82224c
commit
ec591f0c54
@ -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"
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -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',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
@ -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}`);
|
||||||
}}
|
}}
|
||||||
|
@ -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}
|
||||||
@ -55,17 +86,25 @@ export const MonitorListItem: React.FC<{
|
|||||||
<div className="flex-1 pl-2">
|
<div className="flex-1 pl-2">
|
||||||
<div className="text-base">{monitorName}</div>
|
<div className="text-base">{monitorName}</div>
|
||||||
{/* <div>
|
{/* <div>
|
||||||
{monitor.tags.map((tag) => (
|
{monitor.tags.map((tag) => (
|
||||||
<span
|
<span
|
||||||
className="py-0.5 px-1 rounded-full text-white text-sm"
|
className="py-0.5 px-1 rounded-full text-white text-sm"
|
||||||
style={{ backgroundColor: tag.color }}
|
style={{ backgroundColor: tag.color }}
|
||||||
>
|
>
|
||||||
{tag.label}
|
{tag.label}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</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}
|
||||||
|
@ -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">
|
||||||
{fields.map((field, index) => (
|
<div className="flex flex-col gap-2 mb-2">
|
||||||
<div key={field.key} className="flex gap-2 items-start">
|
{fields.map((field, index) => (
|
||||||
<Form.Item
|
// monitor item
|
||||||
name={[field.name, 'id']}
|
<>
|
||||||
className="flex-1"
|
{index !== 0 && <Divider className="my-0.5" />}
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: 'Please select monitor',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<MonitorPicker />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<MinusCircleOutlined
|
<div key={field.key} className="flex flex-col gap-1">
|
||||||
className="text-lg mt-1.5"
|
<Form.Item
|
||||||
onClick={() => remove(field.name)}
|
name={[field.name, 'id']}
|
||||||
/>
|
rules={[
|
||||||
</div>
|
{
|
||||||
))}
|
required: true,
|
||||||
|
message: 'Please select monitor',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
noStyle={true}
|
||||||
|
>
|
||||||
|
<MonitorPicker />
|
||||||
|
</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
|
||||||
|
className="text-lg mt-1.5"
|
||||||
|
onClick={() => remove(field.name)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
@ -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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -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;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user