seismo fleet roster repaired and visible.

This commit is contained in:
serversdwn
2026-01-09 20:02:05 +00:00
parent 5b907c0cd7
commit 94354da611
3 changed files with 143 additions and 1 deletions

View File

@@ -34,6 +34,7 @@ from app.seismo.routers import (
activity as seismo_activity, activity as seismo_activity,
seismo_dashboard as seismo_seismo_dashboard, seismo_dashboard as seismo_seismo_dashboard,
settings as seismo_settings, settings as seismo_settings,
partials as seismo_partials,
) )
from app.seismo import routes as seismo_legacy_routes from app.seismo import routes as seismo_legacy_routes
@@ -104,6 +105,7 @@ app.include_router(seismo_dashboard_tabs.router)
app.include_router(seismo_activity.router) app.include_router(seismo_activity.router)
app.include_router(seismo_seismo_dashboard.router) app.include_router(seismo_seismo_dashboard.router)
app.include_router(seismo_settings.router) app.include_router(seismo_settings.router)
app.include_router(seismo_partials.router, prefix="/partials")
app.include_router(seismo_legacy_routes.router) app.include_router(seismo_legacy_routes.router)
# SLM Feature Module APIs # SLM Feature Module APIs

View File

@@ -0,0 +1,140 @@
"""
Partial routes for HTMX dynamic content loading.
These routes return HTML fragments that are loaded into the page via HTMX.
"""
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from app.seismo.services.snapshot import emit_status_snapshot
router = APIRouter()
templates = Jinja2Templates(directory="app/ui/templates")
@router.get("/unknown-emitters", response_class=HTMLResponse)
async def get_unknown_emitters(request: Request):
"""
Returns HTML partial with unknown emitters (units reporting but not in roster).
Called periodically via HTMX (every 10s) from the roster page.
"""
snapshot = emit_status_snapshot()
# Convert unknown units dict to list and add required fields
unknown_list = []
for unit_id, unit_data in snapshot.get("unknown", {}).items():
unknown_list.append({
"id": unit_id,
"status": unit_data["status"],
"age": unit_data["age"],
"fname": unit_data.get("fname", ""),
})
# Sort by ID for consistent display
unknown_list.sort(key=lambda x: x["id"])
return templates.TemplateResponse(
"partials/unknown_emitters.html",
{
"request": request,
"unknown_units": unknown_list
}
)
@router.get("/devices-all", response_class=HTMLResponse)
async def get_all_devices(request: Request):
"""
Returns HTML partial with all devices (deployed, benched, retired, ignored).
Called on page load and when filters are applied.
"""
snapshot = emit_status_snapshot()
# Combine all units from different buckets
all_units = []
# Add active units (deployed)
for unit_id, unit_data in snapshot.get("active", {}).items():
unit_info = {
"id": unit_id,
"status": unit_data["status"],
"age": unit_data["age"],
"last_seen": unit_data.get("last", ""),
"fname": unit_data.get("fname", ""),
"deployed": True,
"retired": False,
"ignored": False,
"note": unit_data.get("note", ""),
"device_type": unit_data.get("device_type", "seismograph"),
"location": unit_data.get("location", ""),
"address": unit_data.get("address", ""),
"coordinates": unit_data.get("coordinates", ""),
"last_calibrated": unit_data.get("last_calibrated"),
"next_calibration_due": unit_data.get("next_calibration_due"),
"deployed_with_modem_id": unit_data.get("deployed_with_modem_id"),
"ip_address": unit_data.get("ip_address"),
"phone_number": unit_data.get("phone_number"),
"hardware_model": unit_data.get("hardware_model"),
}
all_units.append(unit_info)
# Add benched units (not deployed, not retired)
for unit_id, unit_data in snapshot.get("benched", {}).items():
unit_info = {
"id": unit_id,
"status": unit_data["status"],
"age": unit_data["age"],
"last_seen": unit_data.get("last", ""),
"fname": unit_data.get("fname", ""),
"deployed": False,
"retired": False,
"ignored": False,
"note": unit_data.get("note", ""),
"device_type": unit_data.get("device_type", "seismograph"),
"location": unit_data.get("location", ""),
"address": unit_data.get("address", ""),
"coordinates": unit_data.get("coordinates", ""),
"last_calibrated": unit_data.get("last_calibrated"),
"next_calibration_due": unit_data.get("next_calibration_due"),
"deployed_with_modem_id": unit_data.get("deployed_with_modem_id"),
"ip_address": unit_data.get("ip_address"),
"phone_number": unit_data.get("phone_number"),
"hardware_model": unit_data.get("hardware_model"),
}
all_units.append(unit_info)
# Add retired units
for unit_id, unit_data in snapshot.get("retired", {}).items():
unit_info = {
"id": unit_id,
"status": "Retired",
"age": unit_data["age"],
"last_seen": unit_data.get("last", ""),
"fname": unit_data.get("fname", ""),
"deployed": False,
"retired": True,
"ignored": False,
"note": unit_data.get("note", ""),
"device_type": unit_data.get("device_type", "seismograph"),
"location": unit_data.get("location", ""),
"address": unit_data.get("address", ""),
"coordinates": unit_data.get("coordinates", ""),
"last_calibrated": unit_data.get("last_calibrated"),
"next_calibration_due": unit_data.get("next_calibration_due"),
"deployed_with_modem_id": unit_data.get("deployed_with_modem_id"),
"ip_address": unit_data.get("ip_address"),
"phone_number": unit_data.get("phone_number"),
"hardware_model": unit_data.get("hardware_model"),
}
all_units.append(unit_info)
# Sort by ID for consistent display
all_units.sort(key=lambda x: x["id"])
return templates.TemplateResponse(
"partials/devices_table.html",
{
"request": request,
"units": all_units
}
)

View File

@@ -11,7 +11,7 @@ from app.seismo.database import get_db
from app.seismo.models import RosterUnit from app.seismo.models import RosterUnit
router = APIRouter(prefix="/api/seismo-dashboard", tags=["seismo-dashboard"]) router = APIRouter(prefix="/api/seismo-dashboard", tags=["seismo-dashboard"])
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="app/ui/templates")
@router.get("/stats", response_class=HTMLResponse) @router.get("/stats", response_class=HTMLResponse)