`;
} else if (key === 'CFG_SWAPFILE_SIZE') {
// Extract numeric value from "2G" format
let numericValue = value.replace(/[^0-9.]/g, '');
fieldHTML += `
GB
`;
} else if (key.includes('SIZE') || key.includes('LENGTH') || key.includes('CHECK') || key.includes('MTU') || key.includes('PORT')) {
let min = '';
let max = '';
if (key.includes('PORT')) {
min = '1';
max = '65535';
} else if (key.includes('SIZE') || key.includes('LENGTH')) {
min = '0';
max = key.includes('PASS_LENGTH') ? '128' : '';
}
fieldHTML += `
`;
} else if (key.includes('TIMEZONE')) {
// Special handling for Timezone - create comprehensive timezone dropdown
const timezoneOptions = ConfigOptions.getTimezoneOptions();
//console.log('Timezone key:', key, 'Current value:', value, 'Type:', typeof value);
//console.log('Available timezone options:', timezoneOptions.map(opt => ({value: opt.value, label: opt.label})));
fieldHTML += `
`;
//console.log('Generated timezone dropdown HTML for', key, 'with value', value);
} else if (key === 'CFG_INSTALL_MODE') {
const selectOptions = ConfigOptions.getSelectOptions(key);
fieldHTML += ``;
} else if (/^CFG_BACKUP_LOC_[0-9]+_TYPE$/.test(key)) {
const selectOptions = ConfigOptions.getSelectOptions(key);
fieldHTML += ``;
} else if (ConfigOptions.isDropdownKey(key) || (options && Object.keys(options).length > 0)) {
//console.log('=== GENERIC DROPDOWN BLOCK ENTERED for key:', key);
//console.log('Dropdown detected for key:', key);
//console.log('isDropdownKey result:', ConfigOptions.isDropdownKey(key));
//console.log('options available:', options);
const selectOptions = (options && typeof options === 'string') ? this.parseOptions(options) : ConfigOptions.getSelectOptions(key);
//console.log('selectOptions:', selectOptions);
fieldHTML += `
`;
//console.log('Generated dropdown for', key, 'with value', value);
} else if (key.includes('DESCRIPTION') || key.includes('COMMENTS') || key.includes('NOTES')) {
//console.log('Textarea detected for key:', key);
fieldHTML += `
`;
} else {
//console.log('Default text input for key:', key);
// Default text input with event handlers and options
const inputClass = className ? `form-control ${className}` : 'form-control';
const inputPlaceholder = placeholder || '';
const eventHandlers = [];
if (onchange) eventHandlers.push(`onchange="${onchange}"`);
if (oninput) eventHandlers.push(`oninput="${oninput}"`);
if (onblur) eventHandlers.push(`onblur="${onblur}"`);
const eventAttrs = eventHandlers.length > 0 ? ` ${eventHandlers.join(' ')}` : '';
fieldHTML += `
`;
}
fieldHTML += `
`;
}
// Toggle section fields (modular function)
static toggleSection(sectionId, isEnabled) {
const sectionContent = document.getElementById(`section-content-${sectionId}`);
if (!sectionContent) {
console.warn(`Section content not found: ${sectionId}`);
return;
}
const fields = sectionContent.querySelectorAll('input, select, textarea');
if (isEnabled) {
// Enable section
sectionContent.classList.remove('disabled');
fields.forEach(field => {
field.disabled = false;
const fieldGroup = field.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '1';
fieldGroup.style.pointerEvents = 'auto';
}
});
//console.log(`Section ${sectionId} enabled`);
} else {
// Disable section
sectionContent.classList.add('disabled');
fields.forEach(field => {
field.disabled = true;
const fieldGroup = field.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '0.6';
fieldGroup.style.pointerEvents = 'none';
}
});
//console.log(`Section ${sectionId} disabled`);
}
}
// Toggle section visibility (hide/show entire section)
static toggleSectionVisibility(sectionId, isEnabled) {
const sectionContent = document.getElementById(sectionId);
if (!sectionContent) {
console.warn(`Section content not found: ${sectionId}`);
return;
}
if (isEnabled) {
// Show section
sectionContent.classList.remove('hidden');
//console.log(`Section ${sectionId} shown`);
} else {
// Hide section
sectionContent.classList.add('hidden');
//console.log(`Section ${sectionId} hidden`);
}
}
// Initialize section toggles on page load
static initializeSectionToggles() {
// Find all master toggles and initialize their sections
document.querySelectorAll('[id^="config-CFG_"][onchange*="toggleSection"]').forEach(toggle => {
// Extract section ID from the onchange attribute
const onchangeAttr = toggle.getAttribute('onchange');
const match = onchangeAttr.match(/toggleSection\('([^']+)'/);
if (match) {
const sectionId = match[1];
const isEnabled = toggle.checked;
this.toggleSection(sectionId, isEnabled);
}
});
}
// Git section toggle function (moved from global scope)
static toggleGitSectionFields(isEnabled) {
// If no parameter provided, get the state from the checkbox
if (typeof isEnabled === 'undefined') {
const gitLoginCheckbox = document.getElementById('git-login-toggle');
isEnabled = gitLoginCheckbox ? gitLoginCheckbox.checked : false;
}
const gitSectionContent = document.getElementById('git-section-content');
const gitFields = gitSectionContent?.querySelectorAll('.config-fields input, .config-fields select, .config-fields textarea');
if (gitSectionContent && gitFields) {
if (isEnabled) {
gitSectionContent.classList.remove('hidden');
gitFields.forEach(field => {
field.disabled = false;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '1';
fieldGroup.style.pointerEvents = 'auto';
}
});
} else {
gitSectionContent.classList.add('hidden');
gitFields.forEach(field => {
field.disabled = true;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '0.5';
fieldGroup.style.pointerEvents = 'none';
}
});
}
}
// Force re-initialization of form to ensure proper state
setTimeout(() => {
const form = document.getElementById('config-form');
if (form) {
// Trigger a change event to update any dependent fields
const event = new Event('change', { bubbles: true });
gitSectionContent?.dispatchEvent(event);
}
}, 100);
}
// Universal toggle function for all _ENABLED options
static toggleSection(sectionId, isEnabled) {
//console.log('=== UNIVERSAL TOGGLE DEBUG ===');
//console.log('sectionId:', sectionId);
//console.log('isEnabled:', isEnabled);
const sectionContent = document.getElementById(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('=== UNIVERSAL TOGGLE DEBUG END ===');
}
// Remote backup section toggle function
static toggleMailSection(sectionId, isEnabled) {
//console.log('=== TOGGLE MAIL SECTION DEBUG ===');
//console.log('sectionId:', sectionId);
//console.log('isEnabled:', isEnabled);
alert('toggleMailSection called: ' + sectionId + ', enabled: ' + isEnabled);
//console.log('Looking for sectionContent...');
const sectionContent = document.getElementById(sectionId);
//console.log('sectionContent found:', !!sectionContent);
//console.log('Looking for mailFields...');
const mailFields = sectionContent?.querySelectorAll('.config-fields input, .config-fields select, .config-fields textarea');
//console.log('mailFields found:', mailFields ? mailFields.length : 0);
if (sectionContent && mailFields) {
//console.log('Enabling mail section...');
if (isEnabled) {
//console.log('Removing hidden class...');
sectionContent.classList.remove('hidden');
//console.log('Enabling fields...');
mailFields.forEach((field, index) => {
//console.log(`Disabling field ${index}:`, field);
field.disabled = false;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
//console.log('Enabling field group...');
fieldGroup.style.opacity = '1';
fieldGroup.style.pointerEvents = 'auto';
}
});
} else {
//console.log('Disabling mail section...');
sectionContent.classList.add('hidden');
//console.log('Disabling fields...');
mailFields.forEach((field, index) => {
//console.log(`Enabling field ${index}:`, field);
field.disabled = true;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
//console.log('Disabling field group...');
fieldGroup.style.opacity = '0.5';
fieldGroup.style.pointerEvents = 'none';
}
});
}
}
//console.log('=== TOGGLE MAIL SECTION DEBUG END ===');
}
static toggleRemoteBackupSection(sectionId, isEnabled) {
const sectionContent = document.getElementById(sectionId);
const backupFields = sectionContent?.querySelectorAll('.config-fields input, .config-fields select, .config-fields textarea');
if (sectionContent && backupFields) {
if (isEnabled) {
sectionContent.classList.remove('hidden');
backupFields.forEach(field => {
field.disabled = false;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '1';
fieldGroup.style.pointerEvents = 'auto';
}
});
} else {
sectionContent.classList.add('hidden');
backupFields.forEach(field => {
field.disabled = true;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '0.5';
fieldGroup.style.pointerEvents = 'none';
}
});
}
}
// Force re-initialization of form to ensure proper state
setTimeout(() => {
const form = document.getElementById('config-form');
if (form) {
// Trigger a change event to update any dependent fields
const event = new Event('change', { bubbles: true });
sectionContent?.dispatchEvent(event);
}
}, 100);
}
// Git section toggle function
static toggleGitSection(sectionId, isEnabled) {
const sectionContent = document.getElementById(sectionId);
const gitFields = sectionContent?.querySelectorAll('.config-fields input, .config-fields select, .config-fields textarea');
if (sectionContent && gitFields) {
if (isEnabled) {
sectionContent.classList.remove('hidden');
gitFields.forEach(field => {
field.disabled = false;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '1';
fieldGroup.style.pointerEvents = 'auto';
}
});
} else {
sectionContent.classList.add('hidden');
gitFields.forEach(field => {
field.disabled = true;
const fieldGroup = field?.closest('.field-group');
if (fieldGroup) {
fieldGroup.style.opacity = '0.5';
fieldGroup.style.pointerEvents = 'none';
}
});
}
}
// Force re-initialization of form to ensure proper state
setTimeout(() => {
const form = document.getElementById('config-form');
if (form) {
// Trigger a change event to update any dependent fields
const event = new Event('change', { bubbles: true });
sectionContent?.dispatchEvent(event);
}
}, 100);
}
// Parse options string into array of {value, label} objects
static parseOptions(options) {
if (!options || typeof options !== 'string') {
return [];
}
return options.split('|').map(opt => {
const parts = opt.split('=');
if (parts.length === 2) {
return { value: parts[0].trim(), label: parts[1].trim() };
}
return { value: opt.trim(), label: opt.trim() };
});
}
// Generate fields for category with 3-per-line layout and smart field detection
static generateFieldsForCategory(keys, category, config, generateFieldCallback = null) {
let formHTML = '
';
keys.forEach((key, index) => {
const configItem = config[key] || {};
const value = configItem.value || '';
const title = configItem.title || this.formatConfigLabel(key);
const description = configItem.description || '';
const options = configItem.options || '';
const fieldId = `config-${key}`;
// Add line break every 3 items
if (index > 0 && index % 3 === 0) {
formHTML += `
`;
}
// Use smart field creation if no callback provided, otherwise use callback
if (generateFieldCallback) {
formHTML += generateFieldCallback(fieldId, key, value, title, description, options, config);
} else {
formHTML += this.createSmartField(fieldId, key, value, title, description, {
selectOptions: options,
category: category,
layout: 'inline',
config: config
});
}
});
formHTML += `
`;
return formHTML;
}
// Generate fields for category WITHOUT the leading divider (for master toggle sections)
static generateFieldsForCategoryNoDivider(keys, category, config, generateFieldCallback = null) {
let formHTML = '';
keys.forEach((key, index) => {
const configItem = config[key] || {};
const value = configItem.value || '';
const title = configItem.title || this.formatConfigLabel(key);
const description = configItem.description || '';
const options = configItem.options || '';
const fieldId = `config-${key}`;
let fieldHTML;
if (generateFieldCallback) {
fieldHTML = generateFieldCallback(fieldId, key, value, title, description, options, config);
} else {
fieldHTML = this.generateField(fieldId, key, value, title, description, options, config);
}
formHTML += fieldHTML;
});
return formHTML;
}
// Separate categories into regular, advanced, and unused
static categorizeConfigs(config) {
const groupedConfigs = this.groupConfigKeys(config);
const categoryOrder = this.extractCategoryOrder(config);
const regularCategories = [];
const advancedCategories = [];
const unusedCategories = [];
for (const category of categoryOrder) {
const keys = groupedConfigs[category];
if (keys && keys.length > 0 && category !== 'Hidden/Unused Options') {
// Check if category has advanced items
const hasAdvanced = keys.some(key => {
const configItem = config[key] || {};
return configItem.advanced === true;
});
// Check if category has unused items
const hasUnused = keys.some(key => {
const configItem = config[key] || {};
return configItem.unused === true;
});
// Categorize based on the presence of advanced/unused flags
if (hasUnused) {
unusedCategories.push(category);
} else if (hasAdvanced) {
advancedCategories.push(category);
} else {
regularCategories.push(category);
}
}
}
return {
groupedConfigs,
categoryOrder,
regularCategories,
advancedCategories,
unusedCategories
};
}
// Generate warning notice for requirements page
static generateRequirementsWarning() {
return `
â ī¸ System Requirements Warning
Disabling any of the following system requirements may break LibrePortal functionality. Always create a backup before making changes.
`;
}
// Generate toggle controls HTML for advanced/unused sections
static generateToggleControls(hasAdvanced = false, hasUnused = false) {
if (!hasAdvanced && !hasUnused) {
return ''; // Don't show danger zone if no content
}
let formHTML = `
â ī¸ Danger Zone
These options are for advanced users and may affect system stability
`;
if (hasAdvanced) {
formHTML += `
`;
}
if (hasUnused) {
formHTML += `
`;
}
formHTML += `
`;
return formHTML;
}
// Generate advanced sections HTML
static async generateAdvancedSections(advancedCategories, groupedConfigs, config, getCategoryDescriptionCallback) {
if (advancedCategories.length === 0) {
return '';
}
let formHTML = `
`;
return formHTML;
}
// Toggle advanced sections visibility
static toggleAdvancedSections() {
const checkbox = document.getElementById('show-advanced');
const advancedSections = document.getElementById('advanced-sections');
if (advancedSections) {
advancedSections.style.display = checkbox.checked ? 'block' : 'none';
}
}
// Toggle unused sections visibility
static toggleUnusedSections() {
const checkbox = document.getElementById('show-unused');
const unusedSections = document.getElementById('unused-sections');
if (unusedSections) {
unusedSections.style.display = checkbox.checked ? 'block' : 'none';
}
}
}
// Export for global access
window.ConfigShared = ConfigShared;
// Global toggle change function for checkbox handling
window.handleToggleChange = function(checkbox, key) {
//console.log(`Toggle changed: ${key} = ${checkbox.checked}`);
// This function can be extended to handle specific toggle logic
// For now, it just logs change
};
// Global flag to prevent multiple config reloads
window.isReloadingConfig = false;
// Global function to handle CFG_INSTALL_MODE change
window.handleInstallModeChange = function(selectElement) {
// Prevent multiple simultaneous reloads
if (window.isReloadingConfig) {
//console.log('Config reload already in progress, ignoring...');
return;
}
const installMode = selectElement.value;
//console.log(`Install mode changed to: ${installMode}`);
// Set flag to prevent multiple reloads
window.isReloadingConfig = true;
// Use a longer delay and onchange instead of onblur to avoid the loop
setTimeout(() => {
if (window.configManager) {
// Clear cache to ensure we get fresh data with updated CFG_INSTALL_MODE
window.configManager.cache.clear();
//console.log('Cache cleared for fresh config data');
// Get the current category from the URL or default to 'general'
const currentCategory = window.configCategory || 'general';
//console.log(`Reloading config category: ${currentCategory}`);
window.configManager.renderConfig(currentCategory).finally(() => {
// Clear the flag after reload is complete
setTimeout(() => {
window.isReloadingConfig = false;
}, 500); // Extra delay to ensure DOM is fully settled
});
} else if (window.configRouter) {
// Fallback to configRouter if configManager is not available
const currentCategory = window.configCategory || 'general';
//console.log(`Using configRouter to reload: ${currentCategory}`);
window.configRouter.loadConfigComponentManual(currentCategory).finally(() => {
// Clear the flag after reload is complete
setTimeout(() => {
window.isReloadingConfig = false;
}, 500); // Extra delay to ensure DOM is fully settled
});
} else {
console.warn('Neither configManager nor configRouter available for install mode change');
window.isReloadingConfig = false;
}
}, 500); // Longer delay to avoid conflicts
};
// Global function to initialize git field visibility based on current CFG_INSTALL_MODE
window.initializeGitFieldVisibility = function() {
const installModeSelect = document.querySelector('select[name="CFG_INSTALL_MODE"]');
if (installModeSelect) {
// Trigger the change handler to set initial visibility
handleInstallModeChange(installModeSelect);
}
};
// Global password toggle function for onclick handlers
window.togglePasswordVisibility = function(fieldId) {
ConfigShared.togglePasswordVisibility(fieldId);
};