This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/admin-scaffold.js
2026-03-04 22:37:57 +00:00

335 lines
20 KiB
JavaScript

// ==========================================
// Chat Switchboard - Admin Surface Scaffold
// ==========================================
// Injects section-specific DOM into adminDynamic, wires listeners,
// and calls the appropriate ADMIN_LOADERS[section]() from ui-admin.js.
// Loaded only on the admin surface (/admin/:section).
(function() {
'use strict';
// Each admin section loader (ui-admin.js) expects specific container
// elements. SCAFFOLDING maps section name -> HTML to inject before
// the loader runs.
var SCAFFOLDING = {};
// -- People -----------------------------------------------------------
SCAFFOLDING.users =
'<div id="adminUserList" class="admin-list"></div>';
SCAFFOLDING.teams =
'<div id="adminTeamList" class="admin-list"></div>' +
'<div id="adminTeamDetail" style="display:none">' +
'<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">' +
'<button class="btn-small" id="adminTeamBackBtn">&larr; Back</button>' +
'<h4 id="adminTeamDetailName" style="margin:0"></h4>' +
'</div>' +
'<div style="margin-bottom:12px">' +
'<label class="toggle-label"><input type="checkbox" id="adminTeamPrivatePolicy"><span class="toggle-track"></span><span>Require private providers (BYOK)</span></label>' +
'<label class="toggle-label"><input type="checkbox" id="adminTeamAllowProviders" checked><span class="toggle-track"></span><span>Allow team providers</span></label>' +
'</div>' +
'<div id="adminMemberList" class="admin-list"></div>' +
'<div id="adminAddMemberForm" class="admin-inline-form" style="display:none">' +
'<div class="form-row">' +
'<div class="form-group"><label>User</label><select id="adminMemberUser"><option value="">Select user...</option></select></div>' +
'<div class="form-group"><label>Role</label><select id="adminMemberRole"><option value="member">Member</option><option value="admin">Admin</option></select></div>' +
'</div>' +
'<div class="form-row" style="margin-top:8px">' +
'<button class="btn-small btn-primary" id="adminAddMemberSubmit">Add Member</button>' +
'<button class="btn-small" id="adminCancelMemberBtn">Cancel</button>' +
'</div>' +
'</div>' +
'<button class="btn-small" id="adminAddMemberBtn" style="margin-top:8px">+ Add Member</button>' +
'</div>';
SCAFFOLDING.groups =
'<div id="adminGroupList" class="admin-list"></div>' +
'<div id="adminGroupDetail" style="display:none">' +
'<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">' +
'<button class="btn-small" id="adminGroupBackBtn">&larr; Back</button>' +
'<h4 id="adminGroupDetailName" style="margin:0"></h4>' +
'<span id="adminGroupDetailMeta" class="text-muted"></span>' +
'</div>' +
'<div id="adminGroupMemberList" class="admin-list"></div>' +
'<div id="adminAddGroupMemberForm" class="admin-inline-form" style="display:none">' +
'<div class="form-row">' +
'<div class="form-group"><label>User</label><select id="adminGroupMemberUser"><option value="">Select user...</option></select></div>' +
'</div>' +
'<div class="form-row" style="margin-top:8px">' +
'<button class="btn-small btn-primary" id="adminAddGroupMemberSubmit">Add Member</button>' +
'<button class="btn-small" id="adminCancelGroupMemberBtn">Cancel</button>' +
'</div>' +
'</div>' +
'<button class="btn-small" id="adminAddGroupMemberBtn" style="margin-top:8px">+ Add Member</button>' +
'</div>';
// -- AI ---------------------------------------------------------------
SCAFFOLDING.providers =
'<div id="adminProviderList" class="provider-cards"></div>';
SCAFFOLDING.models =
'<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">' +
'<button class="btn-small btn-primary" id="adminFetchModelsBtn">Fetch Models</button>' +
'<span id="adminModelsHint" class="text-muted" style="font-size:12px"></span>' +
'</div>' +
'<div id="adminModelList" class="admin-list"></div>';
SCAFFOLDING.personas =
'<div id="adminPersonaList" class="admin-list"></div>';
SCAFFOLDING.roles = '<div id="adminRolesContent"></div>';
SCAFFOLDING.knowledgeBases = '<div id="adminKBContent"></div>';
SCAFFOLDING.memory = '<div id="adminMemoryContent"></div>';
// -- System -----------------------------------------------------------
SCAFFOLDING.settings =
'<div class="admin-settings-form">' +
'<div class="settings-section"><h3>Registration</h3>' +
'<label class="toggle-label"><input type="checkbox" id="adminRegToggle"><span class="toggle-track"></span><span>Allow new user registration</span></label>' +
'<div class="form-group" style="margin-top:8px"><label>Default state for new users</label>' +
'<select id="adminRegDefaultState"><option value="pending">Pending (require approval)</option><option value="active">Active (auto-approve)</option></select>' +
'</div>' +
'</div>' +
'<div class="settings-section"><h3>System Prompt</h3>' +
'<textarea id="adminSystemPrompt" rows="4" placeholder="Global system prompt prepended to all chats..."></textarea>' +
'</div>' +
'<div class="settings-section"><h3>Default Model</h3>' +
'<select id="adminDefaultModel"><option value="">-- None (first visible) --</option></select>' +
'</div>' +
'<div class="settings-section"><h3>Policies</h3>' +
'<label class="toggle-label"><input type="checkbox" id="adminUserProvidersToggle"><span class="toggle-track"></span><span>Allow BYOK (user API keys)</span></label>' +
'<label class="toggle-label"><input type="checkbox" id="adminUserPersonasToggle"><span class="toggle-track"></span><span>Allow user personas</span></label>' +
'<label class="toggle-label"><input type="checkbox" id="adminKBDirectAccessToggle"><span class="toggle-track"></span><span>Allow direct KB access (non-persona)</span></label>' +
'</div>' +
'<div class="settings-section"><h3>Environment Banner</h3>' +
'<label class="toggle-label"><input type="checkbox" id="adminBannerEnabled"><span class="toggle-track"></span><span>Show environment banner</span></label>' +
'<div id="bannerConfigFields" style="display:none;margin-top:8px">' +
'<div class="form-group"><label>Text</label><input type="text" id="adminBannerText" placeholder="DEVELOPMENT"></div>' +
'<div class="form-row">' +
'<div class="form-group"><label>Background</label><input type="color" id="adminBannerBg" value="#007a33"><input type="text" id="adminBannerBgHex" value="#007a33" style="width:80px;margin-left:4px"></div>' +
'<div class="form-group"><label>Foreground</label><input type="color" id="adminBannerFg" value="#ffffff"><input type="text" id="adminBannerFgHex" value="#ffffff" style="width:80px;margin-left:4px"></div>' +
'</div>' +
'<div id="bannerPreview" class="banner-preview" style="margin-top:8px;padding:4px 12px;text-align:center;font-size:12px;font-weight:600;border-radius:4px">PREVIEW</div>' +
'</div>' +
'</div>' +
'<div class="settings-section"><h3>Web Search</h3>' +
'<div class="form-group"><label>Provider</label>' +
'<select id="adminSearchProvider"><option value="duckduckgo">DuckDuckGo</option><option value="searxng">SearXNG</option></select>' +
'</div>' +
'<div id="searxngConfigFields" style="display:none">' +
'<div class="form-group"><label>Endpoint</label><input type="text" id="adminSearchEndpoint" placeholder="https://searxng.example.com"></div>' +
'<div class="form-group"><label>API Key</label><input type="text" id="adminSearchApiKey" placeholder="Optional"></div>' +
'</div>' +
'<div class="form-group"><label>Max Results</label><input type="number" id="adminSearchMaxResults" value="5" min="1" max="20"></div>' +
'</div>' +
'<div class="settings-section"><h3>Auto-Compaction</h3>' +
'<label class="toggle-label"><input type="checkbox" id="adminCompactionEnabled"><span class="toggle-track"></span><span>Enable auto-compaction</span></label>' +
'<div id="compactionConfigFields" style="display:none;margin-top:8px">' +
'<div class="form-row">' +
'<div class="form-group"><label>Threshold (%)</label><input type="number" id="adminCompactionThreshold" value="70" min="50" max="95"></div>' +
'<div class="form-group"><label>Cooldown (min)</label><input type="number" id="adminCompactionCooldown" value="30" min="5" max="1440"></div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="settings-section"><h3>Memory Extraction</h3>' +
'<label class="toggle-label"><input type="checkbox" id="adminMemoryExtractionEnabled"><span class="toggle-track"></span><span>Enable memory extraction</span></label>' +
'<div id="memoryExtractionConfigFields" style="display:none;margin-top:8px">' +
'<label class="toggle-label"><input type="checkbox" id="adminMemoryAutoApprove"><span class="toggle-track"></span><span>Auto-approve extracted memories</span></label>' +
'</div>' +
'</div>' +
'<div class="settings-section"><h3>Encryption Vault</h3>' +
'<div id="adminVaultStatus"><span class="text-muted">Loading...</span></div>' +
'</div>' +
'<div id="roleFallbackBanner"></div>' +
'<button class="btn-md btn-primary" id="adminSaveSettings" style="margin-top:16px">Save Settings</button>' +
'</div>';
SCAFFOLDING.storage = '<div id="adminStorageContent"></div>';
SCAFFOLDING.extensions =
'<div id="adminExtensionsList" class="admin-list"></div>' +
'<div id="adminInstallExtForm" class="admin-inline-form" style="display:none">' +
'<h4>Install Extension</h4>' +
'<div class="form-row">' +
'<div class="form-group"><label>ID</label><input type="text" id="extInstallId" placeholder="my-extension"></div>' +
'<div class="form-group"><label>Name</label><input type="text" id="extInstallName" placeholder="My Extension"></div>' +
'<div class="form-group"><label>Version</label><input type="text" id="extInstallVersion" placeholder="1.0.0"></div>' +
'</div>' +
'<div class="form-row">' +
'<div class="form-group"><label>Author</label><input type="text" id="extInstallAuthor" placeholder="Author"></div>' +
'<div class="form-group"><label>System</label>' +
'<select id="extInstallSystem"><option value="browser">Browser JS</option><option value="starlark">Starlark</option><option value="sidecar">Sidecar</option></select>' +
'</div>' +
'</div>' +
'<div class="form-group"><label>Description</label><textarea id="extInstallDesc" rows="2" placeholder="What it does..."></textarea></div>' +
'<div class="form-group"><label>Manifest JSON</label><textarea id="extInstallManifest" rows="4"></textarea></div>' +
'<div class="form-group"><label>Script Source</label><textarea id="extInstallScript" rows="6"></textarea></div>' +
'<label class="toggle-label"><input type="checkbox" id="extInstallEnabled" checked><span class="toggle-track"></span><span>Enabled</span></label>' +
'<div class="form-row" style="margin-top:8px">' +
'<button class="btn-small btn-primary" id="adminInstallExtSubmit">Install</button>' +
'<button class="btn-small" id="adminInstallExtCancelBtn">Cancel</button>' +
'</div>' +
'</div>';
// -- Routing ----------------------------------------------------------
SCAFFOLDING.health =
'<div id="adminHealthCards" class="stat-cards-grid"></div>' +
'<div id="adminHealthContent"></div>';
SCAFFOLDING.routing =
'<div style="margin-bottom:12px"><button class="btn-small btn-primary" id="adminAddRoutingPolicyBtn">+ New Policy</button></div>' +
'<div id="adminRoutingPolicyList" class="admin-list"></div>' +
'<div id="adminRoutingForm" class="admin-inline-form" style="display:none"></div>' +
'<div class="settings-section" style="margin-top:24px"><h4>Test Routing</h4>' +
'<div class="form-row">' +
'<div class="form-group"><label>Model</label><input type="text" id="routingTestModel" placeholder="gpt-4o"></div>' +
'<div class="form-group"><label>User ID</label><input type="text" id="routingTestUser" placeholder="optional"></div>' +
'<button class="btn-small btn-primary" id="routingTestBtn" style="align-self:flex-end">Test</button>' +
'</div>' +
'<pre id="routingTestResult" class="code-block" style="display:none;margin-top:8px;font-size:12px;max-height:200px;overflow:auto"></pre>' +
'</div>';
SCAFFOLDING.capabilities =
'<div id="adminCapabilityList" class="admin-list"><div class="loading">Loading...</div></div>';
// -- Monitoring -------------------------------------------------------
SCAFFOLDING.usage =
'<div id="adminUsageCards" class="stat-cards-grid"></div>' +
'<div id="adminUsageChart" style="margin-bottom:24px"></div>' +
'<div id="adminUsageTotals"></div>' +
'<div id="adminUsageResults" style="margin-top:12px"></div>' +
'<div id="adminPricingTable" style="margin-top:24px"></div>';
SCAFFOLDING.audit =
'<div class="form-row" style="margin-bottom:12px">' +
'<div class="form-group"><label>Action</label><select id="auditFilterAction"><option value="">All</option></select></div>' +
'<div class="form-group"><label>Resource</label><select id="auditFilterResource"><option value="">All</option></select></div>' +
'</div>' +
'<div id="adminAuditList" class="admin-list"><div class="loading">Loading...</div></div>' +
'<div id="auditPagination" style="margin-top:12px;display:flex;align-items:center;gap:8px">' +
'<button class="btn-small" id="auditPrevBtn">&larr; Prev</button>' +
'<span id="auditPageInfo" class="text-muted" style="font-size:12px"></span>' +
'<button class="btn-small" id="auditNextBtn">Next &rarr;</button>' +
'</div>';
SCAFFOLDING.stats =
'<div id="adminStats" class="stat-cards-grid"><div class="loading">Loading...</div></div>';
// === Add button actions ==============================================
var ADD_ACTIONS = {
users: function() {
openModal('createUserModal');
var n = document.getElementById('adminNewUsername');
if (n) n.focus();
},
teams: function() {
openModal('createTeamModal');
var n = document.getElementById('adminTeamName');
if (n) n.focus();
},
providers: function() {
if (typeof UI !== 'undefined' && UI._adminProvForm) UI._adminProvForm.setCreateMode();
document.getElementById('providerFormTitle').textContent = 'Add Provider';
openModal('providerFormModal');
},
personas: function() {
if (typeof ensureAdminPersonaForm === 'function') {
var form = ensureAdminPersonaForm();
if (form) { form.setSubmitLabel('Create'); form.clearForm(); }
}
document.getElementById('personaFormTitle').textContent = 'Create Persona';
openModal('personaFormModal');
},
groups: function() {
openModal('createGroupModal');
var n = document.getElementById('adminGroupName');
if (n) n.focus();
},
extensions: function() {
var f = document.getElementById('adminInstallExtForm');
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
},
};
// === Init on DOMContentLoaded ========================================
document.addEventListener('DOMContentLoaded', function() {
var el = document.getElementById('adminContentBody');
if (!el) return;
var section = el.dataset.section;
if (!section) return;
// -- Category / nav resolution -----------
var catMap = {
users:'people', teams:'people', groups:'people',
providers:'ai', models:'ai', personas:'ai', roles:'ai', knowledgeBases:'ai', memory:'ai',
health:'routing', routing:'routing', capabilities:'routing',
settings:'system', storage:'system', extensions:'system',
usage:'monitoring', audit:'monitoring', stats:'monitoring',
};
var cat = catMap[section] || 'people';
var secMap = {
people: [{id:'users',l:'Users'},{id:'teams',l:'Teams'},{id:'groups',l:'Groups'}],
ai: [{id:'providers',l:'Providers'},{id:'models',l:'Models'},{id:'personas',l:'Personas'},{id:'roles',l:'Roles'},{id:'knowledgeBases',l:'Knowledge'},{id:'memory',l:'Memory'}],
routing: [{id:'health',l:'Health'},{id:'routing',l:'Routing'},{id:'capabilities',l:'Capabilities'}],
system: [{id:'settings',l:'Settings'},{id:'storage',l:'Storage'},{id:'extensions',l:'Extensions'}],
monitoring: [{id:'usage',l:'Usage'},{id:'audit',l:'Audit'},{id:'stats',l:'Stats'}],
};
// Rebuild left nav for current category
var navEl = document.getElementById('adminNav');
var base = window.__BASE__ || '';
if (navEl && secMap[cat]) {
navEl.innerHTML = secMap[cat].map(function(s) {
return '<a href="' + base + '/admin/' + s.id + '" class="admin-nav-link' + (s.id === section ? ' active' : '') + '">' + s.l + '</a>';
}).join('');
}
// Update section title
var titleEl = document.getElementById('adminSectionTitle');
var curSec = secMap[cat] && secMap[cat].find(function(s) { return s.id === section; });
if (titleEl && curSec) titleEl.textContent = curSec.l;
// -- Inject scaffolding ------------------
var target = document.getElementById('adminDynamic');
if (target && SCAFFOLDING[section]) {
target.innerHTML = SCAFFOLDING[section];
} else if (target) {
target.innerHTML = '<div class="empty-hint">Section: ' + section + '</div>';
}
// -- Show/wire Add button for sections that have one --
var addBtn = document.getElementById('adminAddBtn');
if (addBtn && ADD_ACTIONS[section]) {
addBtn.style.display = '';
addBtn.addEventListener('click', ADD_ACTIONS[section]);
}
// -- Wire install extension cancel -------
var extCancel = document.getElementById('adminInstallExtCancelBtn');
if (extCancel) {
extCancel.addEventListener('click', function() {
document.getElementById('adminInstallExtForm').style.display = 'none';
});
}
// -- Wire admin-handlers.js listeners (uses ?. so missing elements are safe) --
if (typeof _initAdminListeners === 'function') _initAdminListeners();
// -- Wire settings toggle show/hide listeners (banner, compaction, etc.) --
if (typeof _initAdminSettingsToggles === 'function') _initAdminSettingsToggles();
// -- Call the section data loader --------
if (typeof ADMIN_LOADERS !== 'undefined' && ADMIN_LOADERS[section]) {
ADMIN_LOADERS[section]();
}
});
})();