- Rename app title to SUB-ALIGATOR (header, page titles, exports, README) - Add dark amber-terminal theme with a header toggle, persisted in localStorage - Add pool deletion (soft delete): allocations are kept in history and block the address space again when the same or an overlapping pool is re-added (re-enable on exact CIDR match, re-attach contained allocations) - Reject new pools that overlap an enabled pool - Move Home link into the page header, keep other links in the footer - Load app.js globally via base template, add theme.js (CSP-safe, no inline JS)
125 lines
4.2 KiB
JavaScript
125 lines
4.2 KiB
JavaScript
function initApp() {
|
||
console.log('sub-aligator app.js initialized');
|
||
|
||
// Ensure every modal starts closed, even if the browser restored a cached state.
|
||
document.querySelectorAll('.modal-overlay').forEach(function (modal) {
|
||
modal.style.display = 'none';
|
||
});
|
||
|
||
// Open allocation modal
|
||
document.querySelectorAll('.allocate-btn').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
console.log('allocate-btn clicked', btn.dataset);
|
||
openAllocateModal(btn);
|
||
});
|
||
});
|
||
|
||
// Open release modal from history table
|
||
document.querySelectorAll('.release-btn').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
const id = btn.dataset.id;
|
||
const modal = document.getElementById('release-modal-' + id);
|
||
if (modal) {
|
||
modal.style.display = 'flex';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Open pool delete modal from the pools page
|
||
document.querySelectorAll('.pool-delete-btn').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
const id = btn.dataset.id;
|
||
const modal = document.getElementById('delete-pool-modal-' + id);
|
||
if (modal) {
|
||
modal.style.display = 'flex';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Close modal via Cancel button
|
||
document.querySelectorAll('.cancel-btn').forEach(function (btn) {
|
||
btn.addEventListener('click', function () {
|
||
const modalId = btn.dataset.modal;
|
||
const modal = document.getElementById(modalId);
|
||
if (modal) {
|
||
modal.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Close modal by clicking on the overlay background
|
||
document.querySelectorAll('.modal-overlay').forEach(function (overlay) {
|
||
overlay.addEventListener('click', function (event) {
|
||
if (event.target === overlay) {
|
||
overlay.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Prevent clicks inside the modal from closing it
|
||
document.querySelectorAll('.modal').forEach(function (modal) {
|
||
modal.addEventListener('click', function (event) {
|
||
event.stopPropagation();
|
||
});
|
||
});
|
||
|
||
// Hide the modal right before its form is submitted.
|
||
document.querySelectorAll('.modal form').forEach(function (form) {
|
||
form.addEventListener('submit', function () {
|
||
const overlay = form.closest('.modal-overlay');
|
||
if (overlay) {
|
||
overlay.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
async function openAllocateModal(btn) {
|
||
const slug = btn.dataset.slug;
|
||
const name = btn.dataset.name;
|
||
const select = document.getElementById('prefix-' + slug);
|
||
let prefixLength = '';
|
||
|
||
if (select && select.value) {
|
||
prefixLength = select.value;
|
||
} else if (select && select.options.length > 0) {
|
||
prefixLength = select.options[0].value;
|
||
} else {
|
||
prefixLength = '24';
|
||
}
|
||
|
||
console.log('openAllocateModal', { slug: slug, name: name, prefixLength: prefixLength });
|
||
|
||
document.getElementById('modal-pool-slug').value = slug;
|
||
document.getElementById('modal-prefix-length').value = prefixLength;
|
||
document.getElementById('modal-pool-name').textContent = name;
|
||
document.getElementById('modal-prefix-display').textContent = '/' + prefixLength;
|
||
|
||
try {
|
||
const resp = await fetch('/partials/pools/' + slug + '/preview?prefix_length=' + prefixLength);
|
||
const html = await resp.text();
|
||
const parser = new DOMParser();
|
||
const doc = parser.parseFromString(html, 'text/html');
|
||
|
||
const cidr = doc.getElementById('preview-cidr')?.textContent || '–';
|
||
const addressCount = doc.getElementById('preview-address-count')?.textContent || '–';
|
||
const usableHosts = doc.getElementById('preview-usable-hosts')?.textContent || '–';
|
||
const warning = doc.getElementById('preview-warning')?.textContent || '';
|
||
|
||
document.getElementById('modal-cidr').textContent = cidr;
|
||
document.getElementById('modal-address-count').textContent = addressCount;
|
||
document.getElementById('modal-usable-hosts').textContent = usableHosts;
|
||
document.getElementById('modal-warning').textContent = warning;
|
||
} catch (e) {
|
||
console.error('preview fetch failed', e);
|
||
document.getElementById('modal-warning').textContent = 'Could not load preview.';
|
||
}
|
||
|
||
document.getElementById('allocate-modal').style.display = 'flex';
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initApp);
|
||
} else {
|
||
initApp();
|
||
}
|