180 lines
6.6 KiB
HTML
180 lines
6.6 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block content %}
|
||
<div id="flash-container" hx-swap-oob="true">
|
||
{% include "fragments/flash.html" %}
|
||
</div>
|
||
|
||
{% if pools.len() == 0 %}
|
||
<section class="box">
|
||
<div class="box-title">No pools configured</div>
|
||
<p>Create at least one pool before allocating subnets.</p>
|
||
<a href="/setup" class="btn">Create first pool</a>
|
||
<a href="/pools" class="btn btn-secondary">Manage pools</a>
|
||
</section>
|
||
{% else %}
|
||
<section class="grid">
|
||
{% for pool in pools %}
|
||
<article class="box" id="pool-card-{{ pool.slug }}">
|
||
<div class="box-title">{{ pool.name }}</div>
|
||
<p><span class="label">Parent prefix:</span> <span class="value">{{ pool.cidr }}</span></p>
|
||
<p><span class="label">Type:</span> <span class="value">{{ pool.kind }}</span></p>
|
||
|
||
<form
|
||
hx-get="/partials/pools/{{ pool.slug }}/preview"
|
||
hx-target="#preview-{{ pool.slug }}"
|
||
hx-trigger="change from:select, load"
|
||
hx-indicator="#indicator-{{ pool.slug }}"
|
||
>
|
||
<label for="prefix-{{ pool.slug }}">Subnet size</label>
|
||
<select name="prefix_length" id="prefix-{{ pool.slug }}" onchange="updateModalPrefix('{{ pool.slug }}', this.value)">
|
||
{% for pl in pool.allowed_prefixes %}
|
||
<option value="{{ pl }}" {% if pl == "24" %}selected{% endif %}>/{{ pl }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<div id="indicator-{{ pool.slug }}" class="small" style="display:none;">Loading...</div>
|
||
<div id="preview-{{ pool.slug }}"></div>
|
||
</form>
|
||
|
||
<button
|
||
class="btn"
|
||
style="margin-top: 1rem;"
|
||
onclick="openAllocateModal('{{ pool.slug }}')"
|
||
>
|
||
[ Allocate next free subnet ]
|
||
</button>
|
||
</article>
|
||
{% endfor %}
|
||
</section>
|
||
{% endif %}
|
||
|
||
<section class="box">
|
||
<div class="box-title">Allocated subnets</div>
|
||
|
||
<form method="get" action="/" class="filters">
|
||
<div>
|
||
<label for="q">Search</label>
|
||
<input type="text" id="q" name="q" value="{{ query }}" placeholder="CIDR or note">
|
||
</div>
|
||
<div>
|
||
<label for="pool">Pool</label>
|
||
<select id="pool" name="pool">
|
||
<option value="" {% if pool_filter == "" %}selected{% endif %}>All</option>
|
||
{% for p in all_pools %}
|
||
<option value="{{ p.slug }}" {% if pool_filter == p.slug %}selected{% endif %}>{{ p.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label for="status">Status</label>
|
||
<select id="status" name="status">
|
||
<option value="" {% if status_filter == "" %}selected{% endif %}>All</option>
|
||
<option value="active" {% if status_filter == "active" %}selected{% endif %}>active</option>
|
||
<option value="reserved" {% if status_filter == "reserved" %}selected{% endif %}>reserved</option>
|
||
<option value="released" {% if status_filter == "released" %}selected{% endif %}>released</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<button type="submit" class="btn">Filter</button>
|
||
<a href="/" class="btn btn-secondary">Reset</a>
|
||
</div>
|
||
</form>
|
||
|
||
<div id="allocations-table">
|
||
{% include "fragments/allocations_table.html" %}
|
||
</div>
|
||
</section>
|
||
|
||
<dialog id="allocate-modal" class="modal-overlay" style="display:none;">
|
||
<div class="modal">
|
||
<h2 class="modal-title">Confirm subnet allocation</h2>
|
||
<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="prefix_length" id="modal-prefix-length">
|
||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||
|
||
<div class="detail-grid">
|
||
<div class="detail-item">
|
||
<div class="label">Pool</div>
|
||
<div class="value" id="modal-pool-name"></div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="label">Prefix</div>
|
||
<div class="value" id="modal-prefix-display"></div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="label">Proposed subnet</div>
|
||
<div class="value" id="modal-cidr"></div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="label">Total addresses</div>
|
||
<div class="value" id="modal-address-count"></div>
|
||
</div>
|
||
<div class="detail-item">
|
||
<div class="label">Usable hosts</div>
|
||
<div class="value" id="modal-usable-hosts"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="small" id="modal-warning"></p>
|
||
|
||
<label for="note">Note *</label>
|
||
<textarea id="note" name="note" rows="3" required minlength="3" maxlength="500"></textarea>
|
||
|
||
<label for="ticket">Ticket / reference</label>
|
||
<input type="text" id="ticket" name="ticket" maxlength="100">
|
||
</form>
|
||
<div class="modal-actions">
|
||
<button type="button" class="btn btn-secondary" onclick="closeAllocateModal()">Cancel</button>
|
||
<button type="submit" class="btn" form="allocate-form">Confirm allocation</button>
|
||
</div>
|
||
</div>
|
||
</dialog>
|
||
|
||
<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;
|
||
|
||
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;
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
function closeAllocateModal() {
|
||
document.getElementById('allocate-modal').style.display = 'none';
|
||
}
|
||
</script>
|
||
{% endblock %}
|