From aa21c81c2eafd780627be5677a77a0955243e2fd Mon Sep 17 00:00:00 2001 From: serversdown Date: Mon, 15 Jun 2026 18:46:59 +0000 Subject: [PATCH] feat(reports): per-NRL Data Files tab reaches parity with the project-wide tab The per-NRL Data Files tab now reuses the same FTP browser + unified-files partials as the project-wide tab, scoped to the one NRL: ftp-browser and files-unified take an optional location_id. nrl_detail.html drops the flat file_list view for 'Download Files from SLMs' (Browse Files -> Download & Save) plus the grouped 'Project Files' view (edit times / download-all / delete), keeping the NRL upload and adding a refresh button. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/routers/projects.py | 27 ++++++++++++++++++++------- templates/nrl_detail.html | 27 ++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/backend/routers/projects.py b/backend/routers/projects.py index ea73d52..b1407de 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -1591,24 +1591,32 @@ async def get_sessions_calendar( async def get_ftp_browser( project_id: str, request: Request, + location_id: Optional[str] = None, db: Session = Depends(get_db), ): """ Get FTP browser interface for downloading files from assigned SLMs. Returns HTML partial with FTP browser. Sound Monitoring projects only. + + When `location_id` is given, scope to just the unit(s) assigned to that NRL + (used by the per-NRL Data Files tab, which mirrors the project-wide tab). """ from backend.models import DataFile project = db.query(Project).filter_by(id=project_id).first() _require_module(project, "sound_monitoring", db) - # Get all assignments for this project (active = assigned_until IS NULL) - assignments = db.query(UnitAssignment).filter( + # Active assignments for this project (active = assigned_until IS NULL), + # optionally scoped to a single NRL/location. + q = db.query(UnitAssignment).filter( and_( UnitAssignment.project_id == project_id, UnitAssignment.assigned_until == None, ) - ).all() + ) + if location_id: + q = q.filter(UnitAssignment.location_id == location_id) + assignments = q.all() # Enrich with unit and location details units_data = [] @@ -1882,21 +1890,26 @@ async def ftp_download_folder_to_server( async def get_unified_files( project_id: str, request: Request, + location_id: Optional[str] = None, db: Session = Depends(get_db), ): """ Get unified view of all files in this project. Groups files by recording session with full metadata. Returns HTML partial with hierarchical file listing. + + When `location_id` is given, scope to a single NRL/location (used by the + per-NRL Data Files tab so it mirrors the project-wide tab). """ from backend.models import DataFile from pathlib import Path import json - # Get all sessions for this project - sessions = db.query(MonitoringSession).filter_by( - project_id=project_id - ).order_by(MonitoringSession.started_at.desc()).all() + # Sessions for this project (optionally scoped to one NRL/location) + q = db.query(MonitoringSession).filter_by(project_id=project_id) + if location_id: + q = q.filter(MonitoringSession.location_id == location_id) + sessions = q.order_by(MonitoringSession.started_at.desc()).all() sessions_data = [] for session in sessions: diff --git a/templates/nrl_detail.html b/templates/nrl_detail.html index cafc12a..656435d 100644 --- a/templates/nrl_detail.html +++ b/templates/nrl_detail.html @@ -357,6 +357,16 @@ -
-
Loading data files...
+
Loading files...
@@ -715,7 +732,7 @@ function submitUpload() { status.textContent = parts.join(' '); status.className = 'text-sm text-green-600 dark:text-green-400'; input.value = ''; - htmx.trigger(document.getElementById('data-files-list'), 'load'); + htmx.trigger(document.getElementById('unified-files'), 'refresh'); } else { status.textContent = `Error: ${data.detail || 'Upload failed'}`; status.className = 'text-sm text-red-600 dark:text-red-400';