v0.2.1. many features added and cleaned up.

This commit is contained in:
serversdwn
2025-12-03 21:23:18 +00:00
parent dc853806bb
commit 4cef580185
13 changed files with 1815 additions and 181 deletions

View File

@@ -31,22 +31,56 @@
<!-- Loading placeholder -->
</div>
<!-- Auto-refresh roster every 10 seconds -->
<div hx-get="/partials/roster-table" hx-trigger="load, every 10s" hx-swap="innerHTML">
<!-- Initial loading state -->
<div class="rounded-xl shadow-lg bg-white dark:bg-slate-800 p-6">
<div class="flex items-center justify-center py-12">
<div class="animate-pulse flex space-x-4">
<div class="flex-1 space-y-4 py-1">
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4"></div>
<div class="space-y-2">
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div class="h-4 bg-gray-300 dark:bg-gray-600 rounded w-5/6"></div>
</div>
</div>
</div>
</div>
<!-- Fleet Roster with Tabs -->
<div class="rounded-xl shadow-lg bg-white dark:bg-slate-800 p-6">
<!-- Tab Bar -->
<div class="flex border-b border-gray-200 dark:border-gray-700 mb-4">
<button
class="px-4 py-2 text-sm font-medium roster-tab-button active-roster-tab"
data-endpoint="/partials/roster-deployed"
hx-get="/partials/roster-deployed"
hx-target="#roster-content"
hx-swap="innerHTML">
Deployed
</button>
<button
class="px-4 py-2 text-sm font-medium roster-tab-button"
data-endpoint="/partials/roster-benched"
hx-get="/partials/roster-benched"
hx-target="#roster-content"
hx-swap="innerHTML">
Benched
</button>
<button
class="px-4 py-2 text-sm font-medium roster-tab-button"
data-endpoint="/partials/roster-retired"
hx-get="/partials/roster-retired"
hx-target="#roster-content"
hx-swap="innerHTML">
Retired
</button>
<button
class="px-4 py-2 text-sm font-medium roster-tab-button"
data-endpoint="/partials/roster-ignored"
hx-get="/partials/roster-ignored"
hx-target="#roster-content"
hx-swap="innerHTML">
Ignored
</button>
</div>
<!-- Tab Content Target -->
<div id="roster-content"
hx-get="/partials/roster-deployed"
hx-trigger="load"
hx-swap="innerHTML">
<p class="text-gray-500 dark:text-gray-400">Loading roster data...</p>
</div>
</div>
<!-- Add Unit Modal -->
@@ -143,6 +177,11 @@
class="w-4 h-4 text-seismo-orange focus:ring-seismo-orange rounded">
<span class="text-sm text-gray-700 dark:text-gray-300">Deployed</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" name="retired" id="retiredCheckbox" value="true"
class="w-4 h-4 text-seismo-orange focus:ring-seismo-orange rounded">
<span class="text-sm text-gray-700 dark:text-gray-300">Retired</span>
</label>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Notes</label>
@@ -175,7 +214,7 @@
</button>
</div>
</div>
<form id="editUnitForm" hx-post="" hx-swap="none" class="p-6 space-y-4">
<form id="editUnitForm" class="p-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Unit ID</label>
<input type="text" name="id" id="editUnitId" readonly
@@ -200,10 +239,15 @@
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Location</label>
<input type="text" name="location" id="editLocation"
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Address</label>
<input type="text" name="address" id="editAddress" placeholder="123 Main St, City, State"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Coordinates</label>
<input type="text" name="coordinates" id="editCoordinates" placeholder="34.0522,-118.2437"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-seismo-orange font-mono">
</div>
<!-- Seismograph-specific fields -->
<div id="editSeismographFields" class="space-y-4 border-t border-gray-200 dark:border-gray-700 pt-4">
@@ -406,8 +450,11 @@
document.getElementById('addUnitForm').addEventListener('htmx:afterRequest', function(event) {
if (event.detail.successful) {
closeAddUnitModal();
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
// Show success message
alert('Unit added successfully!');
} else {
@@ -469,7 +516,8 @@
document.getElementById('editDeviceTypeSelect').value = unit.device_type;
document.getElementById('editUnitType').value = unit.unit_type;
document.getElementById('editProjectId').value = unit.project_id;
document.getElementById('editLocation').value = unit.location;
document.getElementById('editAddress').value = unit.address;
document.getElementById('editCoordinates').value = unit.coordinates;
document.getElementById('editNote').value = unit.note;
// Checkboxes
@@ -486,8 +534,8 @@
document.getElementById('editPhoneNumber').value = unit.phone_number;
document.getElementById('editHardwareModel').value = unit.hardware_model;
// Set form action
document.getElementById('editUnitForm').setAttribute('hx-post', `/api/roster/edit/${unitId}`);
// Store unit ID for form submission
document.getElementById('editUnitForm').dataset.unitId = unitId;
// Show/hide fields based on device type
toggleEditDeviceFields();
@@ -500,14 +548,37 @@
}
// Handle Edit Unit form submission
document.getElementById('editUnitForm').addEventListener('htmx:afterRequest', function(event) {
if (event.detail.successful) {
closeEditUnitModal();
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
alert('Unit updated successfully!');
} else {
alert('Error updating unit. Please check the form and try again.');
document.getElementById('editUnitForm').addEventListener('submit', async function(event) {
event.preventDefault();
const unitId = this.dataset.unitId;
if (!unitId) {
alert('Error: Unit ID not found');
return;
}
const formData = new FormData(this);
try {
const response = await fetch(`/api/roster/edit/${unitId}`, {
method: 'POST',
body: formData
});
if (response.ok) {
closeEditUnitModal();
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
alert('Unit updated successfully!');
} else {
const result = await response.json();
alert(`Error: ${result.detail || 'Unknown error'}`);
}
} catch (error) {
alert(`Error updating unit: ${error.message}`);
}
});
@@ -528,8 +599,11 @@
});
if (response.ok) {
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
alert(`Unit ${deployed ? 'deployed' : 'benched'} successfully!`);
} else {
const result = await response.json();
@@ -557,8 +631,11 @@
});
if (response.ok) {
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
alert(`Unit ${unitId} moved to ignore list`);
} else {
const result = await response.json();
@@ -581,8 +658,11 @@
});
if (response.ok) {
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
alert(`Unit ${unitId} deleted successfully`);
} else {
const result = await response.json();
@@ -621,8 +701,11 @@
`;
resultDiv.classList.remove('hidden');
// Trigger roster refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-table"]'), 'load');
// Trigger roster refresh for current active tab
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
// Close modal after 2 seconds
setTimeout(() => closeImportModal(), 2000);
@@ -637,6 +720,105 @@
resultDiv.classList.remove('hidden');
}
});
// Handle roster tab switching with auto-refresh
let currentRosterEndpoint = '/partials/roster-deployed'; // Default to deployed tab
document.addEventListener('DOMContentLoaded', function() {
const tabButtons = document.querySelectorAll('.roster-tab-button');
tabButtons.forEach(button => {
button.addEventListener('click', function() {
// Remove active-roster-tab class from all buttons
tabButtons.forEach(btn => btn.classList.remove('active-roster-tab'));
// Add active-roster-tab class to clicked button
this.classList.add('active-roster-tab');
// Update current endpoint for auto-refresh
currentRosterEndpoint = this.getAttribute('data-endpoint');
});
});
// Auto-refresh the current active tab every 10 seconds
setInterval(() => {
const rosterContent = document.getElementById('roster-content');
if (rosterContent) {
// Use HTMX to trigger a refresh of the current endpoint
htmx.ajax('GET', currentRosterEndpoint, {
target: '#roster-content',
swap: 'innerHTML'
});
}
}, 10000); // 10 seconds
});
// Un-ignore Unit (remove from ignored list)
async function unignoreUnit(unitId) {
if (!confirm(`Remove unit ${unitId} from ignore list?`)) {
return;
}
try {
const response = await fetch(`/api/roster/ignore/${unitId}`, {
method: 'DELETE'
});
if (response.ok) {
// Trigger ignored tab refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-ignored"]'), 'load');
alert(`Unit ${unitId} removed from ignore list`);
} else {
const result = await response.json();
alert(`Error: ${result.detail || 'Unknown error'}`);
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}
// Delete ignored unit completely (from emitters table)
async function deleteIgnoredUnit(unitId) {
if (!confirm(`Are you sure you want to PERMANENTLY delete unit ${unitId}?\n\nThis will remove it from the ignore list and delete all records.`)) {
return;
}
try {
// First remove from ignore list
await fetch(`/api/roster/ignore/${unitId}`, {
method: 'DELETE'
});
// Then delete the unit
const response = await fetch(`/api/roster/${unitId}`, {
method: 'DELETE'
});
if (response.ok) {
// Trigger ignored tab refresh
htmx.trigger(document.querySelector('[hx-get="/partials/roster-ignored"]'), 'load');
alert(`Unit ${unitId} deleted successfully`);
} else {
const result = await response.json();
alert(`Error: ${result.detail || 'Unknown error'}`);
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}
</script>
<style>
.roster-tab-button {
color: #6b7280; /* gray-500 */
border-bottom: 2px solid transparent;
}
.roster-tab-button:hover {
color: #374151; /* gray-700 */
}
.active-roster-tab {
color: #f48b1c !important; /* seismo orange */
border-bottom: 2px solid #f48b1c !important;
}
</style>
{% endblock %}