add save config
This commit is contained in:
parent
342940d27f
commit
529885498d
@ -9,7 +9,8 @@
|
||||
"@mantine/core": "^6.0.16",
|
||||
"@mantine/dates": "^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",
|
||||
"@tabler/icons-react": "^2.24.0",
|
||||
"@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 { keycloakConfig } from '.';
|
||||
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({
|
||||
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() {
|
||||
const maxErrorAuthCounts = 2
|
||||
const systemColorScheme = useColorScheme()
|
||||
@ -104,8 +118,10 @@ function App() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
<ModalsProvider modals={modals}>
|
||||
<Notifications />
|
||||
<AppBody />
|
||||
</ModalsProvider>
|
||||
</MantineProvider >
|
||||
</ColorSchemeProvider>
|
||||
</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 { 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 { Button, Flex, useMantineTheme } from '@mantine/core';
|
||||
import { Button, Flex, useMantineTheme, Text } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import Editor, { Monaco } from '@monaco-editor/react'
|
||||
import * as monaco from "monaco-editor";
|
||||
@ -13,11 +13,16 @@ import { useAdminRole } from '../hooks/useAdminRole';
|
||||
import Forbidden from './403';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
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 executed = useRef(false)
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const [saveMessage, setSaveMessage] = useState<string>()
|
||||
|
||||
let { id } = useParams<'id'>()
|
||||
const { isAdmin, isLoading: adminLoading } = useAdminRole()
|
||||
@ -25,22 +30,42 @@ const HostConfigPage = () => {
|
||||
const { isPending: configPending, error: configError, data: config, refetch } = useQuery({
|
||||
queryKey: [frigateQueryKeys.getFrigateHost, id],
|
||||
queryFn: async () => {
|
||||
const host = await frigateApi.getHost(id || '')
|
||||
const hostName = mapHostToHostname(host)
|
||||
if (hostName)
|
||||
return proxyApi.getHostConfigRaw(hostName)
|
||||
return null
|
||||
host.current = await frigateApi.getHost(id || '')
|
||||
const hostName = mapHostToHostname(host.current)
|
||||
if (!hostName) return null
|
||||
return proxyApi.getHostConfigRaw(hostName)
|
||||
},
|
||||
})
|
||||
|
||||
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(() => {
|
||||
if (!executed.current) {
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
sideBarsStore.rightVisible = false
|
||||
sideBarsStore.setLeftChildren(null)
|
||||
sideBarsStore.setRightChildren(null)
|
||||
executed.current = true
|
||||
}
|
||||
}, [sideBarsStore])
|
||||
}, [sideBarsStore])
|
||||
|
||||
const clipboard = useClipboard({ timeout: 500 })
|
||||
|
||||
@ -81,11 +106,13 @@ const HostConfigPage = () => {
|
||||
}, [editorRef, clipboard]);
|
||||
|
||||
const onHandleSaveConfig = useCallback(
|
||||
async (save_option: string) => {
|
||||
async (saveOption: SaveOption) => {
|
||||
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])
|
||||
|
||||
if (configPending || adminLoading) return <CenterLoader />
|
||||
@ -99,14 +126,19 @@ const HostConfigPage = () => {
|
||||
<Button size="sm" onClick={handleCopyConfig}>
|
||||
Copy Config
|
||||
</Button>
|
||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig("restart")}>
|
||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig(SaveOption.SaveRestart)}>
|
||||
Save & Restart
|
||||
</Button>
|
||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig("saveonly")}>
|
||||
<Button ml='1rem' size="sm" onClick={(_) => onHandleSaveConfig(SaveOption.SaveOnly)}>
|
||||
Save Only
|
||||
</Button>
|
||||
</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
|
||||
defaultLanguage='yaml'
|
||||
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 { 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 { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
|
||||
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 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 = {
|
||||
hostId: 'hostId',
|
||||
@ -26,6 +29,7 @@ const HostSystemPage = () => {
|
||||
const executed = useRef(false)
|
||||
const { sideBarsStore } = useContext(Context)
|
||||
const { isAdmin } = useAdminRole()
|
||||
const host = useRef<GetFrigateHost | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (!executed.current) {
|
||||
@ -42,11 +46,13 @@ const HostSystemPage = () => {
|
||||
queryKey: [frigateQueryKeys.getHostStats, paramHostId],
|
||||
queryFn: async () => {
|
||||
if (!paramHostId) return null
|
||||
const host = await frigateApi.getHost(paramHostId)
|
||||
const hostName = mapHostToHostname(host)
|
||||
host.current = await frigateApi.getHost(paramHostId)
|
||||
const hostName = mapHostToHostname(host.current)
|
||||
if (!hostName) return null
|
||||
return proxyApi.getHostStats(hostName)
|
||||
}
|
||||
},
|
||||
staleTime: 2 * 60 * 1000,
|
||||
refetchInterval: 60 * 1000,
|
||||
})
|
||||
|
||||
const mapCameraData = useCallback(() => {
|
||||
@ -72,9 +78,9 @@ const HostSystemPage = () => {
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
if (!isAdmin) return <Forbidden />
|
||||
if (isPending) return <CenterLoader />
|
||||
if (isError) return <RetryErrorPage onRetry={refetch} />
|
||||
if (!isAdmin) return <Forbidden />
|
||||
if (!paramHostId || !data) return null
|
||||
|
||||
const mappedCameraStat: CameraItem[] = mapCameraData()
|
||||
@ -97,6 +103,14 @@ const HostSystemPage = () => {
|
||||
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]) => {
|
||||
return (
|
||||
<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}
|
||||
encoder={stats.enc}
|
||||
gpu={stats.gpu}
|
||||
mem={stats.mem} />
|
||||
mem={stats.mem}
|
||||
onVaInfoClick={() => handleVaInfoClick()}
|
||||
/>
|
||||
</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 (
|
||||
<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 size='xl' w='900'>{host.current?.name}</Text>
|
||||
<Text>{t('uptime')} : {formattedUptime()}</Text>
|
||||
</Flex>
|
||||
<Grid mt='sm' justify="center" mb='sm' align='stretch'>
|
||||
@ -140,7 +168,7 @@ const HostSystemPage = () => {
|
||||
{gpuStats}
|
||||
{detectorsStats}
|
||||
</Grid>
|
||||
<FrigateCamerasStateTable data={mappedCameraStat} />
|
||||
<FrigateCamerasStateTable data={mappedCameraStat} onFfprobeClick={handleFfprobeClick} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@ -8,7 +8,6 @@ import { observer } from 'mobx-react-lite';
|
||||
|
||||
export const playRecordPageQuery = {
|
||||
link: 'link',
|
||||
// hostName: 'hostName',
|
||||
}
|
||||
|
||||
const PlayRecordPage = () => {
|
||||
@ -26,7 +25,6 @@ const PlayRecordPage = () => {
|
||||
const location = useLocation()
|
||||
const queryParams = new URLSearchParams(location.search)
|
||||
const paramLink = queryParams.get(playRecordPageQuery.link)
|
||||
// const paramHostName = queryParams.get(playRecordPageQuery.hostName);
|
||||
|
||||
if (!paramLink) return (<NotFound />)
|
||||
return (
|
||||
|
||||
@ -96,7 +96,6 @@ const RecordingsPage = () => {
|
||||
navigate({ pathname: location.pathname, search: queryParams.toString() });
|
||||
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
|
||||
|
||||
if (!isProduction) console.log('RecordingsPage rendered')
|
||||
|
||||
const [startDay, endDay] = period
|
||||
if (startDay && endDay) {
|
||||
@ -115,6 +114,8 @@ const RecordingsPage = () => {
|
||||
return <SelectedHostList hostId={hostId} />
|
||||
}
|
||||
|
||||
if (!isProduction) console.log('RecordingsPage rendered')
|
||||
|
||||
return (
|
||||
<Flex w='100%' h='100%' direction='column' justify='center' align='center'>
|
||||
{!hostId ?
|
||||
|
||||
@ -10,7 +10,9 @@ import { RecordSummary } from "../../types/record";
|
||||
import { EventFrigate } from "../../types/event";
|
||||
import { keycloakConfig } from "../..";
|
||||
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 => {
|
||||
@ -176,7 +178,19 @@ export const proxyApi = {
|
||||
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
|
||||
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[] => {
|
||||
@ -199,7 +213,10 @@ export const frigateQueryKeys = {
|
||||
getCameraWHost: 'camera-frigate-host',
|
||||
getCameraByHostId: 'camera-by-hostId',
|
||||
getHostConfig: 'host-config',
|
||||
postHostConfig: 'host-config-save',
|
||||
getHostStats: 'host-stats',
|
||||
getCameraFfprobe: 'camera-ffprobe',
|
||||
getHostVaInfo: 'host-vainfo',
|
||||
getRecordingsSummary: 'recordings-frigate-summary',
|
||||
getRecordings: 'recordings-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
|
||||
}
|
||||
|
||||
const FullImageModal = observer(({ images, opened, open, close }: FullImageModalProps) => {
|
||||
const FullImageModal = ({ images, opened, open, close }: FullImageModalProps) => {
|
||||
const { modalStore } = useContext(Context)
|
||||
const { isFullImageOpened, fullImageData, closeFullImage } = modalStore
|
||||
const { classes } = useStyles();
|
||||
@ -105,6 +105,6 @@ const FullImageModal = observer(({ images, opened, open, close }: FullImageModal
|
||||
</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 { IconZoomCheck, IconZoomQuestion } from '@tabler/icons-react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -7,7 +8,8 @@ interface GpuStatProps {
|
||||
decoder?: string,
|
||||
encoder?: string,
|
||||
gpu?: string,
|
||||
mem?: string
|
||||
mem?: string,
|
||||
onVaInfoClick?: () => void
|
||||
}
|
||||
|
||||
const GpuStat: React.FC<GpuStatProps> = ({
|
||||
@ -15,13 +17,19 @@ const GpuStat: React.FC<GpuStatProps> = ({
|
||||
decoder,
|
||||
encoder,
|
||||
gpu,
|
||||
mem
|
||||
mem,
|
||||
onVaInfoClick
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Card withBorder radius="md" p='0.7rem'>
|
||||
<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}
|
||||
</Text>
|
||||
<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 {
|
||||
cameras: {
|
||||
[cameraName: string]: CameraStat
|
||||
@ -10,7 +34,7 @@ export interface FrigateStats {
|
||||
[detectorName: string]: DetectorStat
|
||||
}
|
||||
gpu_usages: {
|
||||
[gpuName: string] : GpuStat
|
||||
[gpuName: string]: GpuStat
|
||||
}
|
||||
processes: Processes
|
||||
service: Service
|
||||
@ -71,7 +95,7 @@ export interface Service {
|
||||
last_updated: number
|
||||
latest_version: string
|
||||
storage: {
|
||||
[storagePath: string] : StorageStat
|
||||
[storagePath: string]: StorageStat
|
||||
}
|
||||
temperatures: Temperatures
|
||||
uptime: number
|
||||
|
||||
1
src/types/global.d.ts
vendored
1
src/types/global.d.ts
vendored
@ -6,4 +6,5 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@ -30,10 +31,12 @@ interface TableHead {
|
||||
|
||||
interface TableProps<T> {
|
||||
data: T[],
|
||||
onFfprobeClick?: (cameraName: string) => void
|
||||
}
|
||||
|
||||
const FrigateCamerasStateTable = ({
|
||||
data
|
||||
data,
|
||||
onFfprobeClick
|
||||
}: TableProps<CameraItem>) => {
|
||||
|
||||
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 => {
|
||||
return (
|
||||
<tr key={item.cameraName + item.process}>
|
||||
<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.fps}</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"
|
||||
integrity sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==
|
||||
|
||||
"@mantine/notifications@6.0.16":
|
||||
version "6.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-6.0.16.tgz#e3259f9bea564ae58d34810096b9288be47ca815"
|
||||
integrity sha512-KqlPW51sxgQoJmIC2lEWMVlwPqy04D35iRMkCSget8aNgzk0K5csJppXo6qwMFn2GHKVGXFKJMBUp06IXQbiig==
|
||||
"@mantine/modals@^6.0.16":
|
||||
version "6.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-6.0.21.tgz#6d7f89b55d1817c0a20b10989bf69ecb03f7a642"
|
||||
integrity sha512-Gx2D/ZHMUuYF197JKMWey4K9FeGP9rxYp4lmAEXUrjXiST2fEhLZOdiD75KuOHXd1/sYAU9NcNRo9wXrlF/gUA==
|
||||
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"
|
||||
|
||||
"@mantine/styles@6.0.21":
|
||||
@ -1815,11 +1822,6 @@
|
||||
clsx "1.1.1"
|
||||
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":
|
||||
version "6.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.21.tgz#6185506e91cba3e308aaa8ea9ababc8e767995d6"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user