feat(admin): visible Test/Test-seed badges + filter toggle
Users table shows [Test] next to soak-harness accounts, invite codes list shows [Test-seed] next to codes that flag new accounts as test, and a new "Include test accounts" checkbox lets admins hide bot traffic from the user list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,10 @@
|
|||||||
<input type="checkbox" id="include-banned" checked>
|
<input type="checkbox" id="include-banned" checked>
|
||||||
Include banned
|
Include banned
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="include-test" checked>
|
||||||
|
Include test accounts
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<table id="users-table" class="data-table">
|
<table id="users-table" class="data-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@@ -67,12 +67,13 @@ async function getStats() {
|
|||||||
return apiRequest('/api/admin/stats');
|
return apiRequest('/api/admin/stats');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUsers(query = '', offset = 0, includeBanned = true) {
|
async function getUsers(query = '', offset = 0, includeBanned = true, includeTest = true) {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
query,
|
query,
|
||||||
offset,
|
offset,
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
include_banned: includeBanned,
|
include_banned: includeBanned,
|
||||||
|
include_test: includeTest,
|
||||||
});
|
});
|
||||||
return apiRequest(`/api/admin/users?${params}`);
|
return apiRequest(`/api/admin/users?${params}`);
|
||||||
}
|
}
|
||||||
@@ -306,15 +307,19 @@ async function loadUsers() {
|
|||||||
try {
|
try {
|
||||||
const query = document.getElementById('user-search').value;
|
const query = document.getElementById('user-search').value;
|
||||||
const includeBanned = document.getElementById('include-banned').checked;
|
const includeBanned = document.getElementById('include-banned').checked;
|
||||||
const data = await getUsers(query, usersPage * PAGE_SIZE, includeBanned);
|
const includeTest = document.getElementById('include-test').checked;
|
||||||
|
const data = await getUsers(query, usersPage * PAGE_SIZE, includeBanned, includeTest);
|
||||||
|
|
||||||
const tbody = document.querySelector('#users-table tbody');
|
const tbody = document.querySelector('#users-table tbody');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
data.users.forEach(user => {
|
data.users.forEach(user => {
|
||||||
|
const testBadge = user.is_test_account
|
||||||
|
? ' <span class="badge badge-info" title="Soak harness test account">Test</span>'
|
||||||
|
: '';
|
||||||
tbody.innerHTML += `
|
tbody.innerHTML += `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${escapeHtml(user.username)}</td>
|
<td>${escapeHtml(user.username)}${testBadge}</td>
|
||||||
<td>${escapeHtml(user.email || '-')}</td>
|
<td>${escapeHtml(user.email || '-')}</td>
|
||||||
<td><span class="badge badge-${user.role === 'admin' ? 'info' : 'muted'}">${user.role}</span></td>
|
<td><span class="badge badge-${user.role === 'admin' ? 'info' : 'muted'}">${user.role}</span></td>
|
||||||
<td>${getStatusBadge(user)}</td>
|
<td>${getStatusBadge(user)}</td>
|
||||||
@@ -447,10 +452,13 @@ async function loadInvites() {
|
|||||||
: invite.remaining_uses <= 0
|
: invite.remaining_uses <= 0
|
||||||
? '<span class="badge badge-warning">Used Up</span>'
|
? '<span class="badge badge-warning">Used Up</span>'
|
||||||
: '<span class="badge badge-success">Active</span>';
|
: '<span class="badge badge-success">Active</span>';
|
||||||
|
const testSeedBadge = invite.marks_as_test
|
||||||
|
? ' <span class="badge badge-info" title="Creates test accounts">Test-seed</span>'
|
||||||
|
: '';
|
||||||
|
|
||||||
tbody.innerHTML += `
|
tbody.innerHTML += `
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>${escapeHtml(invite.code)}</code></td>
|
<td><code>${escapeHtml(invite.code)}</code>${testSeedBadge}</td>
|
||||||
<td>${invite.use_count} / ${invite.max_uses}</td>
|
<td>${invite.use_count} / ${invite.max_uses}</td>
|
||||||
<td>${invite.remaining_uses}</td>
|
<td>${invite.remaining_uses}</td>
|
||||||
<td>${escapeHtml(invite.created_by_username)}</td>
|
<td>${escapeHtml(invite.created_by_username)}</td>
|
||||||
@@ -827,6 +835,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
usersPage = 0;
|
usersPage = 0;
|
||||||
loadUsers();
|
loadUsers();
|
||||||
});
|
});
|
||||||
|
document.getElementById('include-test').addEventListener('change', () => {
|
||||||
|
usersPage = 0;
|
||||||
|
loadUsers();
|
||||||
|
});
|
||||||
document.getElementById('users-prev').addEventListener('click', () => {
|
document.getElementById('users-prev').addEventListener('click', () => {
|
||||||
if (usersPage > 0) {
|
if (usersPage > 0) {
|
||||||
usersPage--;
|
usersPage--;
|
||||||
|
|||||||
Reference in New Issue
Block a user