mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +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 datetime import datetime, timedelta | ||||
| import copy | ||||
| import json | ||||
| import glob | ||||
| import logging | ||||
| import os | ||||
| import re | ||||
| import subprocess as sp | ||||
| import time | ||||
| from functools import reduce | ||||
| from pathlib import Path | ||||
| 
 | ||||
| import cv2 | ||||
| from flask.helpers import send_file | ||||
| 
 | ||||
| import numpy as np | ||||
| from flask import ( | ||||
| @ -26,13 +22,12 @@ from flask import ( | ||||
|     request, | ||||
| ) | ||||
| 
 | ||||
| from peewee import SqliteDatabase, operator, fn, DoesNotExist, Value | ||||
| from peewee import SqliteDatabase, operator, fn, DoesNotExist | ||||
| from playhouse.shortcuts import model_to_dict | ||||
| 
 | ||||
| from frigate.const import CLIPS_DIR, PLUS_ENV_VAR | ||||
| from frigate.models import Event, Recordings | ||||
| from frigate.stats import stats_snapshot | ||||
| from frigate.util import calculate_region | ||||
| from frigate.version import VERSION | ||||
| 
 | ||||
| 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",)) | ||||
| def delete_event(id): | ||||
|     try: | ||||
| @ -480,6 +489,7 @@ def events(): | ||||
|     limit = request.args.get("limit", 100) | ||||
|     camera = request.args.get("camera", "all") | ||||
|     label = request.args.get("label", "all") | ||||
|     sub_label = request.args.get("sub_label", "all") | ||||
|     zone = request.args.get("zone", "all") | ||||
|     after = request.args.get("after", type=float) | ||||
|     before = request.args.get("before", type=float) | ||||
| @ -511,6 +521,9 @@ def events(): | ||||
|     if label != "all": | ||||
|         clauses.append((Event.label == label)) | ||||
| 
 | ||||
|     if sub_label != "all": | ||||
|         clauses.append((Event.sub_label == sub_label)) | ||||
| 
 | ||||
|     if zone != "all": | ||||
|         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', | ||||
|     label: props.label ?? 'all', | ||||
|     zone: props.zone ?? 'all', | ||||
|     sub_label: props.sub_label ?? 'all', | ||||
|   }); | ||||
|   const [state, setState] = useState({ | ||||
|     showDownloadMenu: false, | ||||
| @ -86,6 +87,8 @@ export default function Events({ path, ...props }) { | ||||
| 
 | ||||
|   const { data: config } = useSWR('config'); | ||||
| 
 | ||||
|   const { data: allSubLabels } = useSWR('sub_labels') | ||||
| 
 | ||||
|   const filterValues = useMemo( | ||||
|     () => ({ | ||||
|       cameras: Object.keys(config?.cameras || {}), | ||||
| @ -101,8 +104,9 @@ export default function Events({ path, ...props }) { | ||||
|           return memo; | ||||
|         }, config?.objects?.track || []) | ||||
|         .filter((value, i, self) => self.indexOf(value) === i), | ||||
|       sub_labels: Object.values(allSubLabels || []), | ||||
|     }), | ||||
|     [config] | ||||
|     [config, allSubLabels] | ||||
|   ); | ||||
| 
 | ||||
|   const onSave = async (e, eventId, save) => { | ||||
| @ -240,11 +244,11 @@ export default function Events({ path, ...props }) { | ||||
|       <Heading>Events</Heading> | ||||
|       <div className="flex flex-wrap gap-2 items-center"> | ||||
|         <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} | ||||
|           onChange={(e) => onFilter('camera', e.target.value)} | ||||
|         > | ||||
|           <option value="all">all</option> | ||||
|           <option value="all">all cameras</option> | ||||
|           {filterValues.cameras.map((item) => ( | ||||
|             <option key={item} value={item}> | ||||
|               {item} | ||||
| @ -252,11 +256,11 @@ export default function Events({ path, ...props }) { | ||||
|           ))} | ||||
|         </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} | ||||
|           onChange={(e) => onFilter('label', e.target.value)} | ||||
|         > | ||||
|           <option value="all">all</option> | ||||
|           <option value="all">all labels</option> | ||||
|           {filterValues.labels.map((item) => ( | ||||
|             <option key={item} value={item}> | ||||
|               {item} | ||||
| @ -264,17 +268,32 @@ export default function Events({ path, ...props }) { | ||||
|           ))} | ||||
|         </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} | ||||
|           onChange={(e) => onFilter('zone', e.target.value)} | ||||
|         > | ||||
|           <option value="all">all</option> | ||||
|           <option value="all">all zones</option> | ||||
|           {filterValues.zones.map((item) => ( | ||||
|             <option key={item} value={item}> | ||||
|               {item} | ||||
|             </option> | ||||
|           ))} | ||||
|         </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"> | ||||
|           <CalendarIcon | ||||
|             className="h-8 w-8 cursor-pointer" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user