Compare commits

...

12 Commits

Author SHA1 Message Date
Paul Armstrong
5043040530 fix(web): ensure tooltips and menus don't cause scrollbar reflow 2021-02-25 06:34:36 -06:00
Paul Armstrong
3c60aeeef9 fix(web): set events api limit to 25 2021-02-25 06:34:36 -06:00
Blake Blackshear
0344d61b26 use gevent sleep to prevent mjpeg from blocking 2021-02-25 06:34:36 -06:00
Blake Blackshear
0e8467782b version tick 2021-02-25 06:34:36 -06:00
Paul Armstrong
423ea26266 Add paularmstrong to funding.yml 2021-02-24 20:58:44 -06:00
Paul Armstrong
2f3339ba85 docs: add contributing docs 2021-02-23 07:37:19 -06:00
Blake Blackshear
9433b50785 add stalebot 2021-02-22 07:20:39 -06:00
Blake Blackshear
1e7b53dc0e clarify h264 in docs 2021-02-22 07:20:32 -06:00
Blake Blackshear
bc94748f2a clips not playable 2021-02-22 07:10:56 -06:00
Blake Blackshear
2395f93ed1 Update bug_report.md 2021-02-22 06:43:21 -06:00
Blake Blackshear
d771726c2a version tick 2021-02-21 09:32:45 -06:00
Blake Blackshear
b2a2fe898c ensure base url works for websockets 2021-02-21 09:32:45 -06:00
17 changed files with 217 additions and 74 deletions

4
.github/FUNDING.yml vendored
View File

@@ -1 +1,3 @@
github: blakeblackshear
github:
- blakeblackshear
- paularmstrong

View File

@@ -11,7 +11,7 @@ assignees: ''
A clear and concise description of what your issue is.
**Version of frigate**
Output from `/version`
Output from `/api/version`
**Config file**
Include your full config file wrapped in triple back ticks.
@@ -26,7 +26,7 @@ Include relevant log output here
**Frigate stats**
```json
Output from frigate's /stats endpoint
Output from frigate's /api/stats endpoint
```
**FFprobe from your camera**

17
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 3
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -3,7 +3,7 @@ default_target: amd64_frigate
COMMIT_HASH := $(shell git log -1 --pretty=format:"%h"|tail -1)
version:
echo "VERSION='0.8.2-$(COMMIT_HASH)'" > frigate/version.py
echo "VERSION='0.8.4-$(COMMIT_HASH)'" > frigate/version.py
web:
docker build --tag frigate-web --file docker/Dockerfile.web web/

View File

@@ -2,32 +2,4 @@
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
## Installation
```console
yarn install
```
## Local Development
```console
yarn start
```
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
## Build
```console
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
```console
GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
For installation and contributing instructions, please follow the [Contributing Docs](https://blakeblackshear.github.io/frigate/contributing).

View File

@@ -127,6 +127,8 @@ objects:
Frigate can save video clips without any CPU overhead for encoding by simply copying the stream directly with FFmpeg. It leverages FFmpeg's segment functionality to maintain a cache of video for each camera. The cache files are written to disk at `/tmp/cache` and do not introduce memory overhead. When an object is being tracked, it will extend the cache to ensure it can assemble a clip when the event ends. Once the event ends, it again uses FFmpeg to assemble a clip by combining the video clips without any encoding by the CPU. Assembled clips are are saved to `/media/frigate/clips`. Clips are retained according to the retention settings defined on the config for each object type.
These clips will not be playable in the web UI or in HomeAssistant's media browser unless your camera sends video as h264.
:::caution
Previous versions of frigate included `-vsync drop` in input parameters. This is not compatible with FFmpeg's segment feature and must be removed from your input parameters if you have overrides set.
:::

131
docs/docs/contributing.md Normal file
View File

@@ -0,0 +1,131 @@
---
id: contributing
title: Contributing
---
## Getting the source
### Core, Web, Docker, and Documentation
This repository holds the main Frigate application and all of its dependencies.
Fork [blakeblackshear/frigate](https://github.com/blakeblackshear/frigate.git) to your own GitHub profile, then clone the forked repo to your local machine.
From here, follow the guides for:
- [Core](#core)
- [Web Interface](#web-interface)
- [Documentation](#documentation)
### Frigate Home Assistant Addon
This repository holds the Home Assistant Addon, for use with Home Assistant OS and compatible installations. It is the piece that allows you to run Frigate from your Home Assistant Supervisor tab.
Fork [blakeblackshear/frigate-hass-addons](https://github.com/blakeblackshear/frigate-hass-addons) to your own Github profile, then clone the forked repo to your local machine.
### Frigate Home Assistant Integration
This repository holds the custom integration that allows your Home Assistant installation to automatically create entities for your Frigate instance, whether you run that with the [addon](#frigate-home-assistant-addon) or in a separate Docker instance.
Fork [blakeblackshear/frigate-hass-integration](https://github.com/blakeblackshear/frigate-hass-integration) to your own GitHub profile, then clone the forked repo to your local machine.
## Core
### Prerequisites
- [Frigate source code](#frigate-core-web-and-docs)
- GNU make
- Docker
## Web Interface
### Prerequisites
- [Frigate source code](#frigate-core-web-and-docs)
- All [core](#core) prerequisites _or_ another running Frigate instance locally available
- Node.js 14
### Making changes
#### 1. Set up a Frigate instance
The Web UI requires an instance of Frigate to interact with for all of its data. You can either run an instance locally (recommended) or attach to a separate instance accessible on your network.
To run the local instance, follow the [core](#core) development instructions.
If you won't be making any changes to the Frigate HTTP API, you can attach the web development server to any Frigate instance on your network. Skip this step and go to [3a](#3a-run-the-development-server-against-a-non-local-instance).
#### 2. Install dependencies
```console
cd web && npm install
```
#### 3. Run the development server
```console
cd web && npm run start
```
#### 3a. Run the development server against a non-local instance
To run the development server against a non-local instance, you will need to provide an environment variable, `SNOWPACK_PUBLIC_API_HOST` that tells the web application how to connect to the Frigate API:
```console
cd web && SNOWPACK_PUBLIC_API_HOST=http://<ip-address-to-your-frigate-instance>:5000 npm run start
```
#### 4. Making changes
The Web UI is built using [Snowpack](https://www.snowpack.dev/), [Preact](https://preactjs.com), and [Tailwind CSS](https://tailwindcss.com).
Light guidelines and advice:
- Avoid adding more dependencies. The web UI intends to be lightweight and fast to load.
- Do not make large sweeping changes. [Open a discussion on GitHub](https://github.com/blakeblackshear/frigate/discussions/new) for any large or architectural ideas.
- Ensure `lint` passes. This command will ensure basic conformance to styles, applying as many automatic fixes as possible, including Prettier formatting.
```console
npm run lint
```
- Add to unit tests and ensure they pass. As much as possible, you should strive to _increase_ test coverage whenever making changes. This will help ensure features do not accidentally become broken in the future.
```console
npm run test
```
- Test in different browsers. Firefox, Chrome, and Safari all have different quirks that make them unique targets to interact with.
## Documentation
### Prerequisites
- [Frigate source code](#frigate-core-web-and-docs)
- Node.js 14
### Making changes
#### 1. Installation
```console
npm run install
```
#### 2. Local Development
```console
npm run start
```
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
The docs are built using [Docusaurus v2](https://v2.docusaurus.io). Please refer to the Docusaurus docs for more information on how to modify Frigate's documentation.
#### 3. Build (optional)
```console
npm run build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.

View File

@@ -11,10 +11,18 @@ This almost always means that the width/height defined for your camera are not c
![mismatched-resolution](/img/mismatched-resolution.jpg)
## "[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5639eeb6e140] moov atom not found"
### I have clips and snapshots in my clips folder, but I can't view them in the Web UI.
This is usually caused one of two things:
- The permissions on the parent folder don't have execute and nginx returns a 403 error you can see in the browser logs
- In this case, try mounting a volume to `/media/frigate` inside the container instead of `/media/frigate/clips`.
- Your cameras do not send h264 encoded video and the mp4 files are not playable in the browser
### "[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5639eeb6e140] moov atom not found"
These messages in the logs are expected in certain situations. Frigate checks the integrity of the video cache before assembling clips. Occasionally these cached files will be invalid and cleaned up automatically.
## "On connect called"
### "On connect called"
If you see repeated "On connect called" messages in your config, check for another instance of frigate. This happens when multiple frigate containers are trying to connect to mqtt with the same client_id.

View File

@@ -10,5 +10,6 @@ module.exports = {
'configuration/advanced',
],
Usage: ['usage/home-assistant', 'usage/web', 'usage/api', 'usage/mqtt'],
Development: ['contributing'],
},
};

View File

@@ -356,7 +356,7 @@ def latest_frame(camera_name):
def imagestream(detected_frames_processor, camera_name, fps, height, draw_options):
while True:
# max out at specified FPS
time.sleep(1/fps)
gevent.sleep(1/fps)
frame = detected_frames_processor.get_current_frame(camera_name, draw_options)
if frame is None:
frame = np.zeros((height,int(height*16/9),3), np.uint8)

View File

@@ -1,8 +1,3 @@
# Frigate Web UI
## Development
1. Build the docker images in the root of the repository `make amd64_all` (or appropriate for your system)
2. Create a config file in `config/`
3. Run the container: `docker run --rm --name frigate --privileged -v $PWD/config:/config:ro -v /etc/localtime:/etc/localtime:ro -p 5000:5000 frigate`
4. Run the dev ui: `cd web && npm run start`
For installation and contributing instructions, please follow the [Contributing Docs](https://blakeblackshear.github.io/frigate/contributing).

View File

@@ -3,6 +3,7 @@
"private": true,
"scripts": {
"start": "cross-env SNOWPACK_PUBLIC_API_HOST=http://localhost:5000 snowpack dev",
"start:custom": "snowpack dev",
"prebuild": "rimraf build",
"build": "cross-env NODE_ENV=production SNOWPACK_MODE=production SNOWPACK_PUBLIC_API_HOST='' snowpack build",
"lint": "npm run lint:cmd -- --fix",

View File

@@ -1,2 +1,2 @@
import { API_HOST } from '../env';
export const baseUrl = API_HOST || window.baseUrl || `${window.location.protocol}//${window.location.host}`;
export const baseUrl = API_HOST || `${window.location.protocol}//${window.location.host}${window.baseUrl || ''}`;

View File

@@ -2,7 +2,7 @@ import { h, Fragment } from 'preact';
import { createPortal } from 'preact/compat';
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
const WINDOW_PADDING = 10;
const WINDOW_PADDING = 20;
export default function RelativeModal({
className,
@@ -13,7 +13,7 @@ export default function RelativeModal({
relativeTo,
widthRelative = false,
}) {
const [position, setPosition] = useState({ top: -999, left: -999 });
const [position, setPosition] = useState({ top: -9999, left: -9999 });
const [show, setShow] = useState(false);
const portalRoot = portalRootID && document.getElementById(portalRootID);
const ref = useRef(null);
@@ -53,33 +53,43 @@ export default function RelativeModal({
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const { width: menuWidth, height: menuHeight } = ref.current.getBoundingClientRect();
const { x, y, width: relativeWidth, height } = relativeTo.current.getBoundingClientRect();
const {
x: relativeToX,
y: relativeToY,
width: relativeToWidth,
// height: relativeToHeight,
} = relativeTo.current.getBoundingClientRect();
const width = widthRelative ? relativeWidth : menuWidth;
const _width = widthRelative ? relativeToWidth : menuWidth;
const width = _width * 1.1;
const left = relativeToX + window.scrollX;
const top = relativeToY + window.scrollY;
let newTop = top;
let newLeft = left;
let top = y + height;
let left = x;
// too far right
if (left + width >= windowWidth - WINDOW_PADDING) {
left = windowWidth - width - WINDOW_PADDING;
if (newLeft + width + WINDOW_PADDING >= windowWidth - WINDOW_PADDING) {
newLeft = windowWidth - width - WINDOW_PADDING;
}
// too far left
else if (left < WINDOW_PADDING) {
left = WINDOW_PADDING;
newLeft = WINDOW_PADDING;
}
// too close to bottom
if (top + menuHeight > windowHeight - WINDOW_PADDING) {
top = y - menuHeight;
if (top + menuHeight > windowHeight - WINDOW_PADDING + window.scrollY) {
newTop = relativeToY - menuHeight;
}
if (top <= WINDOW_PADDING) {
top = WINDOW_PADDING;
if (top <= WINDOW_PADDING + window.scrollY) {
newTop = WINDOW_PADDING;
}
const maxHeight = windowHeight - WINDOW_PADDING * 2 > menuHeight ? null : windowHeight - WINDOW_PADDING * 2;
const newPosition = { left: left + window.scrollX, top: top + window.scrollY, maxHeight };
const newPosition = { left: newLeft, top: newTop, maxHeight };
if (widthRelative) {
newPosition.width = relativeWidth;
newPosition.width = relativeToWidth;
}
setPosition(newPosition);
const focusable = ref.current.querySelector('[tabindex]');
@@ -89,7 +99,9 @@ export default function RelativeModal({
useEffect(() => {
if (position.top >= 0) {
setShow(true);
window.requestAnimationFrame(() => {
setShow(true);
});
} else {
setShow(false);
}
@@ -100,13 +112,13 @@ export default function RelativeModal({
<div data-testid="scrim" key="scrim" className="absolute inset-0 z-10" onClick={handleDismiss} />
<div
key="menu"
className={`z-10 bg-white dark:bg-gray-700 dark:text-white absolute shadow-lg rounded w-auto h-auto transition-all duration-75 transform scale-90 opacity-0 overflow-scroll ${
className={`z-10 bg-white dark:bg-gray-700 dark:text-white absolute shadow-lg rounded w-auto h-auto transition-transform transition-opacity duration-75 transform scale-90 opacity-0 overflow-x-hidden overflow-y-auto ${
show ? 'scale-100 opacity-100' : ''
} ${className}`}
onKeyDown={handleKeydown}
role={role}
ref={ref}
style={position.top >= 0 ? position : null}
style={position}
>
{children}
</div>

View File

@@ -1,15 +1,15 @@
import { h } from 'preact';
import { createPortal } from 'preact/compat';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useLayoutEffect, useRef, useState } from 'preact/hooks';
const TIP_SPACE = 20;
export default function Tooltip({ relativeTo, text }) {
const [position, setPosition] = useState({ top: -Infinity, left: -Infinity });
const [position, setPosition] = useState({ top: -9999, left: -9999 });
const portalRoot = document.getElementById('tooltips');
const ref = useRef();
useEffect(() => {
useLayoutEffect(() => {
if (ref && ref.current && relativeTo && relativeTo.current) {
const windowWidth = window.innerWidth;
const {
@@ -18,7 +18,9 @@ export default function Tooltip({ relativeTo, text }) {
width: relativeToWidth,
height: relativeToHeight,
} = relativeTo.current.getBoundingClientRect();
const { width: tipWidth, height: tipHeight } = ref.current.getBoundingClientRect();
const { width: _tipWidth, height: _tipHeight } = ref.current.getBoundingClientRect();
const tipWidth = _tipWidth * 1.1;
const tipHeight = _tipHeight * 1.1;
const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX;
const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY;
@@ -47,11 +49,11 @@ export default function Tooltip({ relativeTo, text }) {
const tooltip = (
<div
role="tooltip"
className={`shadow max-w-lg absolute pointer-events-none bg-gray-900 dark:bg-gray-200 bg-opacity-80 rounded px-2 py-1 transition-opacity duration-200 opacity-0 text-gray-100 dark:text-gray-900 text-sm ${
position.top >= 0 ? 'opacity-100' : ''
className={`shadow max-w-lg absolute pointer-events-none bg-gray-900 dark:bg-gray-200 bg-opacity-80 rounded px-2 py-1 transition-transform transition-opacity duration-75 transform scale-90 opacity-0 text-gray-100 dark:text-gray-900 text-sm ${
position.top >= 0 ? 'opacity-100 scale-100' : ''
}`}
ref={ref}
style={position.top >= 0 ? position : null}
style={position}
>
{text}
</div>

View File

@@ -26,8 +26,8 @@ describe('Tooltip', () => {
const tooltip = await screen.findByRole('tooltip');
const style = window.getComputedStyle(tooltip);
expect(style.left).toEqual('105px');
expect(style.top).toEqual('70px');
expect(style.left).toEqual('103px');
expect(style.top).toEqual('68.5px');
});
test('if too far right, renders to the left', async () => {
@@ -54,7 +54,7 @@ describe('Tooltip', () => {
const tooltip = await screen.findByRole('tooltip');
const style = window.getComputedStyle(tooltip);
expect(style.left).toEqual('942px');
expect(style.left).toEqual('937px');
expect(style.top).toEqual('97px');
});
@@ -109,7 +109,7 @@ describe('Tooltip', () => {
const tooltip = await screen.findByRole('tooltip');
const style = window.getComputedStyle(tooltip);
expect(style.left).toEqual('87px');
expect(style.top).toEqual('160px');
expect(style.left).toEqual('84px');
expect(style.top).toEqual('158.5px');
});
});

View File

@@ -10,7 +10,7 @@ import { FetchStatus, useApiHost, useConfig, useEvents } from '../api';
import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from '../components/Table';
import { useCallback, useEffect, useMemo, useReducer, useState } from 'preact/hooks';
const API_LIMIT = 5;
const API_LIMIT = 25;
const initialState = Object.freeze({ events: [], reachedEnd: false, searchStrings: {} });
const reducer = (state = initialState, action) => {