Initial commit: API gateway with admin console
- FastAPI async gateway with httpx proxying to multiple upstreams - SQLite database with SQLAlchemy ORM - Admin console: manage services, users, API keys, endpoint access - Per-key, per-endpoint granular access control - OpenAPI document sync and caching (5-minute TTL) - Request/response logging with full transaction inspection - In-memory rate limiting (per-key, fixed-window) - Tiered log retention (7d payloads, 90d rows, incremental vacuum) - TLS verification toggle per service (for self-signed certificates) - Service connectivity validation with automatic endpoint refresh - Request browser with filters and deep-link inspection - Docker setup with persistent volume - Modal forms for create/edit flows Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Users · API Gateway{% endblock %}
|
||||
|
||||
{% macro trash(label) %}
|
||||
<button class="icon" title="{{ label }}" aria-label="{{ label }}">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pencil(label, modal) %}
|
||||
<button class="icon edit" data-modal="{{ modal }}" title="{{ label }}" aria-label="{{ label }}">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row" style="margin-bottom:20px">
|
||||
<h1 style="margin:0">Users</h1>
|
||||
<button class="right" data-modal="modal-new-user">+ Add user</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<table>
|
||||
<thead><tr><th>Username</th><th>API keys</th><th>Created</th><th>On</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td class="strong">{{ u.username }}</td>
|
||||
<td>{{ u.api_keys | length }}</td>
|
||||
<td>{{ u.created_at.strftime("%Y-%m-%d") }}</td>
|
||||
<td>
|
||||
{% if u.id != user.id %}
|
||||
<form class="inline" method="post" action="/admin/users/{{ u.id }}/toggle">
|
||||
<label class="switch" title="{{ 'Disable' if u.is_active else 'Enable' }}">
|
||||
<input type="checkbox" {% if u.is_active %}checked{% endif %} onchange="this.form.submit()">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</form>
|
||||
{% else %}
|
||||
<label class="switch" title="You cannot deactivate your own account">
|
||||
<input type="checkbox" checked disabled><span class="slider" style="opacity:.5"></span>
|
||||
</label>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
{{ pencil('Edit ' ~ u.username, 'modal-user-' ~ u.id) }}
|
||||
{% if u.id != user.id %}
|
||||
<form class="inline" method="post" action="/admin/users/{{ u.id }}/delete"
|
||||
data-confirm="Deletes {{ u.username }} and all their API keys">
|
||||
{{ trash('Delete ' ~ u.username) }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="hint">Every user can sign in to this console and own API keys.
|
||||
Gateway access itself is granted per key on the <a href="/admin/keys">API Keys</a> page.</div>
|
||||
</div>
|
||||
|
||||
{% for u in users %}
|
||||
<div class="modal-overlay" id="modal-user-{{ u.id }}" role="dialog" aria-modal="true" aria-label="Edit {{ u.username }}">
|
||||
<div class="modal">
|
||||
<h2>Edit {{ u.username }}</h2>
|
||||
<button type="button" class="icon modal-close" title="Close" aria-label="Close">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
<form method="post" action="/admin/users/{{ u.id }}/password">
|
||||
<label>New password</label>
|
||||
<input type="password" name="password" required>
|
||||
<div style="margin-top:14px"><button>Reset password</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="modal-overlay" id="modal-new-user" role="dialog" aria-modal="true" aria-label="Add a user">
|
||||
<div class="modal">
|
||||
<h2>Add a user</h2>
|
||||
<button type="button" class="icon modal-close" title="Close" aria-label="Close">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||
</button>
|
||||
<form method="post" action="/admin/users" class="grid cols-2">
|
||||
<div><label>Username</label><input type="text" name="username" required></div>
|
||||
<div><label>Password</label><input type="password" name="password" required></div>
|
||||
<div><button>Create user</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user