fix lint warns

add env to index
create env script
create docker
This commit is contained in:
NlightN22 2024-03-01 03:21:13 +07:00
parent d59e6c50e0
commit 811bc5bac7
38 changed files with 2477 additions and 1982 deletions

5
.env.docker Normal file
View File

@ -0,0 +1,5 @@
REACT_APP_HOST=localhost
REACT_APP_PORT=5173
REACT_APP_FRIGATE_PROXY=http://localhost:4000
REACT_APP_OPENID_SERVER=https://your.server.com:443/realms/your-realm
REACT_APP_CLIENT_ID=frontend-client

8
.gitignore vendored
View File

@ -13,13 +13,9 @@
/build
# misc
.env
.env.development
.env*
!.env.docker
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.idea/*
.vscode

View File

@ -1,19 +1,26 @@
# syntax=docker/dockerfile:1
# Build commands:
# - rm dist -r -Force ; yarn build
# - rm build -r -Force ; yarn build
# - $VERSION=0.1
# - docker build --pull --rm -t oncharterliz/frigate-proxy:latest -t oncharterliz/frigate-proxy:$VERSION "."
# - docker image push --all-tags oncharterliz/frigate-proxy
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
# - docker image push --all-tags oncharterliz/multi-frigate
FROM node:18-alpine AS frigate-proxy
ENV NODE_ENV=production
FROM nginx:alpine AS multi-frigate
WORKDIR /app
COPY ./build/ /usr/share/nginx/html/
# Nginx config
RUN rm -rf /etc/nginx/conf.d/*
COPY ./nginx/default.conf /etc/nginx/conf.d/
COPY package.json yarn.lock ./
# Default port exposure
EXPOSE 80
RUN yarn install --production
# Copy enviornment script and vars
COPY env.sh .env.docker ./
# Add bash
RUN apk add --no-cache bash
# Make our shell script executable
RUN chmod +x env.sh
COPY ./dist ./dist
CMD yarn prod
EXPOSE 4000
# Start Nginx server
CMD ["/bin/bash", "-c", "/app/env.sh && nginx -g \"daemon off;\""]

View File

@ -1,30 +1,22 @@
# Instruction
- download
- go to download directory
- run `yarn` to install packages
Frontend for [Proxy Frigate](https://github.com/NlightN22/frigate-proxy)
- create file: `docker-compose.yml`
```yml
version: '3.0'
version: '3.1'
services:
front:
image: nginx:alpine
volumes:
- ./build/:/usr/share/nginx/html/
- ./nginx/:/etc/nginx/conf.d/
image: oncharterliz/multi-frigate:latest
environment:
REACT_APP_HOST: localhost
REACT_APP_PORT: 5173
REACT_APP_FRIGATE_PROXY: http://localhost:4000
REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm
REACT_APP_CLIENT_ID: frontend-client
ports:
- 8080:80 # set your port here
```
- create file: `.env.production.local`
```bash
REACT_APP_HOST=localhost
REACT_APP_PORT=4000
REACT_APP_OPENID_SERVER=https://server:port/realms/your-realm
REACT_APP_CLIENT_ID=your-client
- 5173:80 # set your port here
```
- run:
```bash
yarn build
docker compose up -d
```

31
env.ps1 Normal file
View File

@ -0,0 +1,31 @@
$envFileName = ".env.production.local"
$envOutputFile = "./public/env-config.js"
# Recreate config file
Remove-Item -Path $envOutputFile -Force
New-Item -Path $envOutputFile -ItemType File
# Add assignment
Add-Content -Path $envOutputFile -Value "window.env = {"
# Read each line in env file
foreach ($line in Get-Content -Path $envFileName) {
if ($line -match '=') {
$parts = $line -split '=', 2
$varname = $parts[0]
$varvalue = $parts[1]
# Read value of current variable if exists as Environment variable
$value = [System.Environment]::GetEnvironmentVariable($varname)
# Otherwise, use value from env file
if (-not $value) {
$value = $varvalue
}
# Append configuration property to JS file
$lineToAdd = " {0}: `"{1}`"," -f $varname, $value
Add-Content -Path $envOutputFile -Value $lineToAdd
}
}
Add-Content -Path $envOutputFile -Value "}"

32
env.sh Normal file
View File

@ -0,0 +1,32 @@
#!/bin/bash
EnvFileName=".env.docker"
EnvOutputFile="/usr/share/nginx/html/env-config.js"
# Recreate config file
rm -rf $EnvOutputFile
touch $EnvOutputFile
# Add assignment
echo "window.env = {" >> $EnvOutputFile
# Read each line in $EnvFileName file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
# Split env variables by character `=`
if printf '%s\n' "$line" | grep -q -e '='; then
varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
fi
# Read value of current variable if exists as Environment variable
value=$(printf '%s\n' "${!varname}")
# Otherwise use value from $EnvFileName file
[[ -z $value ]] && value=${varvalue}
# Append configuration property to JS file
echo " $varname: \"$value\"," >> $EnvOutputFile
done < $EnvFileName
echo "}" >> $EnvOutputFile

View File

@ -0,0 +1,13 @@
version: '3.0'
services:
front:
image: oncharterliz/multi-frigate:latest
environment:
REACT_APP_HOST: localhost
REACT_APP_PORT: 5173
REACT_APP_FRIGATE_PROXY: http://localhost:4000
REACT_APP_OPENID_SERVER: https://server:port/realms/your-realm
REACT_APP_CLIENT_ID: frontend-client
ports:
- 5173:80 # set your port here

7
public/env-config.js Normal file
View File

@ -0,0 +1,7 @@
window.env = {
REACT_APP_HOST: "localhost",
REACT_APP_PORT: "5173",
REACT_APP_FRIGATE_PROXY: "http://localhost:4000",
REACT_APP_OPENID_SERVER: "https://oauth.komponent-m.ru:8443/realms/frigate-realm",
REACT_APP_CLIENT_ID: "frontend-client",
}

View File

@ -25,6 +25,7 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Multi Frigate</title>
<script src="%PUBLIC_URL%/env-config.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -6,6 +6,7 @@ import AppRouter from './router/AppRouter';
import { Context } from '.';
import SideBar from './shared/components/SideBar';
import { observer } from 'mobx-react-lite';
import { isProduction } from './shared/env.const';
const AppBody = () => {
@ -23,7 +24,7 @@ const AppBody = () => {
const theme = useMantineTheme();
console.log("render Main")
if (!isProduction) console.log("render Main")
return (
<AppShell
styles={{

View File

@ -13,6 +13,7 @@ import { strings } from '../shared/strings/strings';
import { useAdminRole } from '../hooks/useAdminRole';
import Forbidden from './403';
import { observer } from 'mobx-react-lite';
import { isProduction } from '../shared/env.const';
const AccessSettings = () => {
const executed = useRef(false)
@ -45,7 +46,7 @@ const AccessSettings = () => {
setRoleId(value)
}
console.log('AccessSettings rendered')
if (!isProduction) console.log('AccessSettings rendered')
return (
<Flex w='100%' h='100%' direction='column'>
<Text align='center' size='xl'>{strings.pleaseSelectRole}</Text>

View File

@ -12,6 +12,7 @@ import RetryErrorPage from './RetryErrorPage';
import { useAdminRole } from '../hooks/useAdminRole';
import Forbidden from './403';
import { observer } from 'mobx-react-lite';
import { isProduction } from '../shared/env.const';
const HostConfigPage = () => {
@ -84,7 +85,7 @@ const HostConfigPage = () => {
if (!editorRef.current) {
return;
}
console.log('save config', save_option)
if (!isProduction) console.log('save config', save_option)
}, [editorRef])
if (configPending || adminLoading) return <CenterLoader />

View File

@ -10,6 +10,7 @@ import SelectedHostList from '../widgets/SelectedHostList';
import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil';
import SelectedDayList from '../widgets/SelectedDayList';
import CenterLoader from '../shared/components/loaders/CenterLoader';
import { isProduction } from '../shared/env.const';
export const recordingsPageQuery = {
@ -95,7 +96,7 @@ const RecordingsPage = () => {
navigate({ pathname: location.pathname, search: queryParams.toString() });
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
console.log('RecordingsPage rendered')
if (!isProduction) console.log('RecordingsPage rendered')
if (!firstRender) return <CenterLoader />

View File

@ -17,6 +17,7 @@ import { Context } from '..';
import { useAdminRole } from '../hooks/useAdminRole';
import Forbidden from './403';
import { observer } from 'mobx-react-lite';
import { isProduction } from '../shared/env.const';
const SettingsPage = () => {
const executed = useRef(false)
@ -59,17 +60,17 @@ const SettingsPage = () => {
})
const handleDiscard = () => {
console.log('Discard changes')
if (!isProduction) console.log('Discard changes')
refetch()
setConfigs(data ? mapEncryptedToView(data) : [])
}
useEffect(() => {
console.log('data changed')
if (!isProduction) console.log('data changed')
setConfigs(mapEncryptedToView(data))
}, [data])
useEffect(() => {
console.log('configs changed')
if (!isProduction) console.log('configs changed')
}, [configs])
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
@ -95,7 +96,7 @@ const SettingsPage = () => {
value: value,
}
});
console.log('configsToUpdate', configsToUpdate)
if (!isProduction) console.log('configsToUpdate', configsToUpdate)
mutation.mutate(configsToUpdate);
}

View File

@ -6,6 +6,7 @@ import RetryError from './RetryError';
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
import { OneSelectItem } from './filters.aps/OneSelectFilter';
import { strings } from '../strings/strings';
import { isProduction } from '../env.const';
interface CamerasTransferListProps {
roleId: string
@ -60,7 +61,7 @@ const CamerasTransferList = ({
refetch()
}
console.log('CamerasTransferListProps rendered')
if (!isProduction) console.log('CamerasTransferListProps rendered')
return (
<>
<Flex w='100%' justify='center'>

View File

@ -9,6 +9,7 @@ import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil'
import RetryError from '../RetryError';
import { strings } from '../../strings/strings';
import { RecordSummary } from '../../../types/record';
import { isProduction } from '../../env.const';
const CameraAccordion = () => {
const { recordingsStore: recStore } = useContext(Context)
@ -60,7 +61,7 @@ const CameraAccordion = () => {
return []
}
console.log('CameraAccordion rendered')
if (!isProduction) console.log('CameraAccordion rendered')
return (
<Accordion variant='separated' radius="md" w='100%'>

View File

@ -15,6 +15,7 @@ import { IconExternalLink, IconShare } from '@tabler/icons-react';
import { routesPath } from '../../../router/routes.path';
import AccordionShareButton from '../buttons/AccordionShareButton';
import VideoDownloader from '../../../widgets/VideoDownloader';
import { isProduction } from '../../env.const';
interface RecordingAccordionProps {
recordSummary?: RecordSummary
@ -57,7 +58,7 @@ const DayAccordion = ({
if (playedValue) {
const url = createRecordURL(playedValue)
if (url) {
console.log('GET URL: ', url)
if (!isProduction) console.log('GET URL: ', url)
setPlayerUrl(url)
}
} else {
@ -85,7 +86,7 @@ const DayAccordion = ({
setVideoPlayerState(undefined)
}
console.log('DayAccordion rendered')
if (!isProduction) console.log('DayAccordion rendered')
const hourLabel = (hour: string, eventsQty: number) => (
<Group>

View File

@ -16,6 +16,7 @@ import AccordionControlButton from '../buttons/AccordionControlButton';
import AccordionShareButton from '../buttons/AccordionShareButton';
import { useNavigate } from 'react-router-dom';
import EventPanel from './EventPanel';
import { isProduction } from '../../env.const';
/**
* @param day frigate format, e.g day: 2024-02-23
@ -92,10 +93,9 @@ const EventsAccordion = ({
useEffect(() => {
if (playedValue) {
// console.log('openVideoPlayer', playedValue)
if (playedValue && host) {
const url = createEventUrl(playedValue)
console.log('GET EVENT URL: ', url)
if (!isProduction) console.log('GET EVENT URL: ', url)
setPlayerUrl(url)
}
} else {
@ -108,8 +108,6 @@ const EventsAccordion = ({
if (!data || data.length < 1) return <Center><Text>Not have events at that period</Text></Center>
const handleOpenPlayer = (openedValue: string) => {
// console.log(`openVideoPlayer day:${day} hour:${hour}, opened value: ${openedValue}`)
// console.log(`opened value: ${openedValue}, eventId: ${playedValue}`)
if (openedValue !== playedValue) {
setOpenedItem(openedValue)
setPlayedValue(openedValue)

View File

@ -3,6 +3,7 @@ import { IconShare } from '@tabler/icons-react';
import React from 'react';
import AccordionControlButton from './AccordionControlButton';
import { routesPath } from '../../../router/routes.path';
import { isProduction } from '../../env.const';
interface AccordionShareButtonProps {
recordUrl?: string
@ -19,13 +20,13 @@ const AccordionShareButton = ({
if (canShare && url) {
try {
await navigator.share({ url });
console.log('Content shared successfully');
if (!isProduction) console.log('Content shared successfully');
} catch (err) {
console.error('Error sharing content: ', err);
}
} else {
clipboard.copy(url)
console.log('URL copied to clipboard')
if (!isProduction) console.log('URL copied to clipboard')
}
}

View File

@ -8,6 +8,7 @@ import { Center, Loader, Text } from '@mantine/core';
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
import { strings } from '../../strings/strings';
import RetryError from '../RetryError';
import { isProduction } from '../../env.const';
interface CameraSelectFilterProps {
selectedHostId: string,
@ -26,7 +27,7 @@ const CameraSelectFilter = ({
useEffect(() => {
if (!data) return
if (recStore.cameraIdParam) {
console.log('change camera by param')
if (!isProduction) console.log('change camera by param')
recStore.filteredCamera = data.find( camera => camera.id === recStore.cameraIdParam)
recStore.cameraIdParam = undefined
}
@ -47,8 +48,7 @@ const CameraSelectFilter = ({
recStore.filteredCamera = camera
}
console.log('CameraSelectFilter rendered')
// console.log('recStore.selectedCameraId', recStore.selectedCameraId)
if (!isProduction) console.log('CameraSelectFilter rendered')
return (
<OneSelectFilter

View File

@ -5,10 +5,9 @@ import { strings } from '../../strings/strings';
import { Box, Flex, Indicator, Text } from '@mantine/core';
import CloseWithTooltip from '../buttons/CloseWithTooltip';
import { Context } from '../../..';
import { isProduction } from '../../env.const';
interface DateRangeSelectFilterProps {
}
interface DateRangeSelectFilterProps {}
const DateRangeSelectFilter = ({
@ -16,11 +15,10 @@ const DateRangeSelectFilter = ({
const { recordingsStore: recStore } = useContext(Context)
const handlePick = (value: [Date | null, Date | null]) => {
console.log('handlePick',value)
recStore.selectedRange = value
}
console.log('DateRangeSelectFilter rendered')
if (!isProduction) console.log('DateRangeSelectFilter rendered')
return (
<Box>
<Flex
@ -35,7 +33,6 @@ const DateRangeSelectFilter = ({
allowSingleDateInRange
valueFormat="YYYY-MM-DD"
type="range"
placeholder={strings.selectRange}
mx="auto"
maw={400}
value={recStore.selectedRange}

View File

@ -44,7 +44,7 @@ function MSEPlayer({
const wsURL = useMemo(() => {
return wsUrl;
}, [camera]);
}, [wsUrl]);
const play = () => {
const currentVideo = videoRef.current;
@ -70,7 +70,7 @@ function MSEPlayer({
return CODECS.filter((codec) =>
isSupported(`video/mp4; codecs="${codec}"`)
).join();
}, []);
}, [CODECS]);
const onConnect = useCallback(() => {
if (!videoRef.current?.isConnected || !wsURL || wsRef.current) return false;
@ -252,7 +252,7 @@ function MSEPlayer({
return () => {
onDisconnect();
};
}, [playbackEnabled, onDisconnect, onConnect]);
}, [playbackEnabled, onDisconnect, onConnect, visibilityCheck]);
return (
<video

View File

@ -3,73 +3,77 @@ import videojs from 'video.js';
import Player from 'video.js/dist/types/player';
import 'video.js/dist/video-js.css'
import { getToken } from '../../../services/frigate.proxy/frigate.api';
import { isProduction } from '../../env.const';
interface VideoPlayerProps {
videoUrl: string
}
const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
const executed = useRef(false)
const videoRef = useRef<HTMLVideoElement>(null);
const playerRef = useRef<Player | null>(null);
useEffect(() => {
//@ts-ignore
videojs.Vhs.xhr.beforeRequest = function(options: any) {
options.headers = {
...options.headers,
Authorization: `Bearer ${getToken()}`,
if (!executed.current) {
//@ts-ignore
videojs.Vhs.xhr.beforeRequest = function (options: any) {
options.headers = {
...options.headers,
Authorization: `Bearer ${getToken()}`,
};
return options;
};
return options;
};
const defaultOptions = {
preload: 'auto',
autoplay: true,
sources: [
{
src: videoUrl,
type: 'application/vnd.apple.mpegurl',
withCredentials: true,
const defaultOptions = {
preload: 'auto',
autoplay: true,
sources: [
{
src: videoUrl,
type: 'application/vnd.apple.mpegurl',
withCredentials: true,
},
],
controls: true,
controlBar: {
skipButtons: { forward: 10, backward: 5 },
},
],
controls: true,
controlBar: {
skipButtons: { forward: 10, backward: 5 },
},
playbackRates: [0.5, 1, 2, 4, 8],
fluid: true,
};
playbackRates: [0.5, 1, 2, 4, 8],
fluid: true,
};
if (!videojs.browser.IS_FIREFOX) {
defaultOptions.playbackRates.push(16);
}
//TODO add rotations on IOS and android devices
console.log('playerRef.current', playerRef.current)
if (videoRef.current) {
console.log('mount new player')
playerRef.current = videojs(videoRef.current, { ...defaultOptions }, () => {
console.log('player is ready');
});
}
console.log('VideoPlayer rendered')
return () => {
if (playerRef.current !== null) {
playerRef.current.dispose();
playerRef.current = null;
console.log('unmount player')
if (!videojs.browser.IS_FIREFOX) {
defaultOptions.playbackRates.push(16);
}
};
}, []);
//TODO add rotations on IOS and android devices
if (!isProduction) console.log('playerRef.current', playerRef.current)
if (videoRef.current) {
if (!isProduction) console.log('mount new player')
playerRef.current = videojs(videoRef.current, { ...defaultOptions }, () => {
if (!isProduction) console.log('player is ready')
});
}
if (!isProduction) console.log('VideoPlayer rendered')
return () => {
if (playerRef.current !== null) {
playerRef.current.dispose();
playerRef.current = null;
if (!isProduction) console.log('unmount player')
}
};
}
executed.current = true
}, [videoUrl]);
useEffect(() => {
if (playerRef.current) {
playerRef.current.src(videoUrl);
console.log('player change src')
if (!isProduction) console.log('player change src')
}
}, [videoUrl]);

View File

@ -153,7 +153,7 @@ export default function WebRtcPlayer({
pcRef.current = undefined;
}
};
}, [camera, connect, PeerConnection, pcRef, videoRef, playbackEnabled]);
}, [camera, connect, PeerConnection, pcRef, videoRef, playbackEnabled, wsUrl]);
return (
<video

View File

@ -1,12 +1,12 @@
import { Center, Flex, MantineStyleSystemProps, Text, TextProps, Tooltip } from '@mantine/core';
import { Center, Text, TextProps, Tooltip } from '@mantine/core';
import { IconChevronDown, IconChevronUp, IconSelector, } from '@tabler/icons-react';
import React, { FC } from 'react';
import React from 'react';
interface SortedThProps {
title: string,
reversed: boolean,
sortedName: string | null,
textProps?: TextProps & React.RefAttributes<HTMLDivElement>
textProps?: TextProps
sorting?: boolean
onSort: (title: string) => void,
}

View File

@ -1,28 +1,15 @@
export const appMode = process.env.NODE_ENV
const isProduction = appMode === "production"
if (isProduction && typeof process.env.REACT_APP_HOST === 'undefined') {
throw new Error('REACT_APP_HOST environment variable is undefined');
}
export const host = process.env.REACT_APP_HOST
export const isProduction = appMode === "production"
export const host = isProduction ? window.env?.REACT_APP_HOST : process.env.HOST
if (isProduction && typeof process.env.REACT_APP_PORT === 'undefined') {
throw new Error('REACT_APP_PORT environment variable is undefined');
}
export const port = process.env.REACT_APP_PORT
export const port = isProduction ? window.env?.REACT_APP_PORT : process.env.PORT
if (typeof process.env.REACT_APP_FRIGATE_PROXY === 'undefined') {
throw new Error('REACT_APP_FRIGATE_PROXY environment variable is undefined');
}
export const proxyURL = new URL(process.env.REACT_APP_FRIGATE_PROXY)
if (typeof process.env.REACT_APP_OPENID_SERVER === 'undefined') {
throw new Error('REACT_APP_OPENID_SERVER environment variable is undefined');
}
if (typeof process.env.REACT_APP_CLIENT_ID === 'undefined') {
throw new Error('REACT_APP_CLIENT_ID environment variable is undefined');
}
const proxy = isProduction ? window.env?.REACT_APP_FRIGATE_PROXY : process.env.REACT_APP_FRIGATE_PROXY
export const proxyURL = new URL(proxy || '')
const oidpServer = isProduction ? window.env?.REACT_APP_OPENID_SERVER : process.env.REACT_APP_OPENID_SERVER
const oidpClientId = isProduction ? window.env?.REACT_APP_CLIENT_ID : process.env.REACT_APP_CLIENT_ID
export const oidpSettings = {
server: process.env.REACT_APP_OPENID_SERVER,
clientId: process.env.REACT_APP_CLIENT_ID,
server: oidpServer || '',
clientId: oidpClientId || '',
}

View File

@ -1,6 +1,5 @@
import { makeAutoObservable, runInAction } from "mobx"
import { makeAutoObservable } from "mobx"
import RootStore from "./root.store"
import { Resource } from "../utils/resource"
export class ModalStore {

View File

@ -32,7 +32,7 @@ export const DeliveryPointSchema = z.object({
export type DeliveryPoint = z.infer<typeof DeliveryPointSchema>
export class UserStore {
private _user: Resource<UserServer> = new Resource<UserServer>
private _user: Resource<UserServer> = new Resource<UserServer>()
public get user() {
return this._user;
}
@ -55,8 +55,8 @@ export class UserStore {
this._user.isLoading = true
const res = await this.fetchUserFromServer()
try {
runInAction( () => {
this._user = {...this._user, data: res}
runInAction(() => {
this._user = { ...this._user, data: res }
})
} catch (error) {
console.error(error)

View File

@ -228,7 +228,7 @@ export const formatUnixTimestampToDateTime = (unixTimestamp: number, config: Dat
const options: Intl.DateTimeFormatOptions = {
dateStyle: date_style,
timeStyle: time_style,
hour12: time_format !== 'browser' ? time_format == '12hour' : undefined,
hour12: time_format !== 'browser' ? time_format === '12hour' : undefined,
};
// Only set timeZone option when resolvedTimeZone does not match UTC±HH:MM format, or when timezone is set in config

9
src/types/global.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
declare global {
interface Window {
env?: {
[key: string]: string;
};
}
}
export {};

View File

@ -1,12 +1,10 @@
import React from 'react';
import { CameraConfig } from '../types/frigateConfig';
import { AspectRatio, Button, Card, Flex, Grid, Group, Space, Text, createStyles, useMantineTheme } from '@mantine/core';
import { Button, Card, Flex, Grid, Group, Text, createStyles } from '@mantine/core';
import { useNavigate } from 'react-router-dom';
import { routesPath } from '../router/routes.path';
import { GetCameraWHostWConfig, GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
import { frigateApi, mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
import { recordingsPageQuery } from '../pages/RecordingsPage';
import { routesPath } from '../router/routes.path';
import { mapHostToHostname, proxyApi } from '../services/frigate.proxy/frigate.api';
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
import AutoUpdatedImage from '../shared/components/images/AutoUpdatedImage';
import { strings } from '../shared/strings/strings';

View File

@ -1,16 +1,17 @@
import { Button, Flex, Switch, Table, Text, TextInput, useMantineTheme } from '@mantine/core';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Flex, Table } from '@mantine/core';
import { IconPlus, IconTrash } from '@tabler/icons-react';
import ObjectId from 'bson-objectid';
import React, { useCallback, useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
import SortedTh from '../shared/components/table.aps/SortedTh';
import { strings } from '../shared/strings/strings';
import { v4 as uuidv4 } from 'uuid'
import { IconBulbFilled, IconBulbOff, IconDeviceFloppy, IconPencil, IconPlus, IconSettings, IconTrash } from '@tabler/icons-react';
import { debounce } from '../shared/utils/debounce';
import StateCell from './hosts.table/StateCell';
import SwitchCell from './hosts.table/SwitchCell';
import TextInputCell from './hosts.table/TextInputCell';
import ObjectId from 'bson-objectid';
import { debounce } from '../shared/utils/debounce';
import HostSettingsMenu from '../shared/components/menu/HostSettingsMenu';
import { GetFrigateHost } from '../services/frigate.proxy/frigate.schema';
import StateCell from './hosts.table/StateCell';
import { isProduction } from '../shared/env.const';
interface TableProps<T> {
data: T[],
@ -20,7 +21,7 @@ interface TableProps<T> {
}
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
console.log('FrigateHostsTable rendered')
if (!isProduction) console.log('FrigateHostsTable rendered')
const [tableData, setTableData] = useState(data)
const [reversed, setReversed] = useState(false)
const [sortedName, setSortedName] = useState<string | null>(null)
@ -31,7 +32,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => {
if (changedCallback) changedCallback(tableData)
}, 200), [])
}, 200), [tableData])
useEffect(() => {
debouncedChanged(tableData)

View File

@ -1,14 +1,13 @@
import React, { useEffect, useMemo, useState } from 'react';
import JSMpegPlayer from '../shared/components/players/JSMpegPlayer';
import MSEPlayer from '../shared/components/players/MsePlayer';
import { CameraConfig } from '../types/frigateConfig';
import { LivePlayerMode } from '../types/live';
import { useEffect, useMemo, useState } from 'react';
import useCameraActivity from '../hooks/use-camera-activity';
import useCameraLiveMode from '../hooks/use-camera-live-mode';
import WebRtcPlayer from '../shared/components/players/WebRTCPlayer';
import { AspectRatio, Flex } from '@mantine/core';
import { frigateApi, proxyApi } from '../services/frigate.proxy/frigate.api';
import { proxyApi } from '../services/frigate.proxy/frigate.api';
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
import JSMpegPlayer from '../shared/components/players/JSMpegPlayer';
import MSEPlayer from '../shared/components/players/MsePlayer';
import WebRtcPlayer from '../shared/components/players/WebRTCPlayer';
import { LivePlayerMode } from '../types/live';
import { isProduction } from '../shared/env.const';
type LivePlayerProps = {
camera: GetCameraWHostWConfig;
@ -27,7 +26,7 @@ const Player = ({
const wsUrl = proxyApi.cameraWsURL(hostNameWPort, camera.name)
const cameraConfig = camera.config!
const { activeMotion, activeAudio, activeTracking } =
const { activeMotion, activeTracking } =
useCameraActivity(cameraConfig);
const cameraActive = useMemo(
@ -41,7 +40,7 @@ const Player = ({
const [liveReady, setLiveReady] = useState(false);
useEffect(() => {
if (!liveReady) {
if (cameraActive && liveMode == "jsmpeg") {
if (cameraActive && liveMode === "jsmpeg") {
setLiveReady(true);
}
@ -51,11 +50,11 @@ const Player = ({
if (!cameraActive) {
setLiveReady(false);
}
}, [cameraActive, liveReady]);
}, [cameraActive, liveReady, liveMode]);
console.log(`liveMode: `, liveMode)
if (!isProduction) console.log(`liveMode: `, liveMode)
let player;
if (liveMode == "webrtc") {
if (liveMode === "webrtc") {
player = (
<WebRtcPlayer
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
@ -65,7 +64,7 @@ const Player = ({
wsUrl={wsUrl}
/>
);
} else if (liveMode == "mse") {
} else if (liveMode === "mse") {
if ("MediaSource" in window || "ManagedMediaSource" in window) {
player = (
<MSEPlayer
@ -84,7 +83,7 @@ const Player = ({
</div>
);
}
} else if (liveMode == "jsmpeg") {
} else if (liveMode === "jsmpeg") {
player = (
<JSMpegPlayer
wsUrl={wsUrl}

View File

@ -4,15 +4,12 @@ import { Context } from '..';
import CameraSelectFilter from '../shared/components/filters.aps/CameraSelectFilter';
import DateRangeSelectFilter from '../shared/components/filters.aps/DateRangeSelectFilter';
import HostSelectFilter from '../shared/components/filters.aps/HostSelectFilter';
import { isProduction } from '../shared/env.const';
interface RecordingsFiltersRightSideProps {
}
const RecordingsFiltersRightSide = ({
}: RecordingsFiltersRightSideProps) => {
const RecordingsFiltersRightSide = () => {
const { recordingsStore: recStore } = useContext(Context)
console.log('RecordingsFiltersRightSide rendered')
if (!isProduction) console.log('RecordingsFiltersRightSide rendered')
return (
<>
<HostSelectFilter />

View File

@ -1,11 +1,11 @@
import { Button, Loader, Text, Notification, Progress } from '@mantine/core';
import { useMutation } from '@tanstack/react-query';
import { frigateApi, proxyApi } from '../services/frigate.proxy/frigate.api';
import { IconAlertCircle, IconExternalLink } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import RetryError from '../shared/components/RetryError';
import { formatFileTimestamps, unixTimeToDate } from '../shared/utils/dateUtil';
import { Button, Loader, Progress, Text } from '@mantine/core';
import { notifications } from '@mantine/notifications';
import { IconAlertCircle, IconExternalLink } from '@tabler/icons-react';
import { useMutation } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { proxyApi } from '../services/frigate.proxy/frigate.api';
import RetryError from '../shared/components/RetryError';
import { formatFileTimestamps } from '../shared/utils/dateUtil';
interface VideoDownloaderProps {
cameraName: string
@ -84,7 +84,7 @@ const VideoDownloader = ({
setTimer(undefined)
}, 5 * 60 * 1000)
}
}, [createName, link, videoBlob])
}, [createName, link, videoBlob, checkVideo, getVideBlob, hostName, timer])
useEffect(() => {
if (videoBlob && videoBlob instanceof Blob && createName) {
@ -97,7 +97,7 @@ const VideoDownloader = ({
}
}
}
}, [videoBlob, createName, link])
}, [videoBlob, createName, link, deleteVideo])
const checkTime = () => {
const duration = endUnixTime - startUnixTime
@ -105,8 +105,6 @@ const VideoDownloader = ({
notifications.show({
id: 'too-much-time',
withCloseButton: true,
onClose: () => console.log('unmounted'),
onOpen: () => console.log('mounted'),
autoClose: 5000,
title: "Max duration",
message: `Time can not be higher than ${maxVideoTime / 60} hour`,
@ -124,12 +122,13 @@ const VideoDownloader = ({
}
const handleCancel = () => {
clearTimeout(timer)
setTimer(undefined)
setCreateName(undefined)
setLink(undefined)
}
// TODO delete
// const handleCancel = () => {
// clearTimeout(timer)
// setTimer(undefined)
// setCreateName(undefined)
// setLink(undefined)
// }
if (startUnixTime === 0 || endUnixTime === 0) return null

View File

@ -14,7 +14,7 @@ interface SwithCellProps {
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
const theme = useMantineTheme();
if (typeof value === undefined && typeof defaultValue !== undefined) value = defaultValue
if (!value && !defaultValue) value = defaultValue
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
}

View File

@ -21,6 +21,6 @@
"jsx": "react-jsx"
},
"include": [
"src"
"src",
]
}

3974
yarn.lock

File diff suppressed because it is too large Load Diff