// Config Manager - Main orchestrator for modular config system if (typeof window.ConfigManager === 'undefined') { //console.log('ConfigManager: Defining new ConfigManager class...'); class ConfigManager { constructor() { this.core = new ConfigCore(); this.domainManager = new DomainManager(); this.whitelistManager = new IPWhitelistManager(); this.renderer = new ConfigRenderer(); this.sidebar = new ConfigSidebar(); this.form = new ConfigForm(); this.utils = new ConfigUtils(); // Expose IPWhitelistManager globally for wrapper functions window.IPWhitelistManager = this.whitelistManager; } async renderConfig(category) { //console.log('ConfigManager: Rendering ' + category + ' config...'); const configSection = document.getElementById('config-section'); if (!configSection) { console.error('ConfigManager: config-section element not found'); return; } // The sidebar and page headers read window.configData. Load it up front so // populateSidebar() has categories on a cold admin visit (e.g. straight // from the dashboard); otherwise the overview/tools branches below render // an empty sidebar until something else populates configData. Cached after // the first call, so the config-category path below is a cache hit. try { await this.core.loadConfig(category); } catch (e) {} // Overview is the Admin landing — an ops/health board, not a config form. if (category === 'overview') { try { this.sidebar.populateSidebar(); } catch (e) {} if (typeof AdminOverview !== 'undefined') { window.adminOverview = new AdminOverview('config-section'); await window.adminOverview.init(); } else { configSection.innerHTML = '
Admin overview failed to load.
'; } return; } // SSH Access is an admin tool page that lives in this sidebar rather than // a config category — render its own controller into the main pane. if (category === 'ssh-access') { try { this.sidebar.populateSidebar(); } catch (e) {} if (typeof SshPage !== 'undefined') { window.sshPage = new SshPage('config-section'); await window.sshPage.init(); } else { configSection.innerHTML = '
SSH Access page failed to load.
'; } return; } // System is an admin tool page (live host + per-app statistics) with its // own controller, like SSH Access above. if (category === 'system') { try { this.sidebar.populateSidebar(); } catch (e) {} if (typeof AdminSystem !== 'undefined') { window.adminSystem = new AdminSystem('config-section'); await window.adminSystem.init(); } else { configSection.innerHTML = '
System page failed to load.
'; } return; } try { // Show loading state with enhanced box styling configSection.innerHTML = `
Loading configuration...
${this.core.getRandomLoadingMessage()}
`; // Load configuration data const configData = await this.core.loadConfig(category); // Populate sidebar with categories this.sidebar.populateSidebar(); if (Object.keys(configData).length === 0) { configSection.innerHTML = '

No Configuration Available

No configuration items found for this category.

'; return; } // Render configuration sections var formHTML = ''; var self = this; // Preserve 'this' context // Features page is system-level — add a Danger Zone header at the // top so it's visually obvious before the user touches anything. // Reuses the same `.danger-zone-section` / `.danger-zone-header` // styling used elsewhere, but without the advanced/unused toggle // tickboxes that live inside the normal danger zone — this is just // the heading. if (category === 'features') { formHTML += '

⚠️ Danger Zone

These options are for advanced users and may affect system stability

'; // Divider below the features Danger Zone banner, separating it from // the feature fields — same rule used elsewhere in the config form. formHTML += '
'; } //console.log('ConfigManager: About to process configData entries:', Object.keys(configData)); // Filter subcategories by type const subcategoryTypes = this.utils.filterSubcategoriesByType(configData, category); // Render regular subcategories for (const subcategoryName of subcategoryTypes.regular) { const subcategoryData = configData[subcategoryName]; //console.log('ConfigManager: Processing regular subcategory:', subcategoryName, 'data:', subcategoryData); if (typeof subcategoryData === 'object' && subcategoryData !== null) { //console.log('ConfigManager: Calling renderSubcategory for:', subcategoryName); formHTML += await self.renderSubcategory.call(self, category, subcategoryName, subcategoryData); } } // Render advanced and unused sections formHTML = await this.utils.renderSectionedContent(formHTML, subcategoryTypes.advanced, subcategoryTypes.unused, self, category, configData); //console.log('ConfigManager: Final formHTML length:', formHTML.length); if (formHTML) { // Page-level header for the config section. Mirrors the // .backup-page-header used on /backup so /config gets the same // prominent H1 + description above the form fields. Looked up from // window.configData.categories[category] so titles/descriptions // come straight from the .category metadata file. var catMeta = (window.configData && window.configData.categories && window.configData.categories[category]) || {}; var catTitle = catMeta.title || (typeof ConfigShared !== 'undefined' && ConfigShared.formatCategoryName ? ConfigShared.formatCategoryName(category) : category); var catDesc = catMeta.description || ''; var catIcon = catMeta.icon || category; var headerHTML = ''; configSection.innerHTML = headerHTML + '
' + formHTML + '
' + '' + '' + '
'; // Wire the submit event so it dispatches the config-update task // instead of letting the browser fall back to a GET that dumps every // CFG value (including passwords) into the URL. if (this.form && typeof this.form.attachSubmitHandler === 'function') { this.form.attachSubmitHandler(); } } //console.log('ConfigManager: Successfully rendered ' + category + ' config'); // Force rediscover toggles to handle timing issues if (window.toggleManager && window.toggleManager.forceRediscover) { setTimeout(() => { window.toggleManager.forceRediscover(); }, 200); } } catch (error) { console.error('ConfigManager: Error rendering ' + category + ' config: ', error); configSection.innerHTML = '

Error Loading Configuration

Failed to load configuration: ' + error.message + '

'; } } async renderSubcategory(category, subcategoryName, subcategoryData) { //console.log('ConfigManager: renderSubcategory() called - category: ' + category + ', subcategory: ' + subcategoryName); var displaySubcategory = this.utils.formatSubcategoryName(subcategoryName); // Strip the parent-category prefix from the display title so the user // sees "Basic" instead of "General Basic" while on the General page. if (typeof ConfigShared !== 'undefined' && ConfigShared.stripCategoryPrefix) { displaySubcategory = ConfigShared.stripCategoryPrefix(displaySubcategory, category); } var subcategoryDescription = this.utils.cleanDescription(subcategoryData.description || ''); // The subcategoryData IS the config items, not a container for them var configItems = []; // Look for actual config items in the main config object if (window.configData && window.configData.config) { Object.entries(window.configData.config).forEach(function([configKey, configValue]) { if (configValue.subcategory === subcategoryName) { configItems.push({ key: configKey, title: configValue.title, description: configValue.description, value: configValue.value, options: configValue.options, master: configValue.master, subcategory: configValue.subcategory }); } }); } //console.log('ConfigManager: Processing subcategory:', subcategoryName, 'data:', subcategoryData); //console.log('ConfigManager: configItems count: ' + configItems.length); //console.log('ConfigManager: All config items keys:', configItems.map(item => item.key)); if (configItems.length === 0) { //console.log('ConfigManager: No config items, returning empty string'); return ''; } //console.log('ConfigManager: renderSubcategory called with:', { //category, //subcategoryName, //displaySubcategory, //hasData: !!subcategoryData //}); // Check for master toggle in this subcategory var masterKey = configItems.find(function(item) { return item.master === true; }); //console.log('ConfigManager: masterKey found: ' + !!masterKey, masterKey ? masterKey.key : null); // Look for any ENABLED options and use universal toggle renderer var enabledKey = configItems.find(function(item) { //console.log('Checking item for ENABLED:', item.key, item.key.includes('ENABLED')); return item.key.includes('ENABLED') || item.key === 'CFG_INSTALL_MODE'; }); //console.log('ConfigManager: enabledKey found: ' + !!enabledKey, enabledKey ? enabledKey.key : null); // Special handling for domains section var isDomains = subcategoryName.includes('domains') || subcategoryName.includes('network_domains'); //console.log('ConfigManager: isDomains:', isDomains); // Special handling for IP whitelist section var isWhitelist = subcategoryName === 'network_whitelist' || subcategoryName.includes('whitelist'); //console.log('ConfigManager: subcategoryName:', subcategoryName, 'isWhitelist:', isWhitelist); var resultHTML = ''; if (isDomains) { //console.log('ConfigManager: Using domains renderer'); // Render domains section with special handling resultHTML = await this.domainManager.renderDomainsSection(configItems, displaySubcategory, subcategoryDescription); } else if (isWhitelist) { //console.log('ConfigManager: Using whitelist renderer'); resultHTML = await this.whitelistManager.renderWhitelistSection(configItems, displaySubcategory, subcategoryDescription); } else if (enabledKey) { //console.log('ConfigManager: Using universal toggle renderer'); // Use universal toggle renderer for any ENABLED option or CFG_INSTALL_MODE resultHTML = window.toggleManager ? window.toggleManager.renderToggleSection(enabledKey, configItems, displaySubcategory, subcategoryDescription) : ''; } else if (masterKey) { //console.log('ConfigManager: Using master toggle renderer'); // Render with master toggle resultHTML = this.renderer.renderSubcategoryWithMaster(masterKey, configItems, displaySubcategory, subcategoryDescription); } else { //console.log('ConfigManager: Using regular renderer'); // Render regular subcategory resultHTML = this.renderer.renderSubcategorySection(configItems, displaySubcategory, subcategoryDescription); } //console.log('ConfigManager: resultHTML length:', resultHTML.length); return resultHTML; } // Delegate form operations to ConfigForm resetForm() { return this.form.resetForm(); } // Domain management methods addDomain() { return window.domainManager.addDomain(); } deleteDomain(domainKey, buttonElement) { return window.domainManager.deleteDomain(domainKey, buttonElement); } async saveConfig() { return await this.form.saveConfig(); } showNotification(message, type) { return this.form.showNotification(message, type); } } // Export to global scope window.ConfigManager = ConfigManager; } else { //console.log('ConfigManager: Already exists, using existing instance'); }