/** * DHCP Lease Manager — client-side logic. * Uses Zoraxy's $.cjax() for CSRF-safe AJAX (jQuery available from parent frame). */ // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function showToast(message, type) { var toast = document.getElementById('toast'); toast.textContent = message; toast.className = 'toast ' + (type || ''); void toast.offsetWidth; toast.classList.remove('hidden'); setTimeout(function () { toast.classList.add('hidden'); }, 3000); } // --------------------------------------------------------------------------- // Render // --------------------------------------------------------------------------- function renderLeases(data) { var tbody = document.getElementById('leases-body'); var leases = data.leases || []; document.getElementById('active-count').textContent = data.active_count; document.getElementById('pinned-count').textContent = data.pinned_count; if (leases.length === 0) { tbody.innerHTML = 'No active leases'; return; } var rows = leases.map(function (lease) { var isPermanent = lease.status === 'permanent'; var hasHostname = lease.hostname && lease.hostname.trim() !== ''; return ( '' + '' + esc(lease.hostname || '--') + '' + '' + esc(lease.ip) + '' + '' + esc(lease.mac) + '' + '' + '' + esc(lease.status) + '' + '' + '' + (isPermanent ? '' + '' + '' : (hasHostname ? '' : '' + '' ) ) + '' + '' ); }).join(''); tbody.innerHTML = rows; } function esc(s) { if (!s) return ''; return s.replace(/&/g, '&').replace(//g, '>'); } function escAttr(s) { if (!s) return ''; return s.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, '''); } // --------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------- function loadLeases() { $.get('./api/leases', function (data) { renderLeases(data); }).fail(function () { showToast('Failed to load leases', 'error'); }); } function pinLease(mac, ip, hostname) { $.cjax({ url: './api/pin', type: 'POST', dataType: 'json', data: { mac: mac, ip: ip, hostname: hostname || '' }, success: function () { showToast('Pinned ' + (hostname || mac), 'success'); loadLeases(); }, error: function (xhr) { var msg = 'Pin failed'; try { var r = JSON.parse(xhr.responseText); msg = r.message || r.error || msg; } catch(e) {} showToast(msg, 'error'); } }); } function unpinLease(mac) { $.cjax({ url: './api/unpin', type: 'POST', dataType: 'json', data: { mac: mac }, success: function () { showToast('Unpinned', 'success'); loadLeases(); }, error: function (xhr) { var msg = 'Unpin failed'; try { var r = JSON.parse(xhr.responseText); msg = r.message || r.error || msg; } catch(e) {} showToast(msg, 'error'); } }); } function updateHostname(mac, ip, hostname) { // Re-pin with new hostname (server handles unpin + repin atomically) $.cjax({ url: './api/pin', type: 'POST', dataType: 'json', data: { mac: mac, ip: ip, hostname: hostname }, success: function () { showToast('Hostname updated', 'success'); loadLeases(); }, error: function (xhr) { var msg = 'Update failed'; try { var r = JSON.parse(xhr.responseText); msg = r.message || r.error || msg; } catch(e) {} showToast(msg, 'error'); } }); } function reloadDnsmasq() { var btn = document.getElementById('reload-btn'); btn.disabled = true; btn.textContent = 'Reloading...'; $.cjax({ url: './api/reload', type: 'POST', dataType: 'json', data: {}, success: function (data) { if (data.success) { showToast('dnsmasq reloaded', 'success'); } else { showToast(data.message || 'Reload failed', 'error'); } loadLeases(); }, error: function (xhr) { var msg = 'Reload failed'; try { var r = JSON.parse(xhr.responseText); msg = r.message || r.error || msg; } catch(e) {} showToast(msg, 'error'); }, complete: function () { btn.disabled = false; btn.textContent = 'Reload dnsmasq'; } }); } // --------------------------------------------------------------------------- // Form helpers // --------------------------------------------------------------------------- function showEditForm(cell) { cell.querySelector('.edit-btn').style.display = 'none'; cell.querySelector('.pin-needs-name').style.display = 'none'; cell.querySelector('.edit-form').style.display = 'inline-flex'; var input = cell.querySelector('.edit-hostname'); input.focus(); input.select(); } function hideEditForm(cell) { cell.querySelector('.edit-form').style.display = 'none'; var editBtn = cell.querySelector('.edit-btn'); var pinBtn = cell.querySelector('.pin-needs-name'); if (editBtn) editBtn.style.display = 'inline-block'; if (pinBtn) pinBtn.style.display = 'inline-block'; } function confirmEditForm(cell) { var mac = cell.dataset.mac; var ip = cell.dataset.ip; var hostname = cell.querySelector('.edit-hostname').value.trim(); hideEditForm(cell); updateHostname(mac, ip, hostname); } // --------------------------------------------------------------------------- // Event delegation // --------------------------------------------------------------------------- document.getElementById('leases-body').addEventListener('click', function (e) { var cell = e.target.closest('.actions-cell'); if (!cell) return; var mac = cell.dataset.mac; var ip = cell.dataset.ip; var hostname = cell.dataset.hostname; // Pin (one-click — hostname already exists) if (e.target.closest('[data-action="pin"]')) { pinLease(mac, ip, hostname); } // Pin (needs name — show form) if (e.target.closest('[data-action="pin-name"]')) { showEditForm(cell); } // Edit (pinned lease — show form to change hostname) if (e.target.closest('[data-action="edit"]')) { showEditForm(cell); } // Unpin if (e.target.closest('[data-action="unpin"]')) { unpinLease(mac); } // Confirm edit (OK button) if (e.target.closest('.edit-confirm')) { confirmEditForm(cell); } // Cancel edit (✕ button) if (e.target.closest('.edit-cancel')) { hideEditForm(cell); } }); document.getElementById('reload-btn').addEventListener('click', reloadDnsmasq); // --------------------------------------------------------------------------- // Init // --------------------------------------------------------------------------- loadLeases();