diff --git a/.dockerignore b/.dockerignore index f26b729..2e4edce 100644 --- a/.dockerignore +++ b/.dockerignore @@ -35,7 +35,7 @@ data/ .DS_Store Thumbs.db .claude -sfm.code-workspace +terra-view.code-workspace # Tests (optional) tests/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf82e8..3976fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -All notable changes to Seismo Fleet Manager will be documented in this file. +All notable changes to Terra-View will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). @@ -54,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.1] - 2026-01-05 ### Added -- **SLM Integration**: Sound Level Meters are now manageable in SFM +- **SLM Integration**: Sound Level Meters are now manageable in Terra-View ### Fixed - Fixed an issue where unit status was loading from a saved cache and not based on when it was actually heard from last. Unit status is now accurate. diff --git a/README.md b/README.md index e3d447b..90fdec4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# Seismo Fleet Manager v0.4.2 -Backend API and HTMX-powered web interface for managing a mixed fleet of seismographs and field modems. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard. +# Terra-View v0.4.2 +Backend API and HTMX-powered web interface for Terra-View - a unified fleet management system. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard. Terra-View supports seismographs (SFM module), sound level meters, field modems, and other monitoring devices. ## Features diff --git a/backend/main.py b/backend/main.py index 6442ab3..6204e6e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -31,8 +31,8 @@ ENVIRONMENT = os.getenv("ENVIRONMENT", "production") # Initialize FastAPI app VERSION = "0.4.2" app = FastAPI( - title="Seismo Fleet Manager", - description="Backend API for managing seismograph fleet status", + title="Terra-View", + description="Backend API for Terra-View fleet management system", version=VERSION ) @@ -516,7 +516,7 @@ async def devices_all_partial(request: Request): def health_check(): """Health check endpoint""" return { - "message": f"Seismo Fleet Manager v{VERSION}", + "message": f"Terra-View v{VERSION}", "status": "running", "version": VERSION } @@ -524,4 +524,6 @@ def health_check(): if __name__ == "__main__": import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8001) + import os + port = int(os.getenv("PORT", 8001)) + uvicorn.run(app, host="0.0.0.0", port=port) diff --git a/backend/routers/slm_dashboard.py b/backend/routers/slm_dashboard.py index 3d9c0df..bcfe057 100644 --- a/backend/routers/slm_dashboard.py +++ b/backend/routers/slm_dashboard.py @@ -6,7 +6,7 @@ Provides API endpoints for the Sound Level Meters dashboard page. from fastapi import APIRouter, Request, Depends, Query from fastapi.templating import Jinja2Templates -from fastapi.responses import HTMLResponse +from fastapi.responses import HTMLResponse, JSONResponse from sqlalchemy.orm import Session from sqlalchemy import func from datetime import datetime, timedelta @@ -60,14 +60,20 @@ async def get_slm_stats(request: Request, db: Session = Depends(get_db)): async def get_slm_units( request: Request, db: Session = Depends(get_db), - search: str = Query(None) + search: str = Query(None), + project: str = Query(None) ): """ Get list of SLM units for the sidebar. Returns HTML partial with unit cards. + Supports filtering by search term and project. """ query = db.query(RosterUnit).filter_by(device_type="sound_level_meter") + # Filter by project if provided + if project: + query = query.filter(RosterUnit.project_id == project) + # Filter by search term if provided if search: search_term = f"%{search}%" @@ -326,3 +332,55 @@ async def test_modem_connection(modem_id: str, db: Session = Depends(get_db)): "modem_id": modem_id, "detail": str(e) } + + +@router.get("/diagnostics/{unit_id}", response_class=HTMLResponse) +async def get_diagnostics(request: Request, unit_id: str, db: Session = Depends(get_db)): + """ + Get compact diagnostics card for a specific SLM unit. + Returns HTML partial with key metrics only. + """ + unit = db.query(RosterUnit).filter_by(id=unit_id, device_type="sound_level_meter").first() + + if not unit: + return HTMLResponse( + content='
Unit not found
', + status_code=404 + ) + + # Get modem info + modem = None + modem_ip = None + if unit.deployed_with_modem_id: + modem = db.query(RosterUnit).filter_by(id=unit.deployed_with_modem_id, device_type="modem").first() + if modem: + # Try modem_rx_host first (if it exists), then fall back to ip_address + modem_ip = getattr(modem, 'modem_rx_host', None) or modem.ip_address + elif unit.slm_host: + modem_ip = unit.slm_host + + return templates.TemplateResponse("partials/slm_diagnostics_card.html", { + "request": request, + "unit": unit, + "modem": modem, + "modem_ip": modem_ip + }) + + +@router.get("/projects") +async def get_projects(db: Session = Depends(get_db)): + """ + Get list of unique projects from deployed SLMs. + Returns JSON array of project names. + """ + projects = db.query(RosterUnit.project_id).filter( + RosterUnit.device_type == "sound_level_meter", + RosterUnit.deployed == True, + RosterUnit.retired == False, + RosterUnit.project_id.isnot(None) + ).distinct().order_by(RosterUnit.project_id).all() + + # Extract project names from query result tuples + project_list = [p[0] for p in projects if p[0]] + + return JSONResponse(content={"projects": project_list}) diff --git a/backend/routers/slmm.py b/backend/routers/slmm.py index 1c73f5e..1385beb 100644 --- a/backend/routers/slmm.py +++ b/backend/routers/slmm.py @@ -1,7 +1,7 @@ """ SLMM (Sound Level Meter Manager) Proxy Router -Proxies requests from SFM to the standalone SLMM backend service. +Proxies requests from Terra-View to the standalone SLMM backend service. SLMM runs on port 8100 and handles NL43/NL53 sound level meter communication. """ @@ -72,7 +72,7 @@ async def proxy_websocket_stream(websocket: WebSocket, unit_id: str): Proxy WebSocket connections to SLMM's /stream endpoint. This allows real-time streaming of measurement data from NL43 devices - through the SFM unified interface. + through the Terra-View unified interface. """ await websocket.accept() logger.info(f"WebSocket connection accepted for SLMM unit {unit_id}") @@ -237,7 +237,7 @@ async def proxy_to_slmm(path: str, request: Request): """ Proxy all requests to the SLMM backend service. - This allows SFM to act as a unified frontend for all device types, + This allows Terra-View to act as a unified frontend for all device types, while SLMM remains a standalone backend service. """ # Build target URL diff --git a/backend/static/offline-db.js b/backend/static/offline-db.js index 61281f9..ceb1847 100644 --- a/backend/static/offline-db.js +++ b/backend/static/offline-db.js @@ -1,9 +1,9 @@ -/* IndexedDB wrapper for offline data storage in SFM */ +/* IndexedDB wrapper for offline data storage in Terra-View */ /* Handles unit data, status snapshots, and pending edit queue */ class OfflineDB { constructor() { - this.dbName = 'sfm-offline-db'; + this.dbName = 'terra-view-offline-db'; this.version = 1; this.db = null; } diff --git a/backend/static/sw.js b/backend/static/sw.js index 2c24788..b69476a 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -1,10 +1,10 @@ -/* Service Worker for Seismo Fleet Manager PWA */ +/* Service Worker for Terra-View PWA */ /* Network-first strategy with cache fallback for real-time data */ const CACHE_VERSION = 'v1'; -const STATIC_CACHE = `sfm-static-${CACHE_VERSION}`; -const DYNAMIC_CACHE = `sfm-dynamic-${CACHE_VERSION}`; -const DATA_CACHE = `sfm-data-${CACHE_VERSION}`; +const STATIC_CACHE = `terra-view-static-${CACHE_VERSION}`; +const DYNAMIC_CACHE = `terra-view-dynamic-${CACHE_VERSION}`; +const DATA_CACHE = `terra-view-data-${CACHE_VERSION}`; // Files to precache (critical app shell) const STATIC_FILES = [ @@ -137,7 +137,7 @@ async function networkFirstStrategy(request, cacheName) { - Offline - SFM + Offline - Terra-View