import { Button, Card, Select, Space, Spin } from 'antd'; import dayjs, { Dayjs } from 'dayjs'; import React, { useMemo, useState } from 'react'; import { trpc } from '../../api/trpc'; import { useCurrentWorkspaceId } from '../../store/user'; import { Loading } from '../Loading'; import { getMonitorLink, getMonitorProvider } from '../modals/monitor/provider'; import { NotFoundTip } from '../NotFoundTip'; import { MonitorInfo as MonitorInfoType } from '../../../types'; import { Area, AreaConfig } from '@ant-design/charts'; import { MonitorHealthBar } from './MonitorHealthBar'; import { useSocketSubscribeList } from '../../api/socketio'; import { last, uniqBy } from 'lodash-es'; import { ErrorTip } from '../ErrorTip'; import { ColorTag } from '../ColorTag'; import { useNavigate } from 'react-router'; import { MonitorStatsBlock } from './MonitorStatsBlock'; interface MonitorInfoProps { monitorId: string; } export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => { const workspaceId = useCurrentWorkspaceId(); const { monitorId } = props; const [currectResponse, setCurrentResponse] = useState(0); const navigate = useNavigate(); const { data: monitorInfo, isInitialLoading, isLoading, } = trpc.monitor.get.useQuery({ workspaceId, id: monitorId, }); if (isInitialLoading) { return <Loading />; } if (!monitorInfo) { return <NotFoundTip />; } return ( <Spin spinning={isLoading}> <Space className="w-full" direction="vertical"> <div className="flex justify-between"> <Space direction="vertical"> <div className="text-2xl">{monitorInfo.name}</div> <div> <ColorTag label={monitorInfo.type} /> <span> {getMonitorLink(monitorInfo as any as MonitorInfoType)} </span> </div> </Space> <div className="text-black text-opacity-75"> Monitored for {dayjs().diff(dayjs(monitorInfo.createdAt), 'days')}{' '} days </div> </div> <div> <Button type="primary" onClick={() => { navigate(`/monitor/${monitorInfo.id}/edit`); }} > Edit </Button> </div> <Card> <MonitorHealthBar monitorId={monitorId} count={40} size="large" showCurrentStatus={true} onBeatsItemUpdate={(items) => { setCurrentResponse(last(items)?.value ?? 0); }} /> </Card> <Card> <MonitorDataMetrics monitorId={monitorId} monitorType={monitorInfo.type} currectResponse={currectResponse} /> </Card> <Card> <MonitorDataChart monitorId={monitorId} /> </Card> </Space> </Spin> ); }); MonitorInfo.displayName = 'MonitorInfo'; export const MonitorDataMetrics: React.FC<{ monitorId: string; monitorType: string; currectResponse: number; }> = React.memo((props) => { const workspaceId = useCurrentWorkspaceId(); const { monitorId, monitorType, currectResponse } = props; const { data, isLoading } = trpc.monitor.dataMetrics.useQuery({ workspaceId, monitorId, }); const providerOverview = useMemo(() => { const provider = getMonitorProvider(monitorType); if (!provider || !provider.overview) { return null; } return ( <> {provider.overview.map((Component) => ( <Component monitorId={monitorId} /> ))} </> ); }, [monitorType]); if (isLoading) { return <Loading />; } if (!data) { return <ErrorTip />; } return ( <div className="flex justify-between text-center"> <MonitorStatsBlock title="Response" desc="(Current)" text={`${currectResponse} ms`} /> <MonitorStatsBlock title="Avg. Response" desc="(24 hour)" text={`${parseFloat(data.recent1DayAvg.toFixed(0))} ms`} /> <MonitorStatsBlock title="Uptime" desc="(24 hour)" text={`${parseFloat( ( (data.recent1DayOnlineCount / (data.recent1DayOnlineCount + data.recent1DayOfflineCount)) * 100 ).toFixed(2) )} %`} /> <MonitorStatsBlock title="Uptime" desc="(30 days)" text={`${parseFloat( ( (data.recent30DayOnlineCount / (data.recent30DayOnlineCount + data.recent30DayOfflineCount)) * 100 ).toFixed(2) )} %`} /> {providerOverview} </div> ); }); MonitorDataMetrics.displayName = 'MonitorDataMetrics'; const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo( (props) => { const workspaceId = useCurrentWorkspaceId(); const { monitorId } = props; const [rangeType, setRangeType] = useState('recent'); const newDataList = useSocketSubscribeList('onMonitorReceiveNewData', { filter: (data) => { return data.monitorId === props.monitorId; }, }); const range = useMemo((): [Dayjs, Dayjs] => { if (rangeType === '3h') { return [dayjs().subtract(3, 'hour'), dayjs()]; } if (rangeType === '6h') { return [dayjs().subtract(6, 'hour'), dayjs()]; } if (rangeType === '24h') { return [dayjs().subtract(24, 'hour'), dayjs()]; } if (rangeType === '1w') { return [dayjs().subtract(1, 'week'), dayjs()]; } return [dayjs().subtract(0.5, 'hour'), dayjs()]; }, [rangeType]); const { data: _data = [] } = trpc.monitor.data.useQuery({ workspaceId, monitorId, startAt: range[0].valueOf(), endAt: range[1].valueOf(), }); const { data, annotations } = useMemo(() => { const annotations: AreaConfig['annotations'] = []; let start: number | null = null; const data = uniqBy([..._data, ...newDataList], 'createdAt').map( (d, i, arr) => { const value = d.value > 0 ? d.value : null; const time = dayjs(d.createdAt).valueOf(); if (!value && !start && arr[i - 1]) { start = dayjs(arr[i - 1]['createdAt']).valueOf(); } else if (value && start) { annotations.push({ type: 'region', start: [start, 'min'], end: [time, 'max'], style: { fill: 'red', fillOpacity: 0.25, }, }); start = null; } return { value, time, }; } ); return { data, annotations }; }, [_data, newDataList]); const config = useMemo<AreaConfig>(() => { return { data, height: 200, xField: 'time', yField: 'value', smooth: true, meta: { time: { formatter(value) { return dayjs(value).format( rangeType === '1w' ? 'MM-DD HH:mm' : 'HH:mm' ); }, }, }, color: 'rgb(34 197 94 / 0.8)', areaStyle: () => { return { fill: 'l(270) 0:rgb(34 197 94 / 0.2) 0.5:rgb(34 197 94 / 0.5) 1:rgb(34 197 94 / 0.8)', }; }, annotations, tooltip: { title: (title, datum) => { return dayjs(datum.time).format('YYYY-MM-DD HH:mm'); }, formatter(datum) { return { name: 'usage', value: datum.value ? datum.value + 'ms' : 'null', }; }, }, }; }, [data, rangeType]); return ( <div> <div className="mb-4 text-right"> <Select className="w-20 text-center" size="small" value={rangeType} onChange={(val) => setRangeType(val)} > <Select.Option value="recent">Recent</Select.Option> <Select.Option value="3h">3h</Select.Option> <Select.Option value="6h">6h</Select.Option> <Select.Option value="24h">24h</Select.Option> <Select.Option value="1w">1w</Select.Option> </Select> </div> <Area {...config} /> </div> ); } ); MonitorDataChart.displayName = 'MonitorDataChart';