mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
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:
parent
ca693240b1
commit
5f9d477863
@ -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}"*'))
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user