feat: server-side reason/origin/limit filters + client-side row filter
All checks were successful
Deploy / deploy (push) Successful in 17s
All checks were successful
Deploy / deploy (push) Successful in 17s
Two new performance + UX wins: - Server: /decisions now accepts reason (scenarios_containing), origin (origins), and limit. Default 200, max 2000. Header shows current count and warns when at the cap. - Client: a "quick filter rows" input does substring match across all visible columns instantly with no server round-trip. Useful when the server-side result is small enough to scroll but you still want to find one scenario fast. - Bulk select-all now toggles only currently visible rows so you can filter then bulk-unban. Drops inline onclick handlers in favor of delegated change listeners to keep per-row cost low at large limits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2,23 +2,27 @@
|
||||
<p class="err">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% if decisions %}
|
||||
<p style="font-size:.8rem;color:#888;margin:.4rem 0;">
|
||||
Showing <strong>{{ total }}</strong> decision{{ '' if total == 1 else 's' }}
|
||||
(server limit {{ limit }}{% if total == limit %} — at cap, raise limit if more expected{% endif %}).
|
||||
</p>
|
||||
<form hx-post="/unban-bulk" hx-target="#decisions" hx-swap="innerHTML"
|
||||
hx-confirm="Unban all selected decisions?" id="bulk-form">
|
||||
<div class="row" style="margin: .5rem 0;">
|
||||
<button class="danger" type="submit">Unban selected</button>
|
||||
<span class="pill"><span id="sel-count">0</span> selected of {{ decisions|length }}</span>
|
||||
<span class="pill"><span id="sel-count">0</span> selected</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="sel-all" onclick="document.querySelectorAll('#bulk-form .sel').forEach(c=>{c.checked=this.checked});updateCount()"></th>
|
||||
<th><input type="checkbox" id="sel-all"></th>
|
||||
<th>ID</th><th>IP / value</th><th>Scope</th><th>Type</th><th>Reason</th><th>Until</th><th>Origin</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in decisions %}
|
||||
<tr>
|
||||
<td><input type="checkbox" class="sel" name="id" value="{{ d.id }}" onclick="updateCount()"></td>
|
||||
<td><input type="checkbox" class="sel" name="id" value="{{ d.id }}"></td>
|
||||
<td><code>{{ d.id }}</code></td>
|
||||
<td><code>{{ d.value }}</code></td>
|
||||
<td>{{ d.scope }}</td>
|
||||
@@ -38,14 +42,25 @@
|
||||
</table>
|
||||
</form>
|
||||
<script>
|
||||
function updateCount(){
|
||||
const n = document.querySelectorAll('#bulk-form .sel:checked').length;
|
||||
const total = document.querySelectorAll('#bulk-form .sel').length;
|
||||
document.getElementById('sel-count').textContent = n;
|
||||
(function(){
|
||||
const form = document.getElementById('bulk-form');
|
||||
const all = document.getElementById('sel-all');
|
||||
if (all) all.checked = (n === total && total > 0);
|
||||
}
|
||||
updateCount();
|
||||
const cnt = document.getElementById('sel-count');
|
||||
function refresh(){
|
||||
const sel = form.querySelectorAll('.sel');
|
||||
const ck = form.querySelectorAll('.sel:checked');
|
||||
cnt.textContent = ck.length;
|
||||
if (all) all.checked = (sel.length > 0 && ck.length === sel.length);
|
||||
}
|
||||
if (all) all.addEventListener('change', e => {
|
||||
const checked = e.target.checked;
|
||||
// Only toggle visible rows so a filtered subset can be bulk-selected
|
||||
form.querySelectorAll('tbody tr:not(.hidden-row) .sel').forEach(c => c.checked = checked);
|
||||
refresh();
|
||||
});
|
||||
form.addEventListener('change', e => { if (e.target.classList.contains('sel')) refresh(); });
|
||||
refresh();
|
||||
})();
|
||||
</script>
|
||||
{% else %}
|
||||
{% if not error %}<p>No active decisions.</p>{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user