mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-15 03:22:10 +03:00
protect event endpoints
This commit is contained in:
parent
2a9d5f5e51
commit
a984227c9b
@ -8,6 +8,7 @@ import random
|
|||||||
import string
|
import string
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -19,7 +20,11 @@ from pathvalidate import sanitize_filename
|
|||||||
from peewee import JOIN, DoesNotExist, fn, operator
|
from peewee import JOIN, DoesNotExist, fn, operator
|
||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
from frigate.api.auth import require_role
|
from frigate.api.auth import (
|
||||||
|
get_allowed_cameras_for_filter,
|
||||||
|
require_camera_access,
|
||||||
|
require_role,
|
||||||
|
)
|
||||||
from frigate.api.defs.query.events_query_parameters import (
|
from frigate.api.defs.query.events_query_parameters import (
|
||||||
DEFAULT_TIME_RANGE,
|
DEFAULT_TIME_RANGE,
|
||||||
EventsQueryParams,
|
EventsQueryParams,
|
||||||
@ -61,7 +66,10 @@ router = APIRouter(tags=[Tags.events])
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/events", response_model=list[EventResponse])
|
@router.get("/events", response_model=list[EventResponse])
|
||||||
def events(params: EventsQueryParams = Depends()):
|
def events(
|
||||||
|
params: EventsQueryParams = Depends(),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
camera = params.camera
|
camera = params.camera
|
||||||
cameras = params.cameras
|
cameras = params.cameras
|
||||||
|
|
||||||
@ -135,8 +143,14 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
clauses.append((Event.camera == camera))
|
clauses.append((Event.camera == camera))
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
camera_list = cameras.split(",")
|
requested = set(cameras.split(","))
|
||||||
clauses.append((Event.camera << camera_list))
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content=[])
|
||||||
|
camera_list = list(filtered)
|
||||||
|
else:
|
||||||
|
camera_list = allowed_cameras
|
||||||
|
clauses.append((Event.camera << camera_list))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
label_list = labels.split(",")
|
label_list = labels.split(",")
|
||||||
@ -321,9 +335,17 @@ def events(params: EventsQueryParams = Depends()):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/events/explore", response_model=list[EventResponse])
|
@router.get("/events/explore", response_model=list[EventResponse])
|
||||||
def events_explore(limit: int = 10):
|
def events_explore(
|
||||||
|
limit: int = 10,
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
# get distinct labels for all events
|
# get distinct labels for all events
|
||||||
distinct_labels = Event.select(Event.label).distinct().order_by(Event.label)
|
distinct_labels = (
|
||||||
|
Event.select(Event.label)
|
||||||
|
.where(Event.camera << allowed_cameras)
|
||||||
|
.distinct()
|
||||||
|
.order_by(Event.label)
|
||||||
|
)
|
||||||
|
|
||||||
label_counts = {}
|
label_counts = {}
|
||||||
|
|
||||||
@ -334,14 +356,18 @@ def events_explore(limit: int = 10):
|
|||||||
# get most recent events for this label
|
# get most recent events for this label
|
||||||
label_events = (
|
label_events = (
|
||||||
Event.select()
|
Event.select()
|
||||||
.where(Event.label == label)
|
.where((Event.label == label) & (Event.camera << allowed_cameras))
|
||||||
.order_by(Event.start_time.desc())
|
.order_by(Event.start_time.desc())
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.iterator()
|
.iterator()
|
||||||
)
|
)
|
||||||
|
|
||||||
# count total events for this label
|
# count total events for this label
|
||||||
label_counts[label] = Event.select().where(Event.label == label).count()
|
label_counts[label] = (
|
||||||
|
Event.select()
|
||||||
|
.where((Event.label == label) & (Event.camera << allowed_cameras))
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
|
||||||
yield from label_events
|
yield from label_events
|
||||||
|
|
||||||
@ -403,6 +429,16 @@ def event_ids(ids: str):
|
|||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for event_id in ids:
|
||||||
|
try:
|
||||||
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
|
except DoesNotExist:
|
||||||
|
return JSONResponse(
|
||||||
|
content=({"success": False, "message": f"Event {event_id} not found"}),
|
||||||
|
status_code=404,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
events = Event.select().where(Event.id << ids).dicts().iterator()
|
events = Event.select().where(Event.id << ids).dicts().iterator()
|
||||||
return JSONResponse(list(events))
|
return JSONResponse(list(events))
|
||||||
@ -413,7 +449,11 @@ def event_ids(ids: str):
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/events/search")
|
@router.get("/events/search")
|
||||||
def events_search(request: Request, params: EventsSearchQueryParams = Depends()):
|
def events_search(
|
||||||
|
request: Request,
|
||||||
|
params: EventsSearchQueryParams = Depends(),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
query = params.query
|
query = params.query
|
||||||
search_type = params.search_type
|
search_type = params.search_type
|
||||||
include_thumbnails = params.include_thumbnails
|
include_thumbnails = params.include_thumbnails
|
||||||
@ -486,7 +526,13 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
event_filters = []
|
event_filters = []
|
||||||
|
|
||||||
if cameras != "all":
|
if cameras != "all":
|
||||||
event_filters.append((Event.camera << cameras.split(",")))
|
requested = set(cameras.split(","))
|
||||||
|
filtered = requested.intersection(allowed_cameras)
|
||||||
|
if not filtered:
|
||||||
|
return JSONResponse(content=[])
|
||||||
|
event_filters.append((Event.camera << list(filtered)))
|
||||||
|
else:
|
||||||
|
event_filters.append((Event.camera << allowed_cameras))
|
||||||
|
|
||||||
if labels != "all":
|
if labels != "all":
|
||||||
event_filters.append((Event.label << labels.split(",")))
|
event_filters.append((Event.label << labels.split(",")))
|
||||||
@ -739,7 +785,10 @@ def events_search(request: Request, params: EventsSearchQueryParams = Depends())
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/events/summary")
|
@router.get("/events/summary")
|
||||||
def events_summary(params: EventsSummaryQueryParams = Depends()):
|
def events_summary(
|
||||||
|
params: EventsSummaryQueryParams = Depends(),
|
||||||
|
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||||
|
):
|
||||||
tz_name = params.timezone
|
tz_name = params.timezone
|
||||||
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(tz_name)
|
hour_modifier, minute_modifier, seconds_offset = get_tz_modifiers(tz_name)
|
||||||
has_clip = params.has_clip
|
has_clip = params.has_clip
|
||||||
@ -771,7 +820,7 @@ def events_summary(params: EventsSummaryQueryParams = Depends()):
|
|||||||
Event.zones,
|
Event.zones,
|
||||||
fn.COUNT(Event.id).alias("count"),
|
fn.COUNT(Event.id).alias("count"),
|
||||||
)
|
)
|
||||||
.where(reduce(operator.and_, clauses))
|
.where(reduce(operator.and_, clauses) & (Event.camera << allowed_cameras))
|
||||||
.group_by(
|
.group_by(
|
||||||
Event.camera,
|
Event.camera,
|
||||||
Event.label,
|
Event.label,
|
||||||
@ -788,7 +837,9 @@ def events_summary(params: EventsSummaryQueryParams = Depends()):
|
|||||||
@router.get("/events/{event_id}", response_model=EventResponse)
|
@router.get("/events/{event_id}", response_model=EventResponse)
|
||||||
def event(event_id: str):
|
def event(event_id: str):
|
||||||
try:
|
try:
|
||||||
return model_to_dict(Event.get(Event.id == event_id))
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
|
return model_to_dict(event)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(content="Event not found", status_code=404)
|
return JSONResponse(content="Event not found", status_code=404)
|
||||||
|
|
||||||
@ -801,6 +852,7 @@ def event(event_id: str):
|
|||||||
def set_retain(event_id: str):
|
def set_retain(event_id: str):
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
||||||
@ -835,6 +887,7 @@ def send_to_plus(request: Request, event_id: str, body: SubmitPlusBody = None):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
message = f"Event {event_id} not found"
|
message = f"Event {event_id} not found"
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
@ -945,6 +998,7 @@ def false_positive(request: Request, event_id: str):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
message = f"Event {event_id} not found"
|
message = f"Event {event_id} not found"
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
@ -1025,6 +1079,7 @@ def false_positive(request: Request, event_id: str):
|
|||||||
def delete_retain(event_id: str):
|
def delete_retain(event_id: str):
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
||||||
@ -1052,6 +1107,7 @@ def set_sub_label(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
event = None
|
event = None
|
||||||
|
|
||||||
@ -1106,6 +1162,7 @@ def set_plate(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
event = None
|
event = None
|
||||||
|
|
||||||
@ -1161,6 +1218,7 @@ def set_description(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
||||||
@ -1210,6 +1268,7 @@ def regenerate_description(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == event_id)
|
event: Event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
content=({"success": False, "message": "Event " + event_id + " not found"}),
|
||||||
@ -1283,6 +1342,7 @@ def generate_description_embedding(
|
|||||||
def delete_single_event(event_id: str, request: Request) -> dict:
|
def delete_single_event(event_id: str, request: Request) -> dict:
|
||||||
try:
|
try:
|
||||||
event = Event.get(Event.id == event_id)
|
event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
except DoesNotExist:
|
except DoesNotExist:
|
||||||
return {"success": False, "message": f"Event {event_id} not found"}
|
return {"success": False, "message": f"Event {event_id} not found"}
|
||||||
|
|
||||||
@ -1351,7 +1411,10 @@ def delete_events(request: Request, body: EventsDeleteBody):
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/events/{camera_name}/{label}/create",
|
"/events/{camera_name}/{label}/create",
|
||||||
response_model=EventCreateResponse,
|
response_model=EventCreateResponse,
|
||||||
dependencies=[Depends(require_role(["admin"]))],
|
dependencies=[
|
||||||
|
Depends(lambda: require_role(["admin"])),
|
||||||
|
Depends(require_camera_access),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def create_event(
|
def create_event(
|
||||||
request: Request,
|
request: Request,
|
||||||
@ -1412,6 +1475,8 @@ def create_event(
|
|||||||
)
|
)
|
||||||
def end_event(request: Request, event_id: str, body: EventsEndBody):
|
def end_event(request: Request, event_id: str, body: EventsEndBody):
|
||||||
try:
|
try:
|
||||||
|
event: Event = Event.get(Event.id == event_id)
|
||||||
|
require_camera_access(event.camera)
|
||||||
end_time = body.end_time or datetime.datetime.now().timestamp()
|
end_time = body.end_time or datetime.datetime.now().timestamp()
|
||||||
request.app.event_metadata_updater.publish(
|
request.app.event_metadata_updater.publish(
|
||||||
(event_id, end_time), EventMetadataTypeEnum.manual_event_end.value
|
(event_id, end_time), EventMetadataTypeEnum.manual_event_end.value
|
||||||
@ -1433,12 +1498,15 @@ def end_event(request: Request, event_id: str, body: EventsEndBody):
|
|||||||
@router.post(
|
@router.post(
|
||||||
"/trigger/embedding",
|
"/trigger/embedding",
|
||||||
response_model=dict,
|
response_model=dict,
|
||||||
dependencies=[Depends(require_role(["admin"]))],
|
dependencies=[
|
||||||
|
Depends(lambda: require_role(["admin"])),
|
||||||
|
Depends(require_camera_access),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def create_trigger_embedding(
|
def create_trigger_embedding(
|
||||||
request: Request,
|
request: Request,
|
||||||
body: TriggerEmbeddingBody,
|
body: TriggerEmbeddingBody,
|
||||||
camera: str,
|
camera_name: str,
|
||||||
name: str,
|
name: str,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
@ -1454,13 +1522,13 @@ def create_trigger_embedding(
|
|||||||
# Check if trigger already exists
|
# Check if trigger already exists
|
||||||
if (
|
if (
|
||||||
Trigger.select()
|
Trigger.select()
|
||||||
.where(Trigger.camera == camera, Trigger.name == name)
|
.where(Trigger.camera == camera_name, Trigger.name == name)
|
||||||
.exists()
|
.exists()
|
||||||
):
|
):
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"Trigger {camera}:{name} already exists",
|
"message": f"Trigger {camera_name}:{name} already exists",
|
||||||
},
|
},
|
||||||
status_code=400,
|
status_code=400,
|
||||||
)
|
)
|
||||||
@ -1530,28 +1598,29 @@ def create_trigger_embedding(
|
|||||||
# Save image to the triggers directory
|
# Save image to the triggers directory
|
||||||
try:
|
try:
|
||||||
os.makedirs(
|
os.makedirs(
|
||||||
os.path.join(TRIGGER_DIR, sanitize_filename(camera)), exist_ok=True
|
os.path.join(TRIGGER_DIR, sanitize_filename(camera_name)),
|
||||||
|
exist_ok=True,
|
||||||
)
|
)
|
||||||
with open(
|
with open(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
TRIGGER_DIR,
|
TRIGGER_DIR,
|
||||||
sanitize_filename(camera),
|
sanitize_filename(camera_name),
|
||||||
f"{sanitize_filename(body.data)}.webp",
|
f"{sanitize_filename(body.data)}.webp",
|
||||||
),
|
),
|
||||||
"wb",
|
"wb",
|
||||||
) as f:
|
) as f:
|
||||||
f.write(thumbnail)
|
f.write(thumbnail)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Writing thumbnail for trigger with data {body.data} in {camera}."
|
f"Writing thumbnail for trigger with data {body.data} in {camera_name}."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e.with_traceback())
|
logger.error(e.with_traceback())
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to write thumbnail for trigger with data {body.data} in {camera}"
|
f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
Trigger.create(
|
Trigger.create(
|
||||||
camera=camera,
|
camera=camera_name,
|
||||||
name=name,
|
name=name,
|
||||||
type=body.type,
|
type=body.type,
|
||||||
data=body.data,
|
data=body.data,
|
||||||
@ -1565,7 +1634,7 @@ def create_trigger_embedding(
|
|||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Trigger created successfully for {camera}:{name}",
|
"message": f"Trigger created successfully for {camera_name}:{name}",
|
||||||
},
|
},
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
@ -1584,11 +1653,14 @@ def create_trigger_embedding(
|
|||||||
@router.put(
|
@router.put(
|
||||||
"/trigger/embedding/{camera}/{name}",
|
"/trigger/embedding/{camera}/{name}",
|
||||||
response_model=dict,
|
response_model=dict,
|
||||||
dependencies=[Depends(require_role(["admin"]))],
|
dependencies=[
|
||||||
|
Depends(lambda: require_role(["admin"])),
|
||||||
|
Depends(require_camera_access),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def update_trigger_embedding(
|
def update_trigger_embedding(
|
||||||
request: Request,
|
request: Request,
|
||||||
camera: str,
|
camera_name: str,
|
||||||
name: str,
|
name: str,
|
||||||
body: TriggerEmbeddingBody,
|
body: TriggerEmbeddingBody,
|
||||||
):
|
):
|
||||||
@ -1609,7 +1681,9 @@ def update_trigger_embedding(
|
|||||||
embedding = context.generate_description_embedding(body.data)
|
embedding = context.generate_description_embedding(body.data)
|
||||||
elif body.type == "thumbnail":
|
elif body.type == "thumbnail":
|
||||||
webp_file = sanitize_filename(body.data) + ".webp"
|
webp_file = sanitize_filename(body.data) + ".webp"
|
||||||
webp_path = os.path.join(TRIGGER_DIR, sanitize_filename(camera), webp_file)
|
webp_path = os.path.join(
|
||||||
|
TRIGGER_DIR, sanitize_filename(camera_name), webp_file
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
event: Event = Event.get(Event.id == body.data)
|
event: Event = Event.get(Event.id == body.data)
|
||||||
@ -1656,7 +1730,9 @@ def update_trigger_embedding(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check if trigger exists for upsert
|
# Check if trigger exists for upsert
|
||||||
trigger = Trigger.get_or_none(Trigger.camera == camera, Trigger.name == name)
|
trigger = Trigger.get_or_none(
|
||||||
|
Trigger.camera == camera_name, Trigger.name == name
|
||||||
|
)
|
||||||
|
|
||||||
if trigger:
|
if trigger:
|
||||||
# Update existing trigger
|
# Update existing trigger
|
||||||
@ -1665,17 +1741,17 @@ def update_trigger_embedding(
|
|||||||
os.remove(
|
os.remove(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
TRIGGER_DIR,
|
TRIGGER_DIR,
|
||||||
sanitize_filename(camera),
|
sanitize_filename(camera_name),
|
||||||
f"{trigger.data}.webp",
|
f"{trigger.data}.webp",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Deleted thumbnail for trigger with data {trigger.data} in {camera}."
|
f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e.with_traceback())
|
logger.error(e.with_traceback())
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera}"
|
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
Trigger.update(
|
Trigger.update(
|
||||||
@ -1685,11 +1761,11 @@ def update_trigger_embedding(
|
|||||||
threshold=body.threshold,
|
threshold=body.threshold,
|
||||||
triggering_event_id="",
|
triggering_event_id="",
|
||||||
last_triggered=None,
|
last_triggered=None,
|
||||||
).where(Trigger.camera == camera, Trigger.name == name).execute()
|
).where(Trigger.camera == camera_name, Trigger.name == name).execute()
|
||||||
else:
|
else:
|
||||||
# Create new trigger (for rename case)
|
# Create new trigger (for rename case)
|
||||||
Trigger.create(
|
Trigger.create(
|
||||||
camera=camera,
|
camera=camera_name,
|
||||||
name=name,
|
name=name,
|
||||||
type=body.type,
|
type=body.type,
|
||||||
data=body.data,
|
data=body.data,
|
||||||
@ -1703,7 +1779,7 @@ def update_trigger_embedding(
|
|||||||
if body.type == "thumbnail":
|
if body.type == "thumbnail":
|
||||||
# Save image to the triggers directory
|
# Save image to the triggers directory
|
||||||
try:
|
try:
|
||||||
camera_path = os.path.join(TRIGGER_DIR, sanitize_filename(camera))
|
camera_path = os.path.join(TRIGGER_DIR, sanitize_filename(camera_name))
|
||||||
os.makedirs(camera_path, exist_ok=True)
|
os.makedirs(camera_path, exist_ok=True)
|
||||||
with open(
|
with open(
|
||||||
os.path.join(camera_path, f"{sanitize_filename(body.data)}.webp"),
|
os.path.join(camera_path, f"{sanitize_filename(body.data)}.webp"),
|
||||||
@ -1711,18 +1787,18 @@ def update_trigger_embedding(
|
|||||||
) as f:
|
) as f:
|
||||||
f.write(thumbnail)
|
f.write(thumbnail)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Writing thumbnail for trigger with data {body.data} in {camera}."
|
f"Writing thumbnail for trigger with data {body.data} in {camera_name}."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e.with_traceback())
|
logger.error(e.with_traceback())
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to write thumbnail for trigger with data {body.data} in {camera}"
|
f"Failed to write thumbnail for trigger with data {body.data} in {camera_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Trigger updated successfully for {camera}:{name}",
|
"message": f"Trigger updated successfully for {camera_name}:{name}",
|
||||||
},
|
},
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
@ -1741,34 +1817,39 @@ def update_trigger_embedding(
|
|||||||
@router.delete(
|
@router.delete(
|
||||||
"/trigger/embedding/{camera}/{name}",
|
"/trigger/embedding/{camera}/{name}",
|
||||||
response_model=dict,
|
response_model=dict,
|
||||||
dependencies=[Depends(require_role(["admin"]))],
|
dependencies=[
|
||||||
|
Depends(lambda: require_role(["admin"])),
|
||||||
|
Depends(require_camera_access),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def delete_trigger_embedding(
|
def delete_trigger_embedding(
|
||||||
request: Request,
|
request: Request,
|
||||||
camera: str,
|
camera_name: str,
|
||||||
name: str,
|
name: str,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
trigger = Trigger.get_or_none(Trigger.camera == camera, Trigger.name == name)
|
trigger = Trigger.get_or_none(
|
||||||
|
Trigger.camera == camera_name, Trigger.name == name
|
||||||
|
)
|
||||||
if trigger is None:
|
if trigger is None:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"Trigger {camera}:{name} not found",
|
"message": f"Trigger {camera_name}:{name} not found",
|
||||||
},
|
},
|
||||||
status_code=500,
|
status_code=500,
|
||||||
)
|
)
|
||||||
|
|
||||||
deleted = (
|
deleted = (
|
||||||
Trigger.delete()
|
Trigger.delete()
|
||||||
.where(Trigger.camera == camera, Trigger.name == name)
|
.where(Trigger.camera == camera_name, Trigger.name == name)
|
||||||
.execute()
|
.execute()
|
||||||
)
|
)
|
||||||
if deleted == 0:
|
if deleted == 0:
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": f"Error deleting trigger {camera}:{name}",
|
"message": f"Error deleting trigger {camera_name}:{name}",
|
||||||
},
|
},
|
||||||
status_code=401,
|
status_code=401,
|
||||||
)
|
)
|
||||||
@ -1776,22 +1857,22 @@ def delete_trigger_embedding(
|
|||||||
try:
|
try:
|
||||||
os.remove(
|
os.remove(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
TRIGGER_DIR, sanitize_filename(camera), f"{trigger.data}.webp"
|
TRIGGER_DIR, sanitize_filename(camera_name), f"{trigger.data}.webp"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Deleted thumbnail for trigger with data {trigger.data} in {camera}."
|
f"Deleted thumbnail for trigger with data {trigger.data} in {camera_name}."
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e.with_traceback())
|
logger.error(e.with_traceback())
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera}"
|
f"Failed to delete thumbnail for trigger with data {trigger.data} in {camera_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content={
|
content={
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Trigger deleted successfully for {camera}:{name}",
|
"message": f"Trigger deleted successfully for {camera_name}:{name}",
|
||||||
},
|
},
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
@ -1810,7 +1891,10 @@ def delete_trigger_embedding(
|
|||||||
@router.get(
|
@router.get(
|
||||||
"/triggers/status/{camera_name}",
|
"/triggers/status/{camera_name}",
|
||||||
response_model=dict,
|
response_model=dict,
|
||||||
dependencies=[Depends(require_role(["admin"]))],
|
dependencies=[
|
||||||
|
Depends(lambda: require_role(["admin"])),
|
||||||
|
Depends(require_camera_access),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def get_triggers_status(
|
def get_triggers_status(
|
||||||
camera_name: str,
|
camera_name: str,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user