fix: move inline JavaScript to external file to comply with CSP
This commit is contained in:
parent
406ff2f9c2
commit
a0ca704ac1
4 changed files with 97 additions and 64 deletions
79
static/app.js
Normal file
79
static/app.js
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Open allocation modal
|
||||||
|
document.querySelectorAll('.allocate-btn').forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function openAllocateModal(btn) {
|
||||||
|
const slug = btn.dataset.slug;
|
||||||
|
const name = btn.dataset.name;
|
||||||
|
const select = document.getElementById('prefix-' + slug);
|
||||||
|
const prefixLength = select ? select.value : '';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
document.getElementById('modal-warning').textContent = 'Could not load preview.';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('allocate-modal').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if allocation.status == "active" || allocation.status == "reserved" %}
|
{% if allocation.status == "active" || allocation.status == "reserved" %}
|
||||||
<button class="btn btn-danger" onclick="document.getElementById('release-modal').style.display='flex'">Release subnet</button>
|
<button class="btn btn-danger release-btn" data-id="{{ allocation.id }}">Release subnet</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<a href="/" class="btn btn-secondary">Back</a>
|
<a href="/" class="btn btn-secondary">Back</a>
|
||||||
|
|
@ -108,8 +108,8 @@
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="release-modal" class="modal-overlay" style="display:none;" onclick="if (event.target === this) this.style.display='none';">
|
<div id="release-modal-{{ allocation.id }}" class="modal-overlay" style="display:none;">
|
||||||
<div class="modal" onclick="event.stopPropagation();">
|
<div class="modal">
|
||||||
<h2 class="modal-title">Release subnet {{ allocation.cidr }}</h2>
|
<h2 class="modal-title">Release subnet {{ allocation.cidr }}</h2>
|
||||||
<form method="post" action="/allocations/{{ allocation.id }}/release" class="modal-body" id="release-form">
|
<form method="post" action="/allocations/{{ allocation.id }}/release" class="modal-body" id="release-form">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
|
|
@ -117,10 +117,12 @@
|
||||||
<label for="release-note">Release note *</label>
|
<label for="release-note">Release note *</label>
|
||||||
<textarea id="release-note" name="release_note" rows="3" required minlength="3" maxlength="500"></textarea>
|
<textarea id="release-note" name="release_note" rows="3" required minlength="3" maxlength="500"></textarea>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('release-modal').style.display='none'">Cancel</button>
|
<button type="button" class="btn btn-secondary cancel-btn" data-modal="release-modal-{{ allocation.id }}">Cancel</button>
|
||||||
<button type="submit" class="btn btn-danger">Release</button>
|
<button type="submit" class="btn btn-danger">Release</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/app.js"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<td data-label="Actions">
|
<td data-label="Actions">
|
||||||
<a href="/allocations/{{ a.id }}">Detail</a>
|
<a href="/allocations/{{ a.id }}">Detail</a>
|
||||||
{% if a.status == "active" || a.status == "reserved" %}
|
{% if a.status == "active" || a.status == "reserved" %}
|
||||||
| <button type="button" class="btn-link" onclick="document.getElementById('release-modal-{{ a.id }}').style.display='flex'">Release</button>
|
| <button type="button" class="btn-link release-btn" data-id="{{ a.id }}">Release</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -38,8 +38,8 @@
|
||||||
|
|
||||||
{% for a in allocations %}
|
{% for a in allocations %}
|
||||||
{% if a.status == "active" || a.status == "reserved" %}
|
{% if a.status == "active" || a.status == "reserved" %}
|
||||||
<div id="release-modal-{{ a.id }}" class="modal-overlay" style="display:none;" onclick="if (event.target === this) this.style.display='none';">
|
<div id="release-modal-{{ a.id }}" class="modal-overlay" style="display:none;">
|
||||||
<div class="modal" onclick="event.stopPropagation();">
|
<div class="modal">
|
||||||
<h2 class="modal-title">Release subnet {{ a.cidr }}</h2>
|
<h2 class="modal-title">Release subnet {{ a.cidr }}</h2>
|
||||||
<form method="post" action="/allocations/{{ a.id }}/release" class="modal-body" id="release-form-{{ a.id }}">
|
<form method="post" action="/allocations/{{ a.id }}/release" class="modal-body" id="release-form-{{ a.id }}">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
<label for="release-note-{{ a.id }}">Release note *</label>
|
<label for="release-note-{{ a.id }}">Release note *</label>
|
||||||
<textarea id="release-note-{{ a.id }}" name="release_note" rows="3" required minlength="3" maxlength="500"></textarea>
|
<textarea id="release-note-{{ a.id }}" name="release_note" rows="3" required minlength="3" maxlength="500"></textarea>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('release-modal-{{ a.id }}').style.display='none'">Cancel</button>
|
<button type="button" class="btn btn-secondary cancel-btn" data-modal="release-modal-{{ a.id }}">Cancel</button>
|
||||||
<button type="submit" class="btn btn-danger">Release</button>
|
<button type="submit" class="btn btn-danger">Release</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
hx-indicator="#indicator-{{ pool.slug }}"
|
hx-indicator="#indicator-{{ pool.slug }}"
|
||||||
>
|
>
|
||||||
<label for="prefix-{{ pool.slug }}">Subnet size</label>
|
<label for="prefix-{{ pool.slug }}">Subnet size</label>
|
||||||
<select name="prefix_length" id="prefix-{{ pool.slug }}" onchange="updateModalPrefix('{{ pool.slug }}', this.value)">
|
<select name="prefix_length" id="prefix-{{ pool.slug }}">
|
||||||
{% for pl in pool.allowed_prefixes %}
|
{% for pl in pool.allowed_prefixes %}
|
||||||
<option value="{{ pl }}" {% if pl == "24" %}selected{% endif %}>/{{ pl }}</option>
|
<option value="{{ pl }}" {% if pl == "24" %}selected{% endif %}>/{{ pl }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -37,9 +37,10 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn"
|
class="btn allocate-btn"
|
||||||
style="margin-top: 1rem;"
|
style="margin-top: 1rem;"
|
||||||
onclick="openAllocateModal('{{ pool.slug }}')"
|
data-slug="{{ pool.slug }}"
|
||||||
|
data-name="{{ pool.name }}"
|
||||||
>
|
>
|
||||||
[ Allocate next free subnet ]
|
[ Allocate next free subnet ]
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -85,8 +86,8 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div id="allocate-modal" class="modal-overlay" style="display:none;" onclick="if (event.target === this) closeAllocateModal();">
|
<div id="allocate-modal" class="modal-overlay" style="display:none;">
|
||||||
<div class="modal" onclick="event.stopPropagation();">
|
<div class="modal">
|
||||||
<h2 class="modal-title">Confirm subnet allocation</h2>
|
<h2 class="modal-title">Confirm subnet allocation</h2>
|
||||||
<form method="post" action="/allocations" class="modal-body" id="allocate-form">
|
<form method="post" action="/allocations" class="modal-body" id="allocate-form">
|
||||||
<input type="hidden" name="pool_slug" id="modal-pool-slug">
|
<input type="hidden" name="pool_slug" id="modal-pool-slug">
|
||||||
|
|
@ -125,61 +126,12 @@
|
||||||
<input type="text" id="ticket" name="ticket" maxlength="100">
|
<input type="text" id="ticket" name="ticket" maxlength="100">
|
||||||
|
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button type="button" class="btn btn-secondary" onclick="closeAllocateModal()">Cancel</button>
|
<button type="button" class="btn btn-secondary cancel-btn" data-modal="allocate-modal">Cancel</button>
|
||||||
<button type="submit" class="btn">Confirm allocation</button>
|
<button type="submit" class="btn">Confirm allocation</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script src="/static/app.js"></script>
|
||||||
const poolData = {
|
|
||||||
{% for pool in pools %}
|
|
||||||
"{{ pool.slug }}": {
|
|
||||||
name: "{{ pool.name }}",
|
|
||||||
allowed_prefixes: {{ pool.allowed_prefixes_json|safe }}
|
|
||||||
},
|
|
||||||
{% endfor %}
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateModalPrefix(slug, prefixLength) {
|
|
||||||
// preview is updated via HTMX automatically
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openAllocateModal(slug) {
|
|
||||||
const select = document.getElementById('prefix-' + slug);
|
|
||||||
const prefixLength = select.value;
|
|
||||||
const modal = document.getElementById('allocate-modal');
|
|
||||||
|
|
||||||
document.getElementById('modal-pool-slug').value = slug;
|
|
||||||
document.getElementById('modal-prefix-length').value = prefixLength;
|
|
||||||
document.getElementById('modal-pool-name').textContent = poolData[slug].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) {
|
|
||||||
document.getElementById('modal-warning').textContent = 'Could not load preview.';
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeAllocateModal() {
|
|
||||||
document.getElementById('allocate-modal').style.display = 'none';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue