2cf5bf47d3
The sidebar had 10 entries with 5 of them (Devices, Seismographs, Sound
Level Meters, Modems, Pair Devices) all about the physical fleet plus
SFM Events as a debug surface. Operators kept asking "where do I find
BE11529?" without knowing whether it was a seismograph / SLM / modem.
This collapses those 5+1 into a single "Fleet" sidebar entry that opens
into a unified tab strip across the top of the four device pages. Each
page keeps its existing custom layout (seismograph-specific
calibration/deployment columns, SLM live-status panel, modem pairing
view, all-devices roster). The strip just provides the navigation +
the "Pair Devices" button as an action.
Sidebar before (10 items):
Dashboard · Devices · Seismographs · SFM Events · Sound Level Meters
Modems · Pair Devices · Projects · Job Planner · Settings
Sidebar after (5 items):
Dashboard · Fleet · Projects · Job Planner · Settings
Changes:
- templates/partials/fleet_tab_strip.html (new): the shared tab strip.
Auto-detects the active tab from request.url.path. 4 tabs
(Seismographs / Sound Level Meters / Modems / All Devices) plus a
"Pair Devices" button on the right.
- templates/{seismographs,sound_level_meters,modems,roster}.html: added
{% include 'partials/fleet_tab_strip.html' %} as the first thing
inside the content block. No other changes to those templates'
existing layouts.
- templates/base.html: replaced the 6 device-related sidebar links with
one "Fleet" link to /seismographs. The Fleet entry is highlighted
when the current URL is any of /seismographs, /sound-level-meters,
/modems, /roster, /pair-devices, /unit/*, or /slm/*.
- templates/settings.html: SFM Events moved out of the main nav into a
new "SFM Admin" card under Settings → Developer. Daily event
browsing already lives on project / location / unit pages (Phases
1+2+3); the standalone /sfm page is now admin / cross-project debug
surface only.
URLs unchanged — all bookmarks / deep links still work. /sfm still
serves the standalone page, it's just no longer in the main nav.
Mobile bottom-nav unaffected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
140 lines
5.6 KiB
HTML
140 lines
5.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Seismographs - Seismo Fleet Manager{% endblock %}
|
|
|
|
{% block content %}
|
|
{% include "partials/fleet_tab_strip.html" %}
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Seismographs</h1>
|
|
<p class="text-gray-600 dark:text-gray-400 mt-1">Manage and monitor seismograph units</p>
|
|
</div>
|
|
|
|
<!-- Auto-refresh stats every 30 seconds -->
|
|
<div hx-get="/api/seismo-dashboard/stats"
|
|
hx-trigger="load, every 30s"
|
|
hx-swap="innerHTML"
|
|
class="mb-8">
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div class="rounded-lg shadow bg-white dark:bg-slate-800 p-6">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-gray-600 dark:text-gray-400 text-sm">Loading...</p>
|
|
<p class="text-3xl font-bold text-gray-900 dark:text-white mt-2">--</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Seismograph List -->
|
|
<div class="rounded-xl shadow-lg bg-white dark:bg-slate-800 p-6">
|
|
<div class="mb-4 flex flex-col gap-4">
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">All Seismographs</h2>
|
|
|
|
<!-- Search Box -->
|
|
<div class="relative">
|
|
<input
|
|
type="text"
|
|
id="seismo-search"
|
|
placeholder="Search seismographs..."
|
|
class="w-full sm:w-64 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
name="search"
|
|
/>
|
|
<svg class="absolute right-3 top-2.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">Filter:</span>
|
|
|
|
<!-- Status Filter -->
|
|
<select id="seismo-status-filter" name="status"
|
|
class="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option value="">All Status</option>
|
|
<option value="deployed">Deployed</option>
|
|
<option value="benched">Benched</option>
|
|
<option value="out_for_calibration">Out for Calibration</option>
|
|
</select>
|
|
|
|
<!-- Modem Filter -->
|
|
<select id="seismo-modem-filter" name="modem"
|
|
class="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option value="">All Modems</option>
|
|
<option value="with">With Modem</option>
|
|
<option value="without">Without Modem</option>
|
|
</select>
|
|
|
|
<!-- Clear Filters Button -->
|
|
<button id="seismo-clear-filters" type="button"
|
|
class="px-3 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
|
|
Clear Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Units List (loaded via HTMX) -->
|
|
<div id="seismo-units-list"
|
|
hx-get="/api/seismo-dashboard/units"
|
|
hx-trigger="load"
|
|
hx-swap="innerHTML">
|
|
<p class="text-gray-500 dark:text-gray-400">Loading seismographs...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const searchInput = document.getElementById('seismo-search');
|
|
const statusFilter = document.getElementById('seismo-status-filter');
|
|
const modemFilter = document.getElementById('seismo-modem-filter');
|
|
const clearBtn = document.getElementById('seismo-clear-filters');
|
|
const unitsList = document.getElementById('seismo-units-list');
|
|
|
|
// Build URL with current filter values
|
|
function buildUrl() {
|
|
const params = new URLSearchParams();
|
|
if (searchInput.value) params.set('search', searchInput.value);
|
|
if (statusFilter.value) params.set('status', statusFilter.value);
|
|
if (modemFilter.value) params.set('modem', modemFilter.value);
|
|
return '/api/seismo-dashboard/units' + (params.toString() ? '?' + params.toString() : '');
|
|
}
|
|
|
|
// Trigger HTMX refresh
|
|
function refreshList() {
|
|
htmx.ajax('GET', buildUrl(), {target: '#seismo-units-list', swap: 'innerHTML'});
|
|
}
|
|
|
|
// Search input with debounce
|
|
let debounceTimer;
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(refreshList, 300);
|
|
});
|
|
|
|
// Clear search on escape
|
|
searchInput.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
this.value = '';
|
|
refreshList();
|
|
}
|
|
});
|
|
|
|
// Filter changes
|
|
statusFilter.addEventListener('change', refreshList);
|
|
modemFilter.addEventListener('change', refreshList);
|
|
|
|
// Clear all filters
|
|
clearBtn.addEventListener('click', function() {
|
|
searchInput.value = '';
|
|
statusFilter.value = '';
|
|
modemFilter.value = '';
|
|
refreshList();
|
|
});
|
|
});
|
|
</script>
|
|
|
|
{% endblock %}
|