sub-aligator/static/app.js
SW 8c69c1731d feat: JSON REST API /api/v1 with DB-managed Bearer tokens
- New endpoints: GET /api/v1/pools, GET /api/v1/pools/{slug}/next,
  GET/POST /api/v1/allocations, GET /api/v1/allocations/{id},
  POST /api/v1/allocations/{id}/release
- Bearer token auth; only SHA-256 hashes stored, plaintext shown once
- Token management GUI at /tokens (create, revoke, delete) linked in footer
- API actions audited with actor api:<token name>
- Fix latent transaction leak: replace manual BEGIN IMMEDIATE/COMMIT with
  begin_with(), which rolls back on drop; previously a failed allocation
  (e.g. disallowed prefix or full pool) returned a connection with an open
  transaction to the pool, causing 'database is locked' on later writes
- README: API documentation with curl examples
2026-10-09 14:25:17 +02:00

135 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
}
});
});
// Open any modal via a generic data-modal-open="<id>" trigger
document.querySelectorAll('[data-modal-open]').forEach(function (btn) {
btn.addEventListener('click', function () {
const modal = document.getElementById(btn.dataset.modalOpen);
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();
}