101 lines
4.5 KiB
HTML
101 lines
4.5 KiB
HTML
<section class="grid gap-6">
|
|
<!-- Create User -->
|
|
<div class="rounded-2xl border border-slate-800 bg-slate-900/60 p-5">
|
|
<h1 class="text-xl font-bold">Users</h1>
|
|
<p class="text-slate-400 text-sm mt-1">Temp password will be generated and shown as a flash message.</p>
|
|
|
|
<form method="post" action="{{ url_for('admin_users_create') }}"
|
|
class="mt-4 grid gap-3 sm:grid-cols-[1fr,200px,140px]">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input name="username" placeholder="Username (e.g. email)"
|
|
required
|
|
class="px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-brand-600" />
|
|
<select name="role"
|
|
class="px-3 py-2 rounded-lg bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-brand-600">
|
|
<option value="member">Member</option>
|
|
<option value="admin">Admin</option>
|
|
</select>
|
|
<button class="px-4 py-2 rounded-lg bg-brand-600 hover:bg-brand-700 font-semibold" type="submit">
|
|
Create
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Users table -->
|
|
<div class="rounded-2xl border border-slate-800 bg-slate-900/60 p-5 overflow-x-auto">
|
|
<div class="flex items-center justify-between gap-3">
|
|
<h2 class="text-lg font-semibold">All Users</h2>
|
|
<input id="userFilter" placeholder="Filter by username…" class="px-3 py-2 rounded-lg bg-slate-950 border border-slate-700" oninput="filterUsers(this.value)" />
|
|
</div>
|
|
<table class="min-w-full text-sm mt-3">
|
|
<thead class="text-slate-300">
|
|
<tr>
|
|
<th class="py-2 pr-3 text-left font-medium">ID</th>
|
|
<th class="py-2 pr-3 text-left font-medium">Username</th>
|
|
<th class="py-2 pr-3 text-left font-medium">Role</th>
|
|
<th class="py-2 pr-3 text-left font-medium">Active</th>
|
|
<th class="py-2 pr-3 text-left font-medium">Must Change PW</th>
|
|
<th class="py-2 pr-3 text-left font-medium">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-800">
|
|
{% for u in users %}
|
|
<tr id="u{{ u.id }}">
|
|
<td class="py-2 pr-3 text-slate-300">{{ u.id }}</td>
|
|
<td class="py-2 pr-3 font-medium">{{ u.username }}</td>
|
|
|
|
<!-- Role updater -->
|
|
<td class="py-2 pr-3">
|
|
<form method="post" action="{{ url_for('admin_users_role', user_id=u.id) }}" class="flex items-center gap-2">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<select name="role"
|
|
class="px-2 py-1 rounded bg-slate-950 border border-slate-700 focus:outline-none focus:ring-2 focus:ring-brand-600">
|
|
<option value="member" {{ 'selected' if u.role=='member' else '' }}>member</option>
|
|
<option value="admin" {{ 'selected' if u.role=='admin' else '' }}>admin</option>
|
|
</select>
|
|
<button class="px-3 py-1.5 rounded-lg border border-slate-700 hover:border-slate-500" type="submit">
|
|
Update
|
|
</button>
|
|
</form>
|
|
</td>
|
|
|
|
<!-- Active toggle -->
|
|
<td class="py-2 pr-3">
|
|
<form method="post" action="{{ url_for('admin_users_toggle', user_id=u.id) }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button
|
|
class="px-3 py-1.5 rounded-lg {{ 'bg-emerald-600 hover:bg-emerald-700' if u.is_active else 'bg-slate-700 hover:bg-slate-600' }}"
|
|
type="submit">
|
|
{{ 'Active' if u.is_active else 'Inactive' }}
|
|
</button>
|
|
</form>
|
|
</td>
|
|
|
|
<td class="py-2 pr-3">{{ 'yes' if u.must_change_password else 'no' }}</td>
|
|
|
|
<!-- Reset -->
|
|
<td class="py-2 pr-3">
|
|
<form method="post" action="{{ url_for('admin_users_reset', user_id=u.id) }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button class="px-3 py-1.5 rounded-lg bg-brand-600 hover:bg-brand-700 font-semibold" type="submit">
|
|
Reset Password
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<script>
|
|
function filterUsers(q) {
|
|
q = (q || '').toLowerCase();
|
|
document.querySelectorAll('tbody tr').forEach(tr => {
|
|
const name = tr.querySelector('td:nth-child(2)')?.textContent.toLowerCase() || '';
|
|
tr.style.display = name.includes(q) ? '' : 'none';
|
|
});
|
|
}
|
|
</script>
|
|
</div>
|
|
</section>
|