feat: server-side reason/origin/limit filters + client-side row filter
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:
2026-06-17 00:08:27 +02:00
parent a324d5a68d
commit ff8ed3428f
3 changed files with 91 additions and 21 deletions

View File

@@ -87,19 +87,35 @@ def index():
return render_template("index.html", my_ip=caller_ip())
DEFAULT_LIMIT = 200
MAX_LIMIT = 2000
@app.get("/decisions")
def list_decisions():
q = request.args.get("ip", "").strip()
params = {}
if q:
if not valid_ip(q):
return render_template("_decisions.html", error="invalid IP", decisions=[]), 400
params["ip"] = q
ip = request.args.get("ip", "").strip()
reason = request.args.get("reason", "").strip()
origin = request.args.get("origin", "").strip()
try:
limit = max(1, min(MAX_LIMIT, int(request.args.get("limit", DEFAULT_LIMIT))))
except ValueError:
limit = DEFAULT_LIMIT
params = {"limit": limit}
if ip:
if not valid_ip(ip):
return render_template("_decisions.html", error="invalid IP", decisions=[], total=0, limit=limit), 400
params["ip"] = ip
if reason:
params["scenarios_containing"] = reason
if origin:
params["origins"] = origin
r = _bouncer("GET", "/v1/decisions", params=params)
if r.status_code != 200:
return render_template("_decisions.html", error=f"LAPI {r.status_code}: {r.text[:200]}", decisions=[]), 502
return render_template("_decisions.html", error=f"LAPI {r.status_code}: {r.text[:200]}", decisions=[], total=0, limit=limit), 502
decisions = r.json() or []
return render_template("_decisions.html", decisions=decisions, error=None)
return render_template("_decisions.html", decisions=decisions, error=None, total=len(decisions), limit=limit)
@app.post("/unban")