mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-18 18:01:14 +03:00
Miscellaneous fixes (0.18 beta) (#23736)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Catch faces that become empty after cropping * don't drop batched camera add/remove config updates TrackedObjectProcessor drained all pending camera config updates at once but handled them in a mutually exclusive if/elif on enabled/add/remove, so only one topic was processed per drain. When an add arrived in the same batch as an enabled update, the add was skipped and the new camera never got a camera state. Adding a camera reliably produced that batch: config_set now re-applies runtime overrides, which republishes an enabled update for every previously toggled camera immediately before the add, in the same request. The dashboard and camera capture still saw the camera (the maintainer does not subscribe to enabled, so it got a clean add-only batch), but object_processing did not, and disabling the camera then crashed with a KeyError on the unguarded camera_states lookup. Handle add and remove independently instead of as exclusive branches so a batched add is no longer dropped, and guard the remove lookup so a missing state is skipped rather than raising. Drop the enabled branch entirely: it only ever set prev_enabled when it was None, but prev_enabled is seeded to a bool at camera state creation and is never None (mypy flags the body as unreachable), and the actual enable/disable transition is already driven by the disabled-state loop from config.enabled. * Don't stay on motion search page when user cancels flow * fix notification test button being blocked by websocket auth * fix overflowing model names in settings genai widget * add note about auth debugging --------- Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
parent
c17538aff9
commit
d02a1156b7
@ -262,6 +262,19 @@ In this example:
|
||||
|
||||
- Admin precedence: if the `admin` mapping matches, Frigate resolves the session to `admin` to avoid accidental downgrade when a user belongs to multiple groups (for example both `admin` and `viewer` groups).
|
||||
|
||||
:::note
|
||||
|
||||
If a user isn't getting the role you expect, enable debug logging to see exactly what headers Frigate is receiving from your proxy:
|
||||
|
||||
```yaml
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
frigate.api.auth: debug
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### Port Considerations
|
||||
|
||||
**Authenticated Port (8971)**
|
||||
|
||||
@ -23,7 +23,6 @@ from frigate.const import (
|
||||
EXPIRE_AUDIO_ACTIVITY,
|
||||
INSERT_MANY_RECORDINGS,
|
||||
INSERT_PREVIEW,
|
||||
NOTIFICATION_TEST,
|
||||
REQUEST_REGION_GRID,
|
||||
UPDATE_AUDIO_ACTIVITY,
|
||||
UPDATE_AUDIO_TRANSCRIPTION_STATE,
|
||||
@ -57,7 +56,6 @@ _WS_BLOCKED_TOPICS = frozenset(
|
||||
UPDATE_EMBEDDINGS_REINDEX_PROGRESS,
|
||||
UPDATE_BIRDSEYE_LAYOUT,
|
||||
UPDATE_AUDIO_TRANSCRIPTION_STATE,
|
||||
NOTIFICATION_TEST,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -288,6 +288,10 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
|
||||
max(0, face_box[0]) : min(frame.shape[1], face_box[2]),
|
||||
]
|
||||
|
||||
if face_frame.size == 0:
|
||||
logger.debug(f"Empty face crop for {id}")
|
||||
return
|
||||
|
||||
res = self.recognizer.classify(face_frame)
|
||||
|
||||
if not res:
|
||||
|
||||
@ -115,6 +115,13 @@ class TestCheckWsAuthorization(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
def test_viewer_blocked_from_notification_test(self):
|
||||
self.assertFalse(
|
||||
_check_ws_authorization(
|
||||
"notification_test", "viewer", self.DEFAULT_SEPARATOR
|
||||
)
|
||||
)
|
||||
|
||||
# --- Admin access ---
|
||||
|
||||
def test_admin_can_send_restart(self):
|
||||
@ -134,6 +141,13 @@ class TestCheckWsAuthorization(unittest.TestCase):
|
||||
_check_ws_authorization("front_door/ptz", "admin", self.DEFAULT_SEPARATOR)
|
||||
)
|
||||
|
||||
def test_admin_can_send_notification_test(self):
|
||||
self.assertTrue(
|
||||
_check_ws_authorization(
|
||||
"notification_test", "admin", self.DEFAULT_SEPARATOR
|
||||
)
|
||||
)
|
||||
|
||||
# --- Comma-separated roles ---
|
||||
|
||||
def test_comma_separated_admin_viewer_grants_admin(self):
|
||||
|
||||
@ -684,22 +684,21 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
# check for config updates
|
||||
updated_topics = self.camera_config_subscriber.check_for_updates()
|
||||
|
||||
if "enabled" in updated_topics:
|
||||
for camera in updated_topics["enabled"]:
|
||||
if self.camera_states[camera].prev_enabled is None:
|
||||
self.camera_states[camera].prev_enabled = self.config.cameras[
|
||||
camera
|
||||
].enabled
|
||||
elif "add" in updated_topics:
|
||||
for camera in updated_topics["add"]:
|
||||
self.config.cameras[camera] = (
|
||||
self.camera_config_subscriber.camera_configs[camera]
|
||||
)
|
||||
self.create_camera_state(camera)
|
||||
elif "remove" in updated_topics:
|
||||
# a single drain can carry several topics at once, so add and
|
||||
# remove are handled independently rather than as exclusive branches
|
||||
for camera in updated_topics.get("add", []):
|
||||
self.config.cameras[camera] = (
|
||||
self.camera_config_subscriber.camera_configs[camera]
|
||||
)
|
||||
self.create_camera_state(camera)
|
||||
|
||||
if "remove" in updated_topics:
|
||||
for camera in updated_topics["remove"]:
|
||||
removed_camera_state = self.camera_states[camera]
|
||||
removed_camera_state.shutdown()
|
||||
camera_state = self.camera_states.get(camera)
|
||||
if camera_state is None:
|
||||
continue
|
||||
|
||||
camera_state.shutdown()
|
||||
self.camera_states.pop(camera)
|
||||
self.camera_activity.pop(camera, None)
|
||||
self.last_motion_detected.pop(camera, None)
|
||||
|
||||
@ -227,16 +227,18 @@ export function GenAIModelWidget(props: WidgetProps) {
|
||||
aria-expanded={open}
|
||||
disabled={disabled || readonly}
|
||||
className={cn(
|
||||
"justify-between font-normal",
|
||||
"min-w-0 justify-between font-normal",
|
||||
!currentLabel && "text-muted-foreground",
|
||||
fieldClassName,
|
||||
)}
|
||||
>
|
||||
{currentLabel ??
|
||||
t("configForm.genaiModel.placeholder", {
|
||||
ns: "views/settings",
|
||||
defaultValue: "Select or enter a model…",
|
||||
})}
|
||||
<span className="truncate">
|
||||
{currentLabel ??
|
||||
t("configForm.genaiModel.placeholder", {
|
||||
ns: "views/settings",
|
||||
defaultValue: "Select or enter a model…",
|
||||
})}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -263,12 +265,14 @@ export function GenAIModelWidget(props: WidgetProps) {
|
||||
value={trimmedSearch}
|
||||
onSelect={() => commit(trimmedSearch)}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t("configForm.genaiModel.useCustom", {
|
||||
ns: "views/settings",
|
||||
value: trimmedSearch,
|
||||
defaultValue: 'Use "{{value}}"',
|
||||
})}
|
||||
<Plus className="mr-2 h-4 w-4 shrink-0" />
|
||||
<span className="truncate">
|
||||
{t("configForm.genaiModel.useCustom", {
|
||||
ns: "views/settings",
|
||||
value: trimmedSearch,
|
||||
defaultValue: 'Use "{{value}}"',
|
||||
})}
|
||||
</span>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
@ -287,11 +291,11 @@ export function GenAIModelWidget(props: WidgetProps) {
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
"mr-2 h-4 w-4 shrink-0",
|
||||
value === model ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
{model}
|
||||
<span className="truncate">{model}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
|
||||
@ -770,6 +770,34 @@ export default function MotionSearchView({
|
||||
};
|
||||
}, [cancelMotionSearchJobViaBeacon]);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
if (onBack) {
|
||||
onBack();
|
||||
} else {
|
||||
navigate(-1);
|
||||
}
|
||||
}, [navigate, onBack]);
|
||||
|
||||
// Dismissing the entry dialog (escape / click outside) before a search has
|
||||
// run leaves nothing behind it, so cancel the flow instead of revealing an
|
||||
// empty page.
|
||||
const handleSearchDialogOpenChange = useCallback(
|
||||
(nextOpen: boolean) => {
|
||||
if (
|
||||
!nextOpen &&
|
||||
!isSearching &&
|
||||
!hasSearched &&
|
||||
searchResults.length === 0
|
||||
) {
|
||||
handleBack();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSearchDialogOpen(nextOpen);
|
||||
},
|
||||
[handleBack, hasSearched, isSearching, searchResults.length],
|
||||
);
|
||||
|
||||
const handleNewSearch = useCallback(() => {
|
||||
if (jobId && jobCamera) {
|
||||
void cancelMotionSearchJob(jobId, jobCamera);
|
||||
@ -1238,7 +1266,7 @@ export default function MotionSearchView({
|
||||
<Toaster closeButton={true} position="top-center" />
|
||||
<MotionSearchDialog
|
||||
open={isSearchDialogOpen}
|
||||
onOpenChange={setIsSearchDialogOpen}
|
||||
onOpenChange={handleSearchDialogOpenChange}
|
||||
config={config}
|
||||
cameras={cameras}
|
||||
selectedCamera={selectedCamera}
|
||||
@ -1276,7 +1304,7 @@ export default function MotionSearchView({
|
||||
className="flex items-center gap-2.5 rounded-lg"
|
||||
aria-label={t("label.back", { ns: "common" })}
|
||||
size="sm"
|
||||
onClick={() => (onBack ? onBack() : navigate(-1))}
|
||||
onClick={handleBack}
|
||||
>
|
||||
<IoMdArrowRoundBack className="size-5 text-secondary-foreground" />
|
||||
{isDesktop && (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user