Changeset 0.22.7 (#149)
This commit is contained in:
372
src/js/admin-scaffold.js
Normal file
372
src/js/admin-scaffold.js
Normal file
@@ -0,0 +1,372 @@
|
||||
// ==========================================
|
||||
// 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>' +
|
||||
'<div id="adminAddUserForm" class="admin-inline-form" style="display:none">' +
|
||||
'<h4>Create User</h4>' +
|
||||
'<div class="form-row">' +
|
||||
'<div class="form-group"><label>Username</label><input type="text" id="adminNewUsername" placeholder="username"></div>' +
|
||||
'<div class="form-group"><label>Email</label><input type="text" id="adminNewEmail" placeholder="email@example.com"></div>' +
|
||||
'</div>' +
|
||||
'<div class="form-row">' +
|
||||
'<div class="form-group"><label>Password</label><input type="password" id="adminNewPassword" placeholder="min 8 chars"></div>' +
|
||||
'<div class="form-group"><label>Role</label><select id="adminNewRole"><option value="user">User</option><option value="admin">Admin</option></select></div>' +
|
||||
'</div>' +
|
||||
'<div class="form-row" style="margin-top:8px">' +
|
||||
'<button class="btn-small btn-primary" id="adminCreateUserBtn">Create User</button>' +
|
||||
'<button class="btn-small" id="adminCancelUserBtn">Cancel</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
SCAFFOLDING.teams =
|
||||
'<div id="adminTeamList" class="admin-list"></div>' +
|
||||
'<div id="adminAddTeamForm" class="admin-inline-form" style="display:none">' +
|
||||
'<h4>Create Team</h4>' +
|
||||
'<div class="form-group"><label>Name</label><input type="text" id="adminTeamName" placeholder="Team name"></div>' +
|
||||
'<div class="form-group"><label>Description</label><input type="text" id="adminTeamDesc" placeholder="Optional description"></div>' +
|
||||
'<div class="form-row" style="margin-top:8px">' +
|
||||
'<button class="btn-small btn-primary" id="adminCreateTeamBtn">Create Team</button>' +
|
||||
'<button class="btn-small" id="adminCancelTeamBtn">Cancel</button>' +
|
||||
'</div>' +
|
||||
'</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">← Back</button>' +
|
||||
'<h4 id="adminTeamDetailName" style="margin:0"></h4>' +
|
||||
'</div>' +
|
||||
'<div style="margin-bottom:12px">' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminTeamPrivatePolicy"> Require private providers (BYOK)</label>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminTeamAllowProviders" checked> Allow team providers</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="adminAddGroupForm" class="admin-inline-form" style="display:none">' +
|
||||
'<h4>Create Group</h4>' +
|
||||
'<div class="form-group"><label>Name</label><input type="text" id="adminGroupName" placeholder="Group name"></div>' +
|
||||
'<div class="form-group"><label>Description</label><input type="text" id="adminGroupDesc" placeholder="Optional description"></div>' +
|
||||
'<div class="form-row">' +
|
||||
'<div class="form-group"><label>Scope</label><select id="adminGroupScope"><option value="global">Global</option><option value="team">Team</option></select></div>' +
|
||||
'<div class="form-group" id="adminGroupTeamRow" style="display:none"><label>Team</label><select id="adminGroupTeam"></select></div>' +
|
||||
'</div>' +
|
||||
'<div class="form-row" style="margin-top:8px">' +
|
||||
'<button class="btn-small btn-primary" id="adminCreateGroupBtn">Create Group</button>' +
|
||||
'<button class="btn-small" id="adminCancelGroupBtn">Cancel</button>' +
|
||||
'</div>' +
|
||||
'</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">← 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="admin-list"></div>' +
|
||||
'<div id="adminAddProviderForm" class="admin-inline-form" style="display:none"></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>' +
|
||||
'<div id="adminAddPersonaForm" class="admin-inline-form" style="display:none"></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"><h4>Registration</h4>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminRegToggle"> Allow new user registration</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"><h4>System Prompt</h4>' +
|
||||
'<textarea id="adminSystemPrompt" rows="4" placeholder="Global system prompt prepended to all chats..."></textarea>' +
|
||||
'</div>' +
|
||||
'<div class="settings-section"><h4>Default Model</h4>' +
|
||||
'<select id="adminDefaultModel"><option value="">-- None (first visible) --</option></select>' +
|
||||
'</div>' +
|
||||
'<div class="settings-section"><h4>Policies</h4>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminUserProvidersToggle"> Allow BYOK (user API keys)</label>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminUserPersonasToggle"> Allow user personas</label>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminKBDirectAccessToggle"> Allow direct KB access (non-persona)</label>' +
|
||||
'</div>' +
|
||||
'<div class="settings-section"><h4>Environment Banner</h4>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminBannerEnabled"> Show environment banner</label>' +
|
||||
'<div id="bannerConfigFields" style="display:none;margin-top:8px">' +
|
||||
'<div class="form-group"><label>Preset</label><select id="adminBannerPreset"><option value="">Custom</option></select></div>' +
|
||||
'<div class="form-group"><label>Text</label><input type="text" id="adminBannerText" placeholder="DEVELOPMENT"></div>' +
|
||||
'<div class="form-group"><label>Position</label>' +
|
||||
'<select id="adminBannerPosition"><option value="both">Top & Bottom</option><option value="top">Top</option><option value="bottom">Bottom</option></select>' +
|
||||
'</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"><h4>Web Search</h4>' +
|
||||
'<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"><h4>Auto-Compaction</h4>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminCompactionEnabled"> Enable auto-compaction</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"><h4>Memory Extraction</h4>' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminMemoryExtractionEnabled"> Enable memory extraction</label>' +
|
||||
'<div id="memoryExtractionConfigFields" style="display:none;margin-top:8px">' +
|
||||
'<label class="checkbox-label"><input type="checkbox" id="adminMemoryAutoApprove"> Auto-approve extracted memories</label>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="settings-section"><h4>Encryption Vault</h4>' +
|
||||
'<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="checkbox-label"><input type="checkbox" id="extInstallEnabled" checked> Enabled</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 style="margin-bottom:12px"><button class="btn-small" id="adminHealthRefreshBtn">Refresh</button></div>' +
|
||||
'<div id="adminHealthContent"><div class="loading">Loading...</div></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="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">← Prev</button>' +
|
||||
'<span id="auditPageInfo" class="text-muted" style="font-size:12px"></span>' +
|
||||
'<button class="btn-small" id="auditNextBtn">Next →</button>' +
|
||||
'</div>';
|
||||
|
||||
SCAFFOLDING.stats =
|
||||
'<div id="adminStats"><div class="loading">Loading...</div></div>';
|
||||
|
||||
|
||||
// === Add button actions ==============================================
|
||||
|
||||
var ADD_ACTIONS = {
|
||||
users: function() {
|
||||
var f = document.getElementById('adminAddUserForm');
|
||||
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
|
||||
},
|
||||
teams: function() {
|
||||
var f = document.getElementById('adminAddTeamForm');
|
||||
if (f) f.style.display = '';
|
||||
var n = document.getElementById('adminTeamName');
|
||||
if (n) n.focus();
|
||||
},
|
||||
providers: function() {
|
||||
var f = document.getElementById('adminAddProviderForm');
|
||||
if (typeof UI !== 'undefined' && UI._adminProvForm) UI._adminProvForm.setCreateMode();
|
||||
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
|
||||
},
|
||||
personas: function() {
|
||||
if (typeof ensureAdminPersonaForm === 'function') {
|
||||
var form = ensureAdminPersonaForm();
|
||||
if (form) { form.setSubmitLabel('Create'); form.clearForm(); }
|
||||
}
|
||||
var f = document.getElementById('adminAddPersonaForm');
|
||||
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
|
||||
},
|
||||
groups: function() {
|
||||
var f = document.getElementById('adminAddGroupForm');
|
||||
if (f) f.style.display = f.style.display === 'none' ? '' : 'none';
|
||||
},
|
||||
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();
|
||||
|
||||
// -- Call the section data loader --------
|
||||
if (typeof ADMIN_LOADERS !== 'undefined' && ADMIN_LOADERS[section]) {
|
||||
ADMIN_LOADERS[section]();
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user