diff --git a/containers/libreportal/frontend/js/components/app/apps-manager.js b/containers/libreportal/frontend/js/components/app/apps-manager.js index e87f6bd..183b812 100755 --- a/containers/libreportal/frontend/js/components/app/apps-manager.js +++ b/containers/libreportal/frontend/js/components/app/apps-manager.js @@ -997,72 +997,52 @@ class AppsManager { let tabsHTML = ''; let contentHTML = ''; - let firstTab = null; - + // Sort categories by order const sortedCategories = Object.entries(categories) .sort(([,a], [,b]) => a.order - b.order); - - // Find first tab with fields + + // Render each category's fields up front and keep only the ones that + // actually produced fields. A "hasFields" heuristic used to gate the tabs + // separately, but it drifted from what generateConfigFields really emits — + // so a category like Network could pass the check yet render empty, leaving + // a blank tab whose body just reads "No configuration options available". + // Trusting the rendered output keeps tabs and content in lockstep. + const renderedCategories = []; for (const [key, category] of sortedCategories) { - - const hasFields = Object.entries(fieldMappings).some(([fieldKey, fieldConfig]) => { - if (fieldConfig.category === key) { - const cfgKey = this.findMatchingCFGKey(fieldKey, appConfig); - return cfgKey && appConfig.hasOwnProperty(cfgKey); - } - return false; - }); - - if (hasFields) { - firstTab = key; - break; - } + const content = await this.generateConfigFields(key, appData); + if (!content || content.includes('class="no-fields"')) continue; + renderedCategories.push({ key, category, content }); } - - // Use preferred category if available and valid, otherwise use firstTab - const activeTab = preferredCategory && categories[preferredCategory] ? preferredCategory : firstTab; - - // Generate tabs and content together - for (const [key, category] of sortedCategories) { - const hasFields = Object.entries(fieldMappings).some(([fieldKey, fieldConfig]) => { - if (fieldConfig.category === key) { - const cfgKey = this.findMatchingCFGKey(fieldKey, appConfig); - return cfgKey && appConfig.hasOwnProperty(cfgKey); - } - return false; - }); - - if (hasFields) { - const isActive = key === activeTab ? 'active' : ''; - - // Generate tab button - tabsHTML += ` + + // Use the preferred category if it's one that has fields, else the first. + const activeTab = (preferredCategory && renderedCategories.some(c => c.key === preferredCategory)) + ? preferredCategory + : (renderedCategories[0] ? renderedCategories[0].key : null); + + for (const { key, category, content } of renderedCategories) { + const isActive = key === activeTab ? 'active' : ''; + + tabsHTML += ` `; - - // Generate content panel - //// // console.log(`🔧 Generating content for category: ${key}`); - const categoryContent = await this.generateConfigFields(key, appData); - - contentHTML += ` + + contentHTML += `

${category.icon} ${category.name}

${category.description}

- ${categoryContent} + ${content}
`; - } } - - //// // console.log('✅ Tabs and content generated successfully'); + return { tabsHTML, contentHTML }; } diff --git a/containers/libreportal/frontend/js/components/config/config-utils.js b/containers/libreportal/frontend/js/components/config/config-utils.js index f897b0d..8d9f2af 100755 --- a/containers/libreportal/frontend/js/components/config/config-utils.js +++ b/containers/libreportal/frontend/js/components/config/config-utils.js @@ -5,6 +5,13 @@ class ConfigUtils { } formatSubcategoryName(subcategoryName) { + // Display-name overrides where the derived title isn't specific enough. + // The underlying file/key names (e.g. backup_advanced, CFG_BACKUP_*) are + // untouched, so saved values are unaffected. + const overrides = { + backup_advanced: 'Engine' + }; + if (overrides[subcategoryName]) return overrides[subcategoryName]; return subcategoryName.replace(/_/g, ' ').replace(/\b\w/g, function(l) { return l.toUpperCase(); }); }