From 529885498daf9a25611ccdda5f23f40026e4b385 Mon Sep 17 00:00:00 2001 From: NlightN22 Date: Tue, 12 Mar 2024 04:07:17 +0700 Subject: [PATCH] add save config --- package.json | 3 +- src/App.tsx | 20 +++- src/pages/HostConfigPage.tsx | 70 ++++++++++---- src/pages/HostSystemPage.tsx | 68 ++++++++++---- src/pages/PlayRecordPage.tsx | 2 - src/pages/RecordingsPage.tsx | 3 +- src/services/frigate.proxy/frigate.api.ts | 21 ++++- .../components/modal.windows/FfprobeModal.tsx | 62 +++++++++++++ .../modal.windows/FullImageModal.tsx | 6 +- .../components/modal.windows/InputModal.tsx | 91 ------------------- .../components/modal.windows/VaInfoModal.tsx | 41 +++++++++ src/shared/components/stats/GpuStat.tsx | 14 ++- .../components/table.aps/RowCounter.tsx | 73 --------------- src/types/frigateStats.ts | 28 +++++- src/types/global.d.ts | 1 + src/types/saveConfig.ts | 9 ++ .../FrigateCameraStateTable.tsx | 24 ++++- yarn.lock | 22 +++-- 18 files changed, 325 insertions(+), 233 deletions(-) create mode 100644 src/shared/components/modal.windows/FfprobeModal.tsx delete mode 100644 src/shared/components/modal.windows/InputModal.tsx create mode 100644 src/shared/components/modal.windows/VaInfoModal.tsx delete mode 100644 src/shared/components/table.aps/RowCounter.tsx create mode 100644 src/types/saveConfig.ts diff --git a/package.json b/package.json index fb984ea..428ceef 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index df52dc4..3d46542 100644 --- a/src/App.tsx +++ b/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() { } }} > - - + + + + diff --git a/src/pages/HostConfigPage.tsx b/src/pages/HostConfigPage.tsx index 7eb6ef0..4a49442 100644 --- a/src/pages/HostConfigPage.tsx +++ b/src/pages/HostConfigPage.tsx @@ -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() const { sideBarsStore } = useContext(Context) + const [saveMessage, setSaveMessage] = useState() 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 @@ -99,14 +126,19 @@ const HostConfigPage = () => { - - - + {!saveMessage ? null : + + {saveMessage} + + } + { const executed = useRef(false) const { sideBarsStore } = useContext(Context) const { isAdmin } = useAdminRole() + const host = useRef() 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 if (isPending) return if (isError) return + if (!isAdmin) return 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 ( @@ -105,7 +119,9 @@ const HostSystemPage = () => { decoder={stats.dec} encoder={stats.enc} gpu={stats.gpu} - mem={stats.mem} /> + mem={stats.mem} + onVaInfoClick={() => handleVaInfoClick()} + /> ) }) @@ -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 ( - + {t('version')} : {data.service.version} + {host.current?.name} {t('uptime')} : {formattedUptime()} @@ -140,7 +168,7 @@ const HostSystemPage = () => { {gpuStats} {detectorsStats} - + ); }; diff --git a/src/pages/PlayRecordPage.tsx b/src/pages/PlayRecordPage.tsx index 347f683..b3a47d5 100644 --- a/src/pages/PlayRecordPage.tsx +++ b/src/pages/PlayRecordPage.tsx @@ -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 () return ( diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx index 4819a45..f4688a7 100644 --- a/src/pages/RecordingsPage.tsx +++ b/src/pages/RecordingsPage.tsx @@ -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) { @@ -114,6 +113,8 @@ const RecordingsPage = () => { if (hostId && paramHostId && !cameraId) { return } + + if (!isProduction) console.log('RecordingsPage rendered') return ( diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts index 1554ca3..b03ac99 100644 --- a/src/services/frigate.proxy/frigate.api.ts +++ b/src/services/frigate.proxy/frigate.api.ts @@ -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(`proxy/${hostName}/api/stats`).then( res => res.data), + getHostStats: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/stats`).then(res => res.data), + getCameraFfprobe: (hostName: string, cameraName: string) => + instanceApi.get(`proxy/${hostName}/api/ffprobe`, { params: { paths: `camera:${cameraName}` } }).then(res => res.data), + getHostVaInfo: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/vainfo`).then(res => res.data), + postHostConfig: (hostName: string, saveOption: SaveOption, config: string) => + instanceApi.post(`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', diff --git a/src/shared/components/modal.windows/FfprobeModal.tsx b/src/shared/components/modal.windows/FfprobeModal.tsx new file mode 100644 index 0000000..29c69a5 --- /dev/null +++ b/src/shared/components/modal.windows/FfprobeModal.tsx @@ -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) => { + 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 + if (isError) return + if (!data || data.length < 1) return Data is empty + + const streamItems = data.map((res, streamIndex) => { + if (res.return_code !== 0) { + return ( + <> +
Stream: {streamIndex}
+ {res.return_code} + {res.stderr} + + ) + } + const flows = res.stdout.streams.map((stream) => ( + <> + Codec: {stream.codec_long_name} + {!stream.width && !stream.height ? null : + Resolution: {stream.width}x{stream.height} + } + FPS: {stream.avg_frame_rate} + + )) + return ( + <> +
Stream: {streamIndex}
+ {flows} + + ) + }) + + return ( + <> + {streamItems} +
+ +
+ + ) +} \ No newline at end of file diff --git a/src/shared/components/modal.windows/FullImageModal.tsx b/src/shared/components/modal.windows/FullImageModal.tsx index 29227ed..bb61e70 100644 --- a/src/shared/components/modal.windows/FullImageModal.tsx +++ b/src/shared/components/modal.windows/FullImageModal.tsx @@ -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 ); -}) +} -export default FullImageModal; \ No newline at end of file +export default observer(FullImageModal) \ No newline at end of file diff --git a/src/shared/components/modal.windows/InputModal.tsx b/src/shared/components/modal.windows/InputModal.tsx deleted file mode 100644 index 1f83257..0000000 --- a/src/shared/components/modal.windows/InputModal.tsx +++ /dev/null @@ -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 = useRef(null) - - const handeLoaded = (event: FocusEvent) => { - 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 ( - - -
{t('enterQuantity')}
- -
- - -
- -
-
-
- } - /> - - ); -}; - -export default InputModal; \ No newline at end of file diff --git a/src/shared/components/modal.windows/VaInfoModal.tsx b/src/shared/components/modal.windows/VaInfoModal.tsx new file mode 100644 index 0000000..94ddd99 --- /dev/null +++ b/src/shared/components/modal.windows/VaInfoModal.tsx @@ -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) => { + 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 + if (isError) return + if (!data) return Data is empty + + return ( + + Return code: {data.return_code} + {data.stderr ? {data.stderr} : null} + {data.stdout} +
+ +
+
+ ); +}; diff --git a/src/shared/components/stats/GpuStat.tsx b/src/shared/components/stats/GpuStat.tsx index c3372b2..b461d97 100644 --- a/src/shared/components/stats/GpuStat.tsx +++ b/src/shared/components/stats/GpuStat.tsx @@ -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 = ({ @@ -15,13 +17,19 @@ const GpuStat: React.FC = ({ decoder, encoder, gpu, - mem + mem, + onVaInfoClick }) => { const { t } = useTranslation() return ( - + + {name} diff --git a/src/shared/components/table.aps/RowCounter.tsx b/src/shared/components/table.aps/RowCounter.tsx deleted file mode 100644 index 3a0f3f1..0000000 --- a/src/shared/components/table.aps/RowCounter.tsx +++ /dev/null @@ -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) => { - e.stopPropagation() - open() - } - - const handleInrease = (e: React.MouseEvent) => { - e.stopPropagation() - handleSetValue(count + 1) - } - - const handleDerease = (e: React.MouseEvent) => { - e.stopPropagation() - handleSetValue(count - 1) - } - - const handleDelete = (e: React.MouseEvent) => { - e.stopPropagation() - if (onDelete) onDelete() - } - - return ( - <> - - - - - - - - {count} - - - - - - { - showDelete ? - - - - : - <> - } - - - ); -}; - -export default RowCounter; \ No newline at end of file diff --git a/src/types/frigateStats.ts b/src/types/frigateStats.ts index 3b42888..2ceafb7 100644 --- a/src/types/frigateStats.ts +++ b/src/types/frigateStats.ts @@ -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 diff --git a/src/types/global.d.ts b/src/types/global.d.ts index a021014..f1f564c 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -6,4 +6,5 @@ declare global { } } + export {}; \ No newline at end of file diff --git a/src/types/saveConfig.ts b/src/types/saveConfig.ts new file mode 100644 index 0000000..0179fe8 --- /dev/null +++ b/src/types/saveConfig.ts @@ -0,0 +1,9 @@ +export interface PostSaveConfig { + message: string, + success: boolean +} + +export enum SaveOption { + SaveOnly = 'saveonly', + SaveRestart = 'restart', +} \ No newline at end of file diff --git a/src/widgets/camera.stat.table/FrigateCameraStateTable.tsx b/src/widgets/camera.stat.table/FrigateCameraStateTable.tsx index 7bd4233..04f8a41 100644 --- a/src/widgets/camera.stat.table/FrigateCameraStateTable.tsx +++ b/src/widgets/camera.stat.table/FrigateCameraStateTable.tsx @@ -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 { data: T[], + onFfprobeClick?: (cameraName: string) => void } const FrigateCamerasStateTable = ({ - data + data, + onFfprobeClick }: TableProps) => { 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 ( {item.cameraName} - {item.process} + + + {item.process} + {item.process !== ProcessType.Ffmpeg ? null : + handleFfprobe(item.cameraName)} />} + + {item.pid} {item.fps} {item.cpu} diff --git a/yarn.lock b/yarn.lock index 2bb5a8a..6529b34 100644 --- a/yarn.lock +++ b/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"