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>
320 lines
12 KiB
JavaScript
Executable File
320 lines
12 KiB
JavaScript
Executable File
// Universal Toggle Manager - Complete solution for all config toggles
|
||
class ToggleManager {
|
||
constructor() {
|
||
this.toggles = new Map();
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
//console.log('ToggleManager: Initializing...');
|
||
|
||
// Auto-discover all toggle configurations
|
||
this.discoverToggles();
|
||
//console.log('ToggleManager: Discovered', this.toggles.size, 'toggle configurations');
|
||
|
||
// Debug: Log discovered toggles
|
||
if (this.toggles.size > 0) {
|
||
//console.log('ToggleManager: No toggles found - will retry after config loads...');
|
||
// Set up a mutation observer to detect when config content is added
|
||
this.setupContentObserver();
|
||
} else {
|
||
//console.log('ToggleManager: Discovered toggles:', Array.from(this.toggles.keys()));
|
||
}
|
||
|
||
// Also set up observer in case more toggles are added later
|
||
this.setupContentObserver();
|
||
|
||
// Retry discovery after a short delay to handle timing issues
|
||
setTimeout(() => {
|
||
//console.log('ToggleManager: Retrying toggle discovery...');
|
||
this.rediscoverToggles();
|
||
}, 100);
|
||
}
|
||
|
||
// Set up observer to detect when config content is loaded
|
||
setupContentObserver() {
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.type === 'childList') {
|
||
// Check if any new elements with data-toggle-config were added
|
||
const newToggles = Array.from(mutation.addedNodes).filter(node => node.nodeType === Node.ELEMENT_NODE && (node.dataset?.toggleConfig || node.querySelector('[data-toggle-config]')));
|
||
|
||
if (newToggles.length > 0) {
|
||
//console.log('ToggleManager: New toggle elements detected, re-discovering...');
|
||
this.rediscoverToggles();
|
||
observer.disconnect(); // Stop observing once we find toggles
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// Start observing the main content area
|
||
const contentContainer = document.getElementById('main-content') || document.querySelector('.main');
|
||
if (contentContainer) {
|
||
observer.observe(contentContainer, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
}
|
||
}
|
||
|
||
// Re-discover toggles (called after content is loaded)
|
||
rediscoverToggles() {
|
||
//console.log('ToggleManager: Re-discovering toggles...');
|
||
this.toggles.clear(); // Clear existing toggles
|
||
this.discoverToggles();
|
||
//console.log('ToggleManager: Re-discovered', this.toggles.size, 'toggle configurations');
|
||
|
||
if (this.toggles.size > 0) {
|
||
//console.log('ToggleManager: Successfully discovered toggles:', Array.from(this.toggles.keys()));
|
||
}
|
||
}
|
||
|
||
// Auto-discover toggle configurations from the page
|
||
discoverToggles() {
|
||
//console.log('ToggleManager: Looking for elements with [data-toggle-config]...');
|
||
|
||
// Find all elements with data-toggle-config attribute
|
||
const toggleElements = document.querySelectorAll('[data-toggle-config]');
|
||
//console.log('ToggleManager: Found', toggleElements.length, 'elements with data-toggle-config');
|
||
|
||
toggleElements.forEach((element, index) => {
|
||
const config = element.dataset.toggleConfig;
|
||
const sectionId = element.dataset.sectionId;
|
||
const toggleType = element.dataset.toggleType || 'checkbox';
|
||
|
||
//console.log(`ToggleManager: Processing element ${index}:`, {
|
||
//config: config,
|
||
//sectionId: sectionId,
|
||
//toggleType: toggleType,
|
||
//element: element.tagName + (element.id ? '#' + element.id : '') + (element.name ? '[name=' + element.name + ']' : '')
|
||
//});
|
||
|
||
if (config && sectionId) {
|
||
this.toggles.set(config, {
|
||
config: config,
|
||
sectionId: sectionId,
|
||
toggleType: toggleType,
|
||
element: element
|
||
});
|
||
//console.log(`ToggleManager: Registered toggle for config: ${config}`);
|
||
} else {
|
||
//console.log(`ToggleManager: Skipping element - missing config or sectionId`);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Universal toggle function - works for any config option
|
||
toggle(configKey, isEnabled) {
|
||
//console.log('=== TOGGLE MANAGER DEBUG ===');
|
||
//console.log('configKey:', configKey);
|
||
//console.log('isEnabled:', isEnabled);
|
||
|
||
const toggle = this.toggles.get(configKey);
|
||
if (!toggle) {
|
||
console.error('ToggleManager: No toggle found for config:', configKey);
|
||
return false;
|
||
}
|
||
|
||
//console.log('Toggle found:', toggle);
|
||
|
||
const sectionContent = document.getElementById(toggle.sectionId);
|
||
const fields = sectionContent?.querySelectorAll('.config-fields input, .config-fields select, .config-fields textarea');
|
||
|
||
//console.log('sectionContent found:', !!sectionContent);
|
||
//console.log('fields found:', fields ? fields.length : 0);
|
||
|
||
if (sectionContent && fields) {
|
||
if (isEnabled) {
|
||
//console.log('Enabling section...');
|
||
sectionContent.classList.remove('hidden');
|
||
fields.forEach((field, index) => {
|
||
//console.log(`Enabling field ${index}:`, field);
|
||
field.disabled = false;
|
||
const fieldGroup = field?.closest('.field-group');
|
||
if (fieldGroup) {
|
||
fieldGroup.style.opacity = '1';
|
||
fieldGroup.style.pointerEvents = 'auto';
|
||
}
|
||
});
|
||
} else {
|
||
//console.log('Disabling section...');
|
||
sectionContent.classList.add('hidden');
|
||
fields.forEach((field, index) => {
|
||
//console.log(`Disabling field ${index}:`, field);
|
||
field.disabled = true;
|
||
const fieldGroup = field?.closest('.field-group');
|
||
if (fieldGroup) {
|
||
fieldGroup.style.opacity = '0.5';
|
||
fieldGroup.style.pointerEvents = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
//console.log('=== TOGGLE MANAGER SUCCESS ===');
|
||
return true;
|
||
} else {
|
||
console.error('ToggleManager: Section content or fields not found');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Universal toggle section renderer - works for ANY config option
|
||
static renderToggleSection(configKey, configItems, displaySubcategory, subcategoryDescription, config) {
|
||
const isEnabled = configKey.value === 'true' || configKey.value === 'git';
|
||
const sectionId = `${configKey.key.replace(/[^a-zA-Z0-9]/g, '-')}-${configKey.key}`;
|
||
const toggleId = `${configKey.key.toLowerCase()}-toggle`;
|
||
|
||
// Determine toggle type and class based on config key
|
||
let toggleType = 'checkbox';
|
||
let toggleClass = 'generic-master-toggle';
|
||
|
||
if (configKey.key.includes('INSTALL_MODE')) {
|
||
toggleType = 'select';
|
||
toggleClass = 'git-master-toggle';
|
||
} else if (configKey.key.includes('MAIL')) {
|
||
toggleType = 'checkbox';
|
||
toggleClass = 'mail-master-toggle';
|
||
} else if (configKey.key.includes('BACKUP_REMOTE_')) {
|
||
// Specifically target BACKUP_REMOTE_1_ENABLED, BACKUP_REMOTE_2_ENABLED, etc.
|
||
toggleType = 'checkbox';
|
||
toggleClass = 'backup-remote-toggle';
|
||
}
|
||
|
||
let html = `
|
||
<div class="config-category">
|
||
<div class="domains-wrapper">
|
||
<div class="domains-header">
|
||
<div>
|
||
<h3>${displaySubcategory}</h3>
|
||
<p class="category-description">${subcategoryDescription}</p>
|
||
</div>
|
||
</div>
|
||
<div class="domains-divider"></div>
|
||
<div class="${toggleClass}">
|
||
<div class="field-group">
|
||
`;
|
||
|
||
if (toggleType === 'select') {
|
||
// Git/Select toggle
|
||
html += `
|
||
<label for="${toggleId}">
|
||
${configKey.title || 'Install Mode'}
|
||
<span class="tooltip" title="${configKey.description || 'Choose installation mode'}">ℹ️</span>
|
||
</label>
|
||
<select id="${toggleId}"
|
||
name="${configKey.key}"
|
||
class="form-control"
|
||
data-toggle-config="${configKey.key}"
|
||
data-section-id="section-content-${sectionId}"
|
||
data-toggle-type="select"
|
||
onchange="window.toggleManager.toggle('${configKey.key}', this.value === 'git')">
|
||
${window.ConfigOptions?.getSelectOptions(configKey.key)?.map(opt =>
|
||
`<option value="${opt.value}" ${opt.value === configKey.value ? 'selected' : ''}>${opt.label}</option>`
|
||
).join('') || ''}
|
||
</select>
|
||
`;
|
||
} else {
|
||
// Checkbox toggle
|
||
html += `
|
||
<label class="checkbox-label ${toggleClass}">
|
||
<input type="checkbox"
|
||
id="${toggleId}"
|
||
name="${configKey.key}"
|
||
value="true"
|
||
${isEnabled ? 'checked' : ''}
|
||
data-toggle-config="${configKey.key}"
|
||
data-section-id="section-content-${sectionId}"
|
||
data-toggle-type="checkbox"
|
||
onchange="window.toggleManager.toggle('${configKey.key}', this.checked)">
|
||
<span class="checkbox-custom"></span>
|
||
<span class="checkbox-text">
|
||
${configKey.title || 'Enable Configuration'}
|
||
<span class="tooltip" title="${configKey.description || 'Enable this configuration option'}">ℹ️</span>
|
||
</span>
|
||
</label>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
</div>
|
||
</div>
|
||
<div id="section-content-${sectionId}" class="git-section-content ${isEnabled ? '' : 'hidden'}">
|
||
<div class="config-fields">
|
||
`;
|
||
|
||
// Add all other fields (excluding the toggle key itself)
|
||
configItems.filter(item => item.key !== configKey.key).forEach(item => {
|
||
const fieldId = `config-${item.key}`;
|
||
html += window.ConfigShared?.generateField(fieldId, item.key, item.value, item.title, item.description, item.options, config) || '';
|
||
});
|
||
|
||
// Add special buttons for specific config types
|
||
if (configKey.key.includes('MAIL')) {
|
||
html += `
|
||
<div class="field-group">
|
||
<button type="button" class="test-connection-btn" onclick="window.configManager?.testMailConnection('${configKey.key}')">
|
||
<span class="test-icon">📧</span>
|
||
<span class="test-text">Test Mail Connection</span>
|
||
</button>
|
||
<div id="mail-test-result" class="test-result" style="display: none;"></div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
html += `
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="spacer spacer-lg"></div>
|
||
</div>
|
||
`;
|
||
|
||
return html;
|
||
}
|
||
|
||
// Register a new toggle configuration
|
||
register(configKey, sectionId, toggleType = 'checkbox') {
|
||
this.toggles.set(configKey, {
|
||
config: configKey,
|
||
sectionId: sectionId,
|
||
toggleType: toggleType
|
||
});
|
||
}
|
||
|
||
// Get all registered toggles
|
||
getToggles() {
|
||
return Array.from(this.toggles.keys());
|
||
}
|
||
|
||
// Check if a toggle exists
|
||
hasToggle(configKey) {
|
||
return this.toggles.has(configKey);
|
||
}
|
||
}
|
||
|
||
// Global instance
|
||
window.toggleManager = new ToggleManager();
|
||
// Make static method available on instance too
|
||
window.toggleManager.renderToggleSection = ToggleManager.renderToggleSection;
|
||
|
||
// Add method to manually trigger discovery when config is loaded
|
||
window.toggleManager.forceRediscover = function() {
|
||
//console.log('ToggleManager: Force re-discovering toggles...');
|
||
window.toggleManager.rediscoverToggles();
|
||
};
|
||
|
||
// Static access for backward compatibility
|
||
window.ConfigShared = window.ConfigShared || {};
|
||
window.ConfigShared.toggleSection = function(sectionId, isEnabled) {
|
||
// Try to find the config key from the section
|
||
const toggleElements = document.querySelectorAll(`[data-section-id="${sectionId}"]`);
|
||
if (toggleElements.length > 0) {
|
||
const configKey = toggleElements[0].dataset.toggleConfig;
|
||
return window.toggleManager.toggle(configKey, isEnabled);
|
||
} else {
|
||
console.error('ConfigShared.toggleSection: No toggle found for section:', sectionId);
|
||
return false;
|
||
}
|
||
};
|