Changeset 0.13.0 (#64)

This commit is contained in:
2026-02-25 22:52:19 +00:00
parent 88216ec4cb
commit 8292a6efa8
18 changed files with 360 additions and 9363 deletions

View File

@@ -1,14 +1,131 @@
// ==========================================
// Chat Switchboard UI Admin
// ==========================================
// Extends UI with admin modal tabs: users, stats, roles,
// usage, audit, providers, models, presets, teams, settings.
// Fullscreen admin panel with category + section navigation.
// All loadAdmin*() functions unchanged — same endpoints, same DOM IDs.
// ── Category → Section mapping ────────────
const ADMIN_SECTIONS = {
people: ['users', 'teams'],
ai: ['providers', 'models', 'presets', 'roles'],
system: ['settings', 'storage', 'extensions'],
monitoring: ['usage', 'audit', 'stats'],
};
const ADMIN_LABELS = {
users: 'Users', teams: 'Teams',
providers: 'Providers', models: 'Models', presets: 'Personas', roles: 'Roles',
settings: 'Settings', storage: 'Storage', extensions: 'Extensions',
usage: 'Usage', audit: 'Audit', stats: 'Stats',
};
// Section → loader mapping (reuses all existing loadAdmin* functions)
const ADMIN_LOADERS = {
users: () => UI.loadAdminUsers(),
teams: () => UI.loadAdminTeams(),
roles: () => UI.loadAdminRoles(),
providers: () => UI.loadAdminProviders(),
models: () => UI.loadAdminModels(),
presets: () => UI.loadAdminPresets(),
settings: () => UI.loadAdminSettings(),
storage: () => typeof loadAdminStorage === 'function' ? loadAdminStorage() : null,
extensions: () => UI.loadAdminExtensions(),
usage: () => UI.loadAdminUsage(),
audit: () => UI.loadAuditLog(),
stats: () => UI.loadAdminStats(),
};
// Find which category a section belongs to
function _adminCatForSection(section) {
for (const [cat, sections] of Object.entries(ADMIN_SECTIONS)) {
if (sections.includes(section)) return cat;
}
return 'people';
}
Object.assign(UI, {
// ── Admin Modal ──────────────────────────
// ── Admin Panel State ─────────────────────
_adminCat: 'people',
_adminSection: 'users',
openAdmin() { openModal('adminModal'); UI.switchAdminTab('users'); },
closeAdmin() { closeModal('adminModal'); },
// ── Open / Close ──────────────────────────
openAdmin(cat, section) {
UI._adminCat = cat || 'people';
UI._adminSection = section || ADMIN_SECTIONS[UI._adminCat][0];
const panel = document.getElementById('adminPanel');
panel.style.display = 'flex';
// Show current user email
const userEl = document.getElementById('adminCurrentUser');
if (userEl && API.user) userEl.textContent = API.user.email || API.user.username || '';
UI._renderAdminNav();
UI._switchAdminSection(UI._adminSection);
},
closeAdmin() {
document.getElementById('adminPanel').style.display = 'none';
},
// ── Navigate to specific section ──────────
// Called from command palette, deep links, etc.
openAdminSection(section) {
const cat = _adminCatForSection(section);
UI.openAdmin(cat, section);
},
// ── Category switch ───────────────────────
switchAdminCategory(cat) {
UI._adminCat = cat;
UI._adminSection = ADMIN_SECTIONS[cat][0];
UI._renderAdminNav();
UI._switchAdminSection(UI._adminSection);
},
// ── Render navigation state ───────────────
_renderAdminNav() {
// Highlight active category
document.querySelectorAll('.admin-cat').forEach(btn => {
btn.classList.toggle('active', btn.dataset.cat === UI._adminCat);
});
// Populate sidebar
const sidebar = document.getElementById('adminSidebar');
sidebar.innerHTML = ADMIN_SECTIONS[UI._adminCat].map(sec =>
`<button class="${sec === UI._adminSection ? 'active' : ''}" data-section="${sec}">${ADMIN_LABELS[sec]}</button>`
).join('');
// Wire sidebar clicks
sidebar.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => UI._switchAdminSection(btn.dataset.section));
});
},
// ── Section switch (shows content + lazy-loads) ──
async _switchAdminSection(section) {
UI._adminSection = section;
// Update sidebar highlight
document.querySelectorAll('.admin-sidebar button').forEach(btn => {
btn.classList.toggle('active', btn.dataset.section === section);
});
// Hide all, show target
document.querySelectorAll('.admin-section-content').forEach(c => c.style.display = 'none');
const panel = document.getElementById(`admin${section.charAt(0).toUpperCase() + section.slice(1)}Tab`);
if (panel) panel.style.display = '';
// Lazy-load data
await ADMIN_LOADERS[section]?.();
},
// ── Backward compat: switchAdminTab still works ──
// (called by some inline onclick handlers and admin-handlers.js)
async switchAdminTab(tab) {
const cat = _adminCatForSection(tab);
if (document.getElementById('adminPanel').style.display !== 'flex') {
UI.openAdmin(cat, tab);
} else {
if (cat !== UI._adminCat) {
UI._adminCat = cat;
UI._renderAdminNav();
}
await UI._switchAdminSection(tab);
}
},
openTeamAdmin() {
const adminTeams = (UI._myTeams || []).filter(t => t.my_role === 'admin');
@@ -59,26 +176,6 @@ Object.assign(UI, {
if (tab === 'activity') UI.loadTeamAuditLog(1);
},
async switchAdminTab(tab) {
document.querySelectorAll('#adminModal .admin-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tab));
document.querySelectorAll('.admin-tab-content').forEach(c => c.style.display = 'none');
const panel = document.getElementById(`admin${tab.charAt(0).toUpperCase() + tab.slice(1)}Tab`);
if (panel) panel.style.display = '';
if (tab === 'users') await this.loadAdminUsers();
if (tab === 'stats') await this.loadAdminStats();
if (tab === 'audit') await this.loadAuditLog();
if (tab === 'providers') await this.loadAdminProviders();
if (tab === 'models') await this.loadAdminModels();
if (tab === 'presets') await this.loadAdminPresets();
if (tab === 'teams') await this.loadAdminTeams();
if (tab === 'settings') await this.loadAdminSettings();
if (tab === 'roles') await this.loadAdminRoles();
if (tab === 'usage') await this.loadAdminUsage();
if (tab === 'extensions') await this.loadAdminExtensions();
if (tab === 'storage' && typeof loadAdminStorage === 'function') await loadAdminStorage();
},
async loadAdminUsers(quiet) {
const el = document.getElementById('adminUserList');
if (!quiet) el.innerHTML = '<div class="loading">Loading...</div>';