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