mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-07-30 13:48:07 +02:00
Dynamically update masks and zones for cameras (#18359)
* Include config publisher in api * Call update topic for passed topics * Update zones dynamically * Update zones internally * Support zone and mask reset * Handle updating objects config * Don't put status for needing to restart Frigate * Cleanup http tests * Fix tests
This commit is contained in:
parent
559af44682
commit
5dd30b273a
@ -28,6 +28,10 @@ from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryPa
|
|||||||
from frigate.api.defs.request.app_body import AppConfigSetBody
|
from frigate.api.defs.request.app_body import AppConfigSetBody
|
||||||
from frigate.api.defs.tags import Tags
|
from frigate.api.defs.tags import Tags
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.config.camera.updater import (
|
||||||
|
CameraConfigUpdateEnum,
|
||||||
|
CameraConfigUpdateTopic,
|
||||||
|
)
|
||||||
from frigate.models import Event, Timeline
|
from frigate.models import Event, Timeline
|
||||||
from frigate.stats.prometheus import get_metrics, update_metrics
|
from frigate.stats.prometheus import get_metrics, update_metrics
|
||||||
from frigate.util.builtin import (
|
from frigate.util.builtin import (
|
||||||
@ -385,8 +389,18 @@ def config_set(request: Request, body: AppConfigSetBody):
|
|||||||
status_code=500,
|
status_code=500,
|
||||||
)
|
)
|
||||||
|
|
||||||
if body.requires_restart == 0:
|
if body.requires_restart == 0 or body.update_topic:
|
||||||
request.app.frigate_config = config
|
request.app.frigate_config = config
|
||||||
|
|
||||||
|
if body.update_topic and body.update_topic.startswith("config/cameras/"):
|
||||||
|
_, _, camera, field = body.update_topic.split("/")
|
||||||
|
|
||||||
|
settings = config.get_nested_object(body.update_topic)
|
||||||
|
request.app.config_publisher.publish_update(
|
||||||
|
CameraConfigUpdateTopic(CameraConfigUpdateEnum[field], camera),
|
||||||
|
settings,
|
||||||
|
)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
content=(
|
content=(
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
class AppConfigSetBody(BaseModel):
|
class AppConfigSetBody(BaseModel):
|
||||||
requires_restart: int = 1
|
requires_restart: int = 1
|
||||||
|
update_topic: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class AppPutPasswordBody(BaseModel):
|
class AppPutPasswordBody(BaseModel):
|
||||||
|
@ -26,6 +26,7 @@ from frigate.comms.event_metadata_updater import (
|
|||||||
EventMetadataPublisher,
|
EventMetadataPublisher,
|
||||||
)
|
)
|
||||||
from frigate.config import FrigateConfig
|
from frigate.config import FrigateConfig
|
||||||
|
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
||||||
from frigate.embeddings import EmbeddingsContext
|
from frigate.embeddings import EmbeddingsContext
|
||||||
from frigate.ptz.onvif import OnvifController
|
from frigate.ptz.onvif import OnvifController
|
||||||
from frigate.stats.emitter import StatsEmitter
|
from frigate.stats.emitter import StatsEmitter
|
||||||
@ -57,6 +58,7 @@ def create_fastapi_app(
|
|||||||
onvif: OnvifController,
|
onvif: OnvifController,
|
||||||
stats_emitter: StatsEmitter,
|
stats_emitter: StatsEmitter,
|
||||||
event_metadata_updater: EventMetadataPublisher,
|
event_metadata_updater: EventMetadataPublisher,
|
||||||
|
config_publisher: CameraConfigUpdatePublisher,
|
||||||
):
|
):
|
||||||
logger.info("Starting FastAPI app")
|
logger.info("Starting FastAPI app")
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
@ -127,6 +129,7 @@ def create_fastapi_app(
|
|||||||
app.onvif = onvif
|
app.onvif = onvif
|
||||||
app.stats_emitter = stats_emitter
|
app.stats_emitter = stats_emitter
|
||||||
app.event_metadata_updater = event_metadata_updater
|
app.event_metadata_updater = event_metadata_updater
|
||||||
|
app.config_publisher = config_publisher
|
||||||
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
app.jwt_token = get_jwt_secret() if frigate_config.auth.enabled else None
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -673,6 +673,7 @@ class FrigateApp:
|
|||||||
self.onvif_controller,
|
self.onvif_controller,
|
||||||
self.stats_emitter,
|
self.stats_emitter,
|
||||||
self.event_metadata_updater,
|
self.event_metadata_updater,
|
||||||
|
self.inter_config_updater,
|
||||||
),
|
),
|
||||||
host="127.0.0.1",
|
host="127.0.0.1",
|
||||||
port=5001,
|
port=5001,
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
|
|
||||||
class FrigateBaseModel(BaseModel):
|
class FrigateBaseModel(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
model_config = ConfigDict(extra="forbid", protected_namespaces=())
|
||||||
|
|
||||||
|
def get_nested_object(self, path: str) -> Any:
|
||||||
|
parts = path.split("/")
|
||||||
|
obj = self
|
||||||
|
for part in parts:
|
||||||
|
if part == "config":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(obj, BaseModel):
|
||||||
|
try:
|
||||||
|
obj = getattr(obj, part)
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
try:
|
||||||
|
obj = obj[part]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj
|
||||||
|
@ -17,6 +17,7 @@ class CameraConfigUpdateEnum(str, Enum):
|
|||||||
enabled = "enabled"
|
enabled = "enabled"
|
||||||
motion = "motion" # includes motion and motion masks
|
motion = "motion" # includes motion and motion masks
|
||||||
notifications = "notifications"
|
notifications = "notifications"
|
||||||
|
objects = "objects"
|
||||||
record = "record"
|
record = "record"
|
||||||
review = "review"
|
review = "review"
|
||||||
snapshots = "snapshots"
|
snapshots = "snapshots"
|
||||||
@ -83,6 +84,8 @@ class CameraConfigUpdateSubscriber:
|
|||||||
config.motion = updated_config
|
config.motion = updated_config
|
||||||
elif update_type == CameraConfigUpdateEnum.notifications:
|
elif update_type == CameraConfigUpdateEnum.notifications:
|
||||||
config.notifications = updated_config
|
config.notifications = updated_config
|
||||||
|
elif update_type == CameraConfigUpdateEnum.objects:
|
||||||
|
config.objects = updated_config
|
||||||
elif update_type == CameraConfigUpdateEnum.record:
|
elif update_type == CameraConfigUpdateEnum.record:
|
||||||
config.record = updated_config
|
config.record = updated_config
|
||||||
elif update_type == CameraConfigUpdateEnum.review:
|
elif update_type == CameraConfigUpdateEnum.review:
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
from frigate.config import MotionConfig
|
from frigate.config import MotionConfig
|
||||||
|
|
||||||
|
|
||||||
@ -18,13 +20,21 @@ class MotionDetector(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def detect(self, frame):
|
def detect(self, frame: ndarray) -> list:
|
||||||
|
"""Detect motion and return motion boxes."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def is_calibrating(self):
|
def is_calibrating(self):
|
||||||
|
"""Return if motion is recalibrating."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def update_mask(self) -> None:
|
||||||
|
"""Update the motion mask after a config change."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""Stop any ongoing work and processes."""
|
||||||
pass
|
pass
|
||||||
|
@ -35,12 +35,7 @@ class ImprovedMotionDetector(MotionDetector):
|
|||||||
self.avg_frame = np.zeros(self.motion_frame_size, np.float32)
|
self.avg_frame = np.zeros(self.motion_frame_size, np.float32)
|
||||||
self.motion_frame_count = 0
|
self.motion_frame_count = 0
|
||||||
self.frame_counter = 0
|
self.frame_counter = 0
|
||||||
resized_mask = cv2.resize(
|
self.update_mask()
|
||||||
config.mask,
|
|
||||||
dsize=(self.motion_frame_size[1], self.motion_frame_size[0]),
|
|
||||||
interpolation=cv2.INTER_AREA,
|
|
||||||
)
|
|
||||||
self.mask = np.where(resized_mask == [0])
|
|
||||||
self.save_images = False
|
self.save_images = False
|
||||||
self.calibrating = True
|
self.calibrating = True
|
||||||
self.blur_radius = blur_radius
|
self.blur_radius = blur_radius
|
||||||
@ -236,6 +231,14 @@ class ImprovedMotionDetector(MotionDetector):
|
|||||||
|
|
||||||
return motion_boxes
|
return motion_boxes
|
||||||
|
|
||||||
|
def update_mask(self) -> None:
|
||||||
|
resized_mask = cv2.resize(
|
||||||
|
self.config.mask,
|
||||||
|
dsize=(self.motion_frame_size[1], self.motion_frame_size[0]),
|
||||||
|
interpolation=cv2.INTER_AREA,
|
||||||
|
)
|
||||||
|
self.mask = np.where(resized_mask == [0])
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""stop the motion detector."""
|
"""stop the motion detector."""
|
||||||
pass
|
pass
|
||||||
|
@ -119,6 +119,7 @@ class BaseTestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
stats,
|
stats,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def insert_mock_event(
|
def insert_mock_event(
|
||||||
|
@ -2,6 +2,7 @@ import datetime
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
@ -112,8 +113,8 @@ class TestHttp(unittest.TestCase):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_get_good_event(self):
|
def __init_app(self, updater: Any | None = None) -> Any:
|
||||||
app = create_fastapi_app(
|
return create_fastapi_app(
|
||||||
FrigateConfig(**self.minimal_config),
|
FrigateConfig(**self.minimal_config),
|
||||||
self.db,
|
self.db,
|
||||||
None,
|
None,
|
||||||
@ -121,8 +122,12 @@ class TestHttp(unittest.TestCase):
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
updater,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_good_event(self):
|
||||||
|
app = self.__init_app()
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
@ -134,16 +139,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert event["id"] == model_to_dict(Event.get(Event.id == id))["id"]
|
assert event["id"] == model_to_dict(Event.get(Event.id == id))["id"]
|
||||||
|
|
||||||
def test_get_bad_event(self):
|
def test_get_bad_event(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
bad_id = "654321.other"
|
bad_id = "654321.other"
|
||||||
|
|
||||||
@ -154,16 +150,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert event_response.json() == "Event not found"
|
assert event_response.json() == "Event not found"
|
||||||
|
|
||||||
def test_delete_event(self):
|
def test_delete_event(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
@ -176,16 +163,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert event == "Event not found"
|
assert event == "Event not found"
|
||||||
|
|
||||||
def test_event_retention(self):
|
def test_event_retention(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
@ -202,16 +180,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert event["retain_indefinitely"] is False
|
assert event["retain_indefinitely"] is False
|
||||||
|
|
||||||
def test_event_time_filtering(self):
|
def test_event_time_filtering(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
morning_id = "123456.random"
|
morning_id = "123456.random"
|
||||||
evening_id = "654321.random"
|
evening_id = "654321.random"
|
||||||
morning = 1656590400 # 06/30/2022 6 am (GMT)
|
morning = 1656590400 # 06/30/2022 6 am (GMT)
|
||||||
@ -241,16 +210,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_set_delete_sub_label(self):
|
def test_set_delete_sub_label(self):
|
||||||
mock_event_updater = Mock(spec=EventMetadataPublisher)
|
mock_event_updater = Mock(spec=EventMetadataPublisher)
|
||||||
app = create_fastapi_app(
|
app = app = self.__init_app(updater=mock_event_updater)
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
mock_event_updater,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
sub_label = "sub"
|
sub_label = "sub"
|
||||||
|
|
||||||
@ -286,16 +246,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
|
|
||||||
def test_sub_label_list(self):
|
def test_sub_label_list(self):
|
||||||
mock_event_updater = Mock(spec=EventMetadataPublisher)
|
mock_event_updater = Mock(spec=EventMetadataPublisher)
|
||||||
app = create_fastapi_app(
|
app = self.__init_app(updater=mock_event_updater)
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
mock_event_updater,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
sub_label = "sub"
|
sub_label = "sub"
|
||||||
|
|
||||||
@ -318,16 +269,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert sub_labels == [sub_label]
|
assert sub_labels == [sub_label]
|
||||||
|
|
||||||
def test_config(self):
|
def test_config(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
config = client.get("/config").json()
|
config = client.get("/config").json()
|
||||||
@ -335,16 +277,7 @@ class TestHttp(unittest.TestCase):
|
|||||||
assert config["cameras"]["front_door"]
|
assert config["cameras"]["front_door"]
|
||||||
|
|
||||||
def test_recordings(self):
|
def test_recordings(self):
|
||||||
app = create_fastapi_app(
|
app = self.__init_app()
|
||||||
FrigateConfig(**self.minimal_config),
|
|
||||||
self.db,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
id = "123456.random"
|
id = "123456.random"
|
||||||
|
|
||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
|
@ -67,7 +67,8 @@ class TrackedObjectProcessor(threading.Thread):
|
|||||||
self.ptz_autotracker_thread = ptz_autotracker_thread
|
self.ptz_autotracker_thread = ptz_autotracker_thread
|
||||||
|
|
||||||
self.config_subscriber = CameraConfigUpdateSubscriber(
|
self.config_subscriber = CameraConfigUpdateSubscriber(
|
||||||
self.config.cameras, [CameraConfigUpdateEnum.enabled]
|
self.config.cameras,
|
||||||
|
[CameraConfigUpdateEnum.enabled, CameraConfigUpdateEnum.zones],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.requestor = InterProcessRequestor()
|
self.requestor = InterProcessRequestor()
|
||||||
|
@ -494,8 +494,6 @@ def track_camera(
|
|||||||
frame_queue = camera_metrics.frame_queue
|
frame_queue = camera_metrics.frame_queue
|
||||||
|
|
||||||
frame_shape = config.frame_shape
|
frame_shape = config.frame_shape
|
||||||
objects_to_track = config.objects.track
|
|
||||||
object_filters = config.objects.filters
|
|
||||||
|
|
||||||
motion_detector = ImprovedMotionDetector(
|
motion_detector = ImprovedMotionDetector(
|
||||||
frame_shape,
|
frame_shape,
|
||||||
@ -528,8 +526,6 @@ def track_camera(
|
|||||||
object_tracker,
|
object_tracker,
|
||||||
detected_objects_queue,
|
detected_objects_queue,
|
||||||
camera_metrics,
|
camera_metrics,
|
||||||
objects_to_track,
|
|
||||||
object_filters,
|
|
||||||
stop_event,
|
stop_event,
|
||||||
ptz_metrics,
|
ptz_metrics,
|
||||||
region_grid,
|
region_grid,
|
||||||
@ -594,8 +590,6 @@ def process_frames(
|
|||||||
object_tracker: ObjectTracker,
|
object_tracker: ObjectTracker,
|
||||||
detected_objects_queue: Queue,
|
detected_objects_queue: Queue,
|
||||||
camera_metrics: CameraMetrics,
|
camera_metrics: CameraMetrics,
|
||||||
objects_to_track: list[str],
|
|
||||||
object_filters,
|
|
||||||
stop_event: MpEvent,
|
stop_event: MpEvent,
|
||||||
ptz_metrics: PTZMetrics,
|
ptz_metrics: PTZMetrics,
|
||||||
region_grid: list[list[dict[str, Any]]],
|
region_grid: list[list[dict[str, Any]]],
|
||||||
@ -608,6 +602,7 @@ def process_frames(
|
|||||||
CameraConfigUpdateEnum.detect,
|
CameraConfigUpdateEnum.detect,
|
||||||
CameraConfigUpdateEnum.enabled,
|
CameraConfigUpdateEnum.enabled,
|
||||||
CameraConfigUpdateEnum.motion,
|
CameraConfigUpdateEnum.motion,
|
||||||
|
CameraConfigUpdateEnum.objects,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -650,6 +645,9 @@ def process_frames(
|
|||||||
prev_enabled = camera_enabled
|
prev_enabled = camera_enabled
|
||||||
camera_enabled = camera_config.enabled
|
camera_enabled = camera_config.enabled
|
||||||
|
|
||||||
|
if "motion" in updated_configs:
|
||||||
|
motion_detector.update_mask()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not camera_enabled
|
not camera_enabled
|
||||||
and prev_enabled != camera_enabled
|
and prev_enabled != camera_enabled
|
||||||
@ -822,8 +820,8 @@ def process_frames(
|
|||||||
frame,
|
frame,
|
||||||
model_config,
|
model_config,
|
||||||
region,
|
region,
|
||||||
objects_to_track,
|
camera_config.objects.track,
|
||||||
object_filters,
|
camera_config.objects.filters,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -161,6 +161,7 @@ export default function MotionMaskEditPane({
|
|||||||
axios
|
axios
|
||||||
.put(`config/set?${queryString}`, {
|
.put(`config/set?${queryString}`, {
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
|
update_topic: `config/cameras/${polygon.camera}/motion`,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -195,6 +195,7 @@ export default function ObjectMaskEditPane({
|
|||||||
axios
|
axios
|
||||||
.put(`config/set?${queryString}`, {
|
.put(`config/set?${queryString}`, {
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
|
update_topic: `config/cameras/${polygon.camera}/objects`,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -326,6 +326,7 @@ export default function ZoneEditPane({
|
|||||||
`config/set?cameras.${polygon.camera}.zones.${polygon.name}${renameAlertQueries}${renameDetectionQueries}`,
|
`config/set?cameras.${polygon.camera}.zones.${polygon.name}${renameAlertQueries}${renameDetectionQueries}`,
|
||||||
{
|
{
|
||||||
requires_restart: 0,
|
requires_restart: 0,
|
||||||
|
update_topic: `config/cameras/${polygon.camera}/zones`,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -409,7 +410,10 @@ export default function ZoneEditPane({
|
|||||||
axios
|
axios
|
||||||
.put(
|
.put(
|
||||||
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${speedThresholdQuery}${distancesQuery}${objectQueries}${alertQueries}${detectionQueries}`,
|
`config/set?cameras.${polygon?.camera}.zones.${zoneName}.coordinates=${coordinates}${inertiaQuery}${loiteringTimeQuery}${speedThresholdQuery}${distancesQuery}${objectQueries}${alertQueries}${detectionQueries}`,
|
||||||
{ requires_restart: 0 },
|
{
|
||||||
|
requires_restart: 0,
|
||||||
|
update_topic: `config/cameras/${polygon.camera}/zones`,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
import {
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
import { PolygonCanvas } from "@/components/settings/PolygonCanvas";
|
import { PolygonCanvas } from "@/components/settings/PolygonCanvas";
|
||||||
import { Polygon, PolygonType } from "@/types/canvas";
|
import { Polygon, PolygonType } from "@/types/canvas";
|
||||||
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
import { interpolatePoints, parseCoordinates } from "@/utils/canvasUtil";
|
||||||
@ -36,7 +29,6 @@ import ObjectMaskEditPane from "@/components/settings/ObjectMaskEditPane";
|
|||||||
import PolygonItem from "@/components/settings/PolygonItem";
|
import PolygonItem from "@/components/settings/PolygonItem";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { isDesktop } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
|
|
||||||
|
|
||||||
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
import { useSearchEffect } from "@/hooks/use-overlay-state";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -68,8 +60,6 @@ export default function MasksAndZonesView({
|
|||||||
const [activeLine, setActiveLine] = useState<number | undefined>();
|
const [activeLine, setActiveLine] = useState<number | undefined>();
|
||||||
const [snapPoints, setSnapPoints] = useState(false);
|
const [snapPoints, setSnapPoints] = useState(false);
|
||||||
|
|
||||||
const { addMessage } = useContext(StatusBarMessagesContext)!;
|
|
||||||
|
|
||||||
const cameraConfig = useMemo(() => {
|
const cameraConfig = useMemo(() => {
|
||||||
if (config && selectedCamera) {
|
if (config && selectedCamera) {
|
||||||
return config.cameras[selectedCamera];
|
return config.cameras[selectedCamera];
|
||||||
@ -192,13 +182,7 @@ export default function MasksAndZonesView({
|
|||||||
setAllPolygons([...(editingPolygons ?? [])]);
|
setAllPolygons([...(editingPolygons ?? [])]);
|
||||||
setHoveredPolygonIndex(null);
|
setHoveredPolygonIndex(null);
|
||||||
setUnsavedChanges(false);
|
setUnsavedChanges(false);
|
||||||
addMessage(
|
}, [editingPolygons, setUnsavedChanges]);
|
||||||
"masks_zones",
|
|
||||||
t("masksAndZones.restart_required"),
|
|
||||||
undefined,
|
|
||||||
"masks_zones",
|
|
||||||
);
|
|
||||||
}, [t, editingPolygons, setUnsavedChanges, addMessage]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
Loading…
Reference in New Issue
Block a user