Compare commits

..
Author SHA1 Message Date
dependabot[bot]andGitHub c74b366316 Bump follow-redirects
Bumps the web-security group with 1 update in the /web directory: [follow-redirects](https://github.com/follow-redirects/follow-redirects).


Updates `follow-redirects` from 1.16.0 to 1.16.1
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.16.0...v1.16.1)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-version: 1.16.1
  dependency-type: indirect
  dependency-group: web-security
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-20 18:47:22 +00:00
16 changed files with 61 additions and 104 deletions
@@ -1,8 +1,10 @@
set $upstream_auth http://127.0.0.1:5001/auth;
## Virtual endpoint created by nginx to forward auth requests.
location /auth {
## Essential Proxy Configuration
internal;
proxy_pass http://frigate_api/auth;
proxy_pass $upstream_auth;
## Headers
@@ -37,7 +39,6 @@ location /auth {
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; # Timeout if the real server is dead
proxy_redirect http:// $scheme://;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_bypass $cookie_session;
proxy_no_cache $cookie_session;
proxy_buffers 4 32k;
+5 -4
View File
@@ -69,7 +69,7 @@ These gauges report the operating system's figures for the whole filesystem (the
### Event Metrics
- `frigate_camera_events{camera="", label=""}` - Current number of events stored in the database for each camera and label
- `frigate_camera_events{camera="", label=""}` - Count of camera events since exporter started
## Configuring Prometheus
@@ -101,8 +101,8 @@ rate(frigate_detection_fps{camera_name="front_door"}[5m])
# Storage usage percentage
(frigate_storage_used_bytes / frigate_storage_total_bytes) * 100
# Number of events currently stored in the database by camera
sum by (camera) (frigate_camera_events)
# Event count by camera in last hour
increase(frigate_camera_events[1h])
```
## Grafana Dashboard
@@ -121,7 +121,8 @@ A sample Grafana dashboard JSON will be provided in a future update.
The metrics exposed by Frigate use the following Prometheus metric types:
- **Gauge**: Values that can go up and down (e.g., `frigate_cpu_usage_percent`, `frigate_camera_events`)
- **Counter**: Cumulative values that only increase (e.g., `frigate_camera_events`)
- **Gauge**: Values that can go up and down (e.g., `frigate_cpu_usage_percent`)
- **Info**: Key-value pairs for metadata (e.g., `frigate_storage_mount_type`)
For more information about Prometheus metric types, see the [Prometheus documentation](https://prometheus.io/docs/concepts/metric_types/).
+1 -3
View File
@@ -177,9 +177,7 @@ def metrics(request: Request):
# Retrieve the latest statistics and update the Prometheus metrics
stats = request.app.stats_emitter.get_latest_stats()
# query DB for count of events by camera, label
# Materialize the rows; the collector is shared across request threads and a
# lazy query would step one sqlite cursor from two threads.
event_counts: list[dict[str, Any]] = list(
event_counts: list[dict[str, Any]] = (
Event.select(Event.camera, Event.label, fn.Count())
.group_by(Event.camera, Event.label)
.dicts()
+14 -23
View File
@@ -84,13 +84,9 @@ def _is_valid_host(host: str) -> bool:
@router.get("/go2rtc/streams", dependencies=[Depends(allow_any_authenticated())])
async def go2rtc_streams(request: Request):
try:
r = await asyncio.to_thread(
requests.get, "http://127.0.0.1:1984/api/streams", timeout=10
)
r.raise_for_status()
except requests.RequestException as e:
logger.error("Failed to fetch streams from go2rtc: %s", e)
r = await asyncio.to_thread(requests.get, "http://127.0.0.1:1984/api/streams")
if not r.ok:
logger.error("Failed to fetch streams from go2rtc")
return JSONResponse(
content=({"success": False, "message": "Error fetching stream data"}),
status_code=500,
@@ -125,21 +121,16 @@ async def go2rtc_streams(request: Request):
dependencies=[Depends(require_go2rtc_stream_access)],
)
def go2rtc_camera_stream(request: Request, stream_name: str):
try:
r = requests.get(
"http://127.0.0.1:1984/api/streams",
params={
"src": stream_name,
"video": "all",
"audio": "all",
"microphone": "",
},
# go2rtc probes each source in turn, so one unresponsive source
# can take 10s or more
timeout=(2, 35),
)
r.raise_for_status()
except requests.RequestException as e:
r = requests.get(
"http://127.0.0.1:1984/api/streams",
params={
"src": stream_name,
"video": "all",
"audio": "all",
"microphone": "",
},
)
if not r.ok:
camera_config = request.app.frigate_config.cameras.get(stream_name)
if camera_config is None:
@@ -149,7 +140,7 @@ def go2rtc_camera_stream(request: Request, stream_name: str):
break
if camera_config and camera_config.enabled:
logger.error("Failed to fetch streams from go2rtc: %s", e)
logger.error("Failed to fetch streams from go2rtc")
return JSONResponse(
content=({"success": False, "message": "Error fetching stream data"}),
-2
View File
@@ -731,8 +731,6 @@ class FrigateApp:
host="127.0.0.1",
port=5001,
log_level="error",
# outlive nginx's 60s upstream keepalive so nginx closes idle connections first
timeout_keep_alive=75,
)
finally:
self.stop()
+4 -10
View File
@@ -285,16 +285,11 @@ class OutputProcess(FrigateProcess):
)
preview_write_times[camera] = frame_time
manager = websocket_server.manager
with manager.lock:
websockets = list(manager.websockets.values())
# send camera frame to ffmpeg process if websockets are connected
if any(
not ws.terminated
and ws.environ["PATH_INFO"].endswith(camera)
ws.environ["PATH_INFO"].endswith(camera)
and ws_has_camera_access(ws, camera, self.config)
for ws in websockets
for ws in websocket_server.manager
):
# write to the converter for the camera if clients are listening to the specific camera
jsmpeg_cameras[camera].write_frame(frame.tobytes())
@@ -306,10 +301,9 @@ class OutputProcess(FrigateProcess):
and (
self.config.birdseye.restream
or any(
not ws.terminated
and ws.environ["PATH_INFO"].endswith("birdseye")
ws.environ["PATH_INFO"].endswith("birdseye")
and ws_has_camera_access(ws, "birdseye", self.config)
for ws in websockets
for ws in websocket_server.manager
)
)
):
+4 -6
View File
@@ -392,20 +392,18 @@ class PreviewRecorder:
) -> None:
self.offline = False
# always write the first frame
if self.start_time == 0:
self.start_time = frame_time
if self.config.record.enabled:
self.output_frames.append(frame_time)
self.write_frame_to_cache(frame_time, frame)
self.output_frames.append(frame_time)
self.write_frame_to_cache(frame_time, frame)
return
# check if PREVIEW clip should be generated and cached frames reset
if frame_time >= self.segment_end:
if len(self.output_frames) > 0:
# save last frame to ensure consistent duration
if self.config.record.enabled:
if self.config.record:
self.output_frames.append(frame_time)
self.write_frame_to_cache(frame_time, frame)
+6 -9
View File
@@ -516,13 +516,10 @@ class RecordingCleanup(threading.Thread):
logger.info("Exiting recording cleanup...")
break
try:
self.clean_tmp_previews()
self.clean_tmp_previews()
if counter == 0:
self.clean_tmp_clips()
maybe_empty_dirs = self.expire_recordings()
remove_empty_directories(Path(RECORD_DIR), maybe_empty_dirs)
self.truncate_wal()
except Exception:
logger.exception("Recording cleanup pass failed")
if counter == 0:
self.clean_tmp_clips()
maybe_empty_dirs = self.expire_recordings()
remove_empty_directories(Path(RECORD_DIR), maybe_empty_dirs)
self.truncate_wal()
+3 -2
View File
@@ -5,6 +5,7 @@ from typing import Any
from prometheus_client import CONTENT_TYPE_LATEST, generate_latest
from prometheus_client.core import (
REGISTRY,
CounterMetricFamily,
GaugeMetricFamily,
InfoMetricFamily,
)
@@ -471,9 +472,9 @@ class CustomCollector:
yield storage_total
yield storage_used
camera_events = GaugeMetricFamily(
camera_events = CounterMetricFamily(
"frigate_camera_events",
"Current number of events stored in the database per camera and label",
"Count of camera events since exporter started",
labels=["camera", "label"],
)
+12 -9
View File
@@ -563,20 +563,23 @@ class TestHttpApp(BaseTestHttp):
assert "# TYPE frigate_detection_total_fps gauge" in event.text
assert "frigate_detection_total_fps 13.7" in event.text
assert (
"# HELP frigate_camera_events Current number of events stored in the database per camera and label"
"# HELP frigate_camera_events_total Count of camera events since exporter started"
in event.text
)
assert "# TYPE frigate_camera_events gauge" in event.text
assert "# TYPE frigate_camera_events_total counter" in event.text
assert (
'frigate_camera_events{camera="front_door",label="Mock"} 3.0' in event.text
)
assert (
'frigate_camera_events{camera="front_door",label="inside"} 2.0'
'frigate_camera_events_total{camera="front_door",label="Mock"} 3.0'
in event.text
)
assert (
'frigate_camera_events{camera="front_door",label="outside"} 2.0'
'frigate_camera_events_total{camera="front_door",label="inside"} 2.0'
in event.text
)
assert 'frigate_camera_events{camera="porch",label="Mock"} 2.0' in event.text
assert 'frigate_camera_events{camera="porch",label="inside"} 2.0' in event.text
assert (
'frigate_camera_events_total{camera="front_door",label="outside"} 2.0'
in event.text
)
assert (
'frigate_camera_events_total{camera="porch",label="Mock"} 2.0' in event.text
)
assert 'frigate_camera_events_total{camera="porch",label="inside"} 2.0'
+1 -1
View File
@@ -261,7 +261,7 @@ def is_vaapi_amd_driver() -> bool:
output = p.stdout.decode("unicode_escape").split("\n")
# VA Info will print out the friendly name of the driver
return any(" for AMD " in line for line in output)
return any("AMD Radeon Graphics" in line for line in output)
def get_amd_gpu_stats() -> dict[str, str] | None:
+4 -6
View File
@@ -483,18 +483,16 @@ class CameraWatchdog(threading.Thread):
elif self.camera_fps.value >= (self.config.detect.fps + 10):
self.fps_overflow_count += 1
# Require 3 retry intervals of sustained overflow. camera_fps
# averages the last 10s, so one burst of buffered frames stays
# over the limit for several 1s ticks.
if self.fps_overflow_count >= 3 * self.sleeptime and can_restart:
if self.fps_overflow_count == 3:
self.detect_status.send("offline", now)
self.fps_overflow_count = 0
self.camera_fps.value = 0
self.logger.info(
f"{self.config.name} exceeded fps limit. Exiting ffmpeg..."
)
self.reset_capture_thread()
last_restart_time = now
if can_restart:
self.reset_capture_thread()
last_restart_time = now
elif now - self.capture_thread.current_frame.value > 20:
self.detect_status.send("offline", now)
self.camera_fps.value = 0
+3 -3
View File
@@ -5498,9 +5498,9 @@
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz",
"integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==",
"funding": [
{
"type": "individual",
@@ -12,7 +12,6 @@ type AutoUpdatingCameraImageProps = {
};
const MIN_LOAD_TIMEOUT_MS = 200;
const ERROR_RETRY_MS = 1000;
export default function AutoUpdatingCameraImage({
camera,
@@ -69,24 +68,6 @@ export default function AutoUpdatingCameraImage({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, setFps]);
const handleError = useCallback(() => {
if (reloadInterval == -1) {
return;
}
// the periodic cache url doesn't change between retries, so drop it
setIsCached(true);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(
() => setKey(Date.now()),
Math.max(reloadInterval, ERROR_RETRY_MS),
);
}, [reloadInterval]);
// periodic cache to reduce loading indicator
const [isCached, setIsCached] = useState(false);
@@ -111,7 +92,6 @@ export default function AutoUpdatingCameraImage({
<CameraImage
camera={camera}
onload={handleLoad}
onerror={handleError}
searchParams={cacheKey}
className={cameraClasses}
/>
@@ -11,7 +11,6 @@ type CameraImageProps = {
className?: string;
camera: string;
onload?: () => void;
onerror?: () => void;
searchParams?: string;
};
@@ -19,7 +18,6 @@ export default function CameraImage({
className,
camera,
onload,
onerror,
searchParams = "",
}: CameraImageProps) {
const { data: config } = useSWR("config");
@@ -98,7 +96,6 @@ export default function CameraImage({
"rounded-lg md:rounded-2xl",
)}
onLoad={handleImageLoad}
onError={onerror}
loading="lazy"
/>
) : (
@@ -149,7 +149,7 @@ export default function CameraInfoDialog({
<span>
{t("cameras.info.resolution")}{" "}
<span className="text-primary">
{t("cameras.info.unknown")}
t("cameras.info.unknown")
</span>
</span>
)}