Adds per-row checkboxes, a select-all toggle, a live "N selected" counter, and an /unban-bulk endpoint that DELETEs each chosen decision id. Single-row Unban buttons still work via hx-vals so they don't accidentally submit the bulk form. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
19
app/app.py
19
app/app.py
@@ -122,6 +122,25 @@ def unban():
|
|||||||
return list_decisions()
|
return list_decisions()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/unban-bulk")
|
||||||
|
def unban_bulk():
|
||||||
|
ids = [i for i in request.form.getlist("id") if i.isdigit()]
|
||||||
|
if not ids:
|
||||||
|
return list_decisions()
|
||||||
|
deleted = 0
|
||||||
|
errors = []
|
||||||
|
for did in ids:
|
||||||
|
r = _machine("DELETE", f"/v1/decisions/{did}")
|
||||||
|
if r.status_code in (200, 204):
|
||||||
|
deleted += 1
|
||||||
|
else:
|
||||||
|
errors.append(f"{did}:{r.status_code}")
|
||||||
|
log.info("unban-bulk by=%s count=%d errors=%s", caller_ip(), deleted, errors)
|
||||||
|
if errors:
|
||||||
|
return f"deleted {deleted}/{len(ids)}; errors: {', '.join(errors[:5])}", 502
|
||||||
|
return list_decisions()
|
||||||
|
|
||||||
|
|
||||||
@app.post("/unban-me")
|
@app.post("/unban-me")
|
||||||
def unban_me():
|
def unban_me():
|
||||||
ip = caller_ip()
|
ip = caller_ip()
|
||||||
|
|||||||
@@ -2,30 +2,51 @@
|
|||||||
<p class="err">{{ error }}</p>
|
<p class="err">{{ error }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if decisions %}
|
{% if decisions %}
|
||||||
<table>
|
<form hx-post="/unban-bulk" hx-target="#decisions" hx-swap="innerHTML"
|
||||||
<thead>
|
hx-confirm="Unban all selected decisions?" id="bulk-form">
|
||||||
<tr><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>
|
<div class="row" style="margin: .5rem 0;">
|
||||||
</thead>
|
<button class="danger" type="submit">Unban selected</button>
|
||||||
<tbody>
|
<span class="pill"><span id="sel-count">0</span> selected of {{ decisions|length }}</span>
|
||||||
{% for d in decisions %}
|
</div>
|
||||||
<tr>
|
<table>
|
||||||
<td><code>{{ d.id }}</code></td>
|
<thead>
|
||||||
<td><code>{{ d.value }}</code></td>
|
<tr>
|
||||||
<td>{{ d.scope }}</td>
|
<th><input type="checkbox" id="sel-all" onclick="document.querySelectorAll('#bulk-form .sel').forEach(c=>{c.checked=this.checked});updateCount()"></th>
|
||||||
<td>{{ d.type }}</td>
|
<th>ID</th><th>IP / value</th><th>Scope</th><th>Type</th><th>Reason</th><th>Until</th><th>Origin</th><th></th>
|
||||||
<td>{{ d.scenario }}</td>
|
</tr>
|
||||||
<td>{{ d.until }}</td>
|
</thead>
|
||||||
<td>{{ d.origin }}</td>
|
<tbody>
|
||||||
<td>
|
{% for d in decisions %}
|
||||||
<form hx-post="/unban" hx-target="#decisions" hx-swap="innerHTML" hx-confirm="Delete decision {{ d.id }} for {{ d.value }}?">
|
<tr>
|
||||||
<input type="hidden" name="id" value="{{ d.id }}">
|
<td><input type="checkbox" class="sel" name="id" value="{{ d.id }}" onclick="updateCount()"></td>
|
||||||
<button class="danger" type="submit">Unban</button>
|
<td><code>{{ d.id }}</code></td>
|
||||||
</form>
|
<td><code>{{ d.value }}</code></td>
|
||||||
</td>
|
<td>{{ d.scope }}</td>
|
||||||
</tr>
|
<td>{{ d.type }}</td>
|
||||||
{% endfor %}
|
<td>{{ d.scenario }}</td>
|
||||||
</tbody>
|
<td>{{ d.until }}</td>
|
||||||
</table>
|
<td>{{ d.origin }}</td>
|
||||||
|
<td>
|
||||||
|
<button class="danger" type="button"
|
||||||
|
hx-post="/unban" hx-target="#decisions" hx-swap="innerHTML"
|
||||||
|
hx-vals='{"id": "{{ d.id }}"}'
|
||||||
|
hx-confirm="Delete decision {{ d.id }} for {{ d.value }}?">Unban</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</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;
|
||||||
|
const all = document.getElementById('sel-all');
|
||||||
|
if (all) all.checked = (n === total && total > 0);
|
||||||
|
}
|
||||||
|
updateCount();
|
||||||
|
</script>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not error %}<p>No active decisions.</p>{% endif %}
|
{% if not error %}<p>No active decisions.</p>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user