From fad62b996ad9b66420128265f65ce1d035c25db0 Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Mon, 17 Mar 2025 13:44:57 -0500
Subject: [PATCH] Add Frigate+ pane to Settings UI (#17208)
* add plus data to config api response
* add fields to frontend type
* add frigate+ page in settings
* add docs
* fix label in explore detail dialog
---
docs/docs/plus/faq.md | 10 +
frigate/api/app.py | 13 +
web/public/locales/en/views/settings.json | 37 ++-
.../overlay/detail/SearchDetailDialog.tsx | 2 +-
web/src/pages/Settings.tsx | 3 +
web/src/types/frigateConfig.ts | 6 +
.../settings/FrigatePlusSettingsView.tsx | 229 ++++++++++++++++++
7 files changed, 297 insertions(+), 3 deletions(-)
create mode 100644 web/src/views/settings/FrigatePlusSettingsView.tsx
diff --git a/docs/docs/plus/faq.md b/docs/docs/plus/faq.md
index fb0cd2512..151eb3f60 100644
--- a/docs/docs/plus/faq.md
+++ b/docs/docs/plus/faq.md
@@ -22,3 +22,13 @@ Yes. Models and metadata are stored in the `model_cache` directory within the co
### Can I keep using my Frigate+ models even if I do not renew my subscription?
Yes. Subscriptions to Frigate+ provide access to the infrastructure used to train the models. Models trained with your subscription are yours to keep and use forever. However, do note that the terms and conditions prohibit you from sharing, reselling, or creating derivative products from the models.
+
+### Why can't I submit images to Frigate+?
+
+If you've configured your API key and the Frigate+ Settings page in the UI shows that the key is active, you need to ensure that you've enabled both snapshots and `clean_copy` snapshots for the cameras you'd like to submit images for. Note that `clean_copy` is enabled by default when snapshots are enabled.
+
+```yaml
+snapshots:
+ enabled: true
+ clean_copy: true
+```
diff --git a/frigate/api/app.py b/frigate/api/app.py
index 05013ed12..9d7b3768f 100644
--- a/frigate/api/app.py
+++ b/frigate/api/app.py
@@ -9,6 +9,7 @@ import traceback
from datetime import datetime, timedelta
from functools import reduce
from io import StringIO
+from pathlib import Path as FilePath
from typing import Any, Optional
import aiofiles
@@ -174,6 +175,18 @@ def config(request: Request):
config["model"]["all_attributes"] = config_obj.model.all_attributes
config["model"]["non_logo_attributes"] = config_obj.model.non_logo_attributes
+ # Add model plus data if plus is enabled
+ if config["plus"]["enabled"]:
+ model_json_path = FilePath(config["model"]["path"]).with_suffix(".json")
+ try:
+ with open(model_json_path, "r") as f:
+ model_plus_data = json.load(f)
+ config["model"]["plus"] = model_plus_data
+ except FileNotFoundError:
+ config["model"]["plus"] = None
+ except json.JSONDecodeError:
+ config["model"]["plus"] = None
+
# use merged labelamp
for detector_config in config["detectors"].values():
detector_config["model"]["labelmap"] = (
diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json
index f19ac5ee6..3d25b92c1 100644
--- a/web/public/locales/en/views/settings.json
+++ b/web/public/locales/en/views/settings.json
@@ -7,7 +7,8 @@
"masksAndZones": "Mask and Zone Editor - Frigate",
"motionTuner": "Motion Tuner - Frigate",
"object": "Object Settings - Frigate",
- "general": "General Settings - Frigate"
+ "general": "General Settings - Frigate",
+ "frigatePlus": "Frigate+ Settings - Frigate"
},
"menu": {
"uiSettings": "UI Settings",
@@ -17,7 +18,8 @@
"motionTuner": "Motion Tuner",
"debug": "Debug",
"users": "Users",
- "notifications": "Notifications"
+ "notifications": "Notifications",
+ "frigateplus": "Frigate+"
},
"dialog": {
"unsavedChanges": {
@@ -515,5 +517,36 @@
"registerFailed": "Failed to save notification registration."
}
}
+ },
+ "frigatePlus": {
+ "title": "Frigate+ Settings",
+ "apiKey": {
+ "title": "Frigate+ API Key",
+ "validated": "Frigate+ API key is detected and validated",
+ "notValidated": "Frigate+ API key is not detected or not validated",
+ "desc": "The Frigate+ API key enables integration with the Frigate+ service.",
+ "plusLink": "Read more about Frigate+"
+ },
+ "snapshotConfig": {
+ "title": "Snapshot Configuration",
+ "desc": "Submitting to Frigate+ requires both snapshots and clean_copy
snapshots to be enabled in your config.",
+ "documentation": "Read the documentation",
+ "cleanCopyWarning": "Some cameras have snapshots enabled but have the clean copy disabled. You need to enable clean_copy
in your snapshot config to be able to submit images from these cameras to Frigate+.",
+ "table": {
+ "camera": "Camera",
+ "snapshots": "Snapshots",
+ "cleanCopySnapshots": "clean_copy
Snapshots"
+ }
+ },
+ "modelInfo": {
+ "title": "Model Information",
+ "modelType": "Model Type",
+ "trainDate": "Train Date",
+ "baseModel": "Base Model",
+ "supportedDetectors": "Supported Detectors",
+ "cameras": "Cameras",
+ "loading": "Loading model information...",
+ "error": "Failed to load model information"
+ }
}
}
diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx
index d74efdf6d..891ce88b1 100644
--- a/web/src/components/overlay/detail/SearchDetailDialog.tsx
+++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx
@@ -563,7 +563,7 @@ function ObjectDetailsTab({
{t("frigatePlus.apiKey.desc")}
+ {!config?.model.plus && ( + <> ++ {t("frigatePlus.modelInfo.loading")} +
+ )} + {config?.model?.plus === null && ( ++ {t("frigatePlus.modelInfo.error")} +
+ )} + {config?.model?.plus && ( +{config.model.plus.name}
++ {new Date( + config.model.plus.trainDate, + ).toLocaleString()} +
+{config.model.plus.baseModel}
++ {config.model.plus.supportedDetectors.join(", ")} +
+
+
+ {t("frigatePlus.snapshotConfig.table.camera")} + | ++ {t("frigatePlus.snapshotConfig.table.snapshots")} + | +
+ |
+
---|---|---|
{name} | +
+ {camera.snapshots.enabled ? (
+ |
+
+ {camera.snapshots?.enabled &&
+ camera.snapshots?.clean_copy ? (
+ |
+