From e403f4e6b452e27f15971a1b81d09dc21896aa70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kacprzak?= Date: Mon, 3 Aug 2026 01:14:49 +0200 Subject: [PATCH] Add manual camera refresh control --- frigate_camera_control_bridge/app.py | 13 ++++++ frigate_camera_control_bridge/static/app.css | 41 +++++++++++++++++++ .../templates/index.html | 10 +++++ frigate_camera_control_bridge/ui.py | 2 + .../frigate_camera_control_bridge.po | 3 ++ .../frigate_camera_control_bridge.po | 3 ++ tests/test_ui_render.py | 9 ++++ 7 files changed, 81 insertions(+) diff --git a/frigate_camera_control_bridge/app.py b/frigate_camera_control_bridge/app.py index 5a092b0..1deddf4 100644 --- a/frigate_camera_control_bridge/app.py +++ b/frigate_camera_control_bridge/app.py @@ -1660,6 +1660,19 @@ class CameraControlBridge: self.send_response(401) self.end_headers() return + if parsed.path == "/refresh": + try: + bridge.refresh_frigate_inventory() + token_param = urllib.parse.parse_qs(parsed.query).get("token", [None])[0] + token_query = f"&token={urllib.parse.quote(token_param)}" if token_param else "" + self.send_response(303) + self.send_header("Location", f"/?lang={urllib.parse.quote(lang)}{token_query}") + self.end_headers() + except Exception as exc: + self.send_response(500) + self.end_headers() + self.wfile.write(str(exc).encode("utf-8")) + return parts = parsed.path.strip("/").split("/") if len(parts) == 3 and parts[0] == "action": action_id = urllib.parse.unquote(parts[1]) diff --git a/frigate_camera_control_bridge/static/app.css b/frigate_camera_control_bridge/static/app.css index 34e15ab..815753d 100644 --- a/frigate_camera_control_bridge/static/app.css +++ b/frigate_camera_control_bridge/static/app.css @@ -76,6 +76,43 @@ h1 { justify-content: flex-end; } +.refresh-form { + margin: 0; +} + +.refresh-button { + display: grid; + width: 38px; + height: 38px; + place-items: center; + border: 1px solid var(--line-strong); + border-radius: 6px; + padding: 0; + background: #ffffff; + color: #1f3a5f; + cursor: pointer; +} + +.refresh-button:hover { + border-color: var(--blue); + color: var(--blue-dark); +} + +.refresh-button:focus-visible { + outline: 3px solid rgba(29, 78, 216, 0.25); + outline-offset: 2px; +} + +.refresh-button svg { + width: 20px; + height: 20px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + .language-form { display: inline-flex; align-items: center; @@ -517,6 +554,10 @@ h3 { width: 100%; } + .refresh-form { + margin-left: auto; + } + .grid-single, .control-grid { grid-template-columns: 1fr; diff --git a/frigate_camera_control_bridge/templates/index.html b/frigate_camera_control_bridge/templates/index.html index 644f239..2be9233 100644 --- a/frigate_camera_control_bridge/templates/index.html +++ b/frigate_camera_control_bridge/templates/index.html @@ -15,6 +15,16 @@ {global_actions}
+
+ +
{token_input} diff --git a/frigate_camera_control_bridge/ui.py b/frigate_camera_control_bridge/ui.py index fcd53e9..849b112 100644 --- a/frigate_camera_control_bridge/ui.py +++ b/frigate_camera_control_bridge/ui.py @@ -306,8 +306,10 @@ def render_page(actions, lang, token_param=None): action_count=len(groups), actions_label=html.escape(text_for(lang, "cameras")), language_label=html.escape(text_for(lang, "language")), + refresh_label=html.escape(text_for(lang, "button.refresh_cameras")), language_options=render_language_options(lang), token_input=token_input, + token_query=html.escape(token_query), global_actions=global_markup, grid_class=html.escape(grid_class), cards=cards_markup, diff --git a/locale/en/LC_MESSAGES/frigate_camera_control_bridge.po b/locale/en/LC_MESSAGES/frigate_camera_control_bridge.po index ff13931..0f20297 100644 --- a/locale/en/LC_MESSAGES/frigate_camera_control_bridge.po +++ b/locale/en/LC_MESSAGES/frigate_camera_control_bridge.po @@ -92,6 +92,9 @@ msgstr "OFF" msgid "button.press" msgstr "PRESS" +msgid "button.refresh_cameras" +msgstr "Refresh cameras" + msgid "action.all_off" msgstr "{target} all off" diff --git a/locale/pl/LC_MESSAGES/frigate_camera_control_bridge.po b/locale/pl/LC_MESSAGES/frigate_camera_control_bridge.po index f703175..1215f42 100644 --- a/locale/pl/LC_MESSAGES/frigate_camera_control_bridge.po +++ b/locale/pl/LC_MESSAGES/frigate_camera_control_bridge.po @@ -92,6 +92,9 @@ msgstr "WYLACZ" msgid "button.press" msgstr "URUCHOM" +msgid "button.refresh_cameras" +msgstr "Odswiez kamery" + msgid "action.all_off" msgstr "{target} wylacz wszystko" diff --git a/tests/test_ui_render.py b/tests/test_ui_render.py index 9e3f2a1..71da650 100644 --- a/tests/test_ui_render.py +++ b/tests/test_ui_render.py @@ -62,6 +62,15 @@ class UiRenderTest(unittest.TestCase): self.assertIn('class="global-action"', html) self.assertNotIn('class="camera-card"', html) + def test_refresh_button_preserves_language_and_token(self): + html = render_page([DummyAction()], "en", token_param="secret") + + self.assertIn('class="refresh-form"', html) + self.assertIn('action="/refresh?lang=en&token=secret"', html) + self.assertIn('title="Refresh cameras"', html) + self.assertIn('aria-label="Refresh cameras"', html) + self.assertIn("