/**
* 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) {
var editBtn = cell.querySelector('.edit-btn');
var pinBtn = cell.querySelector('.pin-needs-name');
if (editBtn) editBtn.style.display = 'none';
if (pinBtn) pinBtn.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 input = cell.querySelector('.edit-hostname');
if (!input) return;
var hostname = input.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();