librelad d39852aa3d refactor(webui): reorganize into components/ + core/ taxonomy
Final modularization layout (user-chosen): every page is a self-contained
folder under components/<id>/ (controllers + CSS + its html fragment), and all
shared/framework code folds into core/:
  core/kernel  (feature-registry, lifecycle, services, spa)
  core/boot    (auth, system-loader/orchestrator, setup, loaders)
  core/lib     (data-loader, router, helpers, the task kernel, shared modules)
  core/ui      (topbar, modal, notifications, … + topbar.html)
  core/css     (all shared stylesheets)
  core/icons
Top level is now just: components/, core/, themes/, index.html (+ runtime data/).

Every path reference rewritten (index.html, scripts arrays, fetch()/
loadFragment()/loadScript() literals, system-loader + config-manager controller
paths, kernel manifest URL, feature.json, backend FEATURES_DIR). The
/api/features/list endpoint NAME is unchanged (it now scans components/).
Deleted 3 dead files (app-content.html, apps-content.html, html-cache.js).
Verified: 0 stale prefixes, 0 double-rewrites, all JS/JSON valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-30 07:13:52 +01:00

133 lines
5.9 KiB
JavaScript
Executable File

// Config Utils - Utility functions for configuration management
class ConfigUtils {
constructor() {
// No initialization needed
}
formatSubcategoryName(subcategoryName) {
return subcategoryName.replace(/_/g, ' ').replace(/\b\w/g, function(l) { return l.toUpperCase(); });
}
cleanDescription(description) {
return description
.replace(/\*\*ADVANCED\*\*/g, '')
.replace(/\*\*UNUSED\*\*/g, '')
.replace(/\*\*DEV\*\*/g, '')
.replace(/^\s+|\s+$/g, '') // Trim whitespace
.replace(/\s{2,}/g, ' '); // Replace multiple spaces with single space
}
// Per-field test for the **DEV** marker. Dev fields are hidden in the form
// unless CFG_DEV_MODE is on (unlocked via the 10-click LibrePortal-logo
// easter egg, or auto-enabled when CFG_INSTALL_MODE is git/local).
isDevField(description) {
return typeof description === 'string' && description.includes('**DEV**');
}
isDevModeOn() {
const v = (window.systemConfigs && window.systemConfigs.CFG_DEV_MODE) || 'false';
return v === 'true' || v === true;
}
filterSubcategoriesByType(configData, category) {
// Filter subcategories by category and separate into regular, advanced, and unused
var regularSubcategories = [];
var advancedSubcategories = [];
var unusedSubcategories = [];
for (const [subcategoryName, subcategoryData] of Object.entries(configData)) {
if (subcategoryData.category === category) {
if (subcategoryData.description.includes('**ADVANCED**')) {
advancedSubcategories.push(subcategoryName);
} else if (subcategoryData.description.includes('**UNUSED**')) {
unusedSubcategories.push(subcategoryName);
} else {
regularSubcategories.push(subcategoryName);
}
}
}
return {
regular: regularSubcategories,
advanced: advancedSubcategories,
unused: unusedSubcategories
};
}
async renderSectionedContent(formHTML, advancedSubcategories, unusedSubcategories, self, category, configData) {
// Add danger zone toggle controls if needed
if (advancedSubcategories.length > 0 || unusedSubcategories.length > 0) {
// Divider above the advanced/danger-zone container, mirroring the one
// above the Save/Reset buttons — separates the regular fields from the
// "Show Advanced Options" toggle.
formHTML += '<div class="config-divider"></div>';
formHTML += this.generateToggleControls(advancedSubcategories.length > 0, unusedSubcategories.length > 0);
}
// Render advanced sections (hidden by default). The old grouping
// header ("🛠️ Advanced Configuration") is gone — each subcategory
// self-identifies via the red "Advanced" badge on its title (see
// .is-advanced .domains-header h3::after in config.css).
if (advancedSubcategories.length > 0) {
formHTML += '<div id="advanced-sections" class="advanced-sections" style="display: none;">';
// Divider between the "Show Advanced Options" toggle and the advanced
// fields. It lives inside #advanced-sections so it only appears once the
// user reveals them.
formHTML += '<div class="config-divider"></div>';
for (const subcategoryName of advancedSubcategories) {
const subcategoryData = configData[subcategoryName];
if (typeof subcategoryData === 'object' && subcategoryData !== null) {
formHTML += '<div class="is-advanced">';
formHTML += await self.renderSubcategory.call(self, category, subcategoryName, subcategoryData);
formHTML += '</div>';
}
}
formHTML += '</div>';
}
// Render unused sections (hidden by default)
if (unusedSubcategories.length > 0) {
formHTML += '<div id="unused-sections" class="unused-sections" style="display: none;">';
formHTML += '<div class="section-divider"><h3>🗑️ Unused Configuration</h3><p>Deprecated or unused settings</p></div>';
for (const subcategoryName of unusedSubcategories) {
const subcategoryData = configData[subcategoryName];
//console.log('ConfigUtils: Processing unused subcategory:', subcategoryName, 'data:', subcategoryData);
if (typeof subcategoryData === 'object' && subcategoryData !== null) {
//console.log('ConfigUtils: Calling renderSubcategory for:', subcategoryName);
formHTML += await self.renderSubcategory.call(self, category, subcategoryName, subcategoryData);
}
}
formHTML += '</div>';
}
return formHTML;
}
generateToggleControls(hasAdvanced = false, hasUnused = false) {
if (!hasAdvanced && !hasUnused) {
return ''; // Don't show danger zone if no content
}
let formHTML = '<div class="danger-zone-section"><div class="danger-zone-header"><h3>⚠️ Danger Zone</h3><p>These options are for advanced users and may affect system stability</p></div><div class="config-toggles">';
if (hasAdvanced) {
formHTML += '<div class="toggle-section"><label class="checkbox-label"><input type="checkbox" id="show-advanced" onchange="ConfigShared.toggleAdvancedSections()"><span class="checkbox-custom"></span><div class="toggle-content"><span class="checkbox-text">Show Advanced Options</span><span class="checkbox-description">Display advanced configuration settings for experienced users</span></div></label></div>';
}
if (hasUnused) {
formHTML += '<div class="toggle-section"><label class="checkbox-label"><input type="checkbox" id="show-unused" onchange="ConfigShared.toggleUnusedSections()"><span class="checkbox-custom"></span><div class="toggle-content"><span class="checkbox-text">Show Unused Options</span><span class="checkbox-description">Display deprecated or unused configuration options</span></div></label></div>';
}
formHTML += '</div></div>';
return formHTML;
}
}
// Export to global scope
window.ConfigUtils = ConfigUtils;