feat: initial scaffold of CrowdSec admin webapp
All checks were successful
Deploy / deploy (push) Successful in 39s
All checks were successful
Deploy / deploy (push) Successful in 39s
Flask + htmx mini-app to list and delete CrowdSec decisions from a browser, gated behind Authentik. Talks to host LAPI via host.docker.internal:8080 using machine JWT auth (bouncer X-Api-Key is read-only). Gitea Actions CI/CD on push to main: runner rebuilds image and brings the stack up via docker compose on the host (same pattern as flight-radar). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
31
app/templates/_decisions.html
Normal file
31
app/templates/_decisions.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% if error %}
|
||||
<p class="err">{{ error }}</p>
|
||||
{% endif %}
|
||||
{% if decisions %}
|
||||
<table>
|
||||
<thead>
|
||||
<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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in decisions %}
|
||||
<tr>
|
||||
<td><code>{{ d.id }}</code></td>
|
||||
<td><code>{{ d.value }}</code></td>
|
||||
<td>{{ d.scope }}</td>
|
||||
<td>{{ d.type }}</td>
|
||||
<td>{{ d.scenario }}</td>
|
||||
<td>{{ d.until }}</td>
|
||||
<td>{{ d.origin }}</td>
|
||||
<td>
|
||||
<form hx-post="/unban" hx-target="#decisions" hx-swap="innerHTML" hx-confirm="Delete decision {{ d.id }} for {{ d.value }}?">
|
||||
<input type="hidden" name="id" value="{{ d.id }}">
|
||||
<button class="danger" type="submit">Unban</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
{% if not error %}<p>No active decisions.</p>{% endif %}
|
||||
{% endif %}
|
||||
1
app/templates/_unban_me.html
Normal file
1
app/templates/_unban_me.html
Normal file
@@ -0,0 +1 @@
|
||||
<p>Requested unban for <code>{{ ip }}</code>. LAPI: <code>{{ result }}</code></p>
|
||||
55
app/templates/index.html
Normal file
55
app/templates/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CrowdSec Admin</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
body { font-family: system-ui, sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; background:#111; color:#ddd; }
|
||||
h1 { margin-top: 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
|
||||
th, td { padding: .4rem .6rem; border-bottom: 1px solid #333; text-align: left; font-size: 0.9rem; }
|
||||
th { background: #222; }
|
||||
tr:hover { background: #1a1a1a; }
|
||||
button, input[type=submit] { background:#3a6; color:#fff; border:0; padding:.4rem .7rem; border-radius:4px; cursor:pointer; }
|
||||
button.danger { background:#c44; }
|
||||
button.warn { background:#a82; }
|
||||
input[type=text] { background:#222; color:#ddd; border:1px solid #444; padding:.4rem .6rem; border-radius:4px; }
|
||||
.row { display:flex; gap:.6rem; align-items:center; flex-wrap:wrap; }
|
||||
.pill { background:#333; padding:.15rem .5rem; border-radius:99px; font-size:.8rem; }
|
||||
.me { background:#2a4; }
|
||||
section { margin-top: 1.5rem; padding: 1rem; background:#181818; border-radius:6px; }
|
||||
.err { color:#f88; }
|
||||
code { background:#222; padding:.1rem .3rem; border-radius:3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>CrowdSec Admin</h1>
|
||||
<div class="row">
|
||||
<span class="pill me">Your IP: <code>{{ my_ip }}</code></span>
|
||||
<form hx-post="/unban-me" hx-target="#unban-me-result" hx-swap="innerHTML" hx-confirm="Unban {{ my_ip }}?">
|
||||
<button class="warn" type="submit">Unban my IP</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="unban-me-result"></div>
|
||||
|
||||
<section>
|
||||
<h2>Active decisions</h2>
|
||||
<form hx-get="/decisions" hx-target="#decisions" hx-trigger="submit, load delay:200ms" hx-swap="innerHTML" class="row">
|
||||
<input type="text" name="ip" placeholder="Filter by IP (optional)">
|
||||
<button type="submit">Search</button>
|
||||
<button type="button" hx-get="/decisions" hx-target="#decisions" hx-swap="innerHTML">Refresh all</button>
|
||||
</form>
|
||||
<div id="decisions">Loading…</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Unban by IP</h2>
|
||||
<form hx-post="/unban" hx-target="#decisions" hx-swap="innerHTML" hx-confirm="Unban entered IP?" class="row">
|
||||
<input type="text" name="ip" placeholder="e.g. 1.2.3.4" required>
|
||||
<button class="danger" type="submit">Unban IP</button>
|
||||
</form>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user