add storage table
This commit is contained in:
parent
3c231a7bf3
commit
b896c88f6c
@ -1,4 +1,8 @@
|
||||
const en = {
|
||||
systemPage: {
|
||||
cameraStats: 'Cameras stats',
|
||||
storageStats: 'Storages stats',
|
||||
},
|
||||
detectorCard: {
|
||||
pid: 'PID',
|
||||
inferenceSpeed: 'Inference Speed',
|
||||
@ -10,6 +14,11 @@ const en = {
|
||||
decoder: 'Decoder',
|
||||
encoder: 'Encoder',
|
||||
},
|
||||
cameraStorageTable: {
|
||||
usage: 'Usage',
|
||||
usagePercent: 'Usage %',
|
||||
sreamBandwidth: 'Stream Bandwidth',
|
||||
},
|
||||
cameraStatTable: {
|
||||
process: 'Process',
|
||||
pid: 'PID',
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
const ru = {
|
||||
systemPage: {
|
||||
cameraStats: 'Статистика Камер',
|
||||
storageStats: 'Статистика Хранения',
|
||||
},
|
||||
detectorCard: {
|
||||
pid: 'PID',
|
||||
inferenceSpeed: 'Скорость вывода',
|
||||
@ -10,6 +14,18 @@ const ru = {
|
||||
decoder: 'Декодер',
|
||||
encoder: 'Кодер',
|
||||
},
|
||||
cameraStorageTable: {
|
||||
usage: 'Занято',
|
||||
usagePercent: 'Занято %',
|
||||
sreamBandwidth: 'Скорость потока',
|
||||
},
|
||||
cameraStatTable: {
|
||||
process: 'Процесс',
|
||||
pid: 'PID',
|
||||
fps: 'FPS',
|
||||
cpu: 'CPU %',
|
||||
memory: 'Память %'
|
||||
},
|
||||
hostMenu: {
|
||||
editConfig: 'Редакт. конфиг.',
|
||||
restart: 'Перезагрузка',
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Flex, Grid, Text } from '@mantine/core';
|
||||
import { Flex, Grid, SegmentedControl, Text } from '@mantine/core';
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Context } from '..';
|
||||
@ -17,19 +18,24 @@ import { formatUptime } from '../shared/utils/dateUtil';
|
||||
import FrigateCamerasStateTable, { CameraItem, ProcessType } from '../widgets/camera.stat.table/FrigateCameraStateTable';
|
||||
import Forbidden from './403';
|
||||
import RetryErrorPage from './RetryErrorPage';
|
||||
import { openContextModal } from '@mantine/modals';
|
||||
import { FfprobeModalProps } from '../shared/components/modal.windows/FfprobeModal';
|
||||
import FrigateStorageStateTable from '../widgets/camera.stat.table/FrigateStorageStateTable';
|
||||
|
||||
export const hostSystemPageQuery = {
|
||||
hostId: 'hostId',
|
||||
}
|
||||
|
||||
enum SelectorItems {
|
||||
Cameras = 'cameras',
|
||||
Storage = 'storage'
|
||||
}
|
||||
|
||||
const HostSystemPage = () => {
|
||||
const { t } = useTranslation()
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const { isAdmin } = useAdminRole()
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
const [selector, setSelector] = useState(SelectorItems.Cameras)
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
@ -119,9 +125,9 @@ const HostSystemPage = () => {
|
||||
decoder={stats.dec}
|
||||
encoder={stats.enc}
|
||||
gpu={stats.gpu}
|
||||
mem={stats.mem}
|
||||
mem={stats.mem}
|
||||
onVaInfoClick={() => handleVaInfoClick()}
|
||||
/>
|
||||
/>
|
||||
</Grid.Col>
|
||||
)
|
||||
})
|
||||
@ -152,6 +158,11 @@ const HostSystemPage = () => {
|
||||
}
|
||||
})
|
||||
|
||||
const handleSelectView = (value: string) => {
|
||||
if (value === SelectorItems.Cameras) setSelector(SelectorItems.Cameras)
|
||||
else setSelector(SelectorItems.Storage)
|
||||
}
|
||||
|
||||
if (!isProduction) console.log('HostSystemPage rendered')
|
||||
|
||||
return (
|
||||
@ -168,7 +179,19 @@ const HostSystemPage = () => {
|
||||
{gpuStats}
|
||||
{detectorsStats}
|
||||
</Grid>
|
||||
<FrigateCamerasStateTable data={mappedCameraStat} onFfprobeClick={handleFfprobeClick} />
|
||||
<SegmentedControl
|
||||
value={selector}
|
||||
onChange={handleSelectView}
|
||||
data={[
|
||||
{ label: t('systemPage.cameraStats'), value: SelectorItems.Cameras },
|
||||
{ label: t('systemPage.storageStats'), value: SelectorItems.Storage },
|
||||
]}
|
||||
/>
|
||||
{selector === SelectorItems.Cameras ?
|
||||
<FrigateCamerasStateTable data={mappedCameraStat} onFfprobeClick={handleFfprobeClick} />
|
||||
:
|
||||
<FrigateStorageStateTable host={host.current} />
|
||||
}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@ -10,7 +10,7 @@ import { RecordSummary } from "../../types/record";
|
||||
import { EventFrigate } from "../../types/event";
|
||||
import { keycloakConfig } from "../..";
|
||||
import { getResolvedTimeZone } from "../../shared/utils/dateUtil";
|
||||
import { FrigateStats, GetFfprobe, GetVaInfo } from "../../types/frigateStats";
|
||||
import { FrigateStats, GetFfprobe, GetHostStorage, GetVaInfo } from "../../types/frigateStats";
|
||||
import { hostname } from "os";
|
||||
import { PostSaveConfig, SaveOption } from "../../types/saveConfig";
|
||||
|
||||
@ -192,6 +192,7 @@ export const proxyApi = {
|
||||
save_option: saveOption
|
||||
}
|
||||
}).then(res => res.data),
|
||||
getHostStorage: (hostName: string) => instanceApi.get<GetHostStorage>(`proxy/${hostName}/api/recordings/storage`).then(res => res.data),
|
||||
}
|
||||
|
||||
export const mapCamerasFromConfig = (config: FrigateConfig): string[] => {
|
||||
@ -218,6 +219,7 @@ export const frigateQueryKeys = {
|
||||
getHostStats: 'host-stats',
|
||||
getCameraFfprobe: 'camera-ffprobe',
|
||||
getHostVaInfo: 'host-vainfo',
|
||||
getHostStorage: 'host-storage',
|
||||
getRecordingsSummary: 'recordings-frigate-summary',
|
||||
getRecordings: 'recordings-frigate',
|
||||
getEvents: 'events-frigate',
|
||||
|
||||
@ -20,14 +20,18 @@ export function sortArrayByObjectIndex<T extends object>(
|
||||
|
||||
export function sortByKey<T, K extends keyof T>(array: T[], key: K): T[] {
|
||||
return array.sort((a, b) => {
|
||||
let valueA = a[key];
|
||||
let valueB = b[key];
|
||||
let valueA = a[key]
|
||||
let valueB = b[key]
|
||||
|
||||
const stringValueA = String(valueA).toLowerCase();
|
||||
const stringValueB = String(valueB).toLowerCase();
|
||||
if (typeof valueA === 'number' && typeof valueB === 'number') {
|
||||
return valueA - valueB
|
||||
} else {
|
||||
const stringValueA = String(valueA).toLowerCase()
|
||||
const stringValueB = String(valueB).toLowerCase()
|
||||
|
||||
if (stringValueA < stringValueB) return -1;
|
||||
if (stringValueA > stringValueB) return 1;
|
||||
return 0;
|
||||
if (stringValueA < stringValueB) return -1
|
||||
if (stringValueA > stringValueB) return 1
|
||||
}
|
||||
return 0
|
||||
});
|
||||
}
|
||||
@ -1,3 +1,13 @@
|
||||
export interface GetHostStorage {
|
||||
[cameraName: string]: CameraStorage
|
||||
}
|
||||
|
||||
export interface CameraStorage {
|
||||
bandwidth: number // MiB/hr
|
||||
usage: number // MB
|
||||
usage_percent: number // Usage / 1024 / Total storage size * 100
|
||||
}
|
||||
|
||||
export interface GetVaInfo {
|
||||
return_code: number
|
||||
stderr: string
|
||||
|
||||
5
src/types/table.ts
Normal file
5
src/types/table.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface TableHead {
|
||||
propertyName: string,
|
||||
title: string,
|
||||
sorting?: boolean,
|
||||
}
|
||||
@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import SortedTh from '../../shared/components/table.aps/SortedTh';
|
||||
import { isProduction } from '../../shared/env.const';
|
||||
import { sortByKey } from '../../shared/utils/sort.array';
|
||||
import { TableHead } from '../../types/table';
|
||||
|
||||
|
||||
export interface CameraItem {
|
||||
@ -23,12 +24,6 @@ export enum ProcessType {
|
||||
Detect = "Detect",
|
||||
}
|
||||
|
||||
interface TableHead {
|
||||
propertyName: string,
|
||||
title: string,
|
||||
sorting?: boolean,
|
||||
}
|
||||
|
||||
interface TableProps<T> {
|
||||
data: T[],
|
||||
onFfprobeClick?: (cameraName: string) => void
|
||||
|
||||
122
src/widgets/camera.stat.table/FrigateStorageStateTable.tsx
Normal file
122
src/widgets/camera.stat.table/FrigateStorageStateTable.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../services/frigate.proxy/frigate.api';
|
||||
import { GetFrigateHost } from '../../services/frigate.proxy/frigate.schema';
|
||||
import CogwheelLoader from '../../shared/components/loaders/CogwheelLoader';
|
||||
import RetryError from '../../shared/components/RetryError';
|
||||
import { Center, Flex, Table, Text } from '@mantine/core';
|
||||
import { TableHead } from '../../types/table';
|
||||
import SortedTh from '../../shared/components/table.aps/SortedTh';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { sortByKey } from '../../shared/utils/sort.array';
|
||||
import { formatMBytes } from '../../shared/utils/data.size';
|
||||
|
||||
|
||||
export interface StorageItem {
|
||||
cameraName: string
|
||||
usage: number
|
||||
usagePercent: number
|
||||
sreamBandwidth: number // MiB/hr
|
||||
}
|
||||
|
||||
interface TableProps {
|
||||
host?: GetFrigateHost
|
||||
}
|
||||
|
||||
const FrigateStorageStateTable: React.FC<TableProps> = ({
|
||||
host
|
||||
}) => {
|
||||
|
||||
const [reversed, setReversed] = useState(false)
|
||||
const [sortedName, setSortedName] = useState<string | null>(null)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data, isError, isPending, refetch } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getHostStorage, host?.id],
|
||||
queryFn: () => {
|
||||
const hostName = mapHostToHostname(host)
|
||||
if (!hostName) return null
|
||||
return proxyApi.getHostStorage(hostName)
|
||||
}
|
||||
})
|
||||
|
||||
const mapToTable = useCallback(() => {
|
||||
if (!data) return []
|
||||
return Object.entries(data).map<StorageItem>(([name, storage]) => {
|
||||
return {
|
||||
cameraName: name,
|
||||
usage: storage.usage,
|
||||
usagePercent: storage.usage_percent,
|
||||
sreamBandwidth: storage.bandwidth,
|
||||
}
|
||||
})
|
||||
}, [data])
|
||||
|
||||
const [tableData, setTableData] = useState<StorageItem[]>(mapToTable())
|
||||
|
||||
useEffect( () => {
|
||||
setTableData(mapToTable())
|
||||
}, [data])
|
||||
|
||||
const handleSort = (headName: string, propertyName: string,) => {
|
||||
if (!data || !tableData) return
|
||||
const reverse = headName === sortedName ? !reversed : false;
|
||||
setReversed(reverse)
|
||||
const arr = sortByKey(tableData, propertyName as keyof StorageItem)
|
||||
if (reverse) arr.reverse()
|
||||
setTableData(arr)
|
||||
setSortedName(headName)
|
||||
}
|
||||
|
||||
if (isPending) return <CogwheelLoader />
|
||||
if (isError) return <RetryError onRetry={refetch} />
|
||||
// if (!tableData || tableData.length < 1) return <Center><Text>Empty response</Text></Center>
|
||||
|
||||
|
||||
const headTitle: TableHead[] = [
|
||||
{ propertyName: 'cameraName', title: t('camera') },
|
||||
{ propertyName: 'usage', title: t('cameraStorageTable.usage') },
|
||||
{ propertyName: 'usagePercent', title: t('cameraStorageTable.usagePercent') },
|
||||
{ propertyName: 'sreamBandwidth', title: t('cameraStorageTable.sreamBandwidth') },
|
||||
]
|
||||
|
||||
const tableHead = headTitle.map(head => {
|
||||
return (
|
||||
<SortedTh
|
||||
key={uuidv4()}
|
||||
title={head.title}
|
||||
reversed={reversed}
|
||||
sortedName={sortedName}
|
||||
onSort={() => handleSort(head.title, head.propertyName ? head.propertyName : '')}
|
||||
sorting={head.sorting} />
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
const rows = tableData.map(item => {
|
||||
return (
|
||||
<tr key={item.cameraName}>
|
||||
<td><Text align='center'>{item.cameraName}</Text></td>
|
||||
<td><Text align='center'>{formatMBytes(item.usage)}</Text></td>
|
||||
<td><Text align='center'>{item.usagePercent.toFixed(4)} %</Text></td>
|
||||
<td><Text align='center'>{item.sreamBandwidth} MiB/hr</Text></td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Table >
|
||||
<thead>
|
||||
<tr>
|
||||
{tableHead}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrigateStorageStateTable;
|
||||
Loading…
Reference in New Issue
Block a user