// ========================================== // Admin Panel // ========================================== let _adminListenersInit = false; function initAdmin() { if (Backend.user && Backend.user.role === 'admin') { document.getElementById('adminBtn').style.display = ''; } else { document.getElementById('adminBtn').style.display = 'none'; } } function openAdmin() { document.getElementById('adminModal').classList.add('active'); initAdminListeners(); switchAdminTab('users'); loadAdminUsers(); } function closeAdmin() { document.getElementById('adminModal').classList.remove('active'); } function initAdminListeners() { if (_adminListenersInit) return; _adminListenersInit = true; document.getElementById('adminShowAddUser').addEventListener('click', () => { document.getElementById('adminAddUserForm').style.display = ''; document.getElementById('adminShowAddUser').style.display = 'none'; }); document.getElementById('adminCancelAddUser').addEventListener('click', () => { document.getElementById('adminAddUserForm').style.display = 'none'; document.getElementById('adminShowAddUser').style.display = ''; clearAddUserForm(); }); document.getElementById('adminCreateUserBtn').addEventListener('click', handleCreateUser); document.getElementById('adminResetCancelBtn').addEventListener('click', closeResetDialog); document.getElementById('adminResetConfirmBtn').addEventListener('click', handleResetPassword); // Admin providers document.getElementById('adminProviderShowAddBtn').addEventListener('click', () => { document.getElementById('adminProviderAddForm').style.display = ''; document.getElementById('adminProviderShowAddBtn').style.display = 'none'; }); document.getElementById('adminProviderCancelBtn').addEventListener('click', () => { document.getElementById('adminProviderAddForm').style.display = 'none'; document.getElementById('adminProviderShowAddBtn').style.display = ''; clearAdminProviderForm(); }); document.getElementById('adminProviderCreateBtn').addEventListener('click', handleCreateAdminProvider); // Admin models document.getElementById('adminFetchModelsBtn').addEventListener('click', handleAdminFetchModels); document.getElementById('adminResetNewPw').addEventListener('keydown', function(e) { if (e.key === 'Enter') handleResetPassword(); }); } function switchAdminTab(tab) { document.querySelectorAll('.admin-tab').forEach(t => { t.classList.toggle('active', t.dataset.tab === tab); }); document.getElementById('adminUsersTab').style.display = tab === 'users' ? '' : 'none'; document.getElementById('adminModelsTab').style.display = tab === 'models' ? '' : 'none'; document.getElementById('adminProvidersTab').style.display = tab === 'providers' ? '' : 'none'; document.getElementById('adminSettingsTab').style.display = tab === 'settings' ? '' : 'none'; document.getElementById('adminStatsTab').style.display = tab === 'stats' ? '' : 'none'; document.getElementById('adminResetDialog').style.display = 'none'; if (tab === 'models') loadAdminModels(); if (tab === 'providers') loadAdminProviders(); if (tab === 'settings') loadAdminSettings(); if (tab === 'stats') loadAdminStats(); } // ── Add User ──────────────────────────────── function clearAddUserForm() { document.getElementById('adminNewUsername').value = ''; document.getElementById('adminNewEmail').value = ''; document.getElementById('adminNewPassword').value = ''; document.getElementById('adminNewRole').value = 'user'; } async function handleCreateUser() { const username = document.getElementById('adminNewUsername').value.trim(); const email = document.getElementById('adminNewEmail').value.trim(); const password = document.getElementById('adminNewPassword').value; const role = document.getElementById('adminNewRole').value; if (!username || !email || !password) { showToast('⚠️ Fill in all fields', 'warning'); return; } if (password.length < 8) { showToast('⚠️ Password must be at least 8 characters', 'warning'); return; } try { await Backend.adminCreateUser(username, email, password, role); showToast('✅ User "' + username + '" created', 'success'); clearAddUserForm(); document.getElementById('adminAddUserForm').style.display = 'none'; document.getElementById('adminShowAddUser').style.display = ''; loadAdminUsers(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } // ── Users List ────────────────────────────── async function loadAdminUsers() { const container = document.getElementById('adminUserList'); container.innerHTML = '
Loading users...
'; try { const data = await Backend.adminListUsers(1, 100); const users = data.data || []; if (users.length === 0) { container.innerHTML = '
No users found
'; return; } container.innerHTML = users.map(u => { const isSelf = u.id === Backend.user.id; const actions = isSelf ? '(you)' : ` `; return `
${escapeHtml(u.username)} ${escapeHtml(u.email)}
${u.role} ${u.is_active ? 'active' : 'disabled'}
${actions}
`; }).join(''); } catch (e) { container.innerHTML = '
Failed to load users: ' + e.message + '
'; } } async function changeUserRole(userId, role) { try { await Backend.adminUpdateUserRole(userId, role); showToast('✅ Role updated to ' + role, 'success'); } catch (e) { showToast('❌ ' + e.message, 'error'); loadAdminUsers(); } } async function toggleUserActive(userId, isActive) { try { await Backend.adminToggleUserActive(userId, isActive); showToast('✅ User ' + (isActive ? 'enabled' : 'disabled'), 'success'); loadAdminUsers(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } async function deleteUser(userId, username) { if (!confirm('Delete user "' + username + '"? This removes all their chats and data.')) return; try { await Backend.adminDeleteUser(userId); showToast('✅ User deleted', 'success'); loadAdminUsers(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } // ── Reset Password ────────────────────────── let _resetUserId = null; function openResetDialog(userId, username) { _resetUserId = userId; document.getElementById('adminResetTarget').textContent = 'Set new password for: ' + username; document.getElementById('adminResetNewPw').value = ''; document.getElementById('adminResetDialog').style.display = ''; document.getElementById('adminResetNewPw').focus(); } function closeResetDialog() { _resetUserId = null; document.getElementById('adminResetDialog').style.display = 'none'; } async function handleResetPassword() { if (!_resetUserId) return; const pw = document.getElementById('adminResetNewPw').value; if (!pw || pw.length < 8) { showToast('⚠️ Password must be at least 8 characters', 'warning'); return; } try { await Backend.adminResetPassword(_resetUserId, pw); showToast('✅ Password reset', 'success'); closeResetDialog(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } // ── Settings ──────────────────────────────── async function loadAdminSettings() { try { const data = await Backend.adminListSettings(); const settings = data.settings || []; for (const s of settings) { if (s.key === 'registration') { document.getElementById('adminRegToggle').checked = s.value.enabled !== false; } if (s.key === 'site') { document.getElementById('adminSiteName').value = s.value.name || 'Chat Switchboard'; document.getElementById('adminTagline').value = s.value.tagline || 'Multi-Model AI Chat'; } } } catch (e) { showToast('❌ Failed to load settings: ' + e.message, 'error'); } } async function saveAdminSettings() { try { await Backend.adminUpdateSetting('registration', { enabled: document.getElementById('adminRegToggle').checked }); await Backend.adminUpdateSetting('site', { name: document.getElementById('adminSiteName').value.trim(), tagline: document.getElementById('adminTagline').value.trim() }); showToast('✅ Admin settings saved', 'success'); } catch (e) { showToast('❌ ' + e.message, 'error'); } } // ── Stats ─────────────────────────────────── async function loadAdminStats() { const container = document.getElementById('adminStats'); container.innerHTML = '
Loading stats...
'; try { const stats = await Backend.adminGetStats(); container.innerHTML = '
' + statCard(stats.total_users, 'Total Users') + statCard(stats.active_users, 'Active Users') + statCard(stats.total_chats, 'Chats') + statCard(stats.total_messages, 'Messages') + statCard(stats.api_configs, 'API Configs') + '
'; } catch (e) { container.innerHTML = '
Failed to load stats: ' + e.message + '
'; } } function statCard(value, label) { return '
' + (value || 0) + '
' + label + '
'; } function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } // ── Admin Models ──────────────────────────── const CAPS = ['tool', 'thinking', 'vision', 'code']; async function loadAdminModels() { const container = document.getElementById('adminModelList'); container.innerHTML = '
Loading models...
'; try { const data = await Backend.adminListModels(); const models = data.models || []; if (models.length === 0) { container.innerHTML = '
No models configured. Add a provider first, then click "Fetch Models".
'; return; } // Group by provider const grouped = {}; for (const m of models) { const key = m.provider_name || 'Unknown'; if (!grouped[key]) grouped[key] = []; grouped[key].push(m); } let html = ''; for (const [provider, provModels] of Object.entries(grouped)) { html += '
'; html += '
' + escapeHtml(provider) + '
'; for (const m of provModels) { const caps = m.capabilities || {}; const name = m.display_name || m.model_id; html += '
'; html += '
'; html += ' '; html += '
'; html += '
'; html += ' ' + escapeHtml(name) + ''; html += ' ' + escapeHtml(m.model_id) + ''; html += '
'; html += '
'; for (const cap of CAPS) { const active = caps[cap] === true; html += ' '; } html += '
'; html += '
'; html += ' '; html += '
'; html += '
'; } html += '
'; } container.innerHTML = html; } catch (e) { container.innerHTML = '
Failed to load models: ' + e.message + '
'; } } async function handleAdminFetchModels() { const btn = document.getElementById('adminFetchModelsBtn'); btn.disabled = true; btn.textContent = '⏳ Fetching...'; try { const data = await Backend.adminFetchModels(); const added = data.total_added || 0; const results = data.results || []; const errors = results.filter(r => r.error); if (errors.length > 0) { showToast('⚠️ Fetched with ' + errors.length + ' error(s). ' + added + ' new models.', 'warning'); } else { showToast('✅ Fetched ' + added + ' new models', 'success'); } loadAdminModels(); } catch (e) { showToast('❌ ' + e.message, 'error'); } finally { btn.disabled = false; btn.textContent = '🔄 Fetch Models'; } } async function toggleModelEnabled(modelId, enabled) { try { await Backend.adminUpdateModel(modelId, { is_enabled: enabled }); } catch (e) { showToast('❌ ' + e.message, 'error'); loadAdminModels(); } } async function toggleModelCap(modelId, cap, value, capsJson) { try { const caps = JSON.parse(capsJson); caps[cap] = value; await Backend.adminUpdateModel(modelId, { capabilities: caps }); loadAdminModels(); } catch (e) { showToast('❌ ' + e.message, 'error'); loadAdminModels(); } } async function deleteAdminModel(modelId, modelName) { if (!confirm('Remove model "' + modelName + '"? It can be re-fetched later.')) return; try { await Backend.adminDeleteModel(modelId); showToast('✅ Model removed', 'success'); loadAdminModels(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } async function loadAdminProviders() { const container = document.getElementById('adminProviderList'); container.innerHTML = '
Loading...
'; try { const data = await Backend.adminListGlobalConfigs(); const configs = data.configs || []; if (configs.length === 0) { container.innerHTML = '
No global providers. Users must add their own.
'; return; } container.innerHTML = configs.map(c => `
${escapeHtml(c.name)} ${c.provider} · ${c.model_default || 'no default'}
${c.has_key ? '🔑' : '⚠️'}
`).join(''); } catch (e) { container.innerHTML = '
Failed to load providers: ' + e.message + '
'; } } function clearAdminProviderForm() { document.getElementById('adminProviderName').value = ''; document.getElementById('adminProviderType').value = 'openai'; document.getElementById('adminProviderEndpoint').value = ''; document.getElementById('adminProviderApiKey').value = ''; document.getElementById('adminProviderDefaultModel').value = ''; } async function handleCreateAdminProvider() { const name = document.getElementById('adminProviderName').value.trim(); const provider = document.getElementById('adminProviderType').value; const endpoint = document.getElementById('adminProviderEndpoint').value.trim(); const apiKey = document.getElementById('adminProviderApiKey').value.trim(); const modelDefault = document.getElementById('adminProviderDefaultModel').value.trim(); if (!name || !endpoint || !apiKey) { showToast('⚠️ Name, endpoint, and API key are required', 'warning'); return; } try { await Backend.adminCreateGlobalConfig(name, provider, endpoint, apiKey, modelDefault || null); showToast('✅ Global provider added', 'success'); clearAdminProviderForm(); document.getElementById('adminProviderAddForm').style.display = 'none'; document.getElementById('adminProviderShowAddBtn').style.display = ''; loadAdminProviders(); } catch (e) { showToast('❌ ' + e.message, 'error'); } } async function deleteAdminProvider(configId, name) { if (!confirm('Remove global provider "' + name + '"?')) return; try { await Backend.adminDeleteGlobalConfig(configId); showToast('✅ Global provider removed', 'success'); loadAdminProviders(); } catch (e) { showToast('❌ ' + e.message, 'error'); } }