Files
terra-view/templates/sound_level_meters.html

316 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}Sound Level Meters - Seismo Fleet Manager{% endblock %}
{% block content %}
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Sound Level Meters</h1>
<p class="text-gray-600 dark:text-gray-400 mt-1">Monitor and manage sound level measurement devices</p>
</div>
<!-- Summary Stats -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8"
hx-get="/api/slm-dashboard/stats"
hx-trigger="load, every 10s"
hx-swap="innerHTML">
<!-- Stats will be loaded here -->
<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
<div class="animate-pulse bg-gray-200 dark:bg-gray-700 h-24 rounded-xl"></div>
</div>
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- SLM List -->
<div class="lg:col-span-1">
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Active Units</h2>
<!-- Search/Filter -->
<div class="mb-4 space-y-2">
<!-- Project Filter -->
<select id="project-filter"
name="project"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
hx-get="/api/slm-dashboard/units"
hx-trigger="change"
hx-target="#slm-list"
hx-include="#search-input, #project-filter">
<option value="">All Projects</option>
<!-- Will be populated dynamically -->
</select>
<!-- Search Input -->
<input id="search-input"
type="text"
placeholder="Search units..."
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
hx-get="/api/slm-dashboard/units"
hx-trigger="keyup changed delay:300ms"
hx-target="#slm-list"
hx-include="#search-input, #project-filter"
name="search">
</div>
<!-- SLM List -->
<div id="slm-list"
class="space-y-2 max-h-[600px] overflow-y-auto"
hx-get="/api/slm-dashboard/units"
hx-trigger="load, every 10s"
hx-swap="innerHTML">
<!-- Loading skeleton -->
<div class="animate-pulse space-y-2">
<div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
<div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
<div class="bg-gray-200 dark:bg-gray-700 h-20 rounded-lg"></div>
</div>
</div>
</div>
</div>
<!-- Live View Panel -->
<div class="lg:col-span-2">
<div id="live-view-panel" class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
<!-- Initial state - no unit selected -->
<div class="flex flex-col items-center justify-center h-[600px] text-gray-400 dark:text-gray-500">
<svg class="w-24 h-24 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"></path>
</svg>
<p class="text-lg font-medium">No unit selected</p>
<p class="text-sm mt-2">Select a sound level meter from the list to view live data</p>
</div>
</div>
</div>
</div>
<!-- Configuration Modal -->
<div id="config-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-6">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white">Configure SLM</h3>
<button onclick="closeConfigModal()" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="config-modal-content">
<!-- Content loaded via HTMX -->
<div class="animate-pulse space-y-4">
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded"></div>
<div class="h-4 bg-gray-200 dark:bg-gray-700 rounded w-5/6"></div>
</div>
</div>
</div>
</div>
<!-- Command Center Modal -->
<div id="command-center-modal" class="hidden fixed inset-0 bg-black bg-opacity-50 z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="bg-white dark:bg-gray-900 rounded-lg shadow-xl w-full max-w-7xl max-h-[90vh] overflow-y-auto">
<div class="sticky top-0 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-6 py-4 flex items-center justify-between z-10">
<h2 class="text-xl font-bold text-gray-900 dark:text-white">Command Center</h2>
<button onclick="closeCommandCenter()" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div id="command-center-content" class="p-6">
<!-- Command center will load here -->
</div>
</div>
</div>
</div>
<script>
// Global Connection Manager - ensures only one SLM connection at a time
window.SLMConnectionManager = {
activeIntervals: [],
activeWebSocket: null,
currentUnitId: null,
// Clear all existing connections
clearAll: function() {
console.log('SLMConnectionManager: Clearing all connections');
// Clear all intervals
this.activeIntervals.forEach(interval => {
clearInterval(interval);
});
this.activeIntervals = [];
// Close WebSocket if exists
if (this.activeWebSocket) {
this.activeWebSocket.close();
this.activeWebSocket = null;
}
// Clear any global intervals that might exist
if (window.refreshInterval) {
clearInterval(window.refreshInterval);
window.refreshInterval = null;
}
if (window.timerInterval) {
clearInterval(window.timerInterval);
window.timerInterval = null;
}
if (window.diagRefreshInterval) {
clearInterval(window.diagRefreshInterval);
window.diagRefreshInterval = null;
}
console.log('SLMConnectionManager: All connections cleared');
},
// Register a new interval
registerInterval: function(intervalId) {
this.activeIntervals.push(intervalId);
},
// Register WebSocket
registerWebSocket: function(ws) {
if (this.activeWebSocket) {
this.activeWebSocket.close();
}
this.activeWebSocket = ws;
},
// Set current unit
setCurrentUnit: function(unitId) {
if (this.currentUnitId !== unitId) {
this.clearAll();
this.currentUnitId = unitId;
}
}
};
// Function to select a unit and load DIAGNOSTICS CARD (not full command center)
function selectUnit(unitId) {
console.log(`Selecting unit: ${unitId}`);
// Clear all existing connections
window.SLMConnectionManager.clearAll();
// Remove active state from all items
document.querySelectorAll('.slm-unit-item').forEach(item => {
item.classList.remove('bg-seismo-orange', 'text-white');
item.classList.add('bg-gray-100', 'dark:bg-gray-700');
});
// Add active state to clicked item
event.currentTarget.classList.remove('bg-gray-100', 'dark:bg-gray-700');
event.currentTarget.classList.add('bg-seismo-orange', 'text-white');
// Load DIAGNOSTICS CARD (not full live view)
htmx.ajax('GET', `/api/slm-dashboard/diagnostics/${unitId}`, {
target: '#live-view-panel',
swap: 'innerHTML'
});
}
// Open command center in modal
function openCommandCenter(unitId) {
console.log(`Opening command center for: ${unitId}`);
// Clear diagnostics refresh before opening modal
window.SLMConnectionManager.clearAll();
const modal = document.getElementById('command-center-modal');
modal.classList.remove('hidden');
// Load full command center
htmx.ajax('GET', `/api/slm-dashboard/live-view/${unitId}`, {
target: '#command-center-content',
swap: 'innerHTML'
});
}
// Close command center modal
function closeCommandCenter() {
console.log('Closing command center');
// Clear all command center connections
window.SLMConnectionManager.clearAll();
document.getElementById('command-center-modal').classList.add('hidden');
// Reload the diagnostics card for the currently selected unit
const activeUnit = document.querySelector('.slm-unit-item.bg-seismo-orange');
if (activeUnit) {
const unitIdMatch = activeUnit.getAttribute('onclick').match(/selectUnit\('(.+?)'\)/);
if (unitIdMatch) {
const unitId = unitIdMatch[1];
htmx.ajax('GET', `/api/slm-dashboard/diagnostics/${unitId}`, {
target: '#live-view-panel',
swap: 'innerHTML'
});
}
}
}
// Configuration modal functions
function openConfigModal(unitId) {
const modal = document.getElementById('config-modal');
modal.classList.remove('hidden');
// Load configuration form via HTMX
htmx.ajax('GET', `/api/slm-dashboard/config/${unitId}`, {
target: '#config-modal-content',
swap: 'innerHTML'
});
}
function closeConfigModal() {
document.getElementById('config-modal').classList.add('hidden');
}
// Close modals on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeConfigModal();
closeCommandCenter();
}
});
// Close modals when clicking outside
document.getElementById('config-modal')?.addEventListener('click', function(e) {
if (e.target === this) {
closeConfigModal();
}
});
document.getElementById('command-center-modal')?.addEventListener('click', function(e) {
if (e.target === this) {
closeCommandCenter();
}
});
// Load projects for filter dropdown on page load
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/slm-dashboard/projects')
.then(response => response.json())
.then(data => {
const projectFilter = document.getElementById('project-filter');
if (projectFilter && data.projects) {
data.projects.forEach(project => {
const option = document.createElement('option');
option.value = project;
option.textContent = project;
projectFilter.appendChild(option);
});
}
})
.catch(error => console.error('Failed to load projects:', error));
});
// Cleanup on page unload
window.addEventListener('beforeunload', function() {
window.SLMConnectionManager.clearAll();
});
</script>
{% endblock %}