// ========================================== // Chat Switchboard — Admin Surfaces UI // ========================================== // v0.25.0: Surface lifecycle management. // Renders in the "Surfaces" admin section via ADMIN_LOADERS. // // Exports: window._loadAdminSurfaces (function() { 'use strict'; async function _loadAdminSurfaces() { const container = document.getElementById('adminSurfacesContent'); if (!container) return; container.innerHTML = `

Manage which surfaces are available. Disabled surfaces redirect to Chat and are hidden from navigation.

Loading…
`; // Wire install form var installBtn = document.getElementById('adminSurfaceInstallBtn'); var installForm = document.getElementById('adminSurfaceInstallForm'); var fileInput = document.getElementById('adminSurfaceFile'); var uploadBtn = document.getElementById('adminSurfaceUploadBtn'); var cancelBtn = document.getElementById('adminSurfaceCancelBtn'); var statusEl = document.getElementById('adminSurfaceInstallStatus'); if (installBtn) installBtn.addEventListener('click', function() { installForm.style.display = ''; installBtn.style.display = 'none'; }); if (cancelBtn) cancelBtn.addEventListener('click', function() { installForm.style.display = 'none'; installBtn.style.display = ''; fileInput.value = ''; uploadBtn.disabled = true; statusEl.textContent = ''; }); if (fileInput) fileInput.addEventListener('change', function() { uploadBtn.disabled = !fileInput.files.length; }); if (uploadBtn) uploadBtn.addEventListener('click', async function() { var file = fileInput.files[0]; if (!file) return; uploadBtn.disabled = true; uploadBtn.textContent = 'Uploading…'; statusEl.textContent = ''; statusEl.style.color = 'var(--text-3)'; try { var formData = new FormData(); formData.append('file', file); var base = window.__BASE__ || ''; var resp = await fetch(base + '/api/v1/admin/surfaces/install', { method: 'POST', headers: API._authHeaders ? (function() { var h = API._authHeaders(); delete h['Content-Type']; return h; })() : {}, body: formData, }); var data = await resp.json(); if (!resp.ok) throw new Error(data.error || 'Upload failed'); statusEl.style.color = 'var(--success)'; statusEl.textContent = 'Installed: ' + (data.title || data.id); if (typeof UI !== 'undefined') UI.toast('Surface installed: ' + (data.title || data.id), 'success'); // Refresh the list installForm.style.display = 'none'; installBtn.style.display = ''; fileInput.value = ''; _loadSurfaceList(); } catch (e) { statusEl.style.color = 'var(--danger)'; statusEl.textContent = 'Error: ' + e.message; uploadBtn.disabled = false; uploadBtn.textContent = 'Upload'; } }); _loadSurfaceList(); } async function _loadSurfaceList() { var listEl = document.getElementById('adminSurfaceList'); if (!listEl) return; try { var resp = await API._get('/api/v1/admin/surfaces'); var surfaces = resp.surfaces || []; if (surfaces.length === 0) { listEl.innerHTML = '
No surfaces registered
'; return; } listEl.innerHTML = ''; surfaces.forEach(function(s) { var row = document.createElement('div'); row.className = 'admin-surface-row'; row.dataset.surfaceId = s.id; var isProtected = s.id === 'chat' || s.id === 'admin'; var sourceLabel = s.source === 'core' ? 'Core' : 'Extension'; var route = s.manifest && s.manifest.route ? s.manifest.route : ''; row.innerHTML = '
' + '
' + '' + esc(s.title) + '' + '' + sourceLabel + '' + (isProtected ? 'Required' : '') + '
' + '
' + '' + esc(s.id) + '' + (route ? '' + esc(route) + '' : '') + '
' + '
' + '
' + '' + (s.source === 'extension' ? '' : '') + '
'; // Toggle handler var checkbox = row.querySelector('input[type="checkbox"]'); if (checkbox && !isProtected) { checkbox.addEventListener('change', (function(surface) { return async function() { var id = this.dataset.surfaceId; var action = this.checked ? 'enable' : 'disable'; var label = row.querySelector('.admin-surface-toggle-label'); var cb = this; try { await API._put('/api/v1/admin/surfaces/' + id + '/' + action); if (label) label.textContent = cb.checked ? 'Enabled' : 'Disabled'; if (typeof UI !== 'undefined') { UI.toast(surface.title + ' ' + (cb.checked ? 'enabled' : 'disabled') + ' — takes effect on next page load', 'success'); } } catch (e) { cb.checked = !cb.checked; if (label) label.textContent = cb.checked ? 'Enabled' : 'Disabled'; if (typeof UI !== 'undefined') UI.toast('Failed: ' + e.message, 'error'); } }; })(s)); } // Uninstall handler var uninstallBtn = row.querySelector('.admin-surface-uninstall'); if (uninstallBtn) { uninstallBtn.addEventListener('click', (function(surface) { return async function() { var ok = typeof showConfirm === 'function' ? await showConfirm('Uninstall ' + surface.title + '? This removes all files.') : window.confirm('Uninstall ' + surface.title + '?'); if (!ok) return; try { await API._del('/api/v1/admin/surfaces/' + surface.id); row.remove(); if (typeof UI !== 'undefined') UI.toast(surface.title + ' uninstalled', 'success'); } catch (e) { if (typeof UI !== 'undefined') UI.toast('Failed: ' + e.message, 'error'); } }; })(s)); } listEl.appendChild(row); }); } catch (e) { listEl.innerHTML = '
Failed to load: ' + esc(e.message) + '
'; } } // ── Exports ───────────────────────────────── window._loadAdminSurfaces = _loadAdminSurfaces; })();