What this delivers (Stage 1+2 of the dev-mode feature):
1. New `**DEV**` marker for config fields. Mirrors the existing
`**ADVANCED**` pattern: stays in the description string, frontend
strips it for display, presence flips a 'hide unless dev mode is on'
behaviour. Implemented in ConfigUtils.cleanDescription /
isDevField / isDevModeOn and in ConfigShared._filterDevKeys, which
the two generateFieldsForCategory* helpers now call before rendering.
2. New CFG_DEV_MODE field in configs/general/general_install. Visible
under Advanced; defaults to false. The canonical place to toggle
dev mode (the WebUI easter egg writes to it, the auto-detector
writes to it, and users can flip it directly here too).
3. Marked CFG_INSTALL_MODE and CFG_RELEASE_CHANNEL with `**DEV**`.
Normal users no longer see either field — they install Release-
Stable and that's the whole story. Devs see both with the
user-facing labels you asked for:
CFG_INSTALL_MODE Release - Stable | Git clone | Local folder
CFG_RELEASE_CHANNEL Release - Stable | Release - Bleeding Edge
(CFG_INSTALL_MODE label for the release option also renamed to match.)
4. 10-click LibrePortal-logo easter egg in topbar.js:
- Counter on any .libreportal-logo click; idle-reset after 3 s
- Toast countdown from click 6 ('4 clicks away from being a developer…')
- At 10: toggles CFG_DEV_MODE via the standard config_update task
(same path the Config form uses); shows '🛠️ Developer mode
unlocked. Reload to see the extra options.'
- Re-using the same logo when dev mode is on toggles it back off
('… away from disabling developer mode') — symmetric, no separate UI
5. Auto-detect: on every WebUI load, if CFG_INSTALL_MODE is git or
local AND CFG_DEV_MODE is off, auto-flip to on with a one-time
toast 'Developer mode auto-enabled — you're on a git install.
Click the LibrePortal logo 10× to disable.' Stops dev-install
users getting locked out of the very options they need to manage
their install. Idempotent — runs once per page load; no-op if
already on or on release.
Disable surfaces: (a) CFG_DEV_MODE in Advanced on the Config form is
the canonical toggle; (b) 10 more logo clicks. A 3rd surface (a System
page banner) is deferred — those two cover the practical cases.
Signed-off-by: librelad <librelad@digitalangels.vip>
133 lines
5.9 KiB
JavaScript
Executable File
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;
|