108 lines
5.4 KiB
HTML
108 lines
5.4 KiB
HTML
{% extends "layout.html" %}
|
|
|
|
{% block title %}Quota Management - BZOD{% endblock %}
|
|
|
|
{% block active_users %}active{% endblock %}
|
|
|
|
{% block header_title %}User Quota Management{% endblock %}
|
|
|
|
{% block header_actions %}
|
|
<form action="/admin/quotas" method="POST">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="action" value="reconcile_all">
|
|
<button type="submit" class="btn">Sync & Reconcile All Quotas</button>
|
|
</form>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{% if let Some(msg) = success %}
|
|
<div class="alert alert-success">
|
|
{{ msg }}
|
|
</div>
|
|
{% endif %}
|
|
{% if let Some(err) = error %}
|
|
<div class="alert alert-error">
|
|
{{ err }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="card" style="padding: 0; overflow: hidden;">
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>User ID</th>
|
|
<th>URLs (Used/Max)</th>
|
|
<th>Pages (Used/Max)</th>
|
|
<th>API Tokens (Used/Max)</th>
|
|
<th>Storage (Used/Max MB)</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% if quotas.is_empty() %}
|
|
<tr>
|
|
<td colspan="6" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
|
|
No quotas defined.
|
|
</td>
|
|
</tr>
|
|
{% else %}
|
|
{% for q in quotas %}
|
|
<tr>
|
|
<td>
|
|
<a href="/admin/users/{{ q.user_id }}" style="color: var(--text-primary); text-decoration: underline; font-weight: 600;">
|
|
User ID: {{ q.user_id }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
|
|
<span>{{ q.current_urls }} / {{ q.max_urls }}</span>
|
|
<div style="background: rgba(255,255,255,0.05); height: 6px; border-radius: 3px; overflow: hidden; width: 120px;">
|
|
<div style="background: var(--primary-grad); height: 100%; width: {{ q.urls_pct() }}%;"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
|
|
<span>{{ q.current_landings }} / {{ q.max_landings }}</span>
|
|
<div style="background: rgba(255,255,255,0.05); height: 6px; border-radius: 3px; overflow: hidden; width: 120px;">
|
|
<div style="background: var(--primary-grad); height: 100%; width: {{ q.landings_pct() }}%;"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
|
|
<span>{{ q.current_api_tokens }} / {{ q.max_api_tokens }}</span>
|
|
<div style="background: rgba(255,255,255,0.05); height: 6px; border-radius: 3px; overflow: hidden; width: 120px;">
|
|
<div style="background: var(--primary-grad); height: 100%; width: {{ q.api_tokens_pct() }}%;"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display: flex; flex-direction: column; gap: 0.25rem;">
|
|
<span>{{ q.current_storage_mb }} / {{ q.max_storage_mb }} MB</span>
|
|
<div style="background: rgba(255,255,255,0.05); height: 6px; border-radius: 3px; overflow: hidden; width: 120px;">
|
|
<div style="background: var(--primary-grad); height: 100%; width: {{ q.storage_pct() }}%;"></div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div style="display: flex; gap: 0.5rem;">
|
|
<a href="/admin/users/{{ q.user_id }}/edit" class="btn btn-secondary" style="padding: 0.35rem 0.6rem; font-size: 0.85rem;">Edit</a>
|
|
<form action="/admin/quotas" method="POST" style="margin: 0;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<input type="hidden" name="action" value="reconcile">
|
|
<input type="hidden" name="user_id" value="{{ q.user_id }}">
|
|
<button type="submit" class="btn btn-secondary" style="padding: 0.35rem 0.6rem; font-size: 0.85rem;">Reconcile</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|