feat: add admin panel with session auth, user management, and billing dashboard
Implement a full-featured admin backend at `/manage/` with login authentication using signed cookies and constant-time credential verification. The panel includes a dashboard showing total/active users, storage usage, monthly revenue, and pending invoices, plus user management, payment overview, and pricing configuration pages. New environment variables `ADMIN_USERNAME`, `ADMIN_PASSWORD`, `ADMIN_SESSION_SECRET`, and `ADMIN_SESSION_EXPIRE_HOURS` control access. The admin router is registered in main.py and all new templates, auth module, and router files are added.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block title %}Users{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Users</h1>
|
||||
<p class="page-subtitle">Manage all registered users</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<form method="GET" action="/manage/users" class="search-bar">
|
||||
<input type="text" name="search" class="form-input" placeholder="Search by username or email..." value="{{ search }}">
|
||||
<select name="status" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
<option value="active" {% if status == 'active' %}selected{% endif %}>Active</option>
|
||||
<option value="inactive" {% if status == 'inactive' %}selected{% endif %}>Inactive</option>
|
||||
<option value="superuser" {% if status == 'superuser' %}selected{% endif %}>Superuser</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary">Search</button>
|
||||
{% if search or status %}
|
||||
<a href="/manage/users" class="btn btn-outline">Clear</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Email</th>
|
||||
<th>Storage</th>
|
||||
<th>Plan</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ user.username }}</strong>
|
||||
{% if user.is_superuser %}
|
||||
<span class="badge badge-info">Admin</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
{{ user.used_storage_bytes | format_bytes }} / {{ user.storage_quota_bytes | format_bytes }}
|
||||
</td>
|
||||
<td>{{ user.plan_type }}</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
<span class="badge badge-success">Active</span>
|
||||
{% else %}
|
||||
<span class="badge badge-danger">Inactive</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="actions">
|
||||
<a href="/manage/users/{{ user.id }}" class="btn btn-sm btn-outline">Edit</a>
|
||||
<form method="POST" action="/manage/users/{{ user.id }}/toggle-active" style="display: inline;">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<button type="submit" class="btn btn-sm {% if user.is_active %}btn-secondary{% else %}btn-success{% endif %}">
|
||||
{% if user.is_active %}Deactivate{% else %}Activate{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" class="empty-state">
|
||||
<div class="empty-state-icon">👥</div>
|
||||
<div class="empty-state-text">No users found</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if total_pages > 1 %}
|
||||
<div class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="/manage/users?page={{ page - 1 }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}">« Previous</a>
|
||||
{% else %}
|
||||
<span class="disabled">« Previous</span>
|
||||
{% endif %}
|
||||
|
||||
<span>Page {{ page }} of {{ total_pages }}</span>
|
||||
|
||||
{% if page < total_pages %}
|
||||
<a href="/manage/users?page={{ page + 1 }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}">Next »</a>
|
||||
{% else %}
|
||||
<span class="disabled">Next »</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user