From ee6062f9fb5f750def71927abac66d3e651a2ad7 Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 22 Jun 2026 18:12:45 +0000 Subject: [PATCH] feat: Overview live-mode-only NRLs + split locations by type Two Overview improvements for projects that mix vibration + sound: - Live monitoring now includes only live-mode (connected) NRLs. connection_mode lives in the location's metadata JSON (default "connected"); offline/manual NRLs are excluded, and since the section hides when the list is empty, it disappears entirely when no NRL is a live SLM. - The Overview location list is split into separate "Vibration Locations" and "NRLs" sections (driven by enabled modules) instead of one mixed list. Single-module projects still show just their one section. Live-chip repaint listener updated for the per-type list ids. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/routers/projects.py | 13 +++++ .../partials/projects/project_dashboard.html | 48 +++++++++---------- templates/projects/detail.html | 6 ++- 3 files changed, 40 insertions(+), 27 deletions(-) diff --git a/backend/routers/projects.py b/backend/routers/projects.py index cab2a34..e195590 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -1135,6 +1135,7 @@ async def get_project_live_stats(project_id: str, db: Session = Depends(get_db)) import os import asyncio import httpx + import json as _json project = db.query(Project).filter_by(id=project_id).first() if not project: @@ -1152,6 +1153,18 @@ async def get_project_live_stats(project_id: str, db: Session = Depends(get_db)) .all() ) + # Only connected/live-mode NRLs belong in live monitoring. connection_mode + # lives in location_metadata JSON (default "connected"); offline/manual NRLs + # are excluded. With none connected, the caller gets [] and hides the section. + def _is_connected(loc) -> bool: + try: + meta = _json.loads(loc.location_metadata or "{}") + return meta.get("connection_mode", "connected") != "offline" + except Exception: + return True + + locations = [loc for loc in locations if _is_connected(loc)] + # Active SLM unit per location (mirrors portal.active_unit_for_location). def _active_unit(loc_id: str): asg = ( diff --git a/templates/partials/projects/project_dashboard.html b/templates/partials/projects/project_dashboard.html index c4ff551..65e1f14 100644 --- a/templates/partials/projects/project_dashboard.html +++ b/templates/partials/projects/project_dashboard.html @@ -48,34 +48,32 @@ +{# Separate location lists per module type so vibration points and sound NRLs + don't get mixed in one list. Build the section set from the enabled modules. #} +{% set loc_sections = [] %} +{% if 'vibration_monitoring' in modules %}{% set _ = loc_sections.append(('vibration', 'Vibration Locations', 'Add Location')) %}{% endif %} +{% if 'sound_monitoring' in modules %}{% set _ = loc_sections.append(('sound', 'NRLs', 'Add NRL')) %}{% endif %} +{% if not loc_sections %}{% set _ = loc_sections.append(('', 'Locations', 'Add Location')) %}{% endif %} +
-
-
-

- {% if 'sound_monitoring' in modules and 'vibration_monitoring' not in modules %} - NRLs - {% else %} - Locations - {% endif %} -

- -
-
-
-
-
-
+
+ {% for ltype, title, add_label in loc_sections %} +
+
+

{{ title }}

+ +
+
+
+
+
+
+ {% endfor %}
{# Location map — uses the reusable partial that fetches from diff --git a/templates/projects/detail.html b/templates/projects/detail.html index 340b910..c37f190 100644 --- a/templates/projects/detail.html +++ b/templates/projects/detail.html @@ -2287,8 +2287,10 @@ async function loadLiveStats() { // The NRL list partial reloads via htmx (e.g. the 30s dashboard swap), which // wipes the painted chips — repaint from the last poll as soon as it settles. document.body.addEventListener('htmx:afterSwap', (e) => { - const id = e.target && e.target.id; - if (id === 'project-locations' || id === 'sound-locations') lsPaintInline(lsLastData); + const id = (e.target && e.target.id) || ''; + // Overview lists are now per-type (project-locations-sound/-vibration); the + // Sound tab uses sound-locations. Repaint chips whenever any of them swap. + if (id.startsWith('project-locations') || id === 'sound-locations') lsPaintInline(lsLastData); }); function startLiveStats() {