From 765cca1f31d0795d40fd9c59619cbfe9c00d70d6 Mon Sep 17 00:00:00 2001 From: NlightN22 Date: Mon, 11 Mar 2024 03:39:27 +0700 Subject: [PATCH] add system page --- src/locales/en.ts | 17 +++- src/locales/ru.ts | 17 +++- src/pages/HostSystemPage.tsx | 81 ++++++++++++++++--- src/shared/components/stats/DetectorsStat.tsx | 42 ++++++++++ src/shared/components/stats/GpuStat.tsx | 42 ++++++++++ .../components/stats/StorageRingStat.tsx | 65 +++++++++++++++ src/shared/utils/any.helper.ts | 6 -- src/shared/utils/data.size.ts | 13 +++ src/shared/utils/dateUtil.ts | 20 +++++ src/shared/utils/debounce.ts | 13 --- src/shared/utils/resize-observer.ts | 39 --------- 11 files changed, 285 insertions(+), 70 deletions(-) create mode 100644 src/shared/components/stats/DetectorsStat.tsx create mode 100644 src/shared/components/stats/GpuStat.tsx create mode 100644 src/shared/components/stats/StorageRingStat.tsx delete mode 100644 src/shared/utils/any.helper.ts create mode 100644 src/shared/utils/data.size.ts delete mode 100644 src/shared/utils/debounce.ts delete mode 100644 src/shared/utils/resize-observer.ts diff --git a/src/locales/en.ts b/src/locales/en.ts index a416a63..f09b123 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1,4 +1,15 @@ const en = { + detectorCard: { + pid: 'PID', + inferenceSpeed: 'Inference Speed', + memory: 'Memory', + }, + gpuStatCard: { + gpu: 'GPU', + memory: 'Memory', + decoder: 'Decoder', + encoder: 'Encoder', + }, cameraStatTable: { process: 'Process', pid: 'PID', @@ -35,6 +46,8 @@ const en = { doubleClickToFullHint: 'Double click for fullscreen', rating: 'Rating', }, + version: 'Version', + uptime: 'Uptime', pleaseSelectRole: 'Please select Role', pleaseSelectHost: 'Please select Host', pleaseSelectCamera: 'Please select Camera', @@ -46,10 +59,12 @@ const en = { camersDoesNotExist: 'No cameras', search: 'Search', recordings: 'Recordings', + day: 'Day', hour: 'Hour', + minute: 'Minute', + second: 'Second', events: 'Events', notHaveEvents: 'No events', - day: 'Day', selectHost: 'Select host', selectCamera: 'Select Camera', selectRange: 'Select period', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index aab9af5..3400609 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1,4 +1,15 @@ const ru = { + detectorCard: { + pid: 'PID', + inferenceSpeed: 'Скорость вывода', + memory: 'Память', + }, + gpuStatCard: { + gpu: 'GPU', + memory: 'Память', + decoder: 'Декодер', + encoder: 'Кодер', + }, hostMenu: { editConfig: 'Редакт. конфиг.', restart: 'Перезагрузка', @@ -28,6 +39,8 @@ const ru = { doubleClickToFullHint: 'Двойное нажатие мышью для полноэкранного просмотра', rating: 'Рейтинг', }, + version: 'Версия', + uptime: 'Время работы', pleaseSelectRole: 'Пожалуйста выберите роль', pleaseSelectHost: 'Пожалуйста выберите хост', pleaseSelectCamera: 'Пожалуйста выберите камеру', @@ -39,10 +52,12 @@ const ru = { camersDoesNotExist: 'Камер нет', search: 'Поиск', recordings: 'Записи', + day: 'День', hour: 'Час', + minute: 'Минута', + second: 'Час', events: 'События', notHaveEvents: 'Событий нет', - day: 'День', selectHost:'Выбери хост', selectCamera: 'Выбери камеру', selectRange: 'Выбери период', diff --git a/src/pages/HostSystemPage.tsx b/src/pages/HostSystemPage.tsx index 8ed242a..012d9a4 100644 --- a/src/pages/HostSystemPage.tsx +++ b/src/pages/HostSystemPage.tsx @@ -8,14 +8,21 @@ import { useQuery } from '@tanstack/react-query'; import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api'; import CenterLoader from '../shared/components/loaders/CenterLoader'; import RetryErrorPage from './RetryErrorPage'; -import { Flex } from '@mantine/core'; +import { Flex, Grid, Text } from '@mantine/core'; import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable'; +import StorageRingStat from '../shared/components/stats/StorageRingStat'; +import { useTranslation } from 'react-i18next'; +import { formatUptime } from '../shared/utils/dateUtil'; +import GpuStat from '../shared/components/stats/GpuStat'; +import { v4 } from 'uuid'; +import DetectorsStat from '../shared/components/stats/DetectorsStat'; export const hostSystemPageQuery = { hostId: 'hostId', } const HostSystemPage = () => { + const { t } = useTranslation() const executed = useRef(false) const { sideBarsStore } = useContext(Context) const { isAdmin } = useAdminRole() @@ -29,11 +36,6 @@ const HostSystemPage = () => { } }, [sideBarsStore]) - const location = useLocation() - const queryParams = useMemo(() => { - return new URLSearchParams(location.search); - }, [location.search]) - let { id: paramHostId } = useParams<'id'>() const { data, isError, isPending, refetch } = useQuery({ @@ -52,12 +54,12 @@ const HostSystemPage = () => { return Object.entries(data.cameras).flatMap(([name, stats]) => { return (['Ffmpeg', 'Capture', 'Detect'] as ProcessType[]).map(type => { const pid = type === ProcessType.Ffmpeg ? stats.ffmpeg_pid : - type === ProcessType.Capture ? stats.capture_pid : stats.pid; + type === ProcessType.Capture ? stats.capture_pid : stats.pid; const fps = type === ProcessType.Ffmpeg ? stats.camera_fps : - type === ProcessType.Capture ? stats.process_fps : stats.detection_fps; + type === ProcessType.Capture ? stats.process_fps : stats.detection_fps; const cpu = data.cpu_usages[pid]?.cpu; const mem = data.cpu_usages[pid]?.mem; - + return { cameraName: name, process: type, @@ -76,9 +78,68 @@ const HostSystemPage = () => { if (!paramHostId || !data) return null const mappedCameraStat: CameraItem[] = mapCameraData() + const storageStats = Object.entries(data.service.storage).map(([name, stats]) => { + return ( + + + + ) + }) + + const formattedUptime = () => { + const time = formatUptime(data.service.uptime) + const translatedUnit = t(time.unit).toLowerCase().slice(0, 1) + return `${time.value.toFixed(1)} ${translatedUnit}` + } + + const gpuStats = Object.entries(data.gpu_usages).map(([name, stats]) => { + return ( + + + + ) + }) + + const detectorsStats = Object.entries(data.detectors).map(([name, stats]) => { + const pid = stats.pid + const cpu = data.cpu_usages[pid]?.cpu; + const mem = data.cpu_usages[pid]?.mem; + return ( + + + + ) + }) return ( - + + + {t('version')} : {data.service.version} + {t('uptime')} : {formattedUptime()} + + + {storageStats} + + + {gpuStats} + {detectorsStats} + ); diff --git a/src/shared/components/stats/DetectorsStat.tsx b/src/shared/components/stats/DetectorsStat.tsx new file mode 100644 index 0000000..072b10b --- /dev/null +++ b/src/shared/components/stats/DetectorsStat.tsx @@ -0,0 +1,42 @@ +import { Card, Flex, Group, Text } from '@mantine/core'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +interface DetectorsStatProps { + name: string + pid: number, + inferenceSpeed?: number, + cpu?: string, + mem?: string, +} + +const DetectorsStat: React.FC = ({ + name, + pid, + inferenceSpeed, + cpu, + mem +}) => { + const { t } = useTranslation() + return ( + + + + {name} + + + + {t('detectorCard.pid')}: {pid} + {t('detectorCard.inferenceSpeed')}: {inferenceSpeed} + + + CPU: {cpu} + {t('detectorCard.memory')}: {mem} + + + + + ); +}; + +export default DetectorsStat; \ No newline at end of file diff --git a/src/shared/components/stats/GpuStat.tsx b/src/shared/components/stats/GpuStat.tsx new file mode 100644 index 0000000..c3372b2 --- /dev/null +++ b/src/shared/components/stats/GpuStat.tsx @@ -0,0 +1,42 @@ +import { Card, Flex, Group, Text } from '@mantine/core'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +interface GpuStatProps { + name: string, + decoder?: string, + encoder?: string, + gpu?: string, + mem?: string +} + +const GpuStat: React.FC = ({ + name, + decoder, + encoder, + gpu, + mem +}) => { + const { t } = useTranslation() + return ( + + + + {name} + + + + {t('gpuStatCard.gpu')}: {gpu} + {t('gpuStatCard.memory')}: {mem} + + + {t('gpuStatCard.decoder')}: {decoder} + {t('gpuStatCard.encoder')}: {encoder} + + + + + ); +}; + +export default GpuStat; \ No newline at end of file diff --git a/src/shared/components/stats/StorageRingStat.tsx b/src/shared/components/stats/StorageRingStat.tsx new file mode 100644 index 0000000..cb6acfe --- /dev/null +++ b/src/shared/components/stats/StorageRingStat.tsx @@ -0,0 +1,65 @@ +import { Card, Center, Flex, Group, Paper, RingProgress, Stack, Text } from '@mantine/core'; +import React from 'react'; +import { formatMBytes } from '../../utils/data.size'; + +interface StorageRingStatProps { + used: number + free: number + total?: number + storageType: string + path?: string +} + +const StorageRingStat: React.FC = ({ + used, + free, + storageType, + total, + path +}) => { + const calcTotal = total || used + free + const availablePercent = (used / calcTotal * 100) + return ( + + + + {availablePercent.toFixed(0)}% + + } + /> + + + + {storageType} + + {!path ? null : + + {path} + + } + + + Used: + {formatMBytes(used)} + + + Free: + {formatMBytes(free)} + + + + + + + ); +}; + +export default StorageRingStat; \ No newline at end of file diff --git a/src/shared/utils/any.helper.ts b/src/shared/utils/any.helper.ts deleted file mode 100644 index f2c8e23..0000000 --- a/src/shared/utils/any.helper.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function valueIsNotEmpty(value: any) { - if (value) { - return true - } else if( typeof value === 'boolean') return true - return false -} \ No newline at end of file diff --git a/src/shared/utils/data.size.ts b/src/shared/utils/data.size.ts new file mode 100644 index 0000000..a610ef3 --- /dev/null +++ b/src/shared/utils/data.size.ts @@ -0,0 +1,13 @@ +export const formatBytes = (bytes: number, decimals = 2): string => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +} + +export const formatMBytes = (mb: number, decimals?: number): string => { + const bytes = mb * 1024 * 1024; + return formatBytes(bytes, decimals); +} \ No newline at end of file diff --git a/src/shared/utils/dateUtil.ts b/src/shared/utils/dateUtil.ts index b234c5e..cf8de7a 100644 --- a/src/shared/utils/dateUtil.ts +++ b/src/shared/utils/dateUtil.ts @@ -4,6 +4,26 @@ export const longToDate = (long: number): Date => new Date(long * 1000); export const epochToLong = (date: number): number => date / 1000; export const dateToLong = (date: Date): number => epochToLong(date.getTime()); +/** + * + * @param uptimeInSeconds + * @returns value: number, unit: 'day' / 'hour' / 'minute' / 'second' + */ +export const formatUptime = (uptimeInSeconds: number) => { + const secondsInAMinute = 60 + const secondsInAnHour = 3600 + const secondsInADay = 86400 + + if (uptimeInSeconds >= secondsInADay) { + return { value: (uptimeInSeconds / secondsInADay), unit: 'day' }; + } else if (uptimeInSeconds >= secondsInAnHour) { + return { value: (uptimeInSeconds / secondsInAnHour), unit: 'hour' }; + } else if (uptimeInSeconds >= secondsInAMinute) { + return { value: (uptimeInSeconds / secondsInAMinute), unit: 'minute' }; + } else { + return { value: uptimeInSeconds, unit: 'second' }; + } +} export const formatFileTimestamps = (startUnixTime: number, endUnixTime: number, cameraName: string) => { const formatTime = (time: number) => { diff --git a/src/shared/utils/debounce.ts b/src/shared/utils/debounce.ts deleted file mode 100644 index a8078fb..0000000 --- a/src/shared/utils/debounce.ts +++ /dev/null @@ -1,13 +0,0 @@ -export function debounce any>(func: T, wait: number): (...funcArgs: Parameters) => void { - let timeout: ReturnType | null = null; - return function(...args: Parameters): void { - const later = () => { - timeout = null; - func(...args); - }; - if (timeout !== null) { - clearTimeout(timeout); - } - timeout = setTimeout(later, wait); - }; -} \ No newline at end of file diff --git a/src/shared/utils/resize-observer.ts b/src/shared/utils/resize-observer.ts deleted file mode 100644 index a1050c8..0000000 --- a/src/shared/utils/resize-observer.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { MutableRefObject, useEffect, useMemo, useState } from "react"; - -export function useResizeObserver(...refs: MutableRefObject[]) { - const [dimensions, setDimensions] = useState( - new Array(refs.length).fill({ - width: 0, - height: 0, - x: -Infinity, - y: -Infinity, - }) - ); - const resizeObserver = useMemo( - () => - new ResizeObserver((entries) => { - window.requestAnimationFrame(() => { - setDimensions(entries.map((entry) => entry.contentRect)); - }); - }), - [] - ); - - useEffect(() => { - refs.forEach((ref) => { - if (ref.current) { - resizeObserver.observe(ref.current); - } - }); - - return () => { - refs.forEach((ref) => { - if (ref.current) { - resizeObserver.unobserve(ref.current); - } - }); - }; - }, [refs, resizeObserver]); - - return dimensions; -} \ No newline at end of file