add save config
This commit is contained in:
parent
342940d27f
commit
529885498d
@ -9,7 +9,8 @@
|
|||||||
"@mantine/core": "^6.0.16",
|
"@mantine/core": "^6.0.16",
|
||||||
"@mantine/dates": "^6.0.16",
|
"@mantine/dates": "^6.0.16",
|
||||||
"@mantine/hooks": "^6.0.16",
|
"@mantine/hooks": "^6.0.16",
|
||||||
"@mantine/notifications": "6.0.16",
|
"@mantine/modals": "^6.0.16",
|
||||||
|
"@mantine/notifications": "^6.0.16",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@tabler/icons-react": "^2.24.0",
|
"@tabler/icons-react": "^2.24.0",
|
||||||
"@tanstack/react-query": "^5.21.2",
|
"@tanstack/react-query": "^5.21.2",
|
||||||
|
|||||||
20
src/App.tsx
20
src/App.tsx
@ -11,6 +11,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||||||
import RetryErrorPage from './pages/RetryErrorPage';
|
import RetryErrorPage from './pages/RetryErrorPage';
|
||||||
import { keycloakConfig } from '.';
|
import { keycloakConfig } from '.';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
|
import { FfprobeModal } from './shared/components/modal.windows/FfprobeModal';
|
||||||
|
import { VaInfoModal } from './shared/components/modal.windows/VaInfoModal';
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
@ -20,6 +23,17 @@ const queryClient = new QueryClient({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const modals = {
|
||||||
|
ffprobeModal: FfprobeModal,
|
||||||
|
vaInfoModal: VaInfoModal,
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@mantine/modals' {
|
||||||
|
export interface MantineModalsOverride {
|
||||||
|
modals: typeof modals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const maxErrorAuthCounts = 2
|
const maxErrorAuthCounts = 2
|
||||||
const systemColorScheme = useColorScheme()
|
const systemColorScheme = useColorScheme()
|
||||||
@ -104,8 +118,10 @@ function App() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Notifications />
|
<ModalsProvider modals={modals}>
|
||||||
<AppBody />
|
<Notifications />
|
||||||
|
<AppBody />
|
||||||
|
</ModalsProvider>
|
||||||
</MantineProvider >
|
</MantineProvider >
|
||||||
</ColorSchemeProvider>
|
</ColorSchemeProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useRef } from 'react';
|
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import { Button, Flex, useMantineTheme } from '@mantine/core';
|
import { Button, Flex, useMantineTheme, Text } from '@mantine/core';
|
||||||
import { useClipboard } from '@mantine/hooks';
|
import { useClipboard } from '@mantine/hooks';
|
||||||
import Editor, { Monaco } from '@monaco-editor/react'
|
import Editor, { Monaco } from '@monaco-editor/react'
|
||||||
import * as monaco from "monaco-editor";
|
import * as monaco from "monaco-editor";
|
||||||
@ -13,11 +13,16 @@ import { useAdminRole } from '../hooks/useAdminRole';
|
|||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { isProduction } from '../shared/env.const';
|
import { isProduction } from '../shared/env.const';
|
||||||
|
import { SaveOption } from '../types/saveConfig';
|
||||||
|
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||||
|
import { error } from 'console';
|
||||||
|
|
||||||
|
|
||||||
const HostConfigPage = () => {
|
const HostConfigPage = () => {
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
|
const host = useRef<GetFrigateHost | undefined>()
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
|
const [saveMessage, setSaveMessage] = useState<string>()
|
||||||
|
|
||||||
let { id } = useParams<'id'>()
|
let { id } = useParams<'id'>()
|
||||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||||
@ -25,22 +30,42 @@ const HostConfigPage = () => {
|
|||||||
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
||||||
queryKey: [frigateQueryKeys.getFrigateHost, id],
|
queryKey: [frigateQueryKeys.getFrigateHost, id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const host = await frigateApi.getHost(id || '')
|
host.current = await frigateApi.getHost(id || '')
|
||||||
const hostName = mapHostToHostname(host)
|
const hostName = mapHostToHostname(host.current)
|
||||||
if (hostName)
|
if (!hostName) return null
|
||||||
return proxyApi.getHostConfigRaw(hostName)
|
return proxyApi.getHostConfigRaw(hostName)
|
||||||
return null
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { mutate: saveConfig } = useMutation({
|
||||||
|
mutationKey: [frigateQueryKeys.postHostConfig],
|
||||||
|
mutationFn: ({ saveOption, config }: { saveOption: SaveOption, config: string }) => {
|
||||||
|
const hostName = mapHostToHostname(host.current)
|
||||||
|
if (!hostName || !editorRef.current) return Promise.resolve(null)
|
||||||
|
return proxyApi.postHostConfig(hostName, saveOption, config)
|
||||||
|
.catch(error => {
|
||||||
|
if (error.response && error.response.data) {
|
||||||
|
return Promise.reject(error.response.data)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSuccess: (data) => {
|
||||||
|
setSaveMessage(data?.message)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
setSaveMessage(error.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!executed.current) {
|
if (!executed.current) {
|
||||||
sideBarsStore.rightVisible = false
|
sideBarsStore.rightVisible = false
|
||||||
sideBarsStore.setLeftChildren(null)
|
sideBarsStore.setLeftChildren(null)
|
||||||
sideBarsStore.setRightChildren(null)
|
sideBarsStore.setRightChildren(null)
|
||||||
executed.current = true
|
executed.current = true
|
||||||
}
|
}
|
||||||
}, [sideBarsStore])
|
}, [sideBarsStore])
|
||||||
|
|
||||||
const clipboard = useClipboard({ timeout: 500 })
|
const clipboard = useClipboard({ timeout: 500 })
|
||||||
|
|
||||||
@ -81,11 +106,13 @@ const HostConfigPage = () => {
|
|||||||
}, [editorRef, clipboard]);
|
}, [editorRef, clipboard]);
|
||||||
|
|
||||||
const onHandleSaveConfig = useCallback(
|
const onHandleSaveConfig = useCallback(
|
||||||
async (save_option: string) => {
|
async (saveOption: SaveOption) => {
|
||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
return;
|
throw Error('Editor does not exists')
|
||||||
}
|
}
|
||||||
if (!isProduction) console.log('save config', save_option)
|
if (!isProduction) console.log('saveOption', saveOption)
|
||||||
|
if (!isProduction) console.log('editorRef.current', editorRef.current.getValue().slice(0, 50))
|
||||||
|
saveConfig({ saveOption: saveOption, config: editorRef.current.getValue() })
|
||||||
}, [editorRef])
|
}, [editorRef])
|
||||||
|
|
||||||
if (configPending || adminLoading) return <CenterLoader />
|
if (configPending || adminLoading) return <CenterLoader />
|
||||||
@ -99,14 +126,19 @@ const HostConfigPage = () => {
|
|||||||
<Button size="sm" onClick={handleCopyConfig}>
|
<Button size="sm" onClick={handleCopyConfig}>
|
||||||
Copy Config
|
Copy Config
|
||||||
</Button>
|
</Button>
|
||||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig("restart")}>
|
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig(SaveOption.SaveRestart)}>
|
||||||
Save & Restart
|
Save & Restart
|
||||||
</Button>
|
</Button>
|
||||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig("saveonly")}>
|
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig(SaveOption.SaveOnly)}>
|
||||||
Save Only
|
Save Only
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex h='100%'>
|
{!saveMessage ? null :
|
||||||
|
<Flex w='100%' justify='center' wrap='nowrap' mt='1rem'>
|
||||||
|
<Text>{saveMessage}</Text>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
<Flex h='100%' mt='1rem'>
|
||||||
<Editor
|
<Editor
|
||||||
defaultLanguage='yaml'
|
defaultLanguage='yaml'
|
||||||
value={config}
|
value={config}
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
import { Flex, Grid, Text } from '@mantine/core';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { useCallback, useContext, useEffect, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
import { Context } from '..';
|
import { Context } from '..';
|
||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import Forbidden from './403';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
import { frigateApi, frigateQueryKeys, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
|
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
import RetryErrorPage from './RetryErrorPage';
|
|
||||||
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';
|
import DetectorsStat from '../shared/components/stats/DetectorsStat';
|
||||||
|
import GpuStat from '../shared/components/stats/GpuStat';
|
||||||
|
import StorageRingStat from '../shared/components/stats/StorageRingStat';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
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';
|
||||||
|
|
||||||
export const hostSystemPageQuery = {
|
export const hostSystemPageQuery = {
|
||||||
hostId: 'hostId',
|
hostId: 'hostId',
|
||||||
@ -26,6 +29,7 @@ const HostSystemPage = () => {
|
|||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
const { sideBarsStore } = useContext(Context)
|
const { sideBarsStore } = useContext(Context)
|
||||||
const { isAdmin } = useAdminRole()
|
const { isAdmin } = useAdminRole()
|
||||||
|
const host = useRef<GetFrigateHost | undefined>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!executed.current) {
|
if (!executed.current) {
|
||||||
@ -42,11 +46,13 @@ const HostSystemPage = () => {
|
|||||||
queryKey: [frigateQueryKeys.getHostStats, paramHostId],
|
queryKey: [frigateQueryKeys.getHostStats, paramHostId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!paramHostId) return null
|
if (!paramHostId) return null
|
||||||
const host = await frigateApi.getHost(paramHostId)
|
host.current = await frigateApi.getHost(paramHostId)
|
||||||
const hostName = mapHostToHostname(host)
|
const hostName = mapHostToHostname(host.current)
|
||||||
if (!hostName) return null
|
if (!hostName) return null
|
||||||
return proxyApi.getHostStats(hostName)
|
return proxyApi.getHostStats(hostName)
|
||||||
}
|
},
|
||||||
|
staleTime: 2 * 60 * 1000,
|
||||||
|
refetchInterval: 60 * 1000,
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapCameraData = useCallback(() => {
|
const mapCameraData = useCallback(() => {
|
||||||
@ -72,9 +78,9 @@ const HostSystemPage = () => {
|
|||||||
});
|
});
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
if (!isAdmin) return <Forbidden />
|
|
||||||
if (isPending) return <CenterLoader />
|
if (isPending) return <CenterLoader />
|
||||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||||
|
if (!isAdmin) return <Forbidden />
|
||||||
if (!paramHostId || !data) return null
|
if (!paramHostId || !data) return null
|
||||||
|
|
||||||
const mappedCameraStat: CameraItem[] = mapCameraData()
|
const mappedCameraStat: CameraItem[] = mapCameraData()
|
||||||
@ -97,6 +103,14 @@ const HostSystemPage = () => {
|
|||||||
return `${time.value.toFixed(1)} ${translatedUnit}`
|
return `${time.value.toFixed(1)} ${translatedUnit}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleVaInfoClick = () => openContextModal({
|
||||||
|
modal: 'vaInfoModal',
|
||||||
|
title: 'VaInfo',
|
||||||
|
innerProps: {
|
||||||
|
hostName: mapHostToHostname(host.current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const gpuStats = Object.entries(data.gpu_usages).map(([name, stats]) => {
|
const gpuStats = Object.entries(data.gpu_usages).map(([name, stats]) => {
|
||||||
return (
|
return (
|
||||||
<Grid.Col key={name + stats.gpu} xs={7} sm={6} md={5} lg={4} p='0.2rem'>
|
<Grid.Col key={name + stats.gpu} xs={7} sm={6} md={5} lg={4} p='0.2rem'>
|
||||||
@ -105,7 +119,9 @@ const HostSystemPage = () => {
|
|||||||
decoder={stats.dec}
|
decoder={stats.dec}
|
||||||
encoder={stats.enc}
|
encoder={stats.enc}
|
||||||
gpu={stats.gpu}
|
gpu={stats.gpu}
|
||||||
mem={stats.mem} />
|
mem={stats.mem}
|
||||||
|
onVaInfoClick={() => handleVaInfoClick()}
|
||||||
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -127,10 +143,22 @@ const HostSystemPage = () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handleFfprobeClick = (cameraName: string) => openContextModal({
|
||||||
|
modal: 'ffprobeModal',
|
||||||
|
title: 'Ffprobe',
|
||||||
|
innerProps: {
|
||||||
|
hostName: mapHostToHostname(host.current),
|
||||||
|
cameraName: cameraName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isProduction) console.log('HostSystemPage rendered')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column'>
|
<Flex w='100%' h='100%' direction='column'>
|
||||||
<Flex w='100%' justify='space-around'>
|
<Flex w='100%' justify='space-around' align='baseline'>
|
||||||
<Text>{t('version')} : {data.service.version}</Text>
|
<Text>{t('version')} : {data.service.version}</Text>
|
||||||
|
<Text size='xl' w='900'>{host.current?.name}</Text>
|
||||||
<Text>{t('uptime')} : {formattedUptime()}</Text>
|
<Text>{t('uptime')} : {formattedUptime()}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||||
@ -140,7 +168,7 @@ const HostSystemPage = () => {
|
|||||||
{gpuStats}
|
{gpuStats}
|
||||||
{detectorsStats}
|
{detectorsStats}
|
||||||
</Grid>
|
</Grid>
|
||||||
<FrigateCamerasStateTable data={mappedCameraStat} />
|
<FrigateCamerasStateTable data={mappedCameraStat} onFfprobeClick={handleFfprobeClick} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { observer } from 'mobx-react-lite';
|
|||||||
|
|
||||||
export const playRecordPageQuery = {
|
export const playRecordPageQuery = {
|
||||||
link: 'link',
|
link: 'link',
|
||||||
// hostName: 'hostName',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayRecordPage = () => {
|
const PlayRecordPage = () => {
|
||||||
@ -26,7 +25,6 @@ const PlayRecordPage = () => {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const queryParams = new URLSearchParams(location.search)
|
const queryParams = new URLSearchParams(location.search)
|
||||||
const paramLink = queryParams.get(playRecordPageQuery.link)
|
const paramLink = queryParams.get(playRecordPageQuery.link)
|
||||||
// const paramHostName = queryParams.get(playRecordPageQuery.hostName);
|
|
||||||
|
|
||||||
if (!paramLink) return (<NotFound />)
|
if (!paramLink) return (<NotFound />)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -96,7 +96,6 @@ const RecordingsPage = () => {
|
|||||||
navigate({ pathname: location.pathname, search: queryParams.toString() });
|
navigate({ pathname: location.pathname, search: queryParams.toString() });
|
||||||
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
|
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
|
||||||
|
|
||||||
if (!isProduction) console.log('RecordingsPage rendered')
|
|
||||||
|
|
||||||
const [startDay, endDay] = period
|
const [startDay, endDay] = period
|
||||||
if (startDay && endDay) {
|
if (startDay && endDay) {
|
||||||
@ -115,6 +114,8 @@ const RecordingsPage = () => {
|
|||||||
return <SelectedHostList hostId={hostId} />
|
return <SelectedHostList hostId={hostId} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isProduction) console.log('RecordingsPage rendered')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column' justify='center' align='center'>
|
<Flex w='100%' h='100%' direction='column' justify='center' align='center'>
|
||||||
{!hostId ?
|
{!hostId ?
|
||||||
|
|||||||
@ -10,7 +10,9 @@ import { RecordSummary } from "../../types/record";
|
|||||||
import { EventFrigate } from "../../types/event";
|
import { EventFrigate } from "../../types/event";
|
||||||
import { keycloakConfig } from "../..";
|
import { keycloakConfig } from "../..";
|
||||||
import { getResolvedTimeZone } from "../../shared/utils/dateUtil";
|
import { getResolvedTimeZone } from "../../shared/utils/dateUtil";
|
||||||
import { FrigateStats } from "../../types/frigateStats";
|
import { FrigateStats, GetFfprobe, GetVaInfo } from "../../types/frigateStats";
|
||||||
|
import { hostname } from "os";
|
||||||
|
import { PostSaveConfig, SaveOption } from "../../types/saveConfig";
|
||||||
|
|
||||||
|
|
||||||
export const getToken = (): string | undefined => {
|
export const getToken = (): string | undefined => {
|
||||||
@ -176,7 +178,19 @@ export const proxyApi = {
|
|||||||
getVideoUrl: (hostName: string, fileName: string) => `${proxyPrefix}${hostName}/exports/${fileName}`,
|
getVideoUrl: (hostName: string, fileName: string) => `${proxyPrefix}${hostName}/exports/${fileName}`,
|
||||||
// filename example Home_1_Backyard_2024_02_26_16_25__2024_02_26_16_26.mp4
|
// filename example Home_1_Backyard_2024_02_26_16_25__2024_02_26_16_26.mp4
|
||||||
deleteExportedVideo: (hostName: string, videoName: string) => instanceApi.delete(`proxy/${hostName}/api/export/${videoName}`).then(res => res.data),
|
deleteExportedVideo: (hostName: string, videoName: string) => instanceApi.delete(`proxy/${hostName}/api/export/${videoName}`).then(res => res.data),
|
||||||
getHostStats: (hostName: string) => instanceApi.get<FrigateStats>(`proxy/${hostName}/api/stats`).then( res => res.data),
|
getHostStats: (hostName: string) => instanceApi.get<FrigateStats>(`proxy/${hostName}/api/stats`).then(res => res.data),
|
||||||
|
getCameraFfprobe: (hostName: string, cameraName: string) =>
|
||||||
|
instanceApi.get<GetFfprobe[]>(`proxy/${hostName}/api/ffprobe`, { params: { paths: `camera:${cameraName}` } }).then(res => res.data),
|
||||||
|
getHostVaInfo: (hostName: string) => instanceApi.get<GetVaInfo>(`proxy/${hostName}/api/vainfo`).then(res => res.data),
|
||||||
|
postHostConfig: (hostName: string, saveOption: SaveOption, config: string) =>
|
||||||
|
instanceApi.post<PostSaveConfig>(`proxy/${hostName}/api/config/save`, config, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
save_option: saveOption
|
||||||
|
}
|
||||||
|
}).then(res => res.data),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapCamerasFromConfig = (config: FrigateConfig): string[] => {
|
export const mapCamerasFromConfig = (config: FrigateConfig): string[] => {
|
||||||
@ -199,7 +213,10 @@ export const frigateQueryKeys = {
|
|||||||
getCameraWHost: 'camera-frigate-host',
|
getCameraWHost: 'camera-frigate-host',
|
||||||
getCameraByHostId: 'camera-by-hostId',
|
getCameraByHostId: 'camera-by-hostId',
|
||||||
getHostConfig: 'host-config',
|
getHostConfig: 'host-config',
|
||||||
|
postHostConfig: 'host-config-save',
|
||||||
getHostStats: 'host-stats',
|
getHostStats: 'host-stats',
|
||||||
|
getCameraFfprobe: 'camera-ffprobe',
|
||||||
|
getHostVaInfo: 'host-vainfo',
|
||||||
getRecordingsSummary: 'recordings-frigate-summary',
|
getRecordingsSummary: 'recordings-frigate-summary',
|
||||||
getRecordings: 'recordings-frigate',
|
getRecordings: 'recordings-frigate',
|
||||||
getEvents: 'events-frigate',
|
getEvents: 'events-frigate',
|
||||||
|
|||||||
62
src/shared/components/modal.windows/FfprobeModal.tsx
Normal file
62
src/shared/components/modal.windows/FfprobeModal.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query"
|
||||||
|
import { frigateQueryKeys, proxyApi } from "../../../services/frigate.proxy/frigate.api"
|
||||||
|
import CogwheelLoader from "../loaders/CogwheelLoader"
|
||||||
|
import RetryError from "../RetryError"
|
||||||
|
import { Button, Center, Text } from "@mantine/core"
|
||||||
|
import { ContextModalProps } from "@mantine/modals"
|
||||||
|
|
||||||
|
export interface FfprobeModalProps {
|
||||||
|
hostName?: string
|
||||||
|
cameraName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FfprobeModal = ({ context, id, innerProps }: ContextModalProps<FfprobeModalProps>) => {
|
||||||
|
const { hostName, cameraName } = innerProps
|
||||||
|
const { data, isError, isPending, refetch } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getCameraFfprobe, hostName, cameraName],
|
||||||
|
queryFn: () => {
|
||||||
|
if (!hostName) return null
|
||||||
|
return proxyApi.getCameraFfprobe(hostName, cameraName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isPending) return <CogwheelLoader />
|
||||||
|
if (isError) return <RetryError onRetry={refetch} />
|
||||||
|
if (!data || data.length < 1) return <Text>Data is empty</Text>
|
||||||
|
|
||||||
|
const streamItems = data.map((res, streamIndex) => {
|
||||||
|
if (res.return_code !== 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Center><Text weight={700}>Stream: {streamIndex}</Text></Center>
|
||||||
|
<Text>{res.return_code}</Text>
|
||||||
|
<Text>{res.stderr}</Text>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const flows = res.stdout.streams.map((stream) => (
|
||||||
|
<>
|
||||||
|
<Text>Codec: {stream.codec_long_name}</Text>
|
||||||
|
{!stream.width && !stream.height ? null :
|
||||||
|
<Text>Resolution: {stream.width}x{stream.height} </Text>
|
||||||
|
}
|
||||||
|
<Text>FPS: {stream.avg_frame_rate}</Text>
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Center><Text weight={700}>Stream: {streamIndex}</Text></Center>
|
||||||
|
{flows}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{streamItems}
|
||||||
|
<Center>
|
||||||
|
<Button onClick={() => context.closeModal(id)}>Close</Button >
|
||||||
|
</Center>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -47,7 +47,7 @@ interface FullImageModalProps {
|
|||||||
close?(): void
|
close?(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
const FullImageModal = observer(({ images, opened, open, close }: FullImageModalProps) => {
|
const FullImageModal = ({ images, opened, open, close }: FullImageModalProps) => {
|
||||||
const { modalStore } = useContext(Context)
|
const { modalStore } = useContext(Context)
|
||||||
const { isFullImageOpened, fullImageData, closeFullImage } = modalStore
|
const { isFullImageOpened, fullImageData, closeFullImage } = modalStore
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
@ -105,6 +105,6 @@ const FullImageModal = observer(({ images, opened, open, close }: FullImageModal
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
|
|
||||||
export default FullImageModal;
|
export default observer(FullImageModal)
|
||||||
@ -1,91 +0,0 @@
|
|||||||
import { ActionIcon, CloseButton, Flex, Modal, NumberInput, TextInput, Tooltip, createStyles, } from '@mantine/core';
|
|
||||||
import { getHotkeyHandler, useMediaQuery } from '@mantine/hooks';
|
|
||||||
import React, { ReactEventHandler, useState, FocusEvent, useRef, Ref } from 'react';
|
|
||||||
import { IconAlertCircle, IconX } from '@tabler/icons-react';
|
|
||||||
import { dimensions } from '../../dimensions/dimensions';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
rightSection: {
|
|
||||||
width: '3rem',
|
|
||||||
marginRight: '0.2rem',
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
interface InputModalProps {
|
|
||||||
inValue: number
|
|
||||||
putValue?(value: number): void
|
|
||||||
opened: boolean
|
|
||||||
open(): void
|
|
||||||
close(): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const InputModal = ({ inValue, putValue, opened, open, close }: InputModalProps) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { classes } = useStyles()
|
|
||||||
const [value, setValue] = useState(inValue)
|
|
||||||
const isMobile = useMediaQuery(dimensions.mobileSize)
|
|
||||||
|
|
||||||
const refInput: React.LegacyRef<HTMLInputElement> = useRef(null)
|
|
||||||
|
|
||||||
const handeLoaded = (event: FocusEvent<HTMLInputElement, Element>) => {
|
|
||||||
event.target.select()
|
|
||||||
}
|
|
||||||
const handeClear = () => {
|
|
||||||
setValue(0)
|
|
||||||
refInput.current?.select()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSetValue = (value: number | "") => {
|
|
||||||
if (typeof value === "number") {
|
|
||||||
setValue(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
if (putValue) putValue(value)
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
opened={opened}
|
|
||||||
onClose={handleClose}
|
|
||||||
withCloseButton={false}
|
|
||||||
centered
|
|
||||||
fullScreen={isMobile}
|
|
||||||
>
|
|
||||||
<Flex justify="space-between">
|
|
||||||
<div>{t('enterQuantity')}</div>
|
|
||||||
<CloseButton size="lg" onClick={handleClose} />
|
|
||||||
</Flex>
|
|
||||||
<NumberInput
|
|
||||||
ref={refInput}
|
|
||||||
classNames={classes}
|
|
||||||
type="number"
|
|
||||||
value={value}
|
|
||||||
onChange={handleSetValue}
|
|
||||||
data-autofocus
|
|
||||||
placeholder={t('quantity')}
|
|
||||||
hideControls
|
|
||||||
min={0}
|
|
||||||
onFocus={handeLoaded}
|
|
||||||
onKeyDown={
|
|
||||||
getHotkeyHandler([
|
|
||||||
['Enter', handleClose]
|
|
||||||
])
|
|
||||||
}
|
|
||||||
rightSection={
|
|
||||||
<Flex w='100%' h='100%' justify='right' align='center'>
|
|
||||||
<Tooltip label={t('tooltipСlose')} position="top-end" withArrow>
|
|
||||||
<div>
|
|
||||||
<IconAlertCircle size="1.4rem" style={{ display: 'block', opacity: 0.5 }} />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InputModal;
|
|
||||||
41
src/shared/components/modal.windows/VaInfoModal.tsx
Normal file
41
src/shared/components/modal.windows/VaInfoModal.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import React from 'react';
|
||||||
|
import { frigateQueryKeys, proxyApi } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import CogwheelLoader from '../loaders/CogwheelLoader';
|
||||||
|
import RetryError from '../RetryError';
|
||||||
|
import { Button, Center, Flex, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
interface VaInfoModalProps {
|
||||||
|
hostName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VaInfoModal = ({
|
||||||
|
context,
|
||||||
|
id,
|
||||||
|
innerProps
|
||||||
|
}: ContextModalProps<VaInfoModalProps>) => {
|
||||||
|
const { hostName } = innerProps
|
||||||
|
const { data, isError, isPending, refetch } = useQuery({
|
||||||
|
queryKey: [frigateQueryKeys.getHostVaInfo, hostName],
|
||||||
|
queryFn: () => {
|
||||||
|
if (!hostName) return null
|
||||||
|
return proxyApi.getHostVaInfo(hostName)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isPending) return <CogwheelLoader />
|
||||||
|
if (isError) return <RetryError onRetry={refetch} />
|
||||||
|
if (!data) return <Text>Data is empty</Text>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction='column' w='100%'>
|
||||||
|
<Text>Return code: {data.return_code}</Text>
|
||||||
|
{data.stderr ? <Text>{data.stderr}</Text> : null}
|
||||||
|
<Text>{data.stdout}</Text>
|
||||||
|
<Center>
|
||||||
|
<Button onClick={() => context.closeModal(id)}>Close</Button >
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { Card, Flex, Group, Text } from '@mantine/core';
|
import { Card, Flex, Group, Text } from '@mantine/core';
|
||||||
|
import { IconZoomCheck, IconZoomQuestion } from '@tabler/icons-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -7,7 +8,8 @@ interface GpuStatProps {
|
|||||||
decoder?: string,
|
decoder?: string,
|
||||||
encoder?: string,
|
encoder?: string,
|
||||||
gpu?: string,
|
gpu?: string,
|
||||||
mem?: string
|
mem?: string,
|
||||||
|
onVaInfoClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const GpuStat: React.FC<GpuStatProps> = ({
|
const GpuStat: React.FC<GpuStatProps> = ({
|
||||||
@ -15,13 +17,19 @@ const GpuStat: React.FC<GpuStatProps> = ({
|
|||||||
decoder,
|
decoder,
|
||||||
encoder,
|
encoder,
|
||||||
gpu,
|
gpu,
|
||||||
mem
|
mem,
|
||||||
|
onVaInfoClick
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Card withBorder radius="md" p='0.7rem'>
|
<Card withBorder radius="md" p='0.7rem'>
|
||||||
<Flex align='center'>
|
<Flex align='center'>
|
||||||
<Text c="dimmed" size="xs" tt="uppercase" fw={700} mr='0.5rem'>
|
<IconZoomQuestion
|
||||||
|
size='2rem'
|
||||||
|
color='cyan'
|
||||||
|
cursor='pointer'
|
||||||
|
onClick={onVaInfoClick} />
|
||||||
|
<Text ml='0.5rem' c="dimmed" size="xs" tt="uppercase" fw={700} mr='0.5rem'>
|
||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
<Flex w='100%' direction='column' align='center'>
|
<Flex w='100%' direction='column' align='center'>
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
import { ActionIcon, Badge, Box, Flex, Text, useMantineTheme } from '@mantine/core';
|
|
||||||
import { useCounter, useDisclosure } from '@mantine/hooks';
|
|
||||||
import { IconMinus, IconPlus, IconX } from '@tabler/icons-react';
|
|
||||||
import InputModal from '../modal.windows/InputModal';
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
interface RowCounterProps {
|
|
||||||
counter?: number
|
|
||||||
setValue?(value: number): void,
|
|
||||||
showDelete?: boolean
|
|
||||||
onDelete?(): void
|
|
||||||
}
|
|
||||||
|
|
||||||
const RowCounter = ({ counter, setValue, showDelete, onDelete }: RowCounterProps) => {
|
|
||||||
const [opened, { open, close }] = useDisclosure(false)
|
|
||||||
// const [count, handlers] = useCounter(counter, { min: 0 })
|
|
||||||
const count = counter || 0
|
|
||||||
|
|
||||||
const handleSetValue = (value: number) => {
|
|
||||||
if (setValue) setValue(value)
|
|
||||||
// else handlers.set(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOpen = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInrease = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
handleSetValue(count + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDerease = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
handleSetValue(count - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
if (onDelete) onDelete()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<InputModal key={uuidv4()} inValue={counter ? counter : count} putValue={handleSetValue} opened={opened} open={open} close={close} />
|
|
||||||
<Flex direction="row">
|
|
||||||
<ActionIcon onClick={handleDerease} mt="0.1rem" color="red.3" size="md" radius="xl" variant="filled">
|
|
||||||
<IconMinus size="1.125rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
<Box w="3rem">
|
|
||||||
<Badge size="xl" pl="0.2rem" pr="0.2rem" fullWidth onClick={handleOpen}>
|
|
||||||
{count}
|
|
||||||
</Badge>
|
|
||||||
</Box>
|
|
||||||
<ActionIcon onClick={handleInrease} mt="0.1rem" color="blue.6" size="md" radius="xl" variant="filled">
|
|
||||||
<IconPlus size="1.125rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
{
|
|
||||||
showDelete ?
|
|
||||||
<ActionIcon onClick={handleDelete} ml='0.1rem' mt="0.1rem" color="red" size="md" radius="xl" variant="filled">
|
|
||||||
<IconX size="1.125rem" />
|
|
||||||
</ActionIcon>
|
|
||||||
:
|
|
||||||
<></>
|
|
||||||
}
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RowCounter;
|
|
||||||
@ -1,3 +1,27 @@
|
|||||||
|
export interface GetVaInfo {
|
||||||
|
return_code: number
|
||||||
|
stderr: string
|
||||||
|
stdout: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetFfprobe {
|
||||||
|
return_code: number
|
||||||
|
stderr: string
|
||||||
|
stdout: Stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stdout {
|
||||||
|
programs: any[]
|
||||||
|
streams: Stream[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stream {
|
||||||
|
avg_frame_rate: string // FPS
|
||||||
|
codec_long_name: string // Codec
|
||||||
|
height: number
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface FrigateStats {
|
export interface FrigateStats {
|
||||||
cameras: {
|
cameras: {
|
||||||
[cameraName: string]: CameraStat
|
[cameraName: string]: CameraStat
|
||||||
@ -10,7 +34,7 @@ export interface FrigateStats {
|
|||||||
[detectorName: string]: DetectorStat
|
[detectorName: string]: DetectorStat
|
||||||
}
|
}
|
||||||
gpu_usages: {
|
gpu_usages: {
|
||||||
[gpuName: string] : GpuStat
|
[gpuName: string]: GpuStat
|
||||||
}
|
}
|
||||||
processes: Processes
|
processes: Processes
|
||||||
service: Service
|
service: Service
|
||||||
@ -71,7 +95,7 @@ export interface Service {
|
|||||||
last_updated: number
|
last_updated: number
|
||||||
latest_version: string
|
latest_version: string
|
||||||
storage: {
|
storage: {
|
||||||
[storagePath: string] : StorageStat
|
[storagePath: string]: StorageStat
|
||||||
}
|
}
|
||||||
temperatures: Temperatures
|
temperatures: Temperatures
|
||||||
uptime: number
|
uptime: number
|
||||||
|
|||||||
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@ -6,4 +6,5 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
9
src/types/saveConfig.ts
Normal file
9
src/types/saveConfig.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface PostSaveConfig {
|
||||||
|
message: string,
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SaveOption {
|
||||||
|
SaveOnly = 'saveonly',
|
||||||
|
SaveRestart = 'restart',
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { Table, Text } from '@mantine/core';
|
import { Flex, Table, Text } from '@mantine/core';
|
||||||
|
import { IconZoomQuestion } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@ -30,10 +31,12 @@ interface TableHead {
|
|||||||
|
|
||||||
interface TableProps<T> {
|
interface TableProps<T> {
|
||||||
data: T[],
|
data: T[],
|
||||||
|
onFfprobeClick?: (cameraName: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const FrigateCamerasStateTable = ({
|
const FrigateCamerasStateTable = ({
|
||||||
data
|
data,
|
||||||
|
onFfprobeClick
|
||||||
}: TableProps<CameraItem>) => {
|
}: TableProps<CameraItem>) => {
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -74,13 +77,26 @@ const FrigateCamerasStateTable = ({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isProduction) console.log('FrigateHostsTable rendered')
|
if (!isProduction) console.log('FrigateCamerasStateTable rendered')
|
||||||
|
|
||||||
|
const handleFfprobe = (cameraName: string) => {
|
||||||
|
onFfprobeClick?.(cameraName)
|
||||||
|
}
|
||||||
|
|
||||||
const rows = tableData.map(item => {
|
const rows = tableData.map(item => {
|
||||||
return (
|
return (
|
||||||
<tr key={item.cameraName + item.process}>
|
<tr key={item.cameraName + item.process}>
|
||||||
<td><Text align='center'>{item.cameraName}</Text></td>
|
<td><Text align='center'>{item.cameraName}</Text></td>
|
||||||
<td><Text align='center'>{item.process}</Text></td>
|
<td>
|
||||||
|
<Flex justify='center'>
|
||||||
|
<Text align='center' mr='0.2rem'>{item.process}</Text>
|
||||||
|
{item.process !== ProcessType.Ffmpeg ? null :
|
||||||
|
<IconZoomQuestion
|
||||||
|
color='cyan'
|
||||||
|
cursor='pointer'
|
||||||
|
onClick={() => handleFfprobe(item.cameraName)} />}
|
||||||
|
</Flex>
|
||||||
|
</td>
|
||||||
<td><Text align='center'>{item.pid}</Text></td>
|
<td><Text align='center'>{item.pid}</Text></td>
|
||||||
<td><Text align='center'>{item.fps}</Text></td>
|
<td><Text align='center'>{item.fps}</Text></td>
|
||||||
<td><Text align='center'>{item.cpu}</Text></td>
|
<td><Text align='center'>{item.cpu}</Text></td>
|
||||||
|
|||||||
22
yarn.lock
22
yarn.lock
@ -1799,12 +1799,19 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-6.0.21.tgz#bc009d8380ad18455b90f3ddaf484de16a13da95"
|
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-6.0.21.tgz#bc009d8380ad18455b90f3ddaf484de16a13da95"
|
||||||
integrity sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==
|
integrity sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==
|
||||||
|
|
||||||
"@mantine/notifications@6.0.16":
|
"@mantine/modals@^6.0.16":
|
||||||
version "6.0.16"
|
version "6.0.21"
|
||||||
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-6.0.16.tgz#e3259f9bea564ae58d34810096b9288be47ca815"
|
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-6.0.21.tgz#6d7f89b55d1817c0a20b10989bf69ecb03f7a642"
|
||||||
integrity sha512-KqlPW51sxgQoJmIC2lEWMVlwPqy04D35iRMkCSget8aNgzk0K5csJppXo6qwMFn2GHKVGXFKJMBUp06IXQbiig==
|
integrity sha512-Gx2D/ZHMUuYF197JKMWey4K9FeGP9rxYp4lmAEXUrjXiST2fEhLZOdiD75KuOHXd1/sYAU9NcNRo9wXrlF/gUA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@mantine/utils" "6.0.16"
|
"@mantine/utils" "6.0.21"
|
||||||
|
|
||||||
|
"@mantine/notifications@^6.0.16":
|
||||||
|
version "6.0.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-6.0.21.tgz#bec53664abce13a2cc61a1be1840d82a746f62da"
|
||||||
|
integrity sha512-qsrqxuJHK8b67sf9Pfk+xyhvpf9jMsivW8vchfnJfjv7yz1lLvezjytMFp4fMDoYhjHnDPOEc/YFockK4muhOw==
|
||||||
|
dependencies:
|
||||||
|
"@mantine/utils" "6.0.21"
|
||||||
react-transition-group "4.4.2"
|
react-transition-group "4.4.2"
|
||||||
|
|
||||||
"@mantine/styles@6.0.21":
|
"@mantine/styles@6.0.21":
|
||||||
@ -1815,11 +1822,6 @@
|
|||||||
clsx "1.1.1"
|
clsx "1.1.1"
|
||||||
csstype "3.0.9"
|
csstype "3.0.9"
|
||||||
|
|
||||||
"@mantine/utils@6.0.16":
|
|
||||||
version "6.0.16"
|
|
||||||
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.16.tgz#b39e47ef8fa4463322e9aa10cdd5980f4310b705"
|
|
||||||
integrity sha512-UFel9DbifL3zS8pTJlr6GfwGd6464OWXCJdUq0oLydgimbC1VV2PnptBr6FMwIpPVcxouLOtY1cChzwFH95PSA==
|
|
||||||
|
|
||||||
"@mantine/utils@6.0.21":
|
"@mantine/utils@6.0.21":
|
||||||
version "6.0.21"
|
version "6.0.21"
|
||||||
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.21.tgz#6185506e91cba3e308aaa8ea9ababc8e767995d6"
|
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.21.tgz#6185506e91cba3e308aaa8ea9ababc8e767995d6"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user