Update event filters naming and add sub label filter (#3194)

* Use default names so filters are more clear

* Add endpoint to get list of sub labels inside DB

* Fix crash on no internet

* Cleanups for sub_label http

* Add sub label selector to events UI

* Add event filtering for sub label

* Formatting files

* Reduce size of filters to fit on one line

* Add handler for tests

* Remove unused imports

* Only show the sub labels filter when there are sub labels in the DB

* Fix tests

* Use distinct instead of group_by

* Formatting

* Cleanup event logic
This commit is contained in:
Nicolas Mowen 2022-05-29 08:47:43 -06:00 committed by GitHub
parent ca693240b1
commit 5f9d477863
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 13 deletions

View File

@ -2,18 +2,14 @@ import base64
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timedelta from datetime import datetime, timedelta
import copy import copy
import json
import glob
import logging import logging
import os import os
import re
import subprocess as sp import subprocess as sp
import time import time
from functools import reduce from functools import reduce
from pathlib import Path from pathlib import Path
import cv2 import cv2
from flask.helpers import send_file
import numpy as np import numpy as np
from flask import ( from flask import (
@ -26,13 +22,12 @@ from flask import (
request, request,
) )
from peewee import SqliteDatabase, operator, fn, DoesNotExist, Value from peewee import SqliteDatabase, operator, fn, DoesNotExist
from playhouse.shortcuts import model_to_dict from playhouse.shortcuts import model_to_dict
from frigate.const import CLIPS_DIR, PLUS_ENV_VAR from frigate.const import CLIPS_DIR, PLUS_ENV_VAR
from frigate.models import Event, Recordings from frigate.models import Event, Recordings
from frigate.stats import stats_snapshot from frigate.stats import stats_snapshot
from frigate.util import calculate_region
from frigate.version import VERSION from frigate.version import VERSION
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -251,6 +246,20 @@ def set_sub_label(id):
) )
@bp.route("/sub_labels")
def get_sub_labels():
try:
events = Event.select(Event.sub_label).distinct()
except Exception as e:
return jsonify(
{"success": False, "message": f"Failed to get sub_labels: {e}"}, "404"
)
sub_labels = [e.sub_label for e in events]
sub_labels.remove(None)
return jsonify(sub_labels)
@bp.route("/events/<id>", methods=("DELETE",)) @bp.route("/events/<id>", methods=("DELETE",))
def delete_event(id): def delete_event(id):
try: try:
@ -480,6 +489,7 @@ def events():
limit = request.args.get("limit", 100) limit = request.args.get("limit", 100)
camera = request.args.get("camera", "all") camera = request.args.get("camera", "all")
label = request.args.get("label", "all") label = request.args.get("label", "all")
sub_label = request.args.get("sub_label", "all")
zone = request.args.get("zone", "all") zone = request.args.get("zone", "all")
after = request.args.get("after", type=float) after = request.args.get("after", type=float)
before = request.args.get("before", type=float) before = request.args.get("before", type=float)
@ -511,6 +521,9 @@ def events():
if label != "all": if label != "all":
clauses.append((Event.label == label)) clauses.append((Event.label == label))
if sub_label != "all":
clauses.append((Event.sub_label == sub_label))
if zone != "all": if zone != "all":
clauses.append((Event.zones.cast("text") % f'*"{zone}"*')) clauses.append((Event.zones.cast("text") % f'*"{zone}"*'))

View File

@ -72,4 +72,13 @@ export const handlers = [
) )
); );
}), }),
rest.get(`${API_HOST}api/sub_labels`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([
'one',
'two',
])
);
}),
]; ];

View File

@ -44,6 +44,7 @@ export default function Events({ path, ...props }) {
camera: props.camera ?? 'all', camera: props.camera ?? 'all',
label: props.label ?? 'all', label: props.label ?? 'all',
zone: props.zone ?? 'all', zone: props.zone ?? 'all',
sub_label: props.sub_label ?? 'all',
}); });
const [state, setState] = useState({ const [state, setState] = useState({
showDownloadMenu: false, showDownloadMenu: false,
@ -86,6 +87,8 @@ export default function Events({ path, ...props }) {
const { data: config } = useSWR('config'); const { data: config } = useSWR('config');
const { data: allSubLabels } = useSWR('sub_labels')
const filterValues = useMemo( const filterValues = useMemo(
() => ({ () => ({
cameras: Object.keys(config?.cameras || {}), cameras: Object.keys(config?.cameras || {}),
@ -101,8 +104,9 @@ export default function Events({ path, ...props }) {
return memo; return memo;
}, config?.objects?.track || []) }, config?.objects?.track || [])
.filter((value, i, self) => self.indexOf(value) === i), .filter((value, i, self) => self.indexOf(value) === i),
sub_labels: Object.values(allSubLabels || []),
}), }),
[config] [config, allSubLabels]
); );
const onSave = async (e, eventId, save) => { const onSave = async (e, eventId, save) => {
@ -240,11 +244,11 @@ export default function Events({ path, ...props }) {
<Heading>Events</Heading> <Heading>Events</Heading>
<div className="flex flex-wrap gap-2 items-center"> <div className="flex flex-wrap gap-2 items-center">
<select <select
className="basis-1/4 cursor-pointer rounded dark:bg-slate-800" className="basis-1/5 cursor-pointer rounded dark:bg-slate-800"
value={searchParams.camera} value={searchParams.camera}
onChange={(e) => onFilter('camera', e.target.value)} onChange={(e) => onFilter('camera', e.target.value)}
> >
<option value="all">all</option> <option value="all">all cameras</option>
{filterValues.cameras.map((item) => ( {filterValues.cameras.map((item) => (
<option key={item} value={item}> <option key={item} value={item}>
{item} {item}
@ -252,11 +256,11 @@ export default function Events({ path, ...props }) {
))} ))}
</select> </select>
<select <select
className="basis-1/4 cursor-pointer rounded dark:bg-slate-800" className="basis-1/5 cursor-pointer rounded dark:bg-slate-800"
value={searchParams.label} value={searchParams.label}
onChange={(e) => onFilter('label', e.target.value)} onChange={(e) => onFilter('label', e.target.value)}
> >
<option value="all">all</option> <option value="all">all labels</option>
{filterValues.labels.map((item) => ( {filterValues.labels.map((item) => (
<option key={item} value={item}> <option key={item} value={item}>
{item} {item}
@ -264,17 +268,32 @@ export default function Events({ path, ...props }) {
))} ))}
</select> </select>
<select <select
className="basis-1/4 cursor-pointer rounded dark:bg-slate-800" className="basis-1/5 cursor-pointer rounded dark:bg-slate-800"
value={searchParams.zone} value={searchParams.zone}
onChange={(e) => onFilter('zone', e.target.value)} onChange={(e) => onFilter('zone', e.target.value)}
> >
<option value="all">all</option> <option value="all">all zones</option>
{filterValues.zones.map((item) => ( {filterValues.zones.map((item) => (
<option key={item} value={item}> <option key={item} value={item}>
{item} {item}
</option> </option>
))} ))}
</select> </select>
{
filterValues.sub_labels.length > 0 && (
<select
className="basis-1/5 cursor-pointer rounded dark:bg-slate-800"
value={searchParams.sub_label}
onChange={(e) => onFilter('sub_label', e.target.value)}
>
<option value="all">all sub labels</option>
{filterValues.sub_labels.map((item) => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
)}
<div ref={datePicker} className="ml-auto"> <div ref={datePicker} className="ml-auto">
<CalendarIcon <CalendarIcon
className="h-8 w-8 cursor-pointer" className="h-8 w-8 cursor-pointer"