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
|
||||
|
||||
# misc
|
||||
.env
|
||||
.env.development
|
||||
.env*
|
||||
!.env.docker
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.idea/*
|
||||
.vscode
|
||||
|
||||
|
||||
29
Dockerfile
29
Dockerfile
@ -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;\""]
|
||||
28
README.md
28
README.md
@ -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
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`.
|
||||
-->
|
||||
<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>
|
||||
|
||||
@ -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={{
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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 />
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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%'>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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 || '',
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
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 { 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';
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -21,6 +21,6 @@
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
"src",
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user