diff --git a/package.json b/package.json
index 95d0385..1a074ad 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"@mantine/core": "^6.0.16",
"@mantine/dates": "^6.0.16",
"@mantine/hooks": "^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 4a30846..cbb0162 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -8,6 +8,7 @@ import { BrowserRouter } from 'react-router-dom';
import AppBody from './AppBody';
import Forbidden from './pages/403';
import { QueryClient } from '@tanstack/react-query';
+import { Notifications } from '@mantine/notifications';
function App() {
@@ -62,6 +63,7 @@ function App() {
}}
>
+
diff --git a/src/pages/HostConfigPage.tsx b/src/pages/HostConfigPage.tsx
index 8e2d55f..700c0c3 100644
--- a/src/pages/HostConfigPage.tsx
+++ b/src/pages/HostConfigPage.tsx
@@ -21,7 +21,9 @@ const HostConfigPage = () => {
queryFn: async () => {
const host = await frigateApi.getHost(id || '')
const hostName = mapHostToHostname(host)
- return proxyApi.getHostConfigRaw(hostName)
+ if (hostName)
+ return proxyApi.getHostConfigRaw(hostName)
+ return null
},
})
diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx
index 1b07b4b..5473ac0 100644
--- a/src/pages/MainPage.tsx
+++ b/src/pages/MainPage.tsx
@@ -1,22 +1,33 @@
-import { Flex, Grid, Group } from '@mantine/core';
-import HeadSearch from '../shared/components/inputs/HeadSearch';
-import ViewSelector, { SelectorViewState } from '../shared/components/TableGridViewSelector';
-import { useContext, useState, useEffect } from 'react';
-import { getCookie, setCookie } from 'cookies-next';
+import { Flex, Grid, Group, TextInput } from '@mantine/core';
+import { useContext, useState, useEffect, useMemo } from 'react';
import { Context } from '..';
import { observer } from 'mobx-react-lite'
import CenterLoader from '../shared/components/loaders/CenterLoader';
import { useQuery } from '@tanstack/react-query';
import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import RetryErrorPage from './RetryErrorPage';
-import { useMediaQuery } from '@mantine/hooks';
-import { dimensions } from '../shared/dimensions/dimensions';
import CameraCard from '../widgets/CameraCard';
+import { IconSearch } from '@tabler/icons-react';
+import React from 'react';
+import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
const MainPage = () => {
const { sideBarsStore } = useContext(Context)
- const isMobile = useMediaQuery(dimensions.mobileSize)
+ const [searchQuery, setSearchQuery] = useState()
+ const [filteredCameras, setFilteredCameras] = useState()
+ const { data: cameras, isPending, isError, refetch } = useQuery({
+ queryKey: [frigateQueryKeys.getCamerasWHost],
+ queryFn: frigateApi.getCamerasWHost
+ })
+
+ useEffect(() => {
+ if (searchQuery && cameras) {
+ setFilteredCameras(cameras.filter(camera => camera.name.toLowerCase().includes(searchQuery.toLowerCase())))
+ } else {
+ setFilteredCameras(undefined)
+ }
+ }, [searchQuery, cameras])
useEffect(() => {
sideBarsStore.rightVisible = false
@@ -24,31 +35,28 @@ const MainPage = () => {
sideBarsStore.setRightChildren(null)
}, [])
- const [viewState, setTableState] = useState(getCookie('aps-main-view') as SelectorViewState || SelectorViewState.GRID)
- const handleToggleState = (state: SelectorViewState) => {
- setCookie('aps-main-view', state, { maxAge: 60 * 60 * 24 * 30 });
- setTableState(state)
- }
-
- const { data: cameras, isPending, isError, refetch } = useQuery({
- queryKey: [frigateQueryKeys.getCamerasWHost],
- queryFn: frigateApi.getCamerasWHost
- })
+ const cards = useMemo(() => {
+ if (filteredCameras)
+ return filteredCameras.map(camera => (
+ )
+ )
+ else
+ return cameras?.map(
+ camera => (
+ )
+ )
+ }, [cameras, filteredCameras, searchQuery])
if (isPending) return
if (isError) return
- const cards = () => {
- // return cameras.filter(cam => cam.frigateHost?.host.includes('5000')).slice(0, 25).map(camera => (
- return cameras.map(camera => (
- )
- )
- }
-
return (
@@ -56,12 +64,20 @@ const MainPage = () => {
style={{
justifyContent: 'center',
}}
- >
- {/* */}
+ >
+ }
+ value={searchQuery}
+ onChange={(event) => setSearchQuery(event.currentTarget.value)}
+ />
+
- {cards()}
+ {cards}
diff --git a/src/pages/RecordingsPage.tsx b/src/pages/RecordingsPage.tsx
index a9fa7c0..8ac3122 100644
--- a/src/pages/RecordingsPage.tsx
+++ b/src/pages/RecordingsPage.tsx
@@ -1,18 +1,14 @@
-import { useState, useContext, useEffect, lazy, Suspense } from 'react';
-import { Accordion, Flex, Text } from '@mantine/core';
+import { useState, useContext, useEffect } from 'react';
+import { Flex, Text } from '@mantine/core';
import { useLocation, useNavigate } from 'react-router-dom';
import { observer } from 'mobx-react-lite';
import { Context } from '..';
import RecordingsFiltersRightSide from '../widgets/RecordingsFiltersRightSide';
import SelectedCameraList from '../widgets/SelectedCameraList';
import SelectedHostList from '../widgets/SelectedHostList';
-import { useQuery } from '@tanstack/react-query';
-import { frigateApi, frigateQueryKeys } from '../services/frigate.proxy/frigate.api';
import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil';
-import SelecteDayList from '../widgets/SelecteDayList';
-import { useDebouncedValue } from '@mantine/hooks';
-import CogwheelLoader from '../shared/components/loaders/CogwheelLoader';
+import SelectedDayList from '../widgets/SelectedDayList';
import CenterLoader from '../shared/components/loaders/CenterLoader';
@@ -102,7 +98,7 @@ const RecordingsPage = observer(() => {
const [startDay, endDay] = period
if (startDay && endDay) {
if (startDay.getDate() === endDay.getDate()) { // if select only one day
- return
+ return
}
}
diff --git a/src/services/frigate.proxy/frigate.api.ts b/src/services/frigate.proxy/frigate.api.ts
index 00d3e77..616be0e 100644
--- a/src/services/frigate.proxy/frigate.api.ts
+++ b/src/services/frigate.proxy/frigate.api.ts
@@ -4,7 +4,7 @@ import { z } from "zod"
import {
GetConfig, DeleteFrigateHost, GetFrigateHost, PutConfig, PutFrigateHost,
GetFrigateHostWithCameras, GetCameraWHost, GetCameraWHostWConfig, GetRole,
- GetUserByRole, GetRoleWCameras
+ GetUserByRole, GetRoleWCameras, GetExportedFile
} from "./frigate.schema";
import { FrigateConfig } from "../../types/frigateConfig";
import { url } from "inspector";
@@ -46,15 +46,28 @@ export const frigateApi = {
}).then(res => res.data)
}
+export const proxyPrefix = `${proxyURL.protocol}//${proxyURL.host}/proxy/`
+
export const proxyApi = {
getHostConfigRaw: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config/raw`).then(res => res.data),
getHostConfig: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/config`).then(res => res.data),
getImageFrigate: async (imageUrl: string) => {
- const response = await fetch(imageUrl);
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.blob();
+ const response = await axios.get(imageUrl, {
+ responseType: 'blob'
+ })
+ return response.data
+ },
+ getVideoFrigate: async (videoUrl: string, onProgress: (percentage: number | undefined) => void) => {
+ const response = await axios.get(videoUrl, {
+ responseType: 'blob',
+ onDownloadProgress: (progressEvent) => {
+ const total = progressEvent.total
+ const current = progressEvent.loaded;
+ const percentage = total ? (current / total) * 100 : undefined
+ onProgress(percentage);
+ },
+ })
+ return response.data
},
getHostRestart: (hostName: string) => instanceApi.get(`proxy/${hostName}/api/restart`).then(res => res.data),
@@ -108,23 +121,35 @@ export const proxyApi = {
cameraWsURL: (hostName: string, cameraName: string) =>
`ws://${proxyURL.host}/proxy-ws/${hostName}/live/jsmpeg/${cameraName}`,
cameraImageURL: (hostName: string, cameraName: string) =>
- `${proxyURL.protocol}//${proxyURL.host}/proxy/${hostName}/api/${cameraName}/latest.jpg`,
+ `${proxyPrefix}${hostName}/api/${cameraName}/latest.jpg`,
eventURL: (hostName: string, event: string) =>
- `${proxyURL.protocol}//${proxyURL.host}/proxy/${hostName}/vod/event/${event}/master.m3u8`,
+ `${proxyPrefix}${hostName}/vod/event/${event}/master.m3u8`,
+ eventThumbnailUrl: (hostName: string, eventId: string) => `${proxyPrefix}${hostName}/api/events/${eventId}/thumbnail.jpg`,
+ eventDownloadURL: (hostName: string, eventId: string) => `${proxyPrefix}${hostName}/api/events/${eventId}/clip.mp4?download=true`,
// http://127.0.0.1:5000/vod/2024-02/23/19/CameraName/Asia,Krasnoyarsk/master.m3u8
recordingURL: (hostName: string, cameraName: string, timezone: string, day: string, hour: string) => {// day:2024-02-23 hour:19
const parts = day.split('-')
const date = `${parts[0]}-${parts[1]}/${parts[2]}/${hour}`
- return `${proxyURL.protocol}//${proxyURL.host}/proxy/${hostName}/vod/${date}/${cameraName}/${timezone}/master.m3u8`
+ return `${proxyPrefix}${hostName}/vod/${date}/${cameraName}/${timezone}/master.m3u8`
},
- // linkURL: (hostName: string, link: string) => `${proxyURL.protocol}//${proxyURL.host}/proxy/${hostName}${link}`, TODO delete
+ postExportVideoTask: (hostName: string, cameraName: string, startUnixTime: number, endUnixTime: number) => {
+ const url = `proxy/${hostName}/api/export/${cameraName}/start/${startUnixTime}/end/${endUnixTime}`
+ return instanceApi.post(url, { playback: 'realtime' }).then(res => res.data) // Payload: {"playback":"realtime"}
+ },
+ getExportedVideoList: (hostName: string) => instanceApi.get(`proxy/${hostName}/exports/`).then(res => res.data),
+ getVideoUrl: (hostName: string, fileName: string) => `${proxyPrefix}${hostName}/exports/${fileName}`,
+ deleteExportedVideo: (hostName: string, videoName: string) => instanceApi.delete(`proxy/${hostName}/api/export/${videoName}`).then(res => res.data)
+
+ // filename example Home_1_Backyard_2024_02_26_16_25__2024_02_26_16_26.mp4
+
}
export const mapCamerasFromConfig = (config: FrigateConfig): string[] => {
return Object.keys(config.cameras)
}
-export const mapHostToHostname = (host: GetFrigateHost): string => {
+export const mapHostToHostname = (host?: GetFrigateHost): string | undefined => {
+ if (!host) return
const url = new URL(host.host)
const hostName = url.host
return hostName
diff --git a/src/services/frigate.proxy/frigate.schema.ts b/src/services/frigate.proxy/frigate.schema.ts
index cadc73b..ef2e9bd 100644
--- a/src/services/frigate.proxy/frigate.schema.ts
+++ b/src/services/frigate.proxy/frigate.schema.ts
@@ -98,6 +98,13 @@ export const getUserByRoleSchema = z.object({
email: z.string(),
})
+export const getExpotedFile = z.object({
+ name: z.string(),
+ type: z.string(),
+ mtime: z.string(),
+ size: z.number(),
+})
+
export type GetConfig = z.infer
export type PutConfig = z.infer
export type GetFrigateHost = z.infer
@@ -109,4 +116,5 @@ export type PutFrigateHost = z.infer
export type DeleteFrigateHost = z.infer
export type GetRole = z.infer
export type GetRoleWCameras = z.infer
-export type GetUserByRole = z.infer
\ No newline at end of file
+export type GetUserByRole = z.infer
+export type GetExportedFile = z.infer
\ No newline at end of file
diff --git a/src/shared/components/accordion/CameraAccordion.tsx b/src/shared/components/accordion/CameraAccordion.tsx
index f1961df..fd8eb10 100644
--- a/src/shared/components/accordion/CameraAccordion.tsx
+++ b/src/shared/components/accordion/CameraAccordion.tsx
@@ -24,12 +24,12 @@ const CameraAccordion = ({
const camera = recStore.openedCamera || recStore.filteredCamera
const host = recStore.filteredHost
+ const hostName = mapHostToHostname(host)
const { data, isPending, isError, refetch } = useQuery({
queryKey: [frigateQueryKeys.getRecordingsSummary, camera?.id],
queryFn: () => {
- if (camera && host) {
- const hostName = mapHostToHostname(host)
+ if (camera && hostName) {
return proxyApi.getRecordingsSummary(hostName, camera.name, getResolvedTimeZone())
}
return null
diff --git a/src/shared/components/accordion/DayAccordion.tsx b/src/shared/components/accordion/DayAccordion.tsx
index 50fdf39..82a3e1f 100644
--- a/src/shared/components/accordion/DayAccordion.tsx
+++ b/src/shared/components/accordion/DayAccordion.tsx
@@ -1,4 +1,4 @@
-import { Accordion, Center, Flex, Group, NavLink, Text, UnstyledButton } from '@mantine/core';
+import { Accordion, Center, Flex, Group, NavLink, Progress, Text, UnstyledButton } from '@mantine/core';
import React, { useContext, useEffect, useState } from 'react';
import { RecordSummary } from '../../../types/record';
import { observer } from 'mobx-react-lite';
@@ -6,7 +6,7 @@ import PlayControl from '../buttons/PlayControl';
import { mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { Context } from '../../..';
import VideoPlayer from '../players/VideoPlayer';
-import { getResolvedTimeZone } from '../../utils/dateUtil';
+import { getResolvedTimeZone, mapDateHourToUnixTime } from '../../utils/dateUtil';
import DayEventsAccordion from './DayEventsAccordion';
import { strings } from '../../strings/strings';
import { useNavigate } from 'react-router-dom';
@@ -14,6 +14,7 @@ import AccordionControlButton from '../buttons/AccordionControlButton';
import { IconExternalLink, IconShare } from '@tabler/icons-react';
import { routesPath } from '../../../router/routes.path';
import AccordionShareButton from '../buttons/AccordionShareButton';
+import VideoDownloader from '../../../widgets/VideoDownloader';
interface RecordingAccordionProps {
recordSummary?: RecordSummary
@@ -26,14 +27,14 @@ const DayAccordion = ({
const [playedValue, setVideoPlayerState] = useState()
const [openedValue, setOpenedValue] = useState()
const [playerUrl, setPlayerUrl] = useState()
- const [link, setLink] = useState()
const navigate = useNavigate()
const camera = recStore.openedCamera || recStore.filteredCamera
+ const hostName = mapHostToHostname(recStore.filteredHost)
const createRecordURL = (recordId: string): string | undefined => {
const record = {
- hostName: recStore.filteredHost ? mapHostToHostname(recStore.filteredHost) : '',
+ hostName: hostName ? hostName : '',
cameraName: camera?.name,
day: recordSummary?.day,
hour: recordId,
@@ -119,7 +120,7 @@ const DayAccordion = ({
{hourLabel(hour.hour, hour.events)}
-
+
hanleOpenNewLink(hour.hour)}>
@@ -129,10 +130,20 @@ const DayAccordion = ({
onClick={handleOpenPlayer} />
-
{playedValue === hour.hour && playerUrl ? : <>>}
+ { }
+ {recStore.filteredHost && camera && hostName ?
+
+
+
+ : ''}
{hour.events > 0 ?
:
@@ -141,7 +152,9 @@ const DayAccordion = ({
))}
+
+
)
}
diff --git a/src/shared/components/accordion/EventPanel.tsx b/src/shared/components/accordion/EventPanel.tsx
new file mode 100644
index 0000000..d91078a
--- /dev/null
+++ b/src/shared/components/accordion/EventPanel.tsx
@@ -0,0 +1,58 @@
+import { Flex, Group, Button, Text, Image } from '@mantine/core';
+import { IconExternalLink } from '@tabler/icons-react';
+import React from 'react';
+import { proxyApi } from '../../../services/frigate.proxy/frigate.api';
+import { strings } from '../../strings/strings';
+import { unixTimeToDate, getDurationFromTimestamps } from '../../utils/dateUtil';
+import VideoPlayer from '../players/VideoPlayer';
+import { EventFrigate } from '../../../types/event';
+
+interface EventPanelProps {
+ event: EventFrigate
+ playedValue?: string
+ playerUrl?: string
+ hostName?: string
+}
+
+const EventPanel = ({
+ event,
+ playedValue,
+ playerUrl,
+ hostName,
+}: EventPanelProps) => {
+ return (
+ <>
+ {playedValue === event.id && playerUrl ? : <>>}
+
+ {!hostName ? <>> :
+
+ }
+
+ {!hostName ? '' :
+
+ }>
+ Download event
+
+
+ }
+ {strings.camera}: {event.camera}
+ {strings.player.object}: {event.label}
+ {strings.player.startTime}: {unixTimeToDate(event.start_time)}
+ {strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}
+ {strings.player.rating}: {(event.data.score * 100).toFixed(2)}%
+
+
+ >
+ )
+}
+
+export default EventPanel;
\ No newline at end of file
diff --git a/src/shared/components/accordion/EventsAccordion.tsx b/src/shared/components/accordion/EventsAccordion.tsx
index 50170e2..d499281 100644
--- a/src/shared/components/accordion/EventsAccordion.tsx
+++ b/src/shared/components/accordion/EventsAccordion.tsx
@@ -6,7 +6,6 @@ import { useQuery } from '@tanstack/react-query';
import { frigateQueryKeys, mapHostToHostname, proxyApi } from '../../../services/frigate.proxy/frigate.api';
import { getEventsQuerySchema } from '../../../services/frigate.proxy/frigate.schema';
import PlayControl from '../buttons/PlayControl';
-import VideoPlayer from '../players/VideoPlayer';
import { getDurationFromTimestamps, getUnixTime, unixTimeToDate } from '../../utils/dateUtil';
import RetryError from '../RetryError';
import { strings } from '../../strings/strings';
@@ -16,6 +15,7 @@ import { routesPath } from '../../../router/routes.path';
import AccordionControlButton from '../buttons/AccordionControlButton';
import AccordionShareButton from '../buttons/AccordionShareButton';
import { useNavigate } from 'react-router-dom';
+import EventPanel from './EventPanel';
/**
* @param day frigate format, e.g day: 2024-02-23
@@ -47,18 +47,19 @@ const EventsAccordion = observer(({
const [playerUrl, setPlayerUrl] = useState()
const navigate = useNavigate()
- const inHost = recStore.filteredHost
- const inCamera = recStore.openedCamera || recStore.filteredCamera
- const isRequiredParams = inHost && inCamera
+ const host = recStore.filteredHost
+ const hostName = mapHostToHostname(host)
+ const camera = recStore.openedCamera || recStore.filteredCamera
+ const isRequiredParams = host && camera
const { data, isPending, isError, refetch } = useQuery({
- queryKey: [frigateQueryKeys.getEvents, inHost, inCamera, day, hour],
+ queryKey: [frigateQueryKeys.getEvents, host, camera, day, hour],
queryFn: () => {
if (!isRequiredParams) return null
const [startTime, endTime] = getUnixTime(day, hour)
const parsed = getEventsQuerySchema.safeParse({
- hostName: mapHostToHostname(inHost),
- camerasName: [inCamera.name],
+ hostName: mapHostToHostname(host),
+ camerasName: [camera.name],
after: startTime,
before: endTime,
hasClip: true,
@@ -84,15 +85,15 @@ const EventsAccordion = observer(({
})
const createEventUrl = (eventId: string) => {
- if (inHost)
- return proxyApi.eventURL(mapHostToHostname(inHost), eventId)
+ if (hostName)
+ return proxyApi.eventURL(hostName, eventId)
return undefined
}
useEffect(() => {
if (playedValue) {
// console.log('openVideoPlayer', playedValue)
- if (playedValue && inHost) {
+ if (playedValue && host) {
const url = createEventUrl(playedValue)
console.log('GET EVENT URL: ', url)
setPlayerUrl(url)
@@ -173,15 +174,11 @@ const EventsAccordion = observer(({
- {playedValue === event.id && playerUrl ? : <>>}
-
- {strings.camera}: {event.camera}
- {strings.player.object}: {event.label}
-
-
- {strings.player.startTime}: {unixTimeToDate(event.start_time)}
- {strings.player.duration}: {getDurationFromTimestamps(event.start_time, event.end_time)}
-
+
))}
diff --git a/src/shared/components/images/AutoUpdatedImage.tsx b/src/shared/components/images/AutoUpdatedImage.tsx
index 979a079..5878810 100644
--- a/src/shared/components/images/AutoUpdatedImage.tsx
+++ b/src/shared/components/images/AutoUpdatedImage.tsx
@@ -1,10 +1,11 @@
-import { useEffect, useRef } from "react";
+import { useEffect, useRef, useState } from "react";
import { CameraConfig } from "../../../types/frigateConfig";
import { Flex, Text, Image } from "@mantine/core";
import { useQuery } from "@tanstack/react-query";
import { frigateApi, proxyApi } from "../../../services/frigate.proxy/frigate.api";
import { useIntersection } from "@mantine/hooks";
import CogwheelLoader from "../loaders/CogwheelLoader";
+import RetryError from "../RetryError";
interface AutoUpdatedImageProps extends React.ImgHTMLAttributes {
className?: string;
@@ -21,9 +22,10 @@ const AutoUpdatedImage = ({
}: AutoUpdatedImageProps) => {
const { ref, entry } = useIntersection({ threshold: 0.1, })
const isVisible = entry?.isIntersecting
+ const [imageSrc, setImageSrc] = useState(null);
const { data: imageBlob, refetch, isPending, isError } = useQuery({
- queryKey: ['image', imageUrl],
+ queryKey: [imageUrl],
queryFn: () => proxyApi.getImageFrigate(imageUrl),
staleTime: 60 * 1000,
gcTime: Infinity,
@@ -33,27 +35,38 @@ const AutoUpdatedImage = ({
useEffect(() => {
if (isVisible) {
const intervalId = setInterval(() => {
- refetch();
- }, 60 * 1000);
+ refetch()
+ }, 60 * 1000)
return () => clearInterval(intervalId);
}
- }, [refetch, isVisible]);
+ }, [refetch, isVisible])
+
+ useEffect(() => {
+ if (imageBlob && imageBlob instanceof Blob) {
+ const objectURL = URL.createObjectURL(imageBlob);
+ setImageSrc(objectURL);
+
+ return () => {
+ if (objectURL) {
+ URL.revokeObjectURL(objectURL);
+ }
+ }
+ }
+ }, [imageBlob])
if (isPending) return
if (isError) return (
- Error loading!
+
)
- if (!imageBlob || !(imageBlob instanceof Blob)) console.error('imageBlob not Blob object:', imageBlob)
-
- const image = URL.createObjectURL(imageBlob!)
+ if (!imageSrc) return null
return (
- {enabled ?
+ {enabled ?
:
Camera is disabled in config, no stream or snapshot available!
}
diff --git a/src/shared/components/inputs/HeadSearch.tsx b/src/shared/components/inputs/HeadSearch.tsx
index b3648fb..1dc5c95 100644
--- a/src/shared/components/inputs/HeadSearch.tsx
+++ b/src/shared/components/inputs/HeadSearch.tsx
@@ -1,25 +1,22 @@
import { Flex, TextInput } from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
-import React from 'react';
-import ViewSelector from '../TableGridViewSelector';
+import React, { useState } from 'react';
interface HeadSearchProps {
search?: string
- handleSearchChange?(): void
+ onChange?: (event: React.ChangeEvent) => void
}
-const HeadSearch = ({ search, handleSearchChange }: HeadSearchProps) => {
+const HeadSearch = ({ search, onChange }: HeadSearchProps) => {
return (
- <>
- }
- value={search}
- onChange={handleSearchChange}
- />
- >
+ }
+ value={search}
+ onChange={onChange}
+ />
);
};
diff --git a/src/shared/components/menu/HostSettingsMenu.tsx b/src/shared/components/menu/HostSettingsMenu.tsx
index 2d68324..8f322f4 100644
--- a/src/shared/components/menu/HostSettingsMenu.tsx
+++ b/src/shared/components/menu/HostSettingsMenu.tsx
@@ -31,7 +31,8 @@ const HostSettingsMenu = ({ host }: HostSettingsMenuProps) => {
}
const handleRestart = () => {
- mutation.mutate(mapHostToHostname(host))
+ const hostName = mapHostToHostname(host)
+ if (hostName) mutation.mutate(hostName)
}
return (