Allow deleting events and associated clips and snapshots from the event list view

This commit is contained in:
Scott Roach 2021-02-27 17:34:35 -08:00
parent 5043040530
commit 1ef0d99790
No known key found for this signature in database
GPG Key ID: 641478CF54A92761
3 changed files with 56 additions and 12 deletions

View File

@ -5,7 +5,7 @@ title: HTTP API
A web server is available on port 5000 with the following endpoints. A web server is available on port 5000 with the following endpoints.
### `/api/<camera_name>` ### `GET /api/<camera_name>`
An mjpeg stream for debugging. Keep in mind the mjpeg endpoint is for debugging only and will put additional load on the system when in use. An mjpeg stream for debugging. Keep in mind the mjpeg endpoint is for debugging only and will put additional load on the system when in use.
@ -24,7 +24,7 @@ Accepts the following query string parameters:
You can access a higher resolution mjpeg stream by appending `h=height-in-pixels` to the endpoint. For example `http://localhost:5000/back?h=1080`. You can also increase the FPS by appending `fps=frame-rate` to the URL such as `http://localhost:5000/back?fps=10` or both with `?fps=10&h=1000`. You can access a higher resolution mjpeg stream by appending `h=height-in-pixels` to the endpoint. For example `http://localhost:5000/back?h=1080`. You can also increase the FPS by appending `fps=frame-rate` to the URL such as `http://localhost:5000/back?fps=10` or both with `?fps=10&h=1000`.
### `/api/<camera_name>/<object_name>/best.jpg[?h=300&crop=1]` ### `GET /api/<camera_name>/<object_name>/best.jpg[?h=300&crop=1]`
The best snapshot for any object type. It is a full resolution image by default. The best snapshot for any object type. It is a full resolution image by default.
@ -33,7 +33,7 @@ Example parameters:
- `h=300`: resizes the image to 300 pixes tall - `h=300`: resizes the image to 300 pixes tall
- `crop=1`: crops the image to the region of the detection rather than returning the entire image - `crop=1`: crops the image to the region of the detection rather than returning the entire image
### `/api/<camera_name>/latest.jpg[?h=300]` ### `GET /api/<camera_name>/latest.jpg[?h=300]`
The most recent frame that frigate has finished processing. It is a full resolution image by default. The most recent frame that frigate has finished processing. It is a full resolution image by default.
@ -53,7 +53,7 @@ Example parameters:
- `h=300`: resizes the image to 300 pixes tall - `h=300`: resizes the image to 300 pixes tall
### `/api/stats` ### `GET /api/stats`
Contains some granular debug info that can be used for sensors in HomeAssistant. Contains some granular debug info that can be used for sensors in HomeAssistant.
@ -150,15 +150,15 @@ Sample response:
} }
``` ```
### `/api/config` ### `GET /api/config`
A json representation of your configuration A json representation of your configuration
### `/api/version` ### `GET /api/version`
Version info Version info
### `/api/events` ### `GET /api/events`
Events from the database. Accepts the following query string parameters: Events from the database. Accepts the following query string parameters:
@ -174,19 +174,23 @@ Events from the database. Accepts the following query string parameters:
| `has_clip` | int | Filter to events that have clips (0 or 1) | | `has_clip` | int | Filter to events that have clips (0 or 1) |
| `include_thumbnails` | int | Include thumbnails in the response (0 or 1) | | `include_thumbnails` | int | Include thumbnails in the response (0 or 1) |
### `/api/events/summary` ### `GET /api/events/summary`
Returns summary data for events in the database. Used by the HomeAssistant integration. Returns summary data for events in the database. Used by the HomeAssistant integration.
### `/api/events/<id>` ### `GET /api/events/<id>`
Returns data for a single event. Returns data for a single event.
### `/api/events/<id>/thumbnail.jpg` ### `DELETE /api/events/<id>`
Permantly deletes the event along with any clips/snapshots.
### `GET /api/events/<id>/thumbnail.jpg`
Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio. Returns a thumbnail for the event id optimized for notifications. Works while the event is in progress and after completion. Passing `?format=android` will convert the thumbnail to 2:1 aspect ratio.
### `/api/events/<id>/snapshot.jpg` ### `GET /api/events/<id>/snapshot.jpg`
Returns the snapshot image for the event id. Works while the event is in progress and after completion. Returns the snapshot image for the event id. Works while the event is in progress and after completion.

View File

@ -5,6 +5,7 @@ import logging
import os import os
import time import time
from functools import reduce from functools import reduce
from pathlib import Path
import cv2 import cv2
import gevent import gevent
@ -145,13 +146,32 @@ def events_summary():
return jsonify([e for e in groups.dicts()]) return jsonify([e for e in groups.dicts()])
@bp.route('/events/<id>') @bp.route('/events/<id>', methods=('GET',))
def event(id): def event(id):
try: try:
return model_to_dict(Event.get(Event.id == id)) return model_to_dict(Event.get(Event.id == id))
except DoesNotExist: except DoesNotExist:
return "Event not found", 404 return "Event not found", 404
@bp.route('/events/<id>', methods=('DELETE',))
def delete_event(id):
try:
event = Event.get(Event.id == id)
except DoesNotExist:
return "Event not found", 404
media_name = f"{event.camera}-{event.id}"
if event.has_snapshot:
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.jpg")
media.unlink(missing_ok=True)
if event.has_clip:
media = Path(f"{os.path.join(CLIPS_DIR, media_name)}.mp4")
media.unlink(missing_ok=True)
event.delete_instance()
return '', 204
@bp.route('/events/<id>/thumbnail.jpg') @bp.route('/events/<id>/thumbnail.jpg')
def event_thumbnail(id): def event_thumbnail(id):
format = request.args.get('format', 'ios') format = request.args.get('format', 'ios')

View File

@ -7,6 +7,8 @@ import produce from 'immer';
import { route } from 'preact-router'; import { route } from 'preact-router';
import { useIntersectionObserver } from '../hooks'; import { useIntersectionObserver } from '../hooks';
import { FetchStatus, useApiHost, useConfig, useEvents } from '../api'; import { FetchStatus, useApiHost, useConfig, useEvents } from '../api';
import Button from '../components/Button';
import Delete from '../icons/Delete'
import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from '../components/Table'; import { Table, Thead, Tbody, Tfoot, Th, Tr, Td } from '../components/Table';
import { useCallback, useEffect, useMemo, useReducer, useState } from 'preact/hooks'; import { useCallback, useEffect, useMemo, useReducer, useState } from 'preact/hooks';
@ -99,6 +101,18 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
[limit, pathname, setSearchString] [limit, pathname, setSearchString]
); );
const handleDelete = useCallback(
async (eventId) => {
// eslint-disable-next-line no-alert
if(confirm('Are you sure you want to delete this event and any related clips and snapshots?')) {
await fetch(`${apiHost}/api/events/${eventId}`);
const { searchParams } = new URL(window.location);
handleFilter(searchParams)
}
},
[apiHost, handleFilter]
);
const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]); const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
return ( return (
@ -119,6 +133,7 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
<Th>Date</Th> <Th>Date</Th>
<Th>Start</Th> <Th>Start</Th>
<Th>End</Th> <Th>End</Th>
<Th />
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
@ -179,6 +194,11 @@ export default function Events({ path: pathname, limit = API_LIMIT } = {}) {
<Td>{start.toLocaleDateString()}</Td> <Td>{start.toLocaleDateString()}</Td>
<Td>{start.toLocaleTimeString()}</Td> <Td>{start.toLocaleTimeString()}</Td>
<Td>{end.toLocaleTimeString()}</Td> <Td>{end.toLocaleTimeString()}</Td>
<Td>
<Button color="red" name="Delete" onClick={() => handleDelete(id)}>
<Delete className="w-6" />
</Button>
</Td>
</Tr> </Tr>
); );
} }