Panel: add Admin category under Members view; exclude operator accounts from member list/counts

This commit is contained in:
inference-bot committed 2026-10-08 15:08:24 -06:00
1 parent 345eca7f1e
commit bee02c6e4d
1 file changed
+62 -1
+62 -1
View File
@@ -276,7 +276,10 @@ PAGE_HTML = """<!DOCTYPE html>
.range-btn.active { background: var(--green-mid); color: #fff; border-color: var(--green-mid); }
.members-all-time .balance-col.allowance-only,
.members-all-time .balance-col.remaining-only { display: none; }
.members-all-time .credits-col-head .mode-note::after { content: " (total purchased)"; }
.admins-all-time .balance-col.allowance-only,
.admins-all-time .balance-col.remaining-only { display: none; }
.members-all-time .credits-col-head .mode-note::after,
.admins-all-time .credits-col-head .mode-note::after { content: " (total purchased)"; }
.table-scroll { overflow-x: auto; border-radius: 12px; }
.table-scroll table { min-width: 980px; }
th.sortable { cursor: pointer; user-select: none; }
@@ -336,6 +339,29 @@ PAGE_HTML = """<!DOCTYPE html>
</table>
</div>
<div id="admins-section" style="display:none;">
<h3 style="margin:20px 0 8px;">Admin</h3>
<p style="color:var(--muted);font-size:12px;margin:0 0 8px;">Operator accounts (SSO owner and bot). Not members — excluded from member counts; their usage is a collective expense.</p>
<div class="table-scroll">
<table id="table-admins">
<thead>
<tr>
<th>Account</th>
<th>Status</th>
<th>Activated</th>
<th class="balance-col allowance-only">Allowance</th>
<th class="balance-col credits-col-head">Credits<span class="mode-note"></span></th>
<th><span id="spend-mode-label-admin">Spend</span></th>
<th class="balance-col">Remaining</th>
<th class="balance-col">Reset</th>
<th>Adjust</th>
</tr>
</thead>
<tbody id="admin-rows"></tbody>
</table>
</div>
</div>
<div class="table-scroll" style="margin-top:12px;">
<table id="table-models" style="display:none;">
<thead>
@@ -386,6 +412,7 @@ async function load() {
$('tot-spend-cycle').textContent = fmt(d.totals.total_spend);
$('tot-spend-all').textContent = fmt(d.totals.total_all_time_spend ?? d.totals.total_spend);
membersData = d.members || [];
adminsData = d.admins || [];
if (currentView === 'members') renderMembers();
} catch (e) {
flash('Could not load: ' + e.message, false);
@@ -394,6 +421,7 @@ async function load() {
// ---- shared state ----
let membersData = [];
let adminsData = [];
let modelsData = [];
let currentView = 'members';
let memberSort = { key: 'spend', dir: -1 }; // leaderboard default: most spend first
@@ -442,8 +470,12 @@ function setMemberMode(mode) {
$('mmode-all').className = 'range-btn' + (mode === 'all' ? ' active' : '');
// Header label reflects the mode
$('spend-mode-label').textContent = mode === 'all' ? 'All-time spend' : 'Spend';
const adminLabel = document.getElementById('spend-mode-label-admin');
if (adminLabel) adminLabel.textContent = mode === 'all' ? 'All-time spend' : 'Spend';
// Balance-only columns (Allowance/Credits/Remaining/Reset) hide in all-time mode
$('table-members').parentElement.classList.toggle('members-all-time', mode === 'all');
const adminTable = document.getElementById('table-admins');
if (adminTable) adminTable.parentElement.classList.toggle('admins-all-time', mode === 'all');
renderMembers();
}
@@ -512,6 +544,35 @@ function renderMembers() {
'</tr>';
}).join('');
clearArrows('members', memberSort);
// Admin category — operator accounts (SSO owner + bot), separate from members.
const adminSection = document.getElementById('admins-section');
if (!adminsData.length) { adminSection.style.display = 'none'; return; }
adminSection.style.display = '';
$('admin-rows').innerHTML = adminsData.map(m => {
const reset = m.reset_at ? m.reset_at.slice(0,10) : '—';
const credits = m.credit_balance ?? 0;
const creditsLeft = m.credits_remaining ?? credits;
const creditsShown = memberMode === 'all' ? credits : creditsLeft;
const creditsCell = creditsShown > 0
? fmt(creditsShown)
: '<span style="color:var(--muted)">—</span>';
return '<tr>' +
'<td>' + esc(m.email) + (m.username ? '<div style="color:var(--muted);font-size:11px">' + esc(m.username) + '</div>' : '') + '</td>' +
'<td><span class="pill manual" title="Operator account (SSO owner / bot), not a member">admin</span></td>' +
'<td>' + (m.activated ? '<span class="pill active">yes</span>' : '<span class="pill inactive">pending</span>') + '</td>' +
'<td class="num balance-col allowance-only">' + fmt(m.balance) + '</td>' +
'<td class="num balance-col">' + creditsCell + '</td>' +
'<td class="num">' + fmt(memberSpend(m)) + '</td>' +
'<td class="num balance-col remaining-col remaining-only">' + fmt(m.remaining) + '</td>' +
'<td class="balance-col">' + esc(reset) + '</td>' +
'<td><div class="adjust">' +
'<input id="add-' + i(m.email) + '" placeholder="+$"/>' +
'<button data-email="' + esc(m.email) + '" onclick="addBalance(this.dataset.email)" title="Add monthly allowance">Allow</button>' +
'<button data-email="' + esc(m.email) + '" class="credits-btn" onclick="addCredits(this.dataset.email)" title="Add non-expiring credits">Credit</button>' +
'</div></td>' +
'</tr>';
}).join('');
}
async function addCredits(email) {