added: Pairing options now available from the modem page.
This commit is contained in:
@@ -1571,6 +1571,242 @@ function clearPairing(deviceType) {
|
||||
|
||||
closePairDeviceModal();
|
||||
}
|
||||
|
||||
// ===== Modem Pair Device Modal Functions (for modems to pick a device) =====
|
||||
let modemPairDevices = []; // Cache loaded devices
|
||||
let modemHasCurrentPairing = false;
|
||||
|
||||
function openModemPairDeviceModal() {
|
||||
const modal = document.getElementById('modemPairDeviceModal');
|
||||
const searchInput = document.getElementById('modemPairDeviceSearch');
|
||||
|
||||
if (!modal) return;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
searchInput.focus();
|
||||
}
|
||||
|
||||
// Reset checkboxes
|
||||
document.getElementById('modemPairHidePaired').checked = true;
|
||||
document.getElementById('modemPairShowSeismo').checked = true;
|
||||
document.getElementById('modemPairShowSLM').checked = true;
|
||||
|
||||
// Load available devices
|
||||
loadPairableDevices();
|
||||
}
|
||||
|
||||
function closeModemPairDeviceModal() {
|
||||
const modal = document.getElementById('modemPairDeviceModal');
|
||||
if (modal) modal.classList.add('hidden');
|
||||
modemPairDevices = [];
|
||||
}
|
||||
|
||||
async function loadPairableDevices() {
|
||||
const listContainer = document.getElementById('modemPairDeviceList');
|
||||
listContainer.innerHTML = '<div class="text-center py-8"><div class="animate-spin inline-block w-6 h-6 border-2 border-seismo-orange border-t-transparent rounded-full"></div><p class="mt-2 text-sm text-gray-500">Loading devices...</p></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/modem-dashboard/${unitId}/pairable-devices?hide_paired=false`);
|
||||
if (!response.ok) throw new Error('Failed to load devices');
|
||||
|
||||
const data = await response.json();
|
||||
modemPairDevices = data.devices || [];
|
||||
|
||||
// Check if modem has a current pairing
|
||||
modemHasCurrentPairing = modemPairDevices.some(d => d.is_paired_to_this);
|
||||
const unpairBtn = document.getElementById('modemUnpairBtn');
|
||||
if (unpairBtn) {
|
||||
unpairBtn.classList.toggle('hidden', !modemHasCurrentPairing);
|
||||
}
|
||||
|
||||
if (modemPairDevices.length === 0) {
|
||||
listContainer.innerHTML = '<p class="text-center py-8 text-gray-500">No devices found in roster</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
renderModemPairDeviceList();
|
||||
} catch (error) {
|
||||
console.error('Failed to load pairable devices:', error);
|
||||
listContainer.innerHTML = '<p class="text-center py-8 text-red-500">Failed to load devices</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function filterModemPairDeviceList() {
|
||||
renderModemPairDeviceList();
|
||||
}
|
||||
|
||||
function renderModemPairDeviceList() {
|
||||
const listContainer = document.getElementById('modemPairDeviceList');
|
||||
const searchInput = document.getElementById('modemPairDeviceSearch');
|
||||
const hidePairedCheckbox = document.getElementById('modemPairHidePaired');
|
||||
const showSeismoCheckbox = document.getElementById('modemPairShowSeismo');
|
||||
const showSLMCheckbox = document.getElementById('modemPairShowSLM');
|
||||
|
||||
const searchTerm = searchInput?.value?.toLowerCase() || '';
|
||||
const hidePaired = hidePairedCheckbox?.checked ?? true;
|
||||
const showSeismo = showSeismoCheckbox?.checked ?? true;
|
||||
const showSLM = showSLMCheckbox?.checked ?? true;
|
||||
|
||||
// Filter devices
|
||||
let filteredDevices = modemPairDevices.filter(device => {
|
||||
// Filter by device type
|
||||
if (device.device_type === 'seismograph' && !showSeismo) return false;
|
||||
if (device.device_type === 'sound_level_meter' && !showSLM) return false;
|
||||
|
||||
// Hide devices paired to OTHER modems (but show unpaired and paired-to-this)
|
||||
if (hidePaired && device.is_paired_to_other) return false;
|
||||
|
||||
// Search filter
|
||||
if (searchTerm) {
|
||||
const searchFields = [
|
||||
device.id,
|
||||
device.project_id || '',
|
||||
device.location || '',
|
||||
device.note || ''
|
||||
].join(' ').toLowerCase();
|
||||
if (!searchFields.includes(searchTerm)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filteredDevices.length === 0) {
|
||||
listContainer.innerHTML = '<p class="text-center py-8 text-gray-500">No devices match your criteria</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Build device list HTML
|
||||
let html = '<div class="divide-y divide-gray-200 dark:divide-gray-700">';
|
||||
for (const device of filteredDevices) {
|
||||
const deviceTypeLabel = device.device_type === 'sound_level_meter' ? 'SLM' : 'Seismograph';
|
||||
const deviceTypeClass = device.device_type === 'sound_level_meter'
|
||||
? 'bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300'
|
||||
: 'bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300';
|
||||
|
||||
const deployedBadge = device.deployed
|
||||
? '<span class="px-2 py-0.5 bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 text-xs rounded">Deployed</span>'
|
||||
: '<span class="px-2 py-0.5 bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 text-xs rounded">Benched</span>';
|
||||
|
||||
let pairingBadge = '';
|
||||
if (device.is_paired_to_this) {
|
||||
pairingBadge = '<span class="px-2 py-0.5 bg-seismo-orange/20 text-seismo-orange text-xs rounded font-medium">Current</span>';
|
||||
} else if (device.is_paired_to_other) {
|
||||
pairingBadge = `<span class="px-2 py-0.5 bg-gray-200 dark:bg-gray-600 text-gray-600 dark:text-gray-300 text-xs rounded">Paired: ${device.paired_modem_id}</span>`;
|
||||
}
|
||||
|
||||
const isCurrentlyPaired = device.is_paired_to_this;
|
||||
|
||||
html += `
|
||||
<div class="px-6 py-3 hover:bg-gray-50 dark:hover:bg-slate-700 cursor-pointer transition-colors ${isCurrentlyPaired ? 'bg-seismo-orange/5' : ''}"
|
||||
onclick="selectDeviceForModem('${device.id}')">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-900 dark:text-white">${device.id}</span>
|
||||
<span class="px-2 py-0.5 ${deviceTypeClass} text-xs rounded">${deviceTypeLabel}</span>
|
||||
</div>
|
||||
${device.project_id ? `<div class="text-sm text-gray-500 dark:text-gray-400">${device.project_id}</div>` : ''}
|
||||
${device.location ? `<div class="text-sm text-gray-500 dark:text-gray-400 truncate">${device.location}</div>` : ''}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
${deployedBadge}
|
||||
${pairingBadge}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
listContainer.innerHTML = html;
|
||||
}
|
||||
|
||||
async function selectDeviceForModem(deviceId) {
|
||||
const listContainer = document.getElementById('modemPairDeviceList');
|
||||
|
||||
// Show loading state
|
||||
listContainer.innerHTML = '<div class="text-center py-8"><div class="animate-spin inline-block w-6 h-6 border-2 border-seismo-orange border-t-transparent rounded-full"></div><p class="mt-2 text-sm text-gray-500">Pairing device...</p></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/modem-dashboard/${unitId}/pair?device_id=${encodeURIComponent(deviceId)}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
closeModemPairDeviceModal();
|
||||
// Reload the paired device section
|
||||
loadPairedDevice();
|
||||
// Show success message (optional toast)
|
||||
showToast(`Paired with ${deviceId}`, 'success');
|
||||
} else {
|
||||
listContainer.innerHTML = `<p class="text-center py-8 text-red-500">${result.detail || 'Failed to pair device'}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to pair device:', error);
|
||||
listContainer.innerHTML = '<p class="text-center py-8 text-red-500">Failed to pair device</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function unpairDeviceFromModem() {
|
||||
const listContainer = document.getElementById('modemPairDeviceList');
|
||||
|
||||
// Show loading state
|
||||
listContainer.innerHTML = '<div class="text-center py-8"><div class="animate-spin inline-block w-6 h-6 border-2 border-seismo-orange border-t-transparent rounded-full"></div><p class="mt-2 text-sm text-gray-500">Unpairing device...</p></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/modem-dashboard/${unitId}/unpair`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
closeModemPairDeviceModal();
|
||||
// Reload the paired device section
|
||||
loadPairedDevice();
|
||||
// Show success message
|
||||
if (result.unpaired_device_id) {
|
||||
showToast(`Unpaired ${result.unpaired_device_id}`, 'success');
|
||||
} else {
|
||||
showToast('No device was paired', 'info');
|
||||
}
|
||||
} else {
|
||||
listContainer.innerHTML = `<p class="text-center py-8 text-red-500">${result.detail || 'Failed to unpair device'}</p>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to unpair device:', error);
|
||||
listContainer.innerHTML = '<p class="text-center py-8 text-red-500">Failed to unpair device</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Simple toast function (if not already defined)
|
||||
function showToast(message, type = 'info') {
|
||||
// Check if there's already a toast container
|
||||
let toastContainer = document.getElementById('toast-container');
|
||||
if (!toastContainer) {
|
||||
toastContainer = document.createElement('div');
|
||||
toastContainer.id = 'toast-container';
|
||||
toastContainer.className = 'fixed bottom-4 right-4 z-50 space-y-2';
|
||||
document.body.appendChild(toastContainer);
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
const bgColor = type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-gray-700';
|
||||
toast.className = `${bgColor} text-white px-4 py-2 rounded-lg shadow-lg transform transition-all duration-300 translate-x-0`;
|
||||
toast.textContent = message;
|
||||
|
||||
toastContainer.appendChild(toast);
|
||||
|
||||
// Auto-remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
toast.classList.add('opacity-0', 'translate-x-full');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Pair Device Modal -->
|
||||
@@ -1637,4 +1873,81 @@ function clearPairing(deviceType) {
|
||||
<!-- Include Project Create Modal for inline project creation -->
|
||||
{% include "partials/project_create_modal.html" %}
|
||||
|
||||
<!-- Modem Pair Device Modal (for modems to pick a device) -->
|
||||
<div id="modemPairDeviceModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||
<div class="flex items-center justify-center min-h-screen px-4">
|
||||
<!-- Backdrop -->
|
||||
<div class="fixed inset-0 bg-black/50" onclick="closeModemPairDeviceModal()"></div>
|
||||
|
||||
<!-- Modal Content -->
|
||||
<div class="relative bg-white dark:bg-slate-800 rounded-xl shadow-xl max-w-lg w-full max-h-[90vh] flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-700">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Pair Device to Modem</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Select a seismograph or SLM to pair</p>
|
||||
</div>
|
||||
<button onclick="closeModemPairDeviceModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<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>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700 space-y-3">
|
||||
<input type="text"
|
||||
placeholder="Search by ID, project, location..."
|
||||
class="w-full 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-seismo-orange focus:border-transparent"
|
||||
id="modemPairDeviceSearch"
|
||||
autocomplete="off"
|
||||
oninput="filterModemPairDeviceList()">
|
||||
<div class="flex items-center gap-4">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 cursor-pointer">
|
||||
<input type="checkbox"
|
||||
id="modemPairHidePaired"
|
||||
onchange="filterModemPairDeviceList()"
|
||||
checked
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-seismo-orange focus:ring-seismo-orange">
|
||||
Hide paired devices
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 cursor-pointer">
|
||||
<input type="checkbox"
|
||||
id="modemPairShowSeismo"
|
||||
onchange="filterModemPairDeviceList()"
|
||||
checked
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-seismo-orange focus:ring-seismo-orange">
|
||||
Seismographs
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 cursor-pointer">
|
||||
<input type="checkbox"
|
||||
id="modemPairShowSLM"
|
||||
onchange="filterModemPairDeviceList()"
|
||||
checked
|
||||
class="rounded border-gray-300 dark:border-gray-600 text-seismo-orange focus:ring-seismo-orange">
|
||||
SLMs
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device List -->
|
||||
<div id="modemPairDeviceList" class="flex-1 overflow-y-auto max-h-80">
|
||||
<!-- Populated by JavaScript -->
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="px-6 py-4 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-slate-900 flex gap-3">
|
||||
<button onclick="unpairDeviceFromModem()"
|
||||
id="modemUnpairBtn"
|
||||
class="hidden px-4 py-2 bg-red-100 hover:bg-red-200 dark:bg-red-900/30 dark:hover:bg-red-900/50 text-red-700 dark:text-red-300 rounded-lg transition-colors">
|
||||
Unpair Current
|
||||
</button>
|
||||
<button onclick="closeModemPairDeviceModal()" class="flex-1 px-4 py-2 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 rounded-lg transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user