fix lint warns
add env to index create env script create docker
This commit is contained in:
parent
d59e6c50e0
commit
811bc5bac7
5
.env.docker
Normal file
5
.env.docker
Normal 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
8
.gitignore
vendored
@ -13,13 +13,9 @@
|
|||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.env
|
.env*
|
||||||
.env.development
|
!.env.docker
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.idea/*
|
.idea/*
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
|||||||
29
Dockerfile
29
Dockerfile
@ -1,19 +1,26 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
# Build commands:
|
# Build commands:
|
||||||
# - rm dist -r -Force ; yarn build
|
# - rm build -r -Force ; yarn build
|
||||||
# - $VERSION=0.1
|
# - $VERSION=0.1
|
||||||
# - docker build --pull --rm -t oncharterliz/frigate-proxy:latest -t oncharterliz/frigate-proxy:$VERSION "."
|
# - docker build --pull --rm -t oncharterliz/multi-frigate:latest -t oncharterliz/multi-frigate:$VERSION "."
|
||||||
# - docker image push --all-tags oncharterliz/frigate-proxy
|
# - docker image push --all-tags oncharterliz/multi-frigate
|
||||||
|
|
||||||
FROM node:18-alpine AS frigate-proxy
|
FROM nginx:alpine AS multi-frigate
|
||||||
ENV NODE_ENV=production
|
|
||||||
WORKDIR /app
|
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
|
# Start Nginx server
|
||||||
|
CMD ["/bin/bash", "-c", "/app/env.sh && nginx -g \"daemon off;\""]
|
||||||
CMD yarn prod
|
|
||||||
EXPOSE 4000
|
|
||||||
28
README.md
28
README.md
@ -1,30 +1,22 @@
|
|||||||
# Instruction
|
# Instruction
|
||||||
|
Frontend for [Proxy Frigate](https://github.com/NlightN22/frigate-proxy)
|
||||||
- download
|
|
||||||
- go to download directory
|
|
||||||
- run `yarn` to install packages
|
|
||||||
- create file: `docker-compose.yml`
|
- create file: `docker-compose.yml`
|
||||||
```yml
|
```yml
|
||||||
version: '3.0'
|
version: '3.1'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
front:
|
front:
|
||||||
image: nginx:alpine
|
image: oncharterliz/multi-frigate:latest
|
||||||
volumes:
|
environment:
|
||||||
- ./build/:/usr/share/nginx/html/
|
REACT_APP_HOST: localhost
|
||||||
- ./nginx/:/etc/nginx/conf.d/
|
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:
|
ports:
|
||||||
- 8080:80 # set your port here
|
- 5173: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
|
|
||||||
```
|
```
|
||||||
- run:
|
- run:
|
||||||
```bash
|
```bash
|
||||||
yarn build
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|||||||
31
env.ps1
Normal file
31
env.ps1
Normal 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
32
env.sh
Normal 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
|
||||||
13
example/example.docker-compose.yml
Normal file
13
example/example.docker-compose.yml
Normal 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
7
public/env-config.js
Normal 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",
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@
|
|||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Multi Frigate</title>
|
<title>Multi Frigate</title>
|
||||||
|
<script src="%PUBLIC_URL%/env-config.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import AppRouter from './router/AppRouter';
|
|||||||
import { Context } from '.';
|
import { Context } from '.';
|
||||||
import SideBar from './shared/components/SideBar';
|
import SideBar from './shared/components/SideBar';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { isProduction } from './shared/env.const';
|
||||||
|
|
||||||
const AppBody = () => {
|
const AppBody = () => {
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ const AppBody = () => {
|
|||||||
|
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
console.log("render Main")
|
if (!isProduction) console.log("render Main")
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
styles={{
|
styles={{
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { strings } from '../shared/strings/strings';
|
|||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
const AccessSettings = () => {
|
const AccessSettings = () => {
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
@ -45,7 +46,7 @@ const AccessSettings = () => {
|
|||||||
setRoleId(value)
|
setRoleId(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('AccessSettings rendered')
|
if (!isProduction) console.log('AccessSettings rendered')
|
||||||
return (
|
return (
|
||||||
<Flex w='100%' h='100%' direction='column'>
|
<Flex w='100%' h='100%' direction='column'>
|
||||||
<Text align='center' size='xl'>{strings.pleaseSelectRole}</Text>
|
<Text align='center' size='xl'>{strings.pleaseSelectRole}</Text>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import RetryErrorPage from './RetryErrorPage';
|
|||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
|
|
||||||
const HostConfigPage = () => {
|
const HostConfigPage = () => {
|
||||||
@ -84,7 +85,7 @@ const HostConfigPage = () => {
|
|||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('save config', save_option)
|
if (!isProduction) console.log('save config', save_option)
|
||||||
}, [editorRef])
|
}, [editorRef])
|
||||||
|
|
||||||
if (configPending || adminLoading) return <CenterLoader />
|
if (configPending || adminLoading) return <CenterLoader />
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import SelectedHostList from '../widgets/SelectedHostList';
|
|||||||
import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil';
|
import { dateToQueryString, parseQueryDateToDate } from '../shared/utils/dateUtil';
|
||||||
import SelectedDayList from '../widgets/SelectedDayList';
|
import SelectedDayList from '../widgets/SelectedDayList';
|
||||||
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
import CenterLoader from '../shared/components/loaders/CenterLoader';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
|
|
||||||
export const recordingsPageQuery = {
|
export const recordingsPageQuery = {
|
||||||
@ -95,7 +96,7 @@ const RecordingsPage = () => {
|
|||||||
navigate({ pathname: location.pathname, search: queryParams.toString() });
|
navigate({ pathname: location.pathname, search: queryParams.toString() });
|
||||||
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
|
}, [recStore.selectedRange, location.pathname, navigate, queryParams])
|
||||||
|
|
||||||
console.log('RecordingsPage rendered')
|
if (!isProduction) console.log('RecordingsPage rendered')
|
||||||
|
|
||||||
if (!firstRender) return <CenterLoader />
|
if (!firstRender) return <CenterLoader />
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { Context } from '..';
|
|||||||
import { useAdminRole } from '../hooks/useAdminRole';
|
import { useAdminRole } from '../hooks/useAdminRole';
|
||||||
import Forbidden from './403';
|
import Forbidden from './403';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
const executed = useRef(false)
|
const executed = useRef(false)
|
||||||
@ -59,17 +60,17 @@ const SettingsPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleDiscard = () => {
|
const handleDiscard = () => {
|
||||||
console.log('Discard changes')
|
if (!isProduction) console.log('Discard changes')
|
||||||
refetch()
|
refetch()
|
||||||
setConfigs(data ? mapEncryptedToView(data) : [])
|
setConfigs(data ? mapEncryptedToView(data) : [])
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('data changed')
|
if (!isProduction) console.log('data changed')
|
||||||
setConfigs(mapEncryptedToView(data))
|
setConfigs(mapEncryptedToView(data))
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('configs changed')
|
if (!isProduction) console.log('configs changed')
|
||||||
}, [configs])
|
}, [configs])
|
||||||
|
|
||||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
@ -95,7 +96,7 @@ const SettingsPage = () => {
|
|||||||
value: value,
|
value: value,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('configsToUpdate', configsToUpdate)
|
if (!isProduction) console.log('configsToUpdate', configsToUpdate)
|
||||||
mutation.mutate(configsToUpdate);
|
mutation.mutate(configsToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import RetryError from './RetryError';
|
|||||||
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
|
import { TransferList, Text, TransferListData, TransferListProps, TransferListItem, Button, Flex } from '@mantine/core';
|
||||||
import { OneSelectItem } from './filters.aps/OneSelectFilter';
|
import { OneSelectItem } from './filters.aps/OneSelectFilter';
|
||||||
import { strings } from '../strings/strings';
|
import { strings } from '../strings/strings';
|
||||||
|
import { isProduction } from '../env.const';
|
||||||
|
|
||||||
interface CamerasTransferListProps {
|
interface CamerasTransferListProps {
|
||||||
roleId: string
|
roleId: string
|
||||||
@ -60,7 +61,7 @@ const CamerasTransferList = ({
|
|||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('CamerasTransferListProps rendered')
|
if (!isProduction) console.log('CamerasTransferListProps rendered')
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex w='100%' justify='center'>
|
<Flex w='100%' justify='center'>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { getResolvedTimeZone, parseQueryDateToDate } from '../../utils/dateUtil'
|
|||||||
import RetryError from '../RetryError';
|
import RetryError from '../RetryError';
|
||||||
import { strings } from '../../strings/strings';
|
import { strings } from '../../strings/strings';
|
||||||
import { RecordSummary } from '../../../types/record';
|
import { RecordSummary } from '../../../types/record';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
const CameraAccordion = () => {
|
const CameraAccordion = () => {
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
@ -60,7 +61,7 @@ const CameraAccordion = () => {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('CameraAccordion rendered')
|
if (!isProduction) console.log('CameraAccordion rendered')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion variant='separated' radius="md" w='100%'>
|
<Accordion variant='separated' radius="md" w='100%'>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { IconExternalLink, IconShare } from '@tabler/icons-react';
|
|||||||
import { routesPath } from '../../../router/routes.path';
|
import { routesPath } from '../../../router/routes.path';
|
||||||
import AccordionShareButton from '../buttons/AccordionShareButton';
|
import AccordionShareButton from '../buttons/AccordionShareButton';
|
||||||
import VideoDownloader from '../../../widgets/VideoDownloader';
|
import VideoDownloader from '../../../widgets/VideoDownloader';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
interface RecordingAccordionProps {
|
interface RecordingAccordionProps {
|
||||||
recordSummary?: RecordSummary
|
recordSummary?: RecordSummary
|
||||||
@ -57,7 +58,7 @@ const DayAccordion = ({
|
|||||||
if (playedValue) {
|
if (playedValue) {
|
||||||
const url = createRecordURL(playedValue)
|
const url = createRecordURL(playedValue)
|
||||||
if (url) {
|
if (url) {
|
||||||
console.log('GET URL: ', url)
|
if (!isProduction) console.log('GET URL: ', url)
|
||||||
setPlayerUrl(url)
|
setPlayerUrl(url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -85,7 +86,7 @@ const DayAccordion = ({
|
|||||||
setVideoPlayerState(undefined)
|
setVideoPlayerState(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('DayAccordion rendered')
|
if (!isProduction) console.log('DayAccordion rendered')
|
||||||
|
|
||||||
const hourLabel = (hour: string, eventsQty: number) => (
|
const hourLabel = (hour: string, eventsQty: number) => (
|
||||||
<Group>
|
<Group>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import AccordionControlButton from '../buttons/AccordionControlButton';
|
|||||||
import AccordionShareButton from '../buttons/AccordionShareButton';
|
import AccordionShareButton from '../buttons/AccordionShareButton';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import EventPanel from './EventPanel';
|
import EventPanel from './EventPanel';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param day frigate format, e.g day: 2024-02-23
|
* @param day frigate format, e.g day: 2024-02-23
|
||||||
@ -92,10 +93,9 @@ const EventsAccordion = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (playedValue) {
|
if (playedValue) {
|
||||||
// console.log('openVideoPlayer', playedValue)
|
|
||||||
if (playedValue && host) {
|
if (playedValue && host) {
|
||||||
const url = createEventUrl(playedValue)
|
const url = createEventUrl(playedValue)
|
||||||
console.log('GET EVENT URL: ', url)
|
if (!isProduction) console.log('GET EVENT URL: ', url)
|
||||||
setPlayerUrl(url)
|
setPlayerUrl(url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -108,8 +108,6 @@ const EventsAccordion = ({
|
|||||||
if (!data || data.length < 1) return <Center><Text>Not have events at that period</Text></Center>
|
if (!data || data.length < 1) return <Center><Text>Not have events at that period</Text></Center>
|
||||||
|
|
||||||
const handleOpenPlayer = (openedValue: string) => {
|
const handleOpenPlayer = (openedValue: string) => {
|
||||||
// console.log(`openVideoPlayer day:${day} hour:${hour}, opened value: ${openedValue}`)
|
|
||||||
// console.log(`opened value: ${openedValue}, eventId: ${playedValue}`)
|
|
||||||
if (openedValue !== playedValue) {
|
if (openedValue !== playedValue) {
|
||||||
setOpenedItem(openedValue)
|
setOpenedItem(openedValue)
|
||||||
setPlayedValue(openedValue)
|
setPlayedValue(openedValue)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { IconShare } from '@tabler/icons-react';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AccordionControlButton from './AccordionControlButton';
|
import AccordionControlButton from './AccordionControlButton';
|
||||||
import { routesPath } from '../../../router/routes.path';
|
import { routesPath } from '../../../router/routes.path';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
interface AccordionShareButtonProps {
|
interface AccordionShareButtonProps {
|
||||||
recordUrl?: string
|
recordUrl?: string
|
||||||
@ -19,13 +20,13 @@ const AccordionShareButton = ({
|
|||||||
if (canShare && url) {
|
if (canShare && url) {
|
||||||
try {
|
try {
|
||||||
await navigator.share({ url });
|
await navigator.share({ url });
|
||||||
console.log('Content shared successfully');
|
if (!isProduction) console.log('Content shared successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sharing content: ', err);
|
console.error('Error sharing content: ', err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clipboard.copy(url)
|
clipboard.copy(url)
|
||||||
console.log('URL copied to clipboard')
|
if (!isProduction) console.log('URL copied to clipboard')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { Center, Loader, Text } from '@mantine/core';
|
|||||||
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
import OneSelectFilter, { OneSelectItem } from './OneSelectFilter';
|
||||||
import { strings } from '../../strings/strings';
|
import { strings } from '../../strings/strings';
|
||||||
import RetryError from '../RetryError';
|
import RetryError from '../RetryError';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
interface CameraSelectFilterProps {
|
interface CameraSelectFilterProps {
|
||||||
selectedHostId: string,
|
selectedHostId: string,
|
||||||
@ -26,7 +27,7 @@ const CameraSelectFilter = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
if (recStore.cameraIdParam) {
|
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.filteredCamera = data.find( camera => camera.id === recStore.cameraIdParam)
|
||||||
recStore.cameraIdParam = undefined
|
recStore.cameraIdParam = undefined
|
||||||
}
|
}
|
||||||
@ -47,8 +48,7 @@ const CameraSelectFilter = ({
|
|||||||
recStore.filteredCamera = camera
|
recStore.filteredCamera = camera
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('CameraSelectFilter rendered')
|
if (!isProduction) console.log('CameraSelectFilter rendered')
|
||||||
// console.log('recStore.selectedCameraId', recStore.selectedCameraId)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OneSelectFilter
|
<OneSelectFilter
|
||||||
|
|||||||
@ -5,10 +5,9 @@ import { strings } from '../../strings/strings';
|
|||||||
import { Box, Flex, Indicator, Text } from '@mantine/core';
|
import { Box, Flex, Indicator, Text } from '@mantine/core';
|
||||||
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
import CloseWithTooltip from '../buttons/CloseWithTooltip';
|
||||||
import { Context } from '../../..';
|
import { Context } from '../../..';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
interface DateRangeSelectFilterProps {
|
interface DateRangeSelectFilterProps {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const DateRangeSelectFilter = ({
|
const DateRangeSelectFilter = ({
|
||||||
|
|
||||||
@ -16,11 +15,10 @@ const DateRangeSelectFilter = ({
|
|||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
const handlePick = (value: [Date | null, Date | null]) => {
|
const handlePick = (value: [Date | null, Date | null]) => {
|
||||||
console.log('handlePick',value)
|
|
||||||
recStore.selectedRange = value
|
recStore.selectedRange = value
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('DateRangeSelectFilter rendered')
|
if (!isProduction) console.log('DateRangeSelectFilter rendered')
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Flex
|
<Flex
|
||||||
@ -35,7 +33,6 @@ const DateRangeSelectFilter = ({
|
|||||||
allowSingleDateInRange
|
allowSingleDateInRange
|
||||||
valueFormat="YYYY-MM-DD"
|
valueFormat="YYYY-MM-DD"
|
||||||
type="range"
|
type="range"
|
||||||
placeholder={strings.selectRange}
|
|
||||||
mx="auto"
|
mx="auto"
|
||||||
maw={400}
|
maw={400}
|
||||||
value={recStore.selectedRange}
|
value={recStore.selectedRange}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ function MSEPlayer({
|
|||||||
|
|
||||||
const wsURL = useMemo(() => {
|
const wsURL = useMemo(() => {
|
||||||
return wsUrl;
|
return wsUrl;
|
||||||
}, [camera]);
|
}, [wsUrl]);
|
||||||
|
|
||||||
const play = () => {
|
const play = () => {
|
||||||
const currentVideo = videoRef.current;
|
const currentVideo = videoRef.current;
|
||||||
@ -70,7 +70,7 @@ function MSEPlayer({
|
|||||||
return CODECS.filter((codec) =>
|
return CODECS.filter((codec) =>
|
||||||
isSupported(`video/mp4; codecs="${codec}"`)
|
isSupported(`video/mp4; codecs="${codec}"`)
|
||||||
).join();
|
).join();
|
||||||
}, []);
|
}, [CODECS]);
|
||||||
|
|
||||||
const onConnect = useCallback(() => {
|
const onConnect = useCallback(() => {
|
||||||
if (!videoRef.current?.isConnected || !wsURL || wsRef.current) return false;
|
if (!videoRef.current?.isConnected || !wsURL || wsRef.current) return false;
|
||||||
@ -252,7 +252,7 @@ function MSEPlayer({
|
|||||||
return () => {
|
return () => {
|
||||||
onDisconnect();
|
onDisconnect();
|
||||||
};
|
};
|
||||||
}, [playbackEnabled, onDisconnect, onConnect]);
|
}, [playbackEnabled, onDisconnect, onConnect, visibilityCheck]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
|
|||||||
@ -3,73 +3,77 @@ import videojs from 'video.js';
|
|||||||
import Player from 'video.js/dist/types/player';
|
import Player from 'video.js/dist/types/player';
|
||||||
import 'video.js/dist/video-js.css'
|
import 'video.js/dist/video-js.css'
|
||||||
import { getToken } from '../../../services/frigate.proxy/frigate.api';
|
import { getToken } from '../../../services/frigate.proxy/frigate.api';
|
||||||
|
import { isProduction } from '../../env.const';
|
||||||
|
|
||||||
interface VideoPlayerProps {
|
interface VideoPlayerProps {
|
||||||
videoUrl: string
|
videoUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
const VideoPlayer = ({ videoUrl }: VideoPlayerProps) => {
|
||||||
|
const executed = useRef(false)
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const playerRef = useRef<Player | null>(null);
|
const playerRef = useRef<Player | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!executed.current) {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
videojs.Vhs.xhr.beforeRequest = function(options: any) {
|
videojs.Vhs.xhr.beforeRequest = function (options: any) {
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
Authorization: `Bearer ${getToken()}`,
|
Authorization: `Bearer ${getToken()}`,
|
||||||
|
};
|
||||||
|
return options;
|
||||||
};
|
};
|
||||||
return options;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
preload: 'auto',
|
preload: 'auto',
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: videoUrl,
|
src: videoUrl,
|
||||||
type: 'application/vnd.apple.mpegurl',
|
type: 'application/vnd.apple.mpegurl',
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
controls: true,
|
||||||
|
controlBar: {
|
||||||
|
skipButtons: { forward: 10, backward: 5 },
|
||||||
},
|
},
|
||||||
],
|
playbackRates: [0.5, 1, 2, 4, 8],
|
||||||
controls: true,
|
fluid: true,
|
||||||
controlBar: {
|
};
|
||||||
skipButtons: { forward: 10, backward: 5 },
|
|
||||||
},
|
|
||||||
playbackRates: [0.5, 1, 2, 4, 8],
|
|
||||||
fluid: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!videojs.browser.IS_FIREFOX) {
|
if (!videojs.browser.IS_FIREFOX) {
|
||||||
defaultOptions.playbackRates.push(16);
|
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')
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}, []);
|
//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(() => {
|
useEffect(() => {
|
||||||
if (playerRef.current) {
|
if (playerRef.current) {
|
||||||
playerRef.current.src(videoUrl);
|
playerRef.current.src(videoUrl);
|
||||||
console.log('player change src')
|
if (!isProduction) console.log('player change src')
|
||||||
}
|
}
|
||||||
}, [videoUrl]);
|
}, [videoUrl]);
|
||||||
|
|
||||||
|
|||||||
@ -153,7 +153,7 @@ export default function WebRtcPlayer({
|
|||||||
pcRef.current = undefined;
|
pcRef.current = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [camera, connect, PeerConnection, pcRef, videoRef, playbackEnabled]);
|
}, [camera, connect, PeerConnection, pcRef, videoRef, playbackEnabled, wsUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
|
|||||||
@ -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 { IconChevronDown, IconChevronUp, IconSelector, } from '@tabler/icons-react';
|
||||||
import React, { FC } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface SortedThProps {
|
interface SortedThProps {
|
||||||
title: string,
|
title: string,
|
||||||
reversed: boolean,
|
reversed: boolean,
|
||||||
sortedName: string | null,
|
sortedName: string | null,
|
||||||
textProps?: TextProps & React.RefAttributes<HTMLDivElement>
|
textProps?: TextProps
|
||||||
sorting?: boolean
|
sorting?: boolean
|
||||||
onSort: (title: string) => void,
|
onSort: (title: string) => void,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,15 @@
|
|||||||
export const appMode = process.env.NODE_ENV
|
export const appMode = process.env.NODE_ENV
|
||||||
const isProduction = appMode === "production"
|
export const isProduction = appMode === "production"
|
||||||
if (isProduction && typeof process.env.REACT_APP_HOST === 'undefined') {
|
export const host = isProduction ? window.env?.REACT_APP_HOST : process.env.HOST
|
||||||
throw new Error('REACT_APP_HOST environment variable is undefined');
|
|
||||||
}
|
|
||||||
export const host = process.env.REACT_APP_HOST
|
|
||||||
|
|
||||||
if (isProduction && typeof process.env.REACT_APP_PORT === 'undefined') {
|
export const port = isProduction ? window.env?.REACT_APP_PORT : process.env.PORT
|
||||||
throw new Error('REACT_APP_PORT environment variable is undefined');
|
|
||||||
}
|
|
||||||
export const port = process.env.REACT_APP_PORT
|
|
||||||
|
|
||||||
if (typeof process.env.REACT_APP_FRIGATE_PROXY === 'undefined') {
|
const proxy = isProduction ? window.env?.REACT_APP_FRIGATE_PROXY : process.env.REACT_APP_FRIGATE_PROXY
|
||||||
throw new Error('REACT_APP_FRIGATE_PROXY environment variable is undefined');
|
export const proxyURL = new URL(proxy || '')
|
||||||
}
|
|
||||||
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 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 = {
|
export const oidpSettings = {
|
||||||
server: process.env.REACT_APP_OPENID_SERVER,
|
server: oidpServer || '',
|
||||||
clientId: process.env.REACT_APP_CLIENT_ID,
|
clientId: oidpClientId || '',
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { makeAutoObservable, runInAction } from "mobx"
|
import { makeAutoObservable } from "mobx"
|
||||||
import RootStore from "./root.store"
|
import RootStore from "./root.store"
|
||||||
import { Resource } from "../utils/resource"
|
|
||||||
|
|
||||||
export class ModalStore {
|
export class ModalStore {
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export const DeliveryPointSchema = z.object({
|
|||||||
export type DeliveryPoint = z.infer<typeof DeliveryPointSchema>
|
export type DeliveryPoint = z.infer<typeof DeliveryPointSchema>
|
||||||
|
|
||||||
export class UserStore {
|
export class UserStore {
|
||||||
private _user: Resource<UserServer> = new Resource<UserServer>
|
private _user: Resource<UserServer> = new Resource<UserServer>()
|
||||||
public get user() {
|
public get user() {
|
||||||
return this._user;
|
return this._user;
|
||||||
}
|
}
|
||||||
@ -55,8 +55,8 @@ export class UserStore {
|
|||||||
this._user.isLoading = true
|
this._user.isLoading = true
|
||||||
const res = await this.fetchUserFromServer()
|
const res = await this.fetchUserFromServer()
|
||||||
try {
|
try {
|
||||||
runInAction( () => {
|
runInAction(() => {
|
||||||
this._user = {...this._user, data: res}
|
this._user = { ...this._user, data: res }
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
|||||||
@ -228,7 +228,7 @@ export const formatUnixTimestampToDateTime = (unixTimestamp: number, config: Dat
|
|||||||
const options: Intl.DateTimeFormatOptions = {
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
dateStyle: date_style,
|
dateStyle: date_style,
|
||||||
timeStyle: time_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
|
// 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
9
src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
env?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import { Button, Card, Flex, Grid, Group, Text, createStyles } from '@mantine/core';
|
||||||
import { CameraConfig } from '../types/frigateConfig';
|
|
||||||
import { AspectRatio, Button, Card, Flex, Grid, Group, Space, Text, createStyles, useMantineTheme } from '@mantine/core';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 { 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';
|
import { strings } from '../shared/strings/strings';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
import { Button, Flex, Switch, Table, Text, TextInput, useMantineTheme } from '@mantine/core';
|
import { Button, Flex, Table } from '@mantine/core';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
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 SortedTh from '../shared/components/table.aps/SortedTh';
|
||||||
import { strings } from '../shared/strings/strings';
|
import { strings } from '../shared/strings/strings';
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { debounce } from '../shared/utils/debounce';
|
||||||
import { IconBulbFilled, IconBulbOff, IconDeviceFloppy, IconPencil, IconPlus, IconSettings, IconTrash } from '@tabler/icons-react';
|
import StateCell from './hosts.table/StateCell';
|
||||||
import SwitchCell from './hosts.table/SwitchCell';
|
import SwitchCell from './hosts.table/SwitchCell';
|
||||||
import TextInputCell from './hosts.table/TextInputCell';
|
import TextInputCell from './hosts.table/TextInputCell';
|
||||||
import ObjectId from 'bson-objectid';
|
import { isProduction } from '../shared/env.const';
|
||||||
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';
|
|
||||||
|
|
||||||
interface TableProps<T> {
|
interface TableProps<T> {
|
||||||
data: T[],
|
data: T[],
|
||||||
@ -20,7 +21,7 @@ interface TableProps<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedCallback }: TableProps<GetFrigateHost>) => {
|
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 [tableData, setTableData] = useState(data)
|
||||||
const [reversed, setReversed] = useState(false)
|
const [reversed, setReversed] = useState(false)
|
||||||
const [sortedName, setSortedName] = useState<string | null>(null)
|
const [sortedName, setSortedName] = useState<string | null>(null)
|
||||||
@ -31,7 +32,7 @@ const FrigateHostsTable = ({ data, showAddButton = false, saveCallback, changedC
|
|||||||
|
|
||||||
const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => {
|
const debouncedChanged = useCallback(debounce((tableData: GetFrigateHost[]) => {
|
||||||
if (changedCallback) changedCallback(tableData)
|
if (changedCallback) changedCallback(tableData)
|
||||||
}, 200), [])
|
}, 200), [tableData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
debouncedChanged(tableData)
|
debouncedChanged(tableData)
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import { 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 useCameraActivity from '../hooks/use-camera-activity';
|
import useCameraActivity from '../hooks/use-camera-activity';
|
||||||
import useCameraLiveMode from '../hooks/use-camera-live-mode';
|
import useCameraLiveMode from '../hooks/use-camera-live-mode';
|
||||||
import WebRtcPlayer from '../shared/components/players/WebRTCPlayer';
|
import { proxyApi } from '../services/frigate.proxy/frigate.api';
|
||||||
import { AspectRatio, Flex } from '@mantine/core';
|
|
||||||
import { frigateApi, proxyApi } from '../services/frigate.proxy/frigate.api';
|
|
||||||
import { GetCameraWHostWConfig } from '../services/frigate.proxy/frigate.schema';
|
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 = {
|
type LivePlayerProps = {
|
||||||
camera: GetCameraWHostWConfig;
|
camera: GetCameraWHostWConfig;
|
||||||
@ -27,7 +26,7 @@ const Player = ({
|
|||||||
const wsUrl = proxyApi.cameraWsURL(hostNameWPort, camera.name)
|
const wsUrl = proxyApi.cameraWsURL(hostNameWPort, camera.name)
|
||||||
const cameraConfig = camera.config!
|
const cameraConfig = camera.config!
|
||||||
|
|
||||||
const { activeMotion, activeAudio, activeTracking } =
|
const { activeMotion, activeTracking } =
|
||||||
useCameraActivity(cameraConfig);
|
useCameraActivity(cameraConfig);
|
||||||
|
|
||||||
const cameraActive = useMemo(
|
const cameraActive = useMemo(
|
||||||
@ -41,7 +40,7 @@ const Player = ({
|
|||||||
const [liveReady, setLiveReady] = useState(false);
|
const [liveReady, setLiveReady] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!liveReady) {
|
if (!liveReady) {
|
||||||
if (cameraActive && liveMode == "jsmpeg") {
|
if (cameraActive && liveMode === "jsmpeg") {
|
||||||
setLiveReady(true);
|
setLiveReady(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,11 +50,11 @@ const Player = ({
|
|||||||
if (!cameraActive) {
|
if (!cameraActive) {
|
||||||
setLiveReady(false);
|
setLiveReady(false);
|
||||||
}
|
}
|
||||||
}, [cameraActive, liveReady]);
|
}, [cameraActive, liveReady, liveMode]);
|
||||||
|
|
||||||
console.log(`liveMode: `, liveMode)
|
if (!isProduction) console.log(`liveMode: `, liveMode)
|
||||||
let player;
|
let player;
|
||||||
if (liveMode == "webrtc") {
|
if (liveMode === "webrtc") {
|
||||||
player = (
|
player = (
|
||||||
<WebRtcPlayer
|
<WebRtcPlayer
|
||||||
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
|
className={`rounded-2xl h-full ${liveReady ? "" : "hidden"}`}
|
||||||
@ -65,7 +64,7 @@ const Player = ({
|
|||||||
wsUrl={wsUrl}
|
wsUrl={wsUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (liveMode == "mse") {
|
} else if (liveMode === "mse") {
|
||||||
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
if ("MediaSource" in window || "ManagedMediaSource" in window) {
|
||||||
player = (
|
player = (
|
||||||
<MSEPlayer
|
<MSEPlayer
|
||||||
@ -84,7 +83,7 @@ const Player = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (liveMode == "jsmpeg") {
|
} else if (liveMode === "jsmpeg") {
|
||||||
player = (
|
player = (
|
||||||
<JSMpegPlayer
|
<JSMpegPlayer
|
||||||
wsUrl={wsUrl}
|
wsUrl={wsUrl}
|
||||||
|
|||||||
@ -4,15 +4,12 @@ import { Context } from '..';
|
|||||||
import CameraSelectFilter from '../shared/components/filters.aps/CameraSelectFilter';
|
import CameraSelectFilter from '../shared/components/filters.aps/CameraSelectFilter';
|
||||||
import DateRangeSelectFilter from '../shared/components/filters.aps/DateRangeSelectFilter';
|
import DateRangeSelectFilter from '../shared/components/filters.aps/DateRangeSelectFilter';
|
||||||
import HostSelectFilter from '../shared/components/filters.aps/HostSelectFilter';
|
import HostSelectFilter from '../shared/components/filters.aps/HostSelectFilter';
|
||||||
|
import { isProduction } from '../shared/env.const';
|
||||||
|
|
||||||
interface RecordingsFiltersRightSideProps {
|
const RecordingsFiltersRightSide = () => {
|
||||||
}
|
|
||||||
|
|
||||||
const RecordingsFiltersRightSide = ({
|
|
||||||
}: RecordingsFiltersRightSideProps) => {
|
|
||||||
const { recordingsStore: recStore } = useContext(Context)
|
const { recordingsStore: recStore } = useContext(Context)
|
||||||
|
|
||||||
console.log('RecordingsFiltersRightSide rendered')
|
if (!isProduction) console.log('RecordingsFiltersRightSide rendered')
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HostSelectFilter />
|
<HostSelectFilter />
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Button, Loader, Text, Notification, Progress } from '@mantine/core';
|
import { Button, Loader, Progress, Text } 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 { notifications } from '@mantine/notifications';
|
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 {
|
interface VideoDownloaderProps {
|
||||||
cameraName: string
|
cameraName: string
|
||||||
@ -84,7 +84,7 @@ const VideoDownloader = ({
|
|||||||
setTimer(undefined)
|
setTimer(undefined)
|
||||||
}, 5 * 60 * 1000)
|
}, 5 * 60 * 1000)
|
||||||
}
|
}
|
||||||
}, [createName, link, videoBlob])
|
}, [createName, link, videoBlob, checkVideo, getVideBlob, hostName, timer])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoBlob && videoBlob instanceof Blob && createName) {
|
if (videoBlob && videoBlob instanceof Blob && createName) {
|
||||||
@ -97,7 +97,7 @@ const VideoDownloader = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [videoBlob, createName, link])
|
}, [videoBlob, createName, link, deleteVideo])
|
||||||
|
|
||||||
const checkTime = () => {
|
const checkTime = () => {
|
||||||
const duration = endUnixTime - startUnixTime
|
const duration = endUnixTime - startUnixTime
|
||||||
@ -105,8 +105,6 @@ const VideoDownloader = ({
|
|||||||
notifications.show({
|
notifications.show({
|
||||||
id: 'too-much-time',
|
id: 'too-much-time',
|
||||||
withCloseButton: true,
|
withCloseButton: true,
|
||||||
onClose: () => console.log('unmounted'),
|
|
||||||
onOpen: () => console.log('mounted'),
|
|
||||||
autoClose: 5000,
|
autoClose: 5000,
|
||||||
title: "Max duration",
|
title: "Max duration",
|
||||||
message: `Time can not be higher than ${maxVideoTime / 60} hour`,
|
message: `Time can not be higher than ${maxVideoTime / 60} hour`,
|
||||||
@ -124,12 +122,13 @@ const VideoDownloader = ({
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
// TODO delete
|
||||||
clearTimeout(timer)
|
// const handleCancel = () => {
|
||||||
setTimer(undefined)
|
// clearTimeout(timer)
|
||||||
setCreateName(undefined)
|
// setTimer(undefined)
|
||||||
setLink(undefined)
|
// setCreateName(undefined)
|
||||||
}
|
// setLink(undefined)
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
if (startUnixTime === 0 || endUnixTime === 0) return null
|
if (startUnixTime === 0 || endUnixTime === 0) return null
|
||||||
|
|||||||
@ -14,7 +14,7 @@ interface SwithCellProps {
|
|||||||
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
|
export const SwitchCell = ( { value, defaultValue, width, id, propertyName, toggle }: SwithCellProps ) => {
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
if (typeof value === undefined && typeof defaultValue !== undefined) value = defaultValue
|
if (!value && !defaultValue) value = defaultValue
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
|
if (id && toggle && propertyName) toggle(id, propertyName, event.target.value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,6 @@
|
|||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user