BIG update: Update to 0.5.1. Added:
-Project management -Modem Managerment -Modem/unit pairing and more
This commit is contained in:
@@ -121,8 +121,13 @@
|
||||
<p id="viewUnitType" class="mt-1 text-gray-900 dark:text-white font-medium">--</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">Project ID</label>
|
||||
<p id="viewProjectId" class="mt-1 text-gray-900 dark:text-white font-medium">--</p>
|
||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">Project</label>
|
||||
<p id="viewProjectContainer" class="mt-1">
|
||||
<a id="viewProjectLink" href="#" class="text-seismo-orange hover:text-orange-600 font-medium hover:underline hidden">
|
||||
<span id="viewProjectText">--</span>
|
||||
</a>
|
||||
<span id="viewProjectNoLink" class="text-gray-900 dark:text-white font-medium">Not assigned</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">Address</label>
|
||||
@@ -172,6 +177,48 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paired Device (for modems only) -->
|
||||
<div id="viewPairedDeviceSection" class="hidden border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Paired Device</h3>
|
||||
<div id="pairedDeviceInfo">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connectivity (for modems only) -->
|
||||
<div id="viewConnectivitySection" class="hidden border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Connectivity</h3>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<button onclick="pingModem()" id="modemPingBtn"
|
||||
class="px-4 py-2 bg-blue-100 hover:bg-blue-200 text-blue-700 dark:bg-blue-900/30 dark:hover:bg-blue-900/50 dark:text-blue-300 rounded-lg flex items-center gap-2 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0"></path>
|
||||
</svg>
|
||||
Ping Test
|
||||
</button>
|
||||
<span id="modemPingResult" class="text-sm text-gray-500 dark:text-gray-400">--</span>
|
||||
</div>
|
||||
|
||||
<!-- Future Diagnostics Placeholders -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 opacity-60">
|
||||
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Signal Strength</p>
|
||||
<p class="text-xl font-semibold text-gray-400 dark:text-gray-500">-- dBm</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">ModemManager pending</p>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Data Usage</p>
|
||||
<p class="text-xl font-semibold text-gray-400 dark:text-gray-500">-- MB</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">ModemManager pending</p>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-700 rounded-lg p-4">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">Uptime</p>
|
||||
<p class="text-xl font-semibold text-gray-400 dark:text-gray-500">--</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">ModemManager pending</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">Notes</label>
|
||||
@@ -251,11 +298,11 @@
|
||||
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>
|
||||
|
||||
<!-- Project ID -->
|
||||
<!-- Project -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Project ID</label>
|
||||
<input type="text" name="project_id" id="projectId"
|
||||
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">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Project</label>
|
||||
{% set picker_id = "-detail" %}
|
||||
{% include "partials/project_picker.html" with context %}
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
@@ -415,6 +462,26 @@ let currentSnapshot = null;
|
||||
let unitMap = null;
|
||||
let mapMarker = null;
|
||||
|
||||
// Fetch project display name (combines project_number, client_name, name)
|
||||
async function fetchProjectDisplay(projectId) {
|
||||
if (!projectId) return '';
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
if (response.ok) {
|
||||
const project = await response.json();
|
||||
const parts = [
|
||||
project.project_number,
|
||||
project.client_name,
|
||||
project.name
|
||||
].filter(Boolean);
|
||||
return parts.join(' - ') || projectId;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch project:', e);
|
||||
}
|
||||
return projectId;
|
||||
}
|
||||
|
||||
// Load unit data on page load
|
||||
async function loadUnitData() {
|
||||
try {
|
||||
@@ -536,7 +603,31 @@ function populateViewMode() {
|
||||
// Basic info
|
||||
document.getElementById('viewDeviceType').textContent = currentUnit.device_type || '--';
|
||||
document.getElementById('viewUnitType').textContent = currentUnit.unit_type || '--';
|
||||
document.getElementById('viewProjectId').textContent = currentUnit.project_id || '--';
|
||||
|
||||
// Project display with clickable link
|
||||
const projectId = currentUnit.project_id;
|
||||
const projectLink = document.getElementById('viewProjectLink');
|
||||
const projectNoLink = document.getElementById('viewProjectNoLink');
|
||||
const projectText = document.getElementById('viewProjectText');
|
||||
|
||||
if (projectId) {
|
||||
// Fetch project display name and show link
|
||||
fetchProjectDisplay(projectId).then(displayText => {
|
||||
if (projectText) projectText.textContent = displayText;
|
||||
if (projectLink) {
|
||||
projectLink.href = `/projects/${projectId}`;
|
||||
projectLink.classList.remove('hidden');
|
||||
}
|
||||
if (projectNoLink) projectNoLink.classList.add('hidden');
|
||||
});
|
||||
} else {
|
||||
if (projectNoLink) {
|
||||
projectNoLink.textContent = 'Not assigned';
|
||||
projectNoLink.classList.remove('hidden');
|
||||
}
|
||||
if (projectLink) projectLink.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('viewAddress').textContent = currentUnit.address || '--';
|
||||
document.getElementById('viewCoordinates').textContent = currentUnit.coordinates || '--';
|
||||
|
||||
@@ -557,9 +648,15 @@ function populateViewMode() {
|
||||
if (currentUnit.device_type === 'modem') {
|
||||
document.getElementById('viewSeismographFields').classList.add('hidden');
|
||||
document.getElementById('viewModemFields').classList.remove('hidden');
|
||||
document.getElementById('viewPairedDeviceSection').classList.remove('hidden');
|
||||
document.getElementById('viewConnectivitySection').classList.remove('hidden');
|
||||
// Load paired device info
|
||||
loadPairedDevice();
|
||||
} else {
|
||||
document.getElementById('viewSeismographFields').classList.remove('hidden');
|
||||
document.getElementById('viewModemFields').classList.add('hidden');
|
||||
document.getElementById('viewPairedDeviceSection').classList.add('hidden');
|
||||
document.getElementById('viewConnectivitySection').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +664,22 @@ function populateViewMode() {
|
||||
function populateEditForm() {
|
||||
document.getElementById('deviceType').value = currentUnit.device_type || 'seismograph';
|
||||
document.getElementById('unitType').value = currentUnit.unit_type || '';
|
||||
document.getElementById('projectId').value = currentUnit.project_id || '';
|
||||
|
||||
// Populate project picker (uses -detail suffix)
|
||||
const projectPickerValue = document.getElementById('project-picker-value-detail');
|
||||
const projectPickerSearch = document.getElementById('project-picker-search-detail');
|
||||
const projectPickerClear = document.getElementById('project-picker-clear-detail');
|
||||
if (projectPickerValue) projectPickerValue.value = currentUnit.project_id || '';
|
||||
if (currentUnit.project_id) {
|
||||
fetchProjectDisplay(currentUnit.project_id).then(displayText => {
|
||||
if (projectPickerSearch) projectPickerSearch.value = displayText;
|
||||
if (projectPickerClear) projectPickerClear.classList.remove('hidden');
|
||||
});
|
||||
} else {
|
||||
if (projectPickerSearch) projectPickerSearch.value = '';
|
||||
if (projectPickerClear) projectPickerClear.classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('address').value = currentUnit.address || '';
|
||||
document.getElementById('coordinates').value = currentUnit.coordinates || '';
|
||||
document.getElementById('deployed').checked = currentUnit.deployed;
|
||||
@@ -999,10 +1111,66 @@ async function deleteHistoryEntry(historyId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Load paired device info for modems
|
||||
async function loadPairedDevice() {
|
||||
try {
|
||||
const response = await fetch(`/api/modem-dashboard/${unitId}/paired-device-html`);
|
||||
if (response.ok) {
|
||||
const html = await response.text();
|
||||
document.getElementById('pairedDeviceInfo').innerHTML = html;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading paired device:', error);
|
||||
document.getElementById('pairedDeviceInfo').innerHTML = '<p class="text-red-500 text-sm">Failed to load paired device info</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Ping modem and show result
|
||||
async function pingModem() {
|
||||
const btn = document.getElementById('modemPingBtn');
|
||||
const resultSpan = document.getElementById('modemPingResult');
|
||||
|
||||
// Show loading state
|
||||
const originalText = btn.innerHTML;
|
||||
btn.innerHTML = `
|
||||
<svg class="w-5 h-5 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Pinging...
|
||||
`;
|
||||
btn.disabled = true;
|
||||
resultSpan.textContent = 'Testing connection...';
|
||||
resultSpan.className = 'text-sm text-gray-500 dark:text-gray-400';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/modem-dashboard/${unitId}/ping`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'success') {
|
||||
resultSpan.innerHTML = `<span class="inline-block w-2 h-2 bg-green-500 rounded-full mr-1"></span>Online (${data.response_time_ms}ms)`;
|
||||
resultSpan.className = 'text-sm text-green-600 dark:text-green-400';
|
||||
} else {
|
||||
resultSpan.innerHTML = `<span class="inline-block w-2 h-2 bg-red-500 rounded-full mr-1"></span>${data.detail || 'Offline'}`;
|
||||
resultSpan.className = 'text-sm text-red-600 dark:text-red-400';
|
||||
}
|
||||
} catch (error) {
|
||||
resultSpan.textContent = 'Error: ' + error.message;
|
||||
resultSpan.className = 'text-sm text-red-600 dark:text-red-400';
|
||||
}
|
||||
|
||||
// Restore button
|
||||
btn.innerHTML = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
// Load data when page loads
|
||||
loadUnitData().then(() => {
|
||||
loadPhotos();
|
||||
loadUnitHistory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Include Project Create Modal for inline project creation -->
|
||||
{% include "partials/project_create_modal.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user