Changeset 0.13.0 (#64)
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
// ── Admin Actions ────────────────────────────
|
||||
|
||||
function _adminScroll() { return document.querySelector('#adminModal .modal-body'); }
|
||||
function _adminScroll() { return document.getElementById('adminMain'); }
|
||||
function _restoreScroll(el, pos) { if (el) requestAnimationFrame(() => { el.scrollTop = pos; }); }
|
||||
async function toggleUserActive(id, active) {
|
||||
try {
|
||||
@@ -403,10 +403,17 @@ async function settingsDeleteTeamPreset(teamId, presetId, name) {
|
||||
// ── Admin Listeners (extracted from initListeners) ──
|
||||
|
||||
function _initAdminListeners() {
|
||||
// Admin modal (elements may not exist for non-admin users)
|
||||
document.getElementById('adminCloseBtn')?.addEventListener('click', UI.closeAdmin);
|
||||
document.querySelectorAll('.admin-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => UI.switchAdminTab(tab.dataset.tab));
|
||||
// Admin panel navigation (elements may not exist for non-admin users)
|
||||
document.getElementById('adminBackBtn')?.addEventListener('click', UI.closeAdmin);
|
||||
document.querySelectorAll('.admin-cat').forEach(btn => {
|
||||
btn.addEventListener('click', () => UI.switchAdminCategory(btn.dataset.cat));
|
||||
});
|
||||
// Esc closes admin panel
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape' && document.getElementById('adminPanel')?.style.display === 'flex') {
|
||||
UI.closeAdmin();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
document.getElementById('adminSaveSettings')?.addEventListener('click', handleSaveAdminSettings);
|
||||
|
||||
|
||||
@@ -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>';
|
||||
|
||||
@@ -441,14 +441,14 @@ function renderRoleConfig(containerEl, opts = {}) {
|
||||
// Reload after short delay so dropdowns reflect saved state
|
||||
setTimeout(() => refresh(), 500);
|
||||
} catch (e) {
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--error)'; }
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--danger)'; }
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTest(roleId) {
|
||||
if (!opts.onTest) return;
|
||||
const statusEl = containerEl.querySelector(`[data-role-status="${roleId}"]`);
|
||||
if (statusEl) { statusEl.textContent = 'Testing...'; statusEl.style.color = 'var(--text-muted)'; }
|
||||
if (statusEl) { statusEl.textContent = 'Testing...'; statusEl.style.color = 'var(--text-3)'; }
|
||||
try {
|
||||
const result = await opts.onTest(roleId);
|
||||
if (statusEl) {
|
||||
@@ -457,7 +457,7 @@ function renderRoleConfig(containerEl, opts = {}) {
|
||||
statusEl.style.color = 'var(--success)';
|
||||
}
|
||||
} catch (e) {
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--error)'; }
|
||||
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.style.color = 'var(--danger)'; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ Object.assign(UI, {
|
||||
|
||||
applyAppearance(scale, msgFont) {
|
||||
const z = scale === 100 ? '' : scale / 100;
|
||||
// Zoom content areas + modals (but NOT .app or banners)
|
||||
document.querySelectorAll('.sidebar, .chat-area, .modal-overlay').forEach(el => el.style.zoom = z);
|
||||
// Zoom content areas + modals + panels (but NOT .app or banners)
|
||||
document.querySelectorAll('.sidebar, .chat-area, .modal-overlay, .side-panel, .admin-panel').forEach(el => el.style.zoom = z);
|
||||
const splash = document.getElementById('splashGate');
|
||||
if (splash) splash.style.zoom = z;
|
||||
document.documentElement.style.setProperty('--msg-font', msgFont + 'px');
|
||||
|
||||
2582
src/js/ui.js
2582
src/js/ui.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user