PIVOT 1.1.0

This commit is contained in:
marijn committed 2026-10-14 13:34:37 +02:00
commit 8a53234eb6
161 files changed
+31109

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
{% extends "base.html" %}
{% block title %}Toegestane IP-adressen{% endblock %}
{% block page_title %}Toegestane IP-adressen{% endblock %}
{% block extra_css %}
<style>
.ip-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; }
.ip-form .field { margin: 0; flex: 0 1 340px; }
.mijn-ip { display: inline-flex; gap: 8px; align-items: center; padding: 6px 12px; border-radius: 999px;
background: var(--surface-muted); border: 1px solid var(--border); font-size: .92em; }
.mijn-ip button { all: unset; cursor: pointer; color: var(--primary-color); font-size: .9em; }
.mono, td.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.tabel-zoek { margin-bottom: 10px; max-width: 360px; }
th.sorteer { cursor: pointer; user-select: none; white-space: nowrap; }
th.sorteer:hover { color: var(--primary-color); }
th.sorteer .pijl::after { content: "↕"; opacity: .35; margin-left: 2px; }
th.sorteer.op .pijl::after { content: "▲"; opacity: 1; }
th.sorteer.af .pijl::after { content: "▼"; opacity: 1; }
.tabel-voet { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; margin-top: 12px; }
.paginering { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="page-head">
<h2>✅ Toegestane IP-adressen · {{ school_naam }}</h2>
<span class="mijn-ip">Jouw IP-adres: <strong class="mono">{{ mijn_ip }}</strong>
{% if not lokaal %}<button type="button" id="gebruikMijnIp" title="Invullen in het veld hieronder">invullen</button>{% endif %}</span>
</div>
<p class="field-hint" style="margin-top:-6px">Een adres in deze lijst mag PIVOT altijd openen. Je voegt het toe voor
{{ school_naam }}, met jouw naam ({{ naam }}) als opmerking. Je kan enkel de adressen verwijderen die je zelf toegevoegd hebt.</p>
<form method="POST" action="{{ url_for('ipblokkade.toevoegen') }}" class="ip-form">
<div class="field">
<label for="reeks">IP-adres</label>
<input type="text" id="reeks" name="reeks" required class="mono" placeholder="bv. 85.12.3.4" autocomplete="off">
</div>
<button type="submit">➕ Toevoegen</button>
</form>
</div>
<div class="card ip-tabel" data-sleutel="school">
<h2 class="card-title">Lijst <span class="muted small">({{ rijen|length }})</span></h2>
{% if rijen|length > 10 %}<input type="search" class="tabel-zoek" placeholder="Zoek een adres of naam..." aria-label="Zoeken">{% endif %}
<div class="table-wrap">
<table>
<thead>
<tr>
<th class="sorteer" data-tekst>IP-adres <span class="pijl"></span></th>
<th class="sorteer" data-tekst>Opmerking <span class="pijl"></span></th>
<th class="sorteer">Toegevoegd <span class="pijl"></span></th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
{% for r in rijen %}
<tr>
<td class="mono" data-sort="{{ r.Sorteer }}"><strong>{{ r.Reeks }}</strong></td>
<td data-sort="{{ r.Opmerking or '' }}">{{ r.Opmerking or '' }}</td>
<td class="muted small" data-sort="{{ r.Aangemaakt.isoformat() }}">{{ r.Aangemaakt.strftime('%d-%m-%Y %H:%M') }}</td>
<td class="actions">
{% if r.Eigen %}
<form method="POST" action="{{ url_for('ipblokkade.verwijderen', regel_id=r.ID) }}" style="margin:0"
onsubmit="return confirm({{ (r.Reeks ~ ' uit de lijst verwijderen?')|tojson|forceescape }})">
<button type="submit" class="btn-sm btn-ghost">🗑️ Verwijderen</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
<tr class="empty-row"><td colspan="4">Er zijn nog geen toegestane adressen voor {{ school_naam }}.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% if rijen %}
<div class="tabel-voet">
<div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap">
<div class="muted small teller"></div>
<span class="per-pagina-plek"></span>
</div>
<nav class="paginering" aria-label="Pagina's"></nav>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
document.getElementById('gebruikMijnIp')?.addEventListener('click', () => {
const v = document.getElementById('reeks'); v.value = {{ mijn_ip|tojson }}; v.focus();
});
document.querySelectorAll('.ip-tabel').forEach(kaart => {
const body = kaart.querySelector('tbody');
if (!body || body.querySelector('.empty-row')) return;
const sleutel = 'ip-' + kaart.dataset.sleutel;
let PER_PAGINA = perPagina(sleutel), pagina = 1, zoek = '';
const zoekveld = kaart.querySelector('.tabel-zoek');
function toon() {
const rijen = Array.from(body.rows).filter(tr => !zoek || tr.textContent.toLowerCase().includes(zoek));
const grootte = PER_PAGINA || Infinity, paginas = Math.max(1, Math.ceil(rijen.length / grootte));
pagina = Math.min(Math.max(1, pagina), paginas);
const van = PER_PAGINA ? (pagina - 1) * PER_PAGINA : 0, tot = van + grootte;
Array.from(body.rows).forEach(tr => tr.style.display = 'none');
rijen.forEach((tr, i) => tr.style.display = (i >= van && i < tot) ? '' : 'none');
kaart.querySelector('.teller').textContent = rijen.length
? `${van + 1}–${Math.min(tot, rijen.length)} van ${rijen.length}` : 'Niets gevonden';
const nav = kaart.querySelector('.paginering');
nav.innerHTML = '';
if (paginas <= 1) return;
const knop = (label, p, actief) => {
const b = document.createElement('button');
b.type = 'button'; b.textContent = label; b.className = 'btn-sm ' + (actief ? 'btn-secondary' : 'btn-light');
b.onclick = () => { pagina = p; toon(); };
nav.appendChild(b);
};
if (pagina > 1) { knop('«', 1); knop('‹ Vorige', pagina - 1); }
for (let p = Math.max(1, pagina - 2); p <= Math.min(paginas, pagina + 2); p++) knop(String(p), p, p === pagina);
if (pagina < paginas) { knop('Volgende ›', pagina + 1); knop('»', paginas); }
}
if (zoekveld) zoekveld.addEventListener('input', () => { zoek = zoekveld.value.trim().toLowerCase(); pagina = 1; toon(); });
let kol = null, op = true;
kaart.querySelectorAll('th.sorteer').forEach(th => th.addEventListener('click', () => {
const k = th.cellIndex;
op = kol === k ? !op : 'tekst' in th.dataset;
kol = k;
kaart.querySelectorAll('th.sorteer').forEach(x => x.classList.remove('op', 'af'));
th.classList.add(op ? 'op' : 'af');
Array.from(body.rows).sort((a, b) => {
const x = a.cells[k].dataset.sort || '', y = b.cells[k].dataset.sort || '';
if (!x !== !y) return x ? -1 : 1;
const c = x.localeCompare(y, 'nl', { numeric: true, sensitivity: 'base' });
return op ? c : -c;
}).forEach(tr => body.appendChild(tr));
pagina = 1; toon();
}));
perPaginaKiezer(kaart.querySelector('.per-pagina-plek'), sleutel, n => { PER_PAGINA = n; pagina = 1; toon(); });
toon();
});
</script>
{% endblock %}