mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-27 22:08:58 +03:00
Add a notice registry and System Health tab (#24178)
* add a notice registry and System Health tab Problems Frigate detects on its own (ffmpeg crash loops, stuck detectors, failed model downloads, recordings deleted before their retention period) only ever existed as log lines. This adds a `NoticeRegistry` in the main process backed by two tables, an `update_notice` IPC topic so producers in other processes can reach it through the dispatcher, an admin-only API and websocket topic, and a Health tab that lists them. Kinds declare their own mode, severity, and category in one place: state notices are resolved by their producer, event notices are dismissed by the user. * treat a prerelease as behind its final release * fix notices clearing early * rename menu items and update docs * don't resolve the update notice on a failed version lookup
This commit is contained in:
committed by
Nicolas Mowen
parent
af60d2db48
commit
f3a31e2fb4
@@ -7,12 +7,22 @@ from pathlib import Path
|
||||
import requests
|
||||
|
||||
from frigate.comms.inter_process import InterProcessRequestor
|
||||
from frigate.const import UPDATE_MODEL_STATE
|
||||
from frigate.const import UPDATE_MODEL_STATE, UPDATE_NOTICE
|
||||
from frigate.types import ModelStatusTypesEnum
|
||||
from frigate.util.file import FileLock
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# target path -> first line of the last download error for it; every existing
|
||||
# download function swallows its exceptions, so this is how the downloader
|
||||
# thread learns why a file is still missing
|
||||
last_download_error: dict[str, str] = {}
|
||||
|
||||
|
||||
def _first_line(error: BaseException) -> str:
|
||||
text = str(error).strip().splitlines()
|
||||
return (text[0] if text else type(error).__name__)[:200]
|
||||
|
||||
|
||||
class ModelDownloader:
|
||||
def __init__(
|
||||
@@ -48,6 +58,33 @@ class ModelDownloader:
|
||||
)
|
||||
self.download_thread.start()
|
||||
|
||||
def _notice_scope(self, file_name: str) -> str:
|
||||
# per file: several loaders share one model_name with disjoint files,
|
||||
# so a model wide scope lets one of them clear another's notice
|
||||
return f"{self.model_name}/{file_name}"
|
||||
|
||||
def _report_failure(self, file_name: str, error: str) -> None:
|
||||
self.requestor.send_data(
|
||||
UPDATE_NOTICE,
|
||||
{
|
||||
"action": "raise",
|
||||
"kind": "model_download_failed",
|
||||
"scope": self._notice_scope(file_name),
|
||||
"params": {"file": file_name, "error": error},
|
||||
},
|
||||
)
|
||||
|
||||
def _resolve_failure(self, file_name: str) -> None:
|
||||
self.requestor.send_data(
|
||||
UPDATE_NOTICE,
|
||||
{
|
||||
"action": "resolve",
|
||||
"kind": "model_download_failed",
|
||||
"scope": self._notice_scope(file_name),
|
||||
"params": {},
|
||||
},
|
||||
)
|
||||
|
||||
def _download_models(self):
|
||||
for file_name in self.file_names:
|
||||
path = os.path.join(self.download_path, file_name)
|
||||
@@ -57,8 +94,20 @@ class ModelDownloader:
|
||||
if not os.path.exists(path):
|
||||
with lock:
|
||||
if not os.path.exists(path):
|
||||
self.download_func(path)
|
||||
try:
|
||||
self.download_func(path)
|
||||
except Exception as e:
|
||||
self._report_failure(file_name, _first_line(e))
|
||||
raise
|
||||
|
||||
if not os.path.exists(path):
|
||||
self._report_failure(
|
||||
file_name,
|
||||
last_download_error.pop(path, "download failed"),
|
||||
)
|
||||
continue
|
||||
|
||||
self._resolve_failure(file_name)
|
||||
self.requestor.send_data(
|
||||
UPDATE_MODEL_STATE,
|
||||
{
|
||||
@@ -93,6 +142,7 @@ class ModelDownloader:
|
||||
temporary_filename.rename(save_path)
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading model: {str(e)}")
|
||||
last_download_error[save_path] = _first_line(e)
|
||||
raise
|
||||
|
||||
if not silent:
|
||||
|
||||
Reference in New Issue
Block a user