Compare commits

..

No commits in common. "5414b3d407c1561f0bb3ebc836b604abe0220289" and "7a5f4cdc33359e014a1aec238c377e1dcb10b3fb" have entirely different histories.

30 changed files with 665 additions and 0 deletions

View File

@ -21,27 +21,33 @@ class ConfigCore {
}
async loadConfig(category) {
//console.log(`ConfigCore: Loading ${category} config...`);
if (this.cache.has('unified')) {
//console.log(`ConfigCore: Using cached unified config`);
const cachedData = this.cache.get('unified');
window.configData = cachedData; // Make available globally
return cachedData.subcategories || {};
}
try {
//console.log(`ConfigCore: Fetching from /data/config/generated/configs.json`);
const response = await fetch('/data/config/generated/configs.json');
//console.log(`ConfigCore: Response status: ${response.status}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const configData = await response.json();
//console.log(`ConfigCore: Loaded config data:`, configData);
this.cache.set('unified', configData);
window.configData = configData; // Make available globally
// Return the actual subcategories, not just the category metadata
const categoryData = configData.subcategories || {};
//console.log(`ConfigCore: Available subcategories:`, Object.keys(categoryData));
return categoryData;
} catch (error) {

View File

@ -1,5 +1,6 @@
// Config Manager - Main orchestrator for modular config system
if (typeof window.ConfigManager === 'undefined') {
//console.log('ConfigManager: Defining new ConfigManager class...');
class ConfigManager {
constructor() {
@ -16,6 +17,7 @@ if (typeof window.ConfigManager === 'undefined') {
}
async renderConfig(category) {
//console.log('ConfigManager: Rendering ' + category + ' config...');
const configSection = document.getElementById('config-section');
if (!configSection) {
@ -181,6 +183,7 @@ if (typeof window.ConfigManager === 'undefined') {
var formHTML = '';
var self = this; // Preserve 'this' context
//console.log('ConfigManager: About to process configData entries:', Object.keys(configData));
// Filter subcategories by type
const subcategoryTypes = this.utils.filterSubcategoriesByType(configData, category);
@ -188,8 +191,10 @@ if (typeof window.ConfigManager === 'undefined') {
// Render regular subcategories
for (const subcategoryName of subcategoryTypes.regular) {
const subcategoryData = configData[subcategoryName];
//console.log('ConfigManager: Processing regular subcategory:', subcategoryName, 'data:', subcategoryData);
if (typeof subcategoryData === 'object' && subcategoryData !== null) {
//console.log('ConfigManager: Calling renderSubcategory for:', subcategoryName);
formHTML += await self.renderSubcategory.call(self, category, subcategoryName, subcategoryData);
}
}
@ -197,6 +202,7 @@ if (typeof window.ConfigManager === 'undefined') {
// Render advanced and unused sections
formHTML = await this.utils.renderSectionedContent(formHTML, subcategoryTypes.advanced, subcategoryTypes.unused, self, category, configData);
//console.log('ConfigManager: Final formHTML length:', formHTML.length);
if (formHTML) {
// Page-level header for the config section. Mirrors the
@ -243,6 +249,7 @@ if (typeof window.ConfigManager === 'undefined') {
}
}
//console.log('ConfigManager: Successfully rendered ' + category + ' config');
// Force rediscover toggles to handle timing issues
if (window.toggleManager && window.toggleManager.forceRediscover) {
@ -258,6 +265,7 @@ if (typeof window.ConfigManager === 'undefined') {
}
async renderSubcategory(category, subcategoryName, subcategoryData) {
//console.log('ConfigManager: renderSubcategory() called - category: ' + category + ', subcategory: ' + subcategoryName);
var displaySubcategory = this.utils.formatSubcategoryName(subcategoryName);
// Strip the parent-category prefix from the display title so the user
@ -287,44 +295,65 @@ if (typeof window.ConfigManager === 'undefined') {
});
}
//console.log('ConfigManager: Processing subcategory:', subcategoryName, 'data:', subcategoryData);
//console.log('ConfigManager: configItems count: ' + configItems.length);
//console.log('ConfigManager: All config items keys:', configItems.map(item => item.key));
if (configItems.length === 0) {
//console.log('ConfigManager: No config items, returning empty string');
return '';
}
//console.log('ConfigManager: renderSubcategory called with:', {
//category,
//subcategoryName,
//displaySubcategory,
//hasData: !!subcategoryData
//});
// Check for master toggle in this subcategory
var masterKey = configItems.find(function(item) { return item.master === true; });
//console.log('ConfigManager: masterKey found: ' + !!masterKey, masterKey ? masterKey.key : null);
// Look for any ENABLED options and use universal toggle renderer
var enabledKey = configItems.find(function(item) {
//console.log('Checking item for ENABLED:', item.key, item.key.includes('ENABLED'));
return item.key.includes('ENABLED') || item.key === 'CFG_INSTALL_MODE';
});
//console.log('ConfigManager: enabledKey found: ' + !!enabledKey, enabledKey ? enabledKey.key : null);
// Special handling for domains section
var isDomains = subcategoryName.includes('domains') || subcategoryName.includes('network_domains');
//console.log('ConfigManager: isDomains:', isDomains);
// Special handling for IP whitelist section
var isWhitelist = subcategoryName === 'network_whitelist' || subcategoryName.includes('whitelist');
//console.log('ConfigManager: subcategoryName:', subcategoryName, 'isWhitelist:', isWhitelist);
var resultHTML = '';
if (isDomains) {
//console.log('ConfigManager: Using domains renderer');
// Render domains section with special handling
resultHTML = await this.domainManager.renderDomainsSection(configItems, displaySubcategory, subcategoryDescription);
} else if (isWhitelist) {
//console.log('ConfigManager: Using whitelist renderer');
resultHTML = await this.whitelistManager.renderWhitelistSection(configItems, displaySubcategory, subcategoryDescription);
} else if (enabledKey) {
//console.log('ConfigManager: Using universal toggle renderer');
// Use universal toggle renderer for any ENABLED option or CFG_INSTALL_MODE
resultHTML = window.toggleManager ? window.toggleManager.renderToggleSection(enabledKey, configItems, displaySubcategory, subcategoryDescription) : '';
} else if (masterKey) {
//console.log('ConfigManager: Using master toggle renderer');
// Render with master toggle
resultHTML = this.renderer.renderSubcategoryWithMaster(masterKey, configItems, displaySubcategory, subcategoryDescription);
} else {
//console.log('ConfigManager: Using regular renderer');
// Render regular subcategory
resultHTML = this.renderer.renderSubcategorySection(configItems, displaySubcategory, subcategoryDescription);
}
//console.log('ConfigManager: resultHTML length:', resultHTML.length);
return resultHTML;
}
@ -354,4 +383,5 @@ if (typeof window.ConfigManager === 'undefined') {
// Export to global scope
window.ConfigManager = ConfigManager;
} else {
//console.log('ConfigManager: Already exists, using existing instance');
}

View File

@ -5,6 +5,7 @@ class ConfigSidebar {
}
populateSidebar() {
//console.log('ConfigSidebar: Populating sidebar with categories...');
this.categoriesList = document.getElementById('config-categories-list');
if (!this.categoriesList) {
@ -156,6 +157,7 @@ class ConfigSidebar {
// Set initial active category
this.setActiveCategory(window.configCategory || 'overview');
//console.log('ConfigSidebar: Sidebar populated with ' + categoriesArray.length + ' categories');
}
setActiveCategory(categoryId) {

View File

@ -94,8 +94,10 @@ class ConfigUtils {
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);
}
}

View File

@ -35,6 +35,7 @@ window.ConfigValidator = function() {
continue;
}
//console.log(`Config file exists: ${config.name}`);
} catch (error) {
results.valid = false;

View File

@ -13,6 +13,7 @@ class DomainManager {
}
return false;
} catch (error) {
//console.log('DomainManager: Traefik status check failed, assuming not installed:', error.message);
return false;
}
}
@ -191,6 +192,7 @@ class DomainManager {
}
addNewDomain() {
//console.log('Add Domain button clicked!');
try {
// Find the highest existing domain number
@ -206,6 +208,7 @@ class DomainManager {
// Check if we've reached the maximum of 9 domains (count only existing domains, not empty slots)
if (domainNumbers.length >= 9) {
//console.log('Maximum of 9 domains reached');
return;
}
@ -255,6 +258,7 @@ class DomainManager {
console.error('DomainManager not available for deleteDomain');
return;
}
//console.log(`Delete domain button clicked for: ${domainKey}`);
try {
// Find the domain-building-block and remove it
@ -320,6 +324,7 @@ class DomainManager {
// Standalone domain management functions - immediately available
window.addDomain = function() {
//console.log('Add Domain button clicked!');
try {
// Before adding new domain, validate that all existing domains have valid format
@ -352,6 +357,7 @@ window.addDomain = function() {
// Check if we've reached the maximum of 9 domains
if (domainNumbers.length >= 9) {
//console.log('Maximum of 9 domains reached');
return;
}
@ -402,6 +408,7 @@ window.addDomain = function() {
};
window.deleteDomain = function(domainKey, buttonElement) {
//console.log(`Delete domain button clicked for: ${domainKey}`);
try {
// Find the domain-building-block and remove it
@ -499,12 +506,15 @@ function updateAddDomainButton() {
// Validate domain format when user tries to add a new domain
function validateDomainFormat(input, showNotifications = true) {
const value = input.value.trim();
//console.log('validateDomainFormat called with:', value, 'showNotifications:', showNotifications);
//console.log('window.notificationSystem available:', !!window.notificationSystem);
if (!value) {
// Clear styling for empty fields
input.style.borderColor = '#dc3545';
input.title = 'Domain cannot be empty';
if (showNotifications && window.notificationSystem) {
//console.log('Attempting to show empty domain notification');
window.notificationSystem.error('Domain cannot be empty');
} else {
console.error('Domain cannot be empty - notification system not available');
@ -527,6 +537,7 @@ function validateDomainFormat(input, showNotifications = true) {
input.style.borderColor = '#dc3545';
input.title = 'Invalid domain format (e.g., example.com)';
if (showNotifications && window.notificationSystem) {
//console.log('Attempting to show invalid format notification');
window.notificationSystem.error('Invalid domain format: "' + value + '". Please use a valid domain like example.com');
} else {
console.error('Invalid domain format - notification system not available');
@ -536,6 +547,7 @@ function validateDomainFormat(input, showNotifications = true) {
input.style.borderColor = '#dc3545';
input.title = 'Domain already exists';
if (showNotifications && window.notificationSystem) {
//console.log('Attempting to show duplicate notification');
window.notificationSystem.error('Domain "' + value + '" already exists');
} else {
console.error('Domain already exists - notification system not available');
@ -597,6 +609,7 @@ function checkForInvalidDomainFormat(input, domainValue) {
input.style.borderColor = '#dc3545';
input.title = 'Invalid domain format (e.g., example.com)';
if (window.notificationSystem) {
//console.log('Showing invalid domain format notification');
window.notificationSystem.error('Invalid domain format: "' + domainValue + '". Please use a valid domain like example.com');
} else {
console.error('Invalid domain format - notification system not available');
@ -621,6 +634,7 @@ function checkForDuplicateDomain(input, domainValue) {
input.style.borderColor = '#dc3545';
input.title = 'Domain already exists';
if (window.notificationSystem) {
//console.log('Showing duplicate domain notification');
window.notificationSystem.error('Domain "' + domainValue + '" already exists');
} else {
console.error('Domain already exists - notification system not available');

View File

@ -140,18 +140,22 @@ class IPWhitelistManager {
try {
// Get only the visible whitelist input values (exclude hidden input)
const whitelistInputs = document.querySelectorAll('input[name="CFG_IPS_WHITELIST"]:not([type="hidden"])');
//console.log('saveWhitelistEntries: Found inputs:', whitelistInputs.length);
whitelistInputs.forEach((input, index) => {
//console.log(` Input ${index}: id="${input.id}", value="${input.value}"`);
});
const values = Array.from(whitelistInputs)
.map(input => input.value.trim())
.filter(value => value.length > 0); // Filter out empty values
//console.log('saveWhitelistEntries: Filtered values:', values);
// Find the hidden CFG_IPS_WHITELIST input and update it
const hiddenInput = document.querySelector('input[name="CFG_IPS_WHITELIST"][type="hidden"]');
if (hiddenInput) {
hiddenInput.value = values.join(', ');
//console.log('Saved whitelist values:', values.join(', '));
}
} catch (error) {
console.error('Error saving whitelist entries:', error);
@ -160,6 +164,7 @@ class IPWhitelistManager {
// Validate individual whitelist entry
validateWhitelistEntry(input, showNotifications = true) {
//console.log('validateWhitelistEntry called with:', input.value, 'showNotifications:', showNotifications);
const value = input.value.trim();
@ -169,12 +174,14 @@ class IPWhitelistManager {
input.title = '';
if (!value) {
//console.log('Empty value - allowing for now');
// Empty is allowed for now (validation happens on add)
return true;
}
// Special case for localhost
if (value.toLowerCase() === 'localhost') {
//console.log('localhost detected - valid');
return true;
}
@ -188,8 +195,15 @@ class IPWhitelistManager {
const isValidDomain = domainPattern.test(value);
const isValidFormat = isValidIP || isValidDomain || value.toLowerCase() === 'localhost';
//console.log('Format validation:', {
//value: value,
//isValidIP: isValidIP,
//isValidDomain: isValidDomain,
//isValidFormat: isValidFormat
//});
if (!isValidFormat) {
//console.log('Invalid format detected - showing error');
// Flash the input field
input.style.animation = 'whitelist-flash 0.5s ease-in-out 2';
input.focus();
@ -199,8 +213,10 @@ class IPWhitelistManager {
// Show notification
if (showNotifications && window.notificationSystem) {
//console.log('Showing notification for invalid format');
window.notificationSystem.error(`Invalid whitelist format: "${value}". Please use a valid IP address, domain (e.g., example.com), or localhost`);
} else {
//console.log('Notification system not available or showNotifications is false');
}
input.title = 'Invalid IP address, domain, or localhost format (e.g., 192.168.1.100, example.com, or localhost)';
@ -209,6 +225,7 @@ class IPWhitelistManager {
// Check for duplicates
const allWhitelistInputs = document.querySelectorAll('input[name="CFG_IPS_WHITELIST"]:not([type="hidden"])');
//console.log('Checking for duplicates among', allWhitelistInputs.length, 'inputs');
const duplicates = [];
@ -221,8 +238,10 @@ class IPWhitelistManager {
}
});
//console.log('Found duplicates:', duplicates);
if (duplicates.length > 0) {
//console.log('Duplicate detected - showing error');
// Flash the input field
input.style.animation = 'whitelist-flash 0.5s ease-in-out 2';
input.focus();
@ -232,6 +251,7 @@ class IPWhitelistManager {
// Show notification
if (showNotifications && window.notificationSystem) {
//console.log('Showing notification for duplicate');
window.notificationSystem.error(`"${value}" already exists in the whitelist`);
}
@ -240,6 +260,7 @@ class IPWhitelistManager {
}
// Valid entry - save it
//console.log('Valid entry - saving');
this.saveWhitelistEntries();
return true;
}
@ -282,15 +303,18 @@ class IPWhitelistManager {
}
return false;
} catch (error) {
//console.log('Traefik check failed:', error.message);
return false;
}
}
// Add new whitelist entry
addWhitelistEntry() {
//console.log('Add whitelist entry button clicked!');
try {
// Before adding new entry, validate that all existing entries have valid format
//console.log('About to call validateBeforeAddWhitelist()');
// Test if the function exists
if (typeof this.validateBeforeAddWhitelist !== 'function') {
@ -299,11 +323,14 @@ class IPWhitelistManager {
}
const canAdd = this.validateBeforeAddWhitelist();
//console.log('validateBeforeAddWhitelist() returned:', canAdd);
if (!canAdd) {
//console.log('Validation failed - not adding new entry');
return; // Validation failed, don't add new entry
}
//console.log('Validation passed - proceeding with add');
// Find the hidden CFG_IPS_WHITELIST input
const hiddenInput = document.querySelector('input[name="CFG_IPS_WHITELIST"]');
@ -318,9 +345,12 @@ class IPWhitelistManager {
.map(entry => entry.trim())
.filter(entry => entry.length > 0); // Don't filter out empty strings for counting
//console.log('Current values:', currentValues);
//console.log('Current values length:', currentValues.length);
// Check if we've reached the maximum
if (currentValues.length >= this.maxWhitelistEntries) {
//console.log('Maximum of ' + this.maxWhitelistEntries + ' whitelist entries reached');
return;
}
@ -342,11 +372,13 @@ class IPWhitelistManager {
const highestNumber = allNumbers.length > 0 ? Math.max(...allNumbers) : 0;
const newEntryNumber = highestNumber + 1;
//console.log('Highest existing number:', highestNumber, 'New entry number:', newEntryNumber);
currentValues.push('');
// Update the hidden input
hiddenInput.value = currentValues.join(', ');
//console.log('Updated hidden input to:', hiddenInput.value);
// Create new entry HTML and add to DOM
const newEntryHTML = `
@ -374,31 +406,41 @@ class IPWhitelistManager {
// Add the new entry to the DOM
const whitelistContainer = document.querySelector('.whitelist-building-blocks');
//console.log('Step 1: Found whitelist container:', !!whitelistContainer);
if (whitelistContainer) {
// Remove empty state if it exists
const emptyState = whitelistContainer.querySelector('.whitelist-empty-state');
//console.log('Step 2: Found empty state:', !!emptyState);
if (emptyState) {
emptyState.remove();
//console.log('Step 3: Removed empty state');
}
// Add new entry
const tempDiv = document.createElement('div');
tempDiv.innerHTML = newEntryHTML;
const newBlock = tempDiv.firstElementChild;
//console.log('Step 4: Created new block:', !!newBlock);
whitelistContainer.appendChild(newBlock);
//console.log('Step 5: Added new block to container');
// Focus on the new input
const newInput = newBlock.querySelector('input');
//console.log('Step 6: Found new input:', !!newInput);
if (newInput) {
newInput.focus();
//console.log('Step 7: Focused new input');
}
// Update add button state
this.updateAddEntryButton();
//console.log('Step 8: Updated add button state');
//console.log('Step 9: Add function completed successfully');
} else {
//console.log('Step 1: Whitelist container not found!');
}
} catch (error) {
@ -408,11 +450,14 @@ class IPWhitelistManager {
// Check if all entries are valid before allowing new entry addition
validateBeforeAddWhitelist() {
//console.log('=== validateBeforeAddWhitelist START ===');
// Simple test - just check for empty entries
const allInputs = document.querySelectorAll('input[name="CFG_IPS_WHITELIST"]:not([type="hidden"])');
//console.log('Found', allInputs.length, 'inputs');
allInputs.forEach((input, index) => {
//console.log(`Input ${index}: value="${input.value}"`);
});
for (const input of allInputs) {
@ -420,6 +465,7 @@ class IPWhitelistManager {
// Check if entry is empty
if (!entryValue) {
//console.log('Found empty entry - blocking add');
// Flash the empty input field
input.style.animation = 'whitelist-flash 0.5s ease-in-out 2';
input.focus();
@ -431,20 +477,26 @@ class IPWhitelistManager {
if (window.notificationSystem) {
window.notificationSystem.error('Whitelist entry cannot be empty');
}
//console.log('=== validateBeforeAddWhitelist END (false - empty) ===');
return false;
}
// Check entry format with notification (always show)
if (this.checkForInvalidWhitelistFormat(input, entryValue, false)) {
//console.log('Found invalid format - blocking add');
//console.log('=== validateBeforeAddWhitelist END (false - invalid) ===');
return false;
}
// Check for duplicates with notification (always show)
if (this.checkForDuplicateWhitelistEntry(input, entryValue)) {
//console.log('Found duplicate entry - blocking add');
//console.log('=== validateBeforeAddWhitelist END (false - duplicate) ===');
return false;
}
}
//console.log('=== validateBeforeAddWhitelist END (true) ===');
return true;
}
@ -498,6 +550,12 @@ class IPWhitelistManager {
const isValidIP = ipPattern.test(entryValue) || ipv6Pattern.test(entryValue);
const isValidDomain = domainPattern.test(entryValue);
//console.log('checkForInvalidWhitelistFormat validation:', {
//entryValue: entryValue,
//isValidIP: isValidIP,
//isValidDomain: isValidDomain,
//isValidFormat: isValidIP || isValidDomain || entryValue.toLowerCase() === 'localhost'
//});
if (!isValidIP && !isValidDomain) {
// Flash the input field
@ -521,6 +579,7 @@ class IPWhitelistManager {
// Delete whitelist entry
deleteWhitelistEntry(entryIndex, buttonElement) {
//console.log(`Delete whitelist entry button clicked for index: ${entryIndex}`);
try {
// Get the actual whitelist number from the input ID
@ -540,6 +599,7 @@ class IPWhitelistManager {
const match = inputId.match(/config-CFG_IPS_WHITELIST_(\d+)/);
const whitelistNumber = match ? parseInt(match[1]) : 0;
//console.log(`Actual whitelist number: ${whitelistNumber}`);
// Find all whitelist numbers to determine the highest
const allBlocks = document.querySelectorAll('.whitelist-building-block');
@ -556,9 +616,11 @@ class IPWhitelistManager {
});
const highestNumber = Math.max(...allNumbers);
//console.log(`Highest whitelist number: ${highestNumber}`);
// Only allow deletion if this is the highest numbered entry AND it's not #1
if (whitelistNumber === 1) {
//console.log('Cannot delete entry #1');
if (window.notificationSystem) {
window.notificationSystem.error('Entry 1 cannot be deleted');
}
@ -566,6 +628,7 @@ class IPWhitelistManager {
}
if (whitelistNumber !== highestNumber) {
//console.log('Can only delete the highest numbered entry');
if (window.notificationSystem) {
window.notificationSystem.error('Can only delete the highest numbered entry');
}
@ -577,6 +640,7 @@ class IPWhitelistManager {
// Remove the building block from DOM
entryBlock.remove();
//console.log('Removed entry block from DOM');
// Update the hidden input with remaining values
this.saveWhitelistEntries();
@ -763,6 +827,7 @@ function checkForDuplicateWhitelistEntry(input, entryValue) {
input.style.borderColor = '#dc3545';
input.title = 'Whitelist entry already exists';
if (window.notificationSystem) {
//console.log('Showing duplicate whitelist entry notification');
window.notificationSystem.error('Whitelist entry "' + entryValue + '" already exists');
} else {
console.error('Whitelist entry already exists - notification system not available');
@ -790,6 +855,7 @@ function checkForInvalidWhitelistFormat(input, entryValue) {
input.style.borderColor = '#dc3545';
input.title = 'Invalid IP address or domain format (e.g., 192.168.1.100 or example.com)';
if (window.notificationSystem) {
//console.log('Showing invalid whitelist format notification');
window.notificationSystem.error('Invalid whitelist format: "' + entryValue + '". Please use a valid IP address or domain like 192.168.1.100 or example.com');
} else {
console.error('Invalid whitelist format - notification system not available');
@ -803,12 +869,15 @@ function checkForInvalidWhitelistFormat(input, entryValue) {
// Validate whitelist entry format when user tries to add a new entry
function validateWhitelistEntry(input, showNotifications = true) {
const value = input.value.trim();
//console.log('validateWhitelistEntry called with:', value, 'showNotifications:', showNotifications);
//console.log('window.notificationSystem available:', !!window.notificationSystem);
if (!value) {
// Clear styling for empty fields
input.style.borderColor = '#dc3545';
input.title = 'Whitelist entry cannot be empty';
if (showNotifications && window.notificationSystem) {
//console.log('Attempting to show empty whitelist notification');
window.notificationSystem.error('Whitelist entry cannot be empty');
} else {
console.error('Whitelist entry cannot be empty - notification system not available');
@ -836,6 +905,7 @@ function validateWhitelistEntry(input, showNotifications = true) {
input.style.borderColor = '#dc3545';
input.title = 'Invalid IP address, domain, or localhost format (e.g., 192.168.1.100, example.com, or localhost)';
if (showNotifications && window.notificationSystem) {
//console.log('Attempting to show invalid whitelist format notification');
window.notificationSystem.error('Invalid whitelist format: "' + value + '". Please use a valid IP address, domain, or localhost like 192.168.1.100, example.com, or localhost');
} else {
console.error('Invalid whitelist format - notification system not available');

View File

@ -6,15 +6,19 @@ class ToggleManager {
}
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
@ -22,6 +26,7 @@ class ToggleManager {
// Retry discovery after a short delay to handle timing issues
setTimeout(() => {
//console.log('ToggleManager: Retrying toggle discovery...');
this.rediscoverToggles();
}, 100);
}
@ -35,6 +40,7 @@ class ToggleManager {
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
}
@ -54,24 +60,35 @@ class ToggleManager {
// 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, {
@ -80,13 +97,18 @@ class ToggleManager {
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) {
@ -94,15 +116,20 @@ class ToggleManager {
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) {
@ -111,8 +138,10 @@ class ToggleManager {
}
});
} 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) {
@ -122,6 +151,7 @@ class ToggleManager {
});
}
//console.log('=== TOGGLE MANAGER SUCCESS ===');
return true;
} else {
console.error('ToggleManager: Section content or fields not found');
@ -270,6 +300,7 @@ 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();
};

View File

@ -3,6 +3,9 @@
class AppTabbedManager {
constructor() {
// console.log('🔍 AppTabbedManager constructor called');
// console.log('🔍 URL in constructor:', window.location.href);
// console.log('🔍 Search params in constructor:', window.location.search);
// Store original URL for task parameter detection
this.originalUrl = window.location.href;
@ -10,18 +13,22 @@ class AppTabbedManager {
// Check sessionStorage for task parameter (fallback)
const sessionTaskId = sessionStorage.getItem('pendingTaskId');
// console.log('🔍 Session storage task ID:', sessionTaskId);
// Debug: Check if task parameter exists in original URL
const originalParams = new URLSearchParams(this.originalSearch);
const originalTaskId = originalParams.get('task');
// console.log('🔍 Original task ID in constructor:', originalTaskId);
// Try to get task parameter from performance navigation if available
if (performance && performance.getEntriesByType) {
const navigationEntries = performance.getEntriesByType('navigation');
if (navigationEntries.length > 0) {
const navEntry = navigationEntries[0];
// console.log('🔍 Navigation entry URL:', navEntry.name);
const navParams = new URLSearchParams(new URL(navEntry.name).search);
const navTaskId = navParams.get('task');
// console.log('🔍 Navigation task ID:', navTaskId);
}
}
@ -62,6 +69,7 @@ class AppTabbedManager {
setCurrentApp(appName) {
if (this.currentApp === appName) return;
// console.log('🔄 setCurrentApp: switching from %s to %s', this.currentApp, appName);
this.currentApp = appName;
// Before clearing disabled button references, restore any static backup action buttons
@ -81,6 +89,7 @@ class AppTabbedManager {
this.enableTabs();
const running = this.getRunningTaskForApp(appName);
// console.log('🔍 setCurrentApp: running task for %s = %o', appName, running);
if (running) {
this.activeTaskId = running.taskId;
}
@ -100,6 +109,7 @@ class AppTabbedManager {
}
}
// console.log('🔍 Original app name from URL:', appName);
// Convert full app name to slug for task filtering
if (appName && window.apps) {
@ -114,11 +124,14 @@ class AppTabbedManager {
const command = appData.command || '';
const parts = command.split(' ');
const slug = parts[parts.length - 1]; // Return the slug
// console.log('🔄 Converted to slug:', slug, 'from appData:', appData.name);
return slug;
} else {
// console.log('⚠️ No app data found for:', appName);
}
}
// console.log('🔄 Returning original app name:', appName);
return appName;
}
@ -197,6 +210,10 @@ class AppTabbedManager {
// Switch between tabs
switchTab(tabId) {
// console.log('🔄 switchTab called with:', tabId);
// console.log('🔍 Current currentApp before switch:', this.currentApp);
// console.log('🔍 Current URL when switching:', window.location.href);
// console.log('🔍 URL search when switching:', window.location.search);
// Remove active class from all main navigation tabs
document.querySelectorAll('.main-tab-button').forEach(btn => {
@ -228,10 +245,12 @@ class AppTabbedManager {
// Update URL (only tab, not app) - but only on app pages
if (this.isAppPage()) {
// console.log('🔄 About to updateURL with tab:', tabId);
this.updateURL(null, tabId);
}
// Load tab-specific content
// console.log('🔄 About to load tab content for tab:', tabId, 'with currentApp:', this.currentApp);
this.loadTabContent(tabId);
}
@ -240,6 +259,8 @@ class AppTabbedManager {
const actualTabId = tabId === 'logs' ? 'tasks' : tabId;
const currentAppFromUrl = this.getAppFromURL();
// console.log('📂 loadTabContent: tabId=%s, currentApp=%s, fromUrl=%s',
// tabId, this.currentApp, currentAppFromUrl);
// Update currentApp if URL has different app name. Route through setCurrentApp
// so any disable state from the previous app gets cleared before we render.
@ -276,6 +297,7 @@ class AppTabbedManager {
// Make sure app detail view is visible and app is loaded
if (window.appsManager) {
// Use showAppDetail to ensure proper initialization (same as config tab)
// console.log('🔄 Ensuring app detail is loaded for:', this.currentApp);
window.appsManager.showAppDetail(this.currentApp);
// Wait a bit for DOM to be ready after app detail is rendered
@ -284,9 +306,11 @@ class AppTabbedManager {
switch (actualTabId) {
case 'tasks':
// console.log('🔄 loadTabContent: Loading tasks for app:', this.currentApp);
await this.loadAppTasks();
break;
case 'backups':
// console.log('🔄 loadTabContent: Loading backups for app:', this.currentApp);
await this.loadAppBackups();
// IMPORTANT: Re-apply button state if there are running tasks
this.restoreButtonState();
@ -311,6 +335,7 @@ class AppTabbedManager {
break;
case 'config':
// Config is already handled by showAppDetail above
// console.log('🔧 Config content already loaded by showAppDetail');
// IMPORTANT: Re-apply button state if there are running tasks
this.restoreButtonState();
break;
@ -330,6 +355,7 @@ class AppTabbedManager {
// Load tasks specific to current app
async loadAppTasks() {
// console.log('🔄 loadAppTasks called, currentApp:', this.currentApp);
// Show loading spinner by showing the initial loading state
const tasksContainer = document.getElementById('app-tasks');
@ -372,20 +398,32 @@ class AppTabbedManager {
try {
// Load all tasks
// console.log('🔄 Loading tasks...');
// console.log('🔍 Using currentApp for filtering:', this.currentApp);
await this.tasksManager.loadTasks();
const allTasks = this.tasksManager.tasks || [];
// console.log('📊 All tasks loaded:', allTasks.length);
// console.log('📋 All tasks data:', allTasks);
// console.log('📋 Sample task app names:', allTasks.slice(0, 3).map(t => t.app));
// Filter tasks for current app
const appTasks = allTasks.filter(task => task.app === this.currentApp);
// console.log('🎯 Filtering tasks for app:', this.currentApp);
// console.log('📋 Available task.app values:', [...new Set(allTasks.map(t => t.app))]);
// console.log('🎯 Filtered tasks for', this.currentApp, ':', appTasks.length);
// Debug: Show what would match if we used different app names
// console.log('🔍 Debug - Testing different app names:');
['libreportal', 'fail2ban', 'LibrePortal', 'Fail2Ban'].forEach(testApp => {
const testTasks = allTasks.filter(task => task.app === testApp);
// console.log(` - "${testApp}": ${testTasks.length} tasks`);
});
if (appTasks.length === 0) {
// console.log('⚠️ No tasks found for', this.currentApp, '- checking if tasks have different app names');
// Show some task details for debugging
if (allTasks.length > 0) {
// console.log('📋 Sample tasks:', allTasks.slice(0, 3).map(t => ({ id: t.id, app: t.app, command: t.command })));
}
tasksContainer.innerHTML = `<p style="color: #888;">No tasks found for ${this.currentApp}.</p>`;
return;
@ -403,8 +441,10 @@ class AppTabbedManager {
// Handle pending task ID from URL parameter
if (this.pendingTaskId) {
// console.log('🔍 Handling pending task ID after tasks loaded:', this.pendingTaskId);
setTimeout(() => {
if (typeof window.toggleAppTaskDetails === 'function') {
// console.log('🔍 Opening task details for pending task:', this.pendingTaskId);
window.toggleAppTaskDetails(this.pendingTaskId);
// Scroll to the task element after opening details
@ -426,6 +466,7 @@ class AppTabbedManager {
// Scroll to specific task element with smooth animation
scrollToTask(taskId) {
// console.log('🔍 Scrolling to task:', taskId);
// Find the task element by ID or data attribute
let taskElement = document.getElementById(`task-${taskId}`);
@ -444,6 +485,7 @@ class AppTabbedManager {
}
if (taskElement) {
// console.log('🔍 Found task element, scrolling to it:', taskElement);
// Smooth scroll to the task element
taskElement.scrollIntoView({
@ -469,6 +511,7 @@ class AppTabbedManager {
setupAppTaskFunctions() {
// Create app-specific toggleTaskDetails function
window.toggleAppTaskDetails = (taskId) => {
// console.log('🔍 App-specific toggleTaskDetails called for:', taskId);
const details = document.getElementById(`details-${taskId}`);
const toggleBtn = document.querySelector(`.task-btn.toggle-details[onclick*="toggleTaskDetails('${taskId}')"]`);
@ -556,9 +599,11 @@ class AppTabbedManager {
async initialize() {
// Prevent double initialization
if (this.initialized) {
// console.log('⚠️ AppTabbedManager already initialized, skipping');
return;
}
// console.log('🚀 AppTabbedManager initializing, currentApp:', this.currentApp);
// Initialize task system if not already done (with retry)
if (this.tasksManager && !this.tasksManager.commands) {
@ -567,9 +612,11 @@ class AppTabbedManager {
const maxAttempts = 5;
while (!initialized && attempts < maxAttempts) {
// console.log(`🔄 Attempting to initialize task system (${attempts + 1}/${maxAttempts})...`);
try {
initialized = this.tasksManager.initializeTaskSystem();
if (initialized) {
// console.log('✅ Task system initialized successfully');
}
} catch (error) {
console.error('❌ Task system initialization error:', error);
@ -623,6 +670,7 @@ class AppTabbedManager {
// Set current app from URL BEFORE setting up URL monitoring
const urlAppName = this.getAppFromURL();
// console.log('🔍 Setting initial currentApp from URL:', urlAppName);
this.currentApp = urlAppName;
// Check for running tasks for this app and auto-switch to tasks tab if found
@ -638,17 +686,22 @@ class AppTabbedManager {
// Check for task parameter and handle it AFTER tasks are loaded
// Use original URL since the current URL might have been modified
const urlParams = new URLSearchParams(this.originalSearch);
// console.log('🔍 Original URL search during init:', this.originalSearch);
// console.log('🔍 Original URL params during init:', Object.fromEntries(urlParams.entries()));
let taskId = urlParams.get('task');
// console.log('🔍 Task ID from original params:', taskId);
// Fallback: Check sessionStorage if URL doesn't have task parameter
if (!taskId) {
taskId = sessionStorage.getItem('pendingTaskId');
// console.log('🔍 Task ID from sessionStorage fallback:', taskId);
// Clear sessionStorage after using it
if (taskId) {
sessionStorage.removeItem('pendingTaskId');
}
}
if (taskId) {
// console.log('🔍 Task parameter found:', taskId);
// Store the task ID to handle after tasks are loaded
this.pendingTaskId = taskId;
// Force tasks tab
@ -669,6 +722,7 @@ class AppTabbedManager {
// Set initial active tab (only if no task parameter)
if (!taskId) {
const initialTab = this.getTabFromURL();
// console.log('🔄 Setting initial tab:', initialTab, 'with currentApp:', this.currentApp);
this.switchTab(initialTab);
}
@ -685,6 +739,7 @@ class AppTabbedManager {
const newAppName = this.getAppFromURL();
// Only update if currentApp is already set and app actually changed
if (this.currentApp && newAppName !== this.currentApp) {
// console.log('🔄 URL changed, updating app from', this.currentApp, 'to', newAppName);
this.updateApp(newAppName);
}
});
@ -721,6 +776,7 @@ class AppTabbedManager {
// Create backup (placeholder function)
async createBackup(appName) {
// Placeholder - will be implemented with actual backup logic
// console.log(`Creating backup for ${appName}...`);
}
// Setup task event listeners for button state management
@ -729,6 +785,8 @@ class AppTabbedManager {
const { taskId, appName, action } = event.detail;
const key = this.taskKey(appName, action);
// console.log('📌 taskCreated: appName=%s, currentApp=%s, action=%s, key=%s',
// appName, this.currentApp, action, key);
if (this.runningTasks.has(key)) {
const existing = this.runningTasks.get(key);
@ -760,6 +818,7 @@ class AppTabbedManager {
}
this.runningTasks.set(key, { taskId, appName, action });
// console.log('📌 taskCreated: stored in runningTasks, will disable=%s', appName === this.currentApp);
if (appName === this.currentApp) {
this.disableAppButtons(appName, action);
@ -865,6 +924,8 @@ class AppTabbedManager {
// Disable app buttons during task execution
disableAppButtons(appName, action) {
// console.log('🚫 disableAppButtons called: appName=%s, action=%s, currentApp=%s',
// appName, action, this.currentApp);
// Also disable config and backup tabs
this.disableTabs();
@ -879,6 +940,7 @@ class AppTabbedManager {
// Disable ALL buttons in the app content section
const allButtons = appContent.querySelectorAll('button:not([disabled]):not(.tab-button)');
// console.log('🚫 disableAppButtons found %d buttons to disable', allButtons.length);
allButtons.forEach(button => {
// Skip tab buttons (config, backup, tasks tabs)
@ -916,6 +978,7 @@ class AppTabbedManager {
tempDiv.innerHTML = originalContent;
const textContent = tempDiv.textContent || tempDiv.innerText || originalContent;
// console.log('🔃 Adding spinner to button:', button.textContent.trim(), 'for app:', appName);
button.innerHTML = `
<span class="spinner" style="
display: inline-block;
@ -938,6 +1001,7 @@ class AppTabbedManager {
this.disabledButtons.add(button);
});
// console.log(`🔍 Disabled ${allButtons.length} buttons for ${appName} during ${action}`);
}
// Restore button state when switching tabs. Only disable if the *current* app
@ -1028,10 +1092,12 @@ window.AppTabbedManager = AppTabbedManager;
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', async () => {
// console.log('🔍 DOMContentLoaded: Skipping automatic initialization - SPA will handle it');
// Don't initialize here - let SPA handle it
});
// Also initialize when scripts are loaded (for SPA navigation)
window.addEventListener('load', async () => {
// console.log('🔍 Window load: Skipping automatic initialization - SPA will handle it');
// Don't initialize here - let SPA handle it
});

View File

@ -206,6 +206,7 @@ class AppsManager {
if (response.ok) {
const appsData = await response.json();
window.apps = appsData.apps || [];
// console.log(`✅ Reloaded ${window.apps.length} apps`);
}
} catch (error) {
console.error('❌ Failed to reload apps data:', error);
@ -339,6 +340,7 @@ class AppsManager {
}
showAppDetail(appName, forceConfigTab = false) {
// console.log('🔍 showAppDetail called with:', { appName, forceConfigTab });
//// // console.log(`AppsManager: Showing app detail: ${appName}`);
// Don't proceed if appName is empty - redirect to apps list instead
@ -360,10 +362,12 @@ class AppsManager {
if (forceConfigTab) {
// Force config tab for install/manage buttons
targetTab = 'config';
// console.log('🔍 Forcing config tab due to forceConfigTab=true');
} else {
// Preserve existing tab or default to config for direct navigation
const currentUrl = new URL(window.location.href);
targetTab = currentUrl.searchParams.get('tab') || 'config';
// console.log('🔍 Preserving existing tab:', targetTab);
}
const newUrl = window.appPath(appName, targetTab);
@ -407,6 +411,8 @@ class AppsManager {
// Show app detail with config tab (for install/manage buttons)
showAppDetailWithConfig(appName) {
// console.log('🔍 showAppDetailWithConfig called with:', appName);
// console.log('🔍 Forcing config tab for button click');
// Check if there's a running task for this app — switch straight to the tasks
// tab if so, instead of landing on config (whose buttons would be disabled).
@ -434,6 +440,7 @@ class AppsManager {
}
// Simulate clicking target tab functionally
// console.log('🔄 Simulating target tab click:', targetTab);
setTimeout(() => {
window.appTabbedManager.switchTab(targetTab);
// Highlight the running task if switching to tasks tab
@ -1431,6 +1438,7 @@ class AppsManager {
} else if (window.librePortalSPA) {
// Fallback: navigate to app with tasks tab
const taskUrl = window.appPath(appName, 'tasks', null, task ? task.id : null);
// console.log(`🔄 Navigating to app tasks with uninstall task: ${task?.id}`);
window.librePortalSPA.navigateTo(taskUrl);
} else if (window.navigateToRoute) {
window.navigateToRoute(window.appPath(appName, 'tasks', null, task ? task.id : null).replace(/^\//, ''));
@ -1505,6 +1513,7 @@ class AppsManager {
`;
}
// console.log(`🔍 Install button disabled for ${appName} during ${action}`);
}
enableInstallButton(appName) {
@ -1529,6 +1538,7 @@ class AppsManager {
const spinners = button.querySelectorAll('.spinner');
spinners.forEach(spinner => spinner.remove());
// console.log(`🔍 Install button enabled for ${appName}`);
}
disableUninstallButton(appName, action) {
@ -1569,6 +1579,7 @@ class AppsManager {
`;
}
// console.log(`🔍 Uninstall button disabled for ${appName} during ${action}`);
}
enableUninstallButton(appName) {
@ -1593,6 +1604,7 @@ class AppsManager {
const spinners = button.querySelectorAll('.spinner');
spinners.forEach(spinner => spinner.remove());
// console.log(`🔍 Uninstall button enabled for ${appName}`);
}
// Static methods for global access
@ -1836,7 +1848,9 @@ class ServiceButtons {
// Get services for a specific app from config, then fill in real IPs/ports from apps-services.json
async getServicesForApp(appName) {
const portConfig = this.parsePortConfig(appName);
// console.log(`📦 Port config for ${appName}:`, portConfig);
const portServices = portConfig.filter(p => p.buttonEnabled);
// console.log(`✅ Enabled services for ${appName}:`, portServices);
if (portServices.length === 0) return [];
@ -1844,6 +1858,7 @@ class ServiceButtons {
if (this.services.length === 0) {
await this.loadServices();
}
// console.log(`🌐 Loaded ${this.services.length} services from apps-services.json`);
// Merge port config with real data from apps-services.json
return portServices.map(portService => {
@ -1853,6 +1868,7 @@ class ServiceButtons {
s.name === portService.name
);
// console.log(`🔗 Matching service for ${portService.name}:`, serviceData);
const merged = {
...portService,
@ -1863,6 +1879,7 @@ class ServiceButtons {
externalURL: serviceData?.externalURL || '',
internalURL: serviceData?.internalURL || ''
};
// console.log(`🎯 Merged service data:`, merged);
return merged;
});
}

View File

@ -77,17 +77,22 @@ class PortManager {
// Get available services for an app
async getAvailableServices(appName) {
//console.log(`🔌 PortManager: Getting services for app: ${appName}`);
try {
// Load apps data to get services for this app (with cache busting)
const timestamp = Date.now();
const response = await fetch(`/data/apps/generated/apps.json?t=${timestamp}`);
if (!response.ok) {
//console.log(`🔌 Failed to fetch apps.json: ${response.status}`);
return [];
}
const appsData = await response.json();
//console.log(`🔌 Apps data loaded:`, appsData);
//console.log(`🔌 Available app names:`, appsData.apps.map(app => app.name));
let app = appsData.apps.find(a => a.name === appName);
//console.log(`🔌 Found app for ${appName}:`, app);
// Try fuzzy matching if exact match fails
if (!app) {
@ -95,15 +100,18 @@ class PortManager {
a.name.toLowerCase().includes(appName.toLowerCase()) ||
appName.toLowerCase().includes(a.name.toLowerCase())
);
//console.log(`🔌 Fuzzy match for ${appName}:`, fuzzyApp);
if (fuzzyApp) {
app = fuzzyApp;
}
}
if (app && app.services) {
//console.log(`🔌 Services found for ${appName}:`, app.services);
return app.services;
}
//console.log(`🔌 No services found for ${appName}`);
return [];
} catch (error) {
console.error('Error loading services:', error);
@ -188,6 +196,7 @@ class PortManager {
}
}
} catch (error) {
//console.log('Could not fetch app title, using app name');
}
// Show confirmation dialog
@ -468,6 +477,8 @@ class PortManager {
this.appName = appName;
this.availableServices = await this.getAvailableServices(appName);
//console.log(`🔌 PortManager: Available services for ${appName}:`, this.availableServices);
//console.log(`🔌 PortManager: Number of services: ${this.availableServices.length}`);
// Force full-width layout for port manager containers
this.forceFullWidthLayout();
@ -478,6 +489,7 @@ class PortManager {
const index = parseInt(select.dataset.index);
const currentService = this.ports[index]?.service || '';
//console.log(`🔌 PortManager: Port ${index} current service: "${currentService}"`);
// Clear existing options
select.innerHTML = '<option value="">Select a service...</option>';
@ -496,18 +508,22 @@ class PortManager {
// Case 1: Only one service and no current service - auto-select
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Auto-selecting single service "${service}" for port ${index}`);
} else if (this.availableServices.length === 1 && currentService && currentService !== service) {
// Case 2: Only one service but current service doesn't match - auto-match
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Auto-matching service "${service}" (was "${currentService}") for port ${index}`);
} else if (this.availableServices.length === 1 && currentService === service) {
// Case 3: Single service and current service matches - still show auto-match indicator
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Single service matches "${service}" for port ${index} - showing auto-match indicator`);
} else if (this.availableServices.length > 1 && currentService === service) {
// Case 4: Multiple services and current service matches - normal selection
shouldAutoSelect = true;
isAutoMatched = false;
//console.log(`🔌 PortManager: Normal selection of service "${service}" for port ${index}`);
}
if (shouldAutoSelect) {
@ -716,6 +732,7 @@ class PortManager {
const index = parseInt(select.dataset.index);
const currentService = this.ports[index]?.service || '';
//console.log(`🔌 PortManager: Port ${index} current service: "${currentService}"`);
// Clear existing options
select.innerHTML = '<option value="">Select a service...</option>';
@ -734,18 +751,22 @@ class PortManager {
// Case 1: Only one service and no current service - auto-select
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Auto-selecting single service "${service}" for port ${index}`);
} else if (this.availableServices.length === 1 && currentService && currentService !== service) {
// Case 2: Only one service but current service doesn't match - auto-match
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Auto-matching service "${service}" (was "${currentService}") for port ${index}`);
} else if (this.availableServices.length === 1 && currentService === service) {
// Case 3: Single service and current service matches - still show auto-match indicator
shouldAutoSelect = true;
isAutoMatched = true;
//console.log(`🔌 PortManager: Single service matches "${service}" for port ${index} - showing auto-match indicator`);
} else if (this.availableServices.length > 1 && currentService === service) {
// Case 4: Multiple services and current service matches - normal selection
shouldAutoSelect = true;
isAutoMatched = false;
//console.log(`🔌 PortManager: Normal selection of service "${service}" for port ${index}`);
}
if (shouldAutoSelect) {

View File

@ -114,12 +114,15 @@ function navigateToApp(appName) {
// Filter apps by search term (removed - not used in dashboard)
function filterApps(searchTerm) {
//console.log('Filter apps functionality removed from dashboard');
}
// Filter apps by category (removed - not used in dashboard)
function filterAppsByCategory(category) {
//console.log('Filter apps by category functionality removed from dashboard');
}
// Populate category filter (removed - not used in dashboard)
function populateCategoryFilter() {
//console.log('Category filter population removed from dashboard');
}

View File

@ -55,6 +55,7 @@ Object.assign(TasksManager.prototype, {
}
// Get tasks using new system
// console.log('📥 Getting tasks using new queue system...');
// Get queue and current status
let queue = [];
@ -74,6 +75,7 @@ Object.assign(TasksManager.prototype, {
}
}
} catch (error) {
// console.log('📝 Queue file not found, starting with empty queue');
}
try {
@ -90,6 +92,7 @@ Object.assign(TasksManager.prototype, {
}
}
} catch (error) {
// console.log('📝 Current file not found, no current task');
}
// Load individual task files
@ -117,6 +120,7 @@ Object.assign(TasksManager.prototype, {
// Scan tasks folder for all task files (including completed ones) - OPTIMIZED
try {
// console.log('🔍 Scanning tasks folder for all task files...');
const tasksResponse = await fetch('/read-directory?path=tasks');
if (tasksResponse.ok) {
const files = await tasksResponse.json();
@ -126,6 +130,7 @@ Object.assign(TasksManager.prototype, {
file !== 'current.json'
);
// console.log(`📁 Found ${taskFiles.length} task files in folder`);
// OPTIMIZATION: Batch load tasks instead of individual calls
const missingTaskIds = taskFiles
@ -133,6 +138,7 @@ Object.assign(TasksManager.prototype, {
.filter(taskId => !allTasks.find(task => task.id === taskId));
if (missingTaskIds.length > 0) {
// console.log(`📦 Batch loading ${missingTaskIds.length} missing tasks...`);
try {
const batchResponse = await fetch('/read-tasks-batch', {
method: 'POST',
@ -145,10 +151,12 @@ Object.assign(TasksManager.prototype, {
batchTasks.forEach(task => {
if (task) {
allTasks.push(task);
// console.log(`✅ Added completed task ${task.id} from batch load`);
}
});
} else {
// Fallback to individual loading if batch endpoint not available
// console.log('⚠️ Batch endpoint not available, falling back to individual loading');
await this.loadTasksIndividually(missingTaskIds, allTasks);
}
} catch (error) {
@ -173,6 +181,7 @@ Object.assign(TasksManager.prototype, {
// Sort by creation time (newest first)
this.tasks.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// console.log(`✅ Loaded ${this.tasks.length} tasks`);
//// // console.log('📋 All tasks:', this.tasks);
this.renderTasks();
@ -198,6 +207,7 @@ Object.assign(TasksManager.prototype, {
const task = await this.taskManager.getTask(taskId);
if (task) {
allTasks.push(task);
// console.log(`✅ Added completed task ${taskId} from individual load`);
}
} catch (error) {
console.warn(`⚠️ Failed to load task ${taskId}:`, error);

View File

@ -149,6 +149,7 @@ Object.assign(TasksManager.prototype, {
return displayNames[category] || category.charAt(0).toUpperCase() + category.slice(1);
},
renderTask(task) {
// console.log(`🔍 renderTask called with task:`, task);
// Debug undefined status
if (!task.status) {
@ -163,10 +164,24 @@ Object.assign(TasksManager.prototype, {
const hasError = task.error && task.error.length > 0;
const hasLogs = task.log && Array.isArray(task.log) && task.log.length > 0;
// console.log(`🔍 Task fields check:`, {
//hasOutput: hasOutput,
//hasError: hasError,
//hasLogs: hasLogs,
//isRunning: isRunning,
//outputLength: task.output ? task.output.length : 0,
//error: task.error,
//logCount: task.log ? task.log.length : 0
//});
const executionTime = task.startedAt && task.completedAt ?
this.calculateExecutionTime(task.startedAt, task.completedAt) : null;
// console.log('🔍 renderTask debug:', {
//taskStatus: task.status,
//statusClass: statusClass,
//statusDisplay: task.status ? task.status.toUpperCase() : 'UNKNOWN'
//});
return `
<div class="task-item" data-task-id="${task.id}">
@ -375,6 +390,7 @@ Object.assign(TasksManager.prototype, {
}
}
} catch (error) {
// console.log('Could not load apps.json, will use fallback');
}
}
@ -397,6 +413,7 @@ Object.assign(TasksManager.prototype, {
// If no installed apps found from apps data, fall back to task-based apps
if (installedApps.length === 0) {
// console.log('No installed apps found, using task-based app list');
const taskApps = [...new Set(this.tasks.map(task => task.app).filter(Boolean))];
if (taskApps.length === 0) {

View File

@ -92,6 +92,7 @@ Object.assign(TasksManager.prototype, {
const streamData = this.activeLogStreams.get(taskId);
streamData.stream.stop();
this.activeLogStreams.delete(taskId);
// console.log(`⏹️ Stopped log streaming for task ${taskId}`);
}
},
// Load task logs automatically
@ -200,20 +201,26 @@ Object.assign(TasksManager.prototype, {
},
// Start global live log updater - simple 2-second updates for all running tasks
startGlobalLiveLogUpdater() {
// console.log(`🔄 Starting global live log updater`);
// Update every 2 seconds
setInterval(async () => {
// console.log(`🔄 Global updater running - checking tasks...`);
// Find all running tasks
const runningTasks = this.tasks.filter(task => task.status === 'running');
// console.log(`🔄 Found ${runningTasks.length} running tasks:`, runningTasks.map(t => t.id));
if (runningTasks.length > 0) {
// console.log(`🔄 Updating live logs for ${runningTasks.length} running tasks`);
// Update each running task's live logs
for (const task of runningTasks) {
// console.log(`🔄 About to update live logs for task ${task.id}`);
await this.updateLiveLogsSimple(task.id);
}
} else {
// console.log(`🔄 No running tasks found, skipping live log updates`);
}
}, 2000); // Every 2 seconds
},
@ -221,20 +228,26 @@ Object.assign(TasksManager.prototype, {
async updateLiveLogsSimple(taskId) {
const liveLogsElement = document.getElementById(`live-logs-${taskId}`);
if (!liveLogsElement) {
// console.log(`⚠️ Live logs element not found for task ${taskId}`);
return; // Silently skip if element not found
}
try {
// console.log(`🔄 Reading log file for task ${taskId}`);
// Read the log file content
const response = await fetch(`/read-file?path=tasks/${taskId}.log`);
// console.log(`🔄 Log file response status: ${response.status} for task ${taskId}`);
if (response.ok) {
const logContent = await response.text();
// console.log(`🔄 Log file content length: ${logContent.length} chars for task ${taskId}`);
// console.log(`🔄 First 100 chars of log content: "${logContent.substring(0, 100)}..."`);
if (logContent.trim()) {
// Split into lines and display
const lines = logContent.split('\n').filter(line => line.trim());
// console.log(`🔄 Displaying ${lines.length} log lines for task ${taskId}`);
liveLogsElement.innerHTML = lines.map(line =>
`<div class="log-entry">${this.parseAnsiColors(line)}</div>`
@ -242,6 +255,7 @@ Object.assign(TasksManager.prototype, {
// Auto-scroll to bottom
liveLogsElement.scrollTop = liveLogsElement.scrollHeight;
} else {
// console.log(`🔄 Log file is empty for task ${taskId}`);
liveLogsElement.innerHTML = '<div class="log-entry">🔄 Waiting for logs...</div>';
}
} else {
@ -262,10 +276,12 @@ Object.assign(TasksManager.prototype, {
const detailsElement = taskElement.querySelector('.task-details');
if (!detailsElement) return;
// console.log(`🔄 Updating task structure for ${taskId} to show simplified logs`);
// Check if logs container already exists
const existingLogs = detailsElement.querySelector('.task-logs .log-container');
if (existingLogs) {
// console.log(`🔄 Logs container already exists for ${taskId}`);
return; // Already exists, no need to update
}
@ -281,6 +297,7 @@ Object.assign(TasksManager.prototype, {
// Insert logs section at the bottom of details
detailsElement.insertAdjacentHTML('beforeend', logsHtml);
// console.log(`✅ Added simplified logs section for task ${taskId}`);
// Auto-load logs
this.loadTaskLogs(taskId);
@ -295,6 +312,7 @@ Object.assign(TasksManager.prototype, {
return;
}
// console.log(`🔄 Found task element:`, taskElement);
// Update status and content
const statusElement = taskElement.querySelector('.task-status');
@ -320,6 +338,7 @@ Object.assign(TasksManager.prototype, {
contentElement.textContent = task.command;
}
// console.log(`🔄 Updated task ${task.id} display: ${task.status}`);
},
// Update highlighted task status and UI
async updateHighlightedTaskStatus(taskId) {
@ -328,12 +347,14 @@ Object.assign(TasksManager.prototype, {
const task = await this.taskManager.getTaskSummary(taskId);
if (!task) return;
// console.log(`🔄 Updating highlighted task ${taskId} status: ${task.status}`);
// Update task display
this.updateTaskDisplay(task);
// If task completed or failed, always load output
if ((task.status === 'completed' || task.status === 'failed')) {
// console.log(`🔄 Task ${taskId} is ${task.status}, loading output...`);
const details = document.getElementById(`details-${taskId}`);
if (details && details.style.display === 'block') {

View File

@ -145,10 +145,12 @@ class TasksManager {
// Only check for specific task parameter if we're not coming from an app page
const taskParam = searchParams.get('task');
if (taskParam) {
// console.log(`🎯 Found task parameter in URL: ${taskParam} on main tasks page`);
this.highlightedTaskId = taskParam;
} else {
// Clear any existing highlighted task when on main tasks page without task param
this.highlightedTaskId = null;
// console.log(`🎯 Clearing highlighted task on main tasks page`);
}
} else {
// Not on main tasks page, get default filter from localStorage
@ -156,6 +158,7 @@ class TasksManager {
this.highlightedTaskId = null; // Always clear when not on tasks page
}
// console.log(`🎯 Tasks category from URL: ${this.currentCategory}`);
}
updateURL(category, taskId = null) {
@ -183,6 +186,7 @@ class TasksManager {
await this.loadTasks();
// Force a refresh to ensure latest data
// console.log('🔄 Refreshing tasks data on initialization...');
await this.loadTasks();
// Setup auto-refresh

View File

@ -99,6 +99,7 @@ Object.assign(TasksManager.prototype, {
},
// Auto-expand a task when it's created
async autoExpandTask(taskId) {
// console.log(`🔄 Auto-expanding task ${taskId}`);
// Wait for task to be rendered
let attempts = 0;
@ -106,10 +107,12 @@ Object.assign(TasksManager.prototype, {
const tryExpand = async () => {
attempts++;
// console.log(`🔄 Auto-expand attempt ${attempts}/${maxAttempts} for task ${taskId}`);
// Check if task element exists
const taskElement = document.querySelector(`[data-task-id="${taskId}"]`);
if (!taskElement) {
// console.log(`⚠️ Task element not found for ${taskId}, attempt ${attempts}`);
if (attempts < maxAttempts) {
setTimeout(tryExpand, 500); // Try again in 500ms
} else {
@ -118,10 +121,12 @@ Object.assign(TasksManager.prototype, {
return;
}
// console.log(`✅ Found task element for ${taskId}`);
// Get the details element
const details = document.getElementById(`details-${taskId}`);
if (!details) {
// console.log(`⚠️ Details element not found for ${taskId}, attempt ${attempts}`);
if (attempts < maxAttempts) {
setTimeout(tryExpand, 500);
} else {
@ -130,6 +135,7 @@ Object.assign(TasksManager.prototype, {
return;
}
// console.log(`✅ Found details element for ${taskId}`);
// Expand the task details
details.style.display = 'block';
@ -152,6 +158,7 @@ Object.assign(TasksManager.prototype, {
}, 1000);
}
// console.log(`✅ Auto-expanded task ${taskId}`);
};
tryExpand();

View File

@ -18,7 +18,9 @@ class SystemLoader {
this.initializeSystems();
this.initializeHealthChecks();
// console.log('🚨 BREAKPOINT: About to call initializeComponents()');
this.initializeComponentRegistry();
// console.log('BREAKPOINT: initializeComponents() completed');
}
// Initialize component registry
@ -58,6 +60,7 @@ class SystemLoader {
initializer: () => {
if (typeof setupMobileMenu === 'function') {
setupMobileMenu();
// console.log('✅ Mobile menu initialized');
return true;
}
console.warn('⚠️ setupMobileMenu not available');
@ -78,6 +81,7 @@ class SystemLoader {
initializer: () => {
if (typeof initConfirmationDialog === 'function') {
initConfirmationDialog();
// console.log('✅ Confirmation dialog initialized');
return true;
}
console.warn('⚠️ initConfirmationDialog not available');
@ -94,6 +98,7 @@ class SystemLoader {
initializer: () => {
if (typeof NotificationSystem !== 'undefined') {
window.notificationSystem = new NotificationSystem();
// console.log('✅ Notification system initialized');
return window.notificationSystem;
}
console.warn('⚠️ NotificationSystem not available');
@ -111,15 +116,18 @@ class SystemLoader {
// Initialize data loading system
if (typeof initializeData === 'function') {
initializeData();
// console.log('✅ Data loading system initialized');
}
// Initialize dashboard-specific functions
if (typeof loadSystemInfo === 'function') {
loadSystemInfo();
// console.log('✅ System info loaded');
}
if (typeof setupEventListeners === 'function') {
setupEventListeners();
// console.log('✅ Dashboard event listeners set up');
}
return true;
@ -133,6 +141,7 @@ class SystemLoader {
this.components.set('task-system', {
system: 'task',
initializer: () => {
// console.log('🔧 Initializing Task System...');
// Open the SSE feed before anything else so we don't miss the upserts
// that fire as the page boots.
@ -143,6 +152,7 @@ class SystemLoader {
// Create TasksManager instance
if (typeof TasksManager !== 'undefined') {
window.tasksManager = new TasksManager();
// console.log('✅ TasksManager initialized and available globally');
return window.tasksManager;
}
@ -175,10 +185,15 @@ class SystemLoader {
this.components.set('apps-manager', {
system: 'managers',
initializer: () => {
// console.log('🔧 DEBUG: AppsManager initializer called');
// console.log('🔧 DEBUG: AppsManager class available:', typeof AppsManager !== 'undefined');
// console.log('🔧 DEBUG: Available globals:', Object.keys(window).filter(key => key.includes('Manager')));
if (typeof AppsManager !== 'undefined') {
// console.log('🔧 DEBUG: Creating AppsManager instance...');
try {
window.appsManager = new AppsManager();
// console.log('✅ AppsManager instance created successfully');
return window.appsManager;
} catch (error) {
console.error('❌ Failed to create AppsManager instance:', error);
@ -214,9 +229,13 @@ class SystemLoader {
this.components.set('app-tabbed-manager', {
system: 'managers',
initializer: () => {
// console.log('🔧 DEBUG: Attempting to initialize AppTabbedManager...');
// console.log('🔧 DEBUG: AppTabbedManager class available:', typeof AppTabbedManager !== 'undefined');
// console.log('🔧 DEBUG: Available globals:', Object.keys(window).filter(key => key.includes('Tabbed') || key.includes('Manager')));
if (typeof AppTabbedManager !== 'undefined') {
window.appTabbedManager = new AppTabbedManager();
// console.log('✅ App Tabbed Manager initialized');
return window.appTabbedManager;
}
console.warn('⚠️ AppTabbedManager not available');
@ -227,6 +246,8 @@ class SystemLoader {
script: '/components/apps/core/js/app-tabbed-manager.js'
});
// console.log('TEST: Components added. Total components:', this.components.size);
// console.log('TEST: Method completed');
}
// Initialize all system definitions
@ -238,6 +259,7 @@ class SystemLoader {
dependencies: [],
checks: ['dom', 'spa', 'utils']
});
// console.log('🔍 Core System checks:', ['dom', 'spa', 'utils']);
this.systems.set('data', {
name: 'Data Functions',
@ -246,6 +268,7 @@ class SystemLoader {
dependencies: ['core'],
checks: []
});
// console.log('🔍 Data Functions checks:', []);
this.systems.set('components', {
name: 'UI Components',
@ -346,6 +369,16 @@ class SystemLoader {
const hasDataLoader = typeof DataLoader !== 'undefined';
const hasInitializeData = typeof initializeData === 'function';
// console.log('🔍 Utils Check:', {
//hasDataLoader,
//hasSafeGetElement,
//hasSafeQuerySelector,
//hasGetAppIcon,
//hasSetupThemeToggle,
//hasInitializeData,
// Check what's actually available globally
//availableGlobals: Object.keys(window).filter(key =>
//key.includes('safe') ||
//key.includes('get') ||
//key.includes('Data') ||
//key.includes('Theme') ||
@ -377,6 +410,19 @@ class SystemLoader {
const hasLoadConfigDetailData = typeof loadConfigDetailData === 'function';
const hasLoadMinimalData = typeof loadMinimalData === 'function';
// console.log('🔍 Data Loader Check:', {
//hasDataLoader,
//hasInitializeData,
//hasLoadDashboardData,
//hasLoadAppsPageData,
//hasLoadConfigDetailData,
//hasLoadMinimalData,
//availableDataFunctions: Object.keys(window).filter(key =>
//key.includes('load') ||
//key.includes('Data') ||
//key.includes('initialize')
//).slice(0, 10)
//});
// Check if data loader functions are available
return hasInitializeData && (hasLoadDashboardData || hasLoadAppsPageData || hasLoadConfigDetailData);
@ -393,6 +439,7 @@ class SystemLoader {
weight: 2,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 Topbar Check: Will be loaded dynamically during component initialization');
// Always pass - TopbarComponent will be loaded dynamically by mobile-menu component
return true;
}
@ -405,6 +452,7 @@ class SystemLoader {
weight: 1,
timeout: 1000,
check: async () => {
// console.log('🔍 Confirmation Dialog Check: Will be loaded dynamically during component initialization');
return true;
}
});
@ -415,6 +463,7 @@ class SystemLoader {
weight: 1,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 Notification System Check: Will be loaded dynamically during component initialization');
// Always pass - NotificationSystem will be loaded dynamically by notifications component
return true;
}
@ -435,6 +484,7 @@ class SystemLoader {
weight: 3,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 Apps Manager Check: Will be loaded dynamically during component initialization');
// Always pass - AppsManager will be loaded dynamically by apps-manager component
return true;
}
@ -446,6 +496,7 @@ class SystemLoader {
weight: 3,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 Task Manager Check: Will be loaded dynamically during component initialization');
// Always pass - TasksManager will be loaded dynamically by task-manager component
return true;
}
@ -457,6 +508,7 @@ class SystemLoader {
weight: 2,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 Config Manager Check: Will be loaded dynamically during component initialization');
// Always pass - ConfigManager will be loaded dynamically by config-manager component
return true;
}
@ -468,6 +520,7 @@ class SystemLoader {
weight: 3,
timeout: 1000, // Reduced timeout since we're not actually checking
check: async () => {
// console.log('🔍 App Tabbed Manager Check: Will be loaded dynamically during component initialization');
// Always pass - AppTabbedManager will be loaded dynamically by app-tabbed-manager component
return true;
}
@ -545,30 +598,38 @@ class SystemLoader {
// Icon Preloading Check
async checkIconPreloading() {
// console.log('🔄 Starting icon preloading check...');
// Load data directly since window.apps isn't populated yet
let appsData = [];
let categoriesData = [];
try {
// console.log('🌐 Fetching data from /data/apps/generated/apps.json and /data/apps/apps-categories.json');
const [appsResponse, categoriesResponse] = await Promise.all([
fetch('/data/apps/generated/apps.json'),
fetch('/data/apps/apps-categories.json')
]);
// console.log('📊 Apps response status:', appsResponse.status, appsResponse.statusText);
// console.log('📊 Categories response status:', categoriesResponse.status, categoriesResponse.statusText);
if (appsResponse.ok) {
const appsText = await appsResponse.text();
// console.log('📄 Apps response text length:', appsText.length);
const appsParsed = JSON.parse(appsText);
appsData = appsParsed.apps || [];
// console.log('✅ Apps data loaded successfully');
} else {
console.warn('⚠️ Apps response not ok:', appsResponse.status);
}
if (categoriesResponse.ok) {
const categoriesText = await categoriesResponse.text();
// console.log('📄 Categories response text length:', categoriesText.length);
const categoriesParsed = JSON.parse(categoriesText);
categoriesData = categoriesParsed.categories || [];
// console.log('✅ Categories data loaded successfully');
} else {
console.warn('⚠️ Categories response not ok:', categoriesResponse.status);
}
@ -576,6 +637,8 @@ class SystemLoader {
console.warn('⚠️ Failed to load data for icon preloading:', error);
}
// console.log('📊 Apps data length:', appsData?.length || 0);
// console.log('📊 Categories data length:', categoriesData?.length || 0);
// Collect all icon URLs to preload
const iconUrls = new Set();
@ -606,6 +669,7 @@ class SystemLoader {
iconUrls.add('/core/icons/categories/all.svg');
iconUrls.add('/core/icons/categories/installed.svg');
// console.log(`📦 Found ${iconUrls.size} unique icons to preload`);
// Preload all icons using Image objects
const preloadPromises = Array.from(iconUrls).map(iconUrl => {
@ -618,11 +682,14 @@ class SystemLoader {
});
await Promise.all(preloadPromises);
// console.log(`✅ Preloaded ${iconUrls.size} app center icons`);
// Pre-render app center in background
if (window.appsManager && typeof window.appsManager.showAppsList === 'function') {
// console.log('🔄 Pre-rendering app center content...');
window.appsManager.showAppsList('all');
await new Promise(resolve => setTimeout(resolve, 300)); // Wait for rendering
// console.log('✅ App center pre-rendered');
}
return true;
@ -658,12 +725,15 @@ class SystemLoader {
// Initialize all components in dependency order
async initializeComponents() {
// console.log('🔧 BREAKPOINT: initializeComponents() called!');
// console.log('🔧 Initializing components...');
// Get systems in priority order
const sortedSystems = Array.from(this.systems.entries())
.sort(([,a], [,b]) => a.priority - b.priority);
for (const [systemId, system] of sortedSystems) {
// console.log(`🔄 Processing system: ${systemId}`);
// Check dependencies first
const dependenciesMet = system.dependencies.every(dep =>
@ -671,6 +741,7 @@ class SystemLoader {
);
if (!dependenciesMet) {
// console.log(`⏳ Skipping ${system.name} - dependencies not met:`, system.dependencies);
continue;
}
@ -678,15 +749,19 @@ class SystemLoader {
const systemComponents = Array.from(this.components.entries())
.filter(([, component]) => component.system === systemId);
// console.log(`📦 Found ${systemComponents.length} components for ${systemId}:`, systemComponents.map(([id]) => id));
// Initialize components for this system
for (const [componentId, component] of systemComponents) {
try {
// console.log(`🔧 Initializing ${componentId}...`);
// Load component script(s) if specified
if (component.script) {
// console.log(`📦 Loading script for ${componentId}: ${component.script}`);
await this.loadScript(component.script);
} else if (component.scripts) {
// console.log(`📦 Loading scripts for ${componentId}:`, component.scripts);
await this.loadScripts(component.scripts);
}
@ -696,6 +771,7 @@ class SystemLoader {
);
if (!componentDepsMet) {
// console.log(`⏳ Skipping ${componentId} - component dependencies not met:`, component.dependencies);
continue;
}
@ -705,8 +781,10 @@ class SystemLoader {
// Store result if available
if (result && component.global) {
window[component.global] = result;
// console.log(`✅ ${componentId} stored as global: ${component.global}`);
}
// console.log(`✅ ${componentId} initialized successfully`);
} catch (error) {
console.error(`❌ Failed to initialize ${componentId}:`, error);
@ -720,8 +798,10 @@ class SystemLoader {
// Mark system as initialized
system.initialized = true;
// console.log(`✅ ${system.name} system initialized`);
}
// console.log('🎉 Component initialization complete');
}
// Load a script dynamically
@ -759,6 +839,8 @@ class SystemLoader {
results.get(dep)?.status === 'passed'
);
// console.log(`🔍 Processing system: ${system.name} (priority: ${system.priority}, critical: ${system.critical})`);
// console.log(`🔍 Dependencies met: ${dependenciesMet}`);
if (!dependenciesMet) {
results.set(systemId, {
@ -774,6 +856,7 @@ class SystemLoader {
// If system has no checks, it automatically passes
if (system.checks.length === 0) {
// console.log(`✅ System "${system.name}" (${systemId}) has no checks - automatically passing`);
// Trigger status update for systems with no checks
this.trigger('onSystemCheck', {
@ -797,12 +880,15 @@ class SystemLoader {
status: 'checking'
});
// console.log(`🔍 System "${system.name}" (${systemId}) is running check "${check.name}" (${checkId})`);
try {
const result = await this.runSingleCheck(checkId, check);
systemResults.push(result);
if (result.status === 'failed') {
// console.log(`❌ Check failed: ${check.name} for system ${system.name}`);
// console.log(`🔍 System critical: ${system.critical}`);
this.errors.push({
system: system.name,
@ -835,6 +921,8 @@ class SystemLoader {
}
}
} catch (error) {
// console.log(`❌ Exception in check: ${check.name} for system ${system.name}:`, error.message);
// console.log(`🔍 System critical: ${system.critical}`);
systemResults.push({
id: checkId,
@ -901,6 +989,7 @@ class SystemLoader {
const startTime = Date.now();
try {
// console.log(`🔍 Running health check: ${check.name} (${checkId})`);
const result = await Promise.race([
Promise.resolve(check.check()),
@ -912,6 +1001,7 @@ class SystemLoader {
const duration = Date.now() - startTime;
const passed = Boolean(result);
// console.log(`✅ Health check ${check.name}: ${passed ? 'PASSED' : 'FAILED'} (${duration}ms)`);
this.completedChecks += check.weight;
this.progress = Math.min((this.completedChecks / this.totalChecks) * 100, 100);
@ -992,6 +1082,7 @@ class SystemLoader {
// Check required config files
async checkConfigFiles() {
// console.log('🔍 Config Files Check: Starting validation');
// Critical files that MUST exist and not be empty
const criticalFiles = [
@ -1008,6 +1099,7 @@ class SystemLoader {
'/data/config/generated/configs.json'
];
// console.log('🔍 Critical files to check:', criticalFiles);
// Optional files (can be missing but should exist if possible)
const optionalFiles = [
@ -1025,26 +1117,34 @@ class SystemLoader {
for (const file of allFiles) {
try {
// console.log(`🔍 Checking file: ${file}`);
const response = await fetch(file, { method: 'HEAD' });
// console.log(`📊 Response status: ${response.status} ${response.statusText}`);
// console.log(`📊 Content-Length: ${response.headers.get('content-length')}`);
// console.log(`📊 Response ok: ${response.ok}`);
if (response.ok) {
// Get content length to check if file is empty
const contentLength = response.headers.get('content-length');
const isEmpty = contentLength === '0' || contentLength === null;
// console.log(`🔍 File ${file} - contentLength: ${contentLength}, isEmpty: ${isEmpty}`);
// For critical files, also check actual content
if (criticalFiles.includes(file) && !isEmpty) {
try {
const getResponse = await fetch(file);
const content = await getResponse.text();
// console.log(`📄 Content preview for ${file}:`, content.substring(0, 100) + '...');
// Check if content starts with '{' or '[' (basic JSON file check)
const startsWithBrace = content.trim().startsWith('{');
const startsWithBracket = content.trim().startsWith('[');
const isValidJSONStart = startsWithBrace || startsWithBracket;
// console.log(`🔍 JSON structure check for ${file}: starts with '{' or '['? ${isValidJSONStart} (content: ${content.trim().substring(0, 10)}...)`);
if (!isValidJSONStart) {
// console.log(`❌ Critical file does not start with '{' or '[' (not valid JSON): ${file}`);
results.criticalEmpty.push(file);
results.empty.push(file);
}
@ -1052,6 +1152,7 @@ class SystemLoader {
else if (content.includes('404 Not Found') || content.includes('404') && content.includes('Not Found') ||
(content.includes('Error') && (content.includes('<!DOCTYPE') || content.includes('<html>') || content.includes('status'))) ||
content.includes('<!DOCTYPE') || content.includes('<html>')) {
// console.log(`❌ Critical file contains error/fallback content: ${file}`);
results.criticalEmpty.push(file);
results.empty.push(file);
}
@ -1059,13 +1160,17 @@ class SystemLoader {
else {
try {
JSON.parse(content);
// console.log(`✅ JSON syntax valid for: ${file}`);
results.available.push(file);
// console.log(`✅ File is available and valid: ${file}`);
} catch (jsonError) {
// console.log(`❌ Critical file has invalid JSON syntax: ${file} - ${jsonError.message}`);
results.criticalEmpty.push(file);
results.empty.push(file);
}
}
} catch (contentError) {
// console.log(`❌ Failed to read content for ${file}:`, contentError.message);
results.criticalEmpty.push(file);
results.empty.push(file);
}
@ -1073,24 +1178,39 @@ class SystemLoader {
results.empty.push(file);
if (criticalFiles.includes(file)) {
results.criticalEmpty.push(file);
// console.log(`❌ Critical file is EMPTY: ${file}`);
}
} else {
results.available.push(file);
// console.log(`✅ File is available: ${file}`);
}
} else {
results.missing.push(file);
if (criticalFiles.includes(file)) {
results.criticalMissing.push(file);
// console.log(`❌ Critical file is MISSING: ${file}`);
}
// console.log(`❌ File not accessible: ${file} - ${response.status} ${response.statusText}`);
}
} catch (error) {
// console.log(`❌ Exception checking file ${file}:`, error.message);
results.missing.push(file);
if (criticalFiles.includes(file)) {
results.criticalMissing.push(file);
// console.log(`❌ Critical file exception: ${file}`);
}
}
}
// console.log('Config Files Results:', {
//available: results.available.length,
//missing: results.missing.length,
//empty: results.empty.length,
//criticalMissing: results.criticalMissing.length,
//criticalEmpty: results.criticalEmpty.length,
//missingFiles: results.missing,
//emptyFiles: results.empty
//});
// Fail if ANY critical files are missing or empty
if (results.criticalMissing.length > 0 || results.criticalEmpty.length > 0) {
@ -1106,16 +1226,19 @@ class SystemLoader {
throw new Error(errorMessage);
}
// console.log('✅ Config Files Check PASSED');
return results;
}
// Check for update lock file to prevent concurrent updates
async checkUpdateLock() {
// console.log('🔍 Update Lock Check: Checking for update lock file');
try {
const response = await fetch('/data/updater-lock', { method: 'HEAD' });
const lockExists = response.ok;
// console.log(`🔒 Update lock file exists: ${lockExists}`);
if (lockExists) {
return {
@ -1139,11 +1262,13 @@ class SystemLoader {
// Check for pause lock file to prevent operations during pause
async checkPauseLock() {
// console.log('🔍 Pause Lock Check: Checking for pause lock file');
try {
const response = await fetch('/data/pause.lock', { method: 'HEAD' });
const lockExists = response.ok;
// console.log(`🔒 Pause lock file exists: ${lockExists}`);
if (lockExists) {
return {
@ -1195,10 +1320,12 @@ class SystemLoader {
return new Promise((resolve, reject) => {
// Check if script is already loaded
if (document.querySelector(`script[src="${src}"]`)) {
// console.log(`📦 Script already loaded: ${src}`);
resolve();
return;
}
// console.log(`📦 Loading script: ${src}`);
const script = document.createElement('script');
script.src = src;
// Dynamically-inserted scripts default to async=true and run in whatever
@ -1208,6 +1335,7 @@ class SystemLoader {
// prototype). Mirrors the ordered kernel ctx.loadScripts() path.
script.async = false;
script.onload = () => {
// console.log(`✅ Script loaded: ${src}`);
resolve();
};
script.onerror = () => {
@ -1222,7 +1350,9 @@ class SystemLoader {
async loadScripts(scripts) {
if (!scripts || scripts.length === 0) return;
// console.log(`📦 Loading ${scripts.length} scripts for component...`);
await Promise.all(scripts.map(script => this.loadScript(script)));
// console.log(`✅ All scripts loaded for component`);
}
}

View File

@ -1,6 +1,7 @@
// Main System Orchestrator - Ties together loading, setup, and initialization
class SystemOrchestrator {
constructor() {
// console.log('🏗️ SystemOrchestrator: Constructor called');
this.systemLoader = null;
this.loadingUI = null;
this.setupDetector = null;
@ -10,11 +11,14 @@ class SystemOrchestrator {
// Initialize the entire system
async initialize() {
// console.log('🚀 SystemOrchestrator: initialize() called');
if (this.isInitialized) {
// console.log('🔄 SystemOrchestrator: Already initialized, returning existing promise');
return this.loadingPromise;
}
// console.log('🆕 SystemOrchestrator: Starting initialization...');
try {
// Auth gate — must complete before anything else loads or fetches data
if (window.authManager) {
@ -22,15 +26,20 @@ class SystemOrchestrator {
window.authManager.interceptFetch();
}
// console.log('🚀 System Orchestrator: Initializing...');
// Initialize components
// console.log('🔧 SystemOrchestrator: Creating SystemLoader...');
this.systemLoader = new SystemLoader();
// console.log('🎨 SystemOrchestrator: Creating LoadingUI...');
this.loadingUI = new LoadingUI();
// console.log('🔍 SystemOrchestrator: Creating SetupDetector...');
this.setupDetector = new SetupDetector();
// Show loading screen
// console.log('📺 SystemOrchestrator: Showing loading screen...');
this.loadingUI.initialize();
// Setup event listeners
@ -45,15 +54,18 @@ class SystemOrchestrator {
})();
if (setupStatus.isFirstTime && !hasActiveHandoff) {
// console.log('🔧 First-time setup detected');
await this.handleFirstTimeSetup();
} else if (setupStatus.isFirstTime && hasActiveHandoff) {
console.log('[orchestrator] setup handoff in progress, skipping wizard');
await this.handleNormalLoading();
} else {
// console.log('✅ Returning user detected, proceeding with normal loading');
await this.handleNormalLoading();
}
this.isInitialized = true;
// console.log('✅ System Orchestrator: Initialization complete');
} catch (error) {
console.error('❌ System Orchestrator: Initialization failed:', error);
@ -85,6 +97,7 @@ class SystemOrchestrator {
});
this.systemLoader.on('onComplete', (data) => {
// console.log('✅ System Loader completed:', data.success ? 'SUCCESS' : 'FAILED');
// Don't show errors here - let handleNormalLoading handle all error display logic
if (data.success) {
@ -105,6 +118,7 @@ class SystemOrchestrator {
// Handle first-time setup
async handleFirstTimeSetup() {
// console.log('🎯 Starting first-time setup flow...');
return new Promise((resolve, reject) => {
// Create and show setup wizard
@ -125,6 +139,7 @@ class SystemOrchestrator {
// Handle normal loading sequence
async handleNormalLoading() {
// console.log('📊 Starting normal loading sequence...');
// Wait longer for all scripts to fully load
await new Promise(resolve => setTimeout(resolve, 2500));
@ -141,6 +156,7 @@ class SystemOrchestrator {
if (missingDataFiles.length > 0) {
console.warn('⚠️ Missing LibrePortal data files detected');
// console.log('🔍 Showing enhanced error for missing data files');
// Show enhanced error message with setup instructions
const enhancedErrors = results.errors.map(error => {
@ -162,16 +178,21 @@ class SystemOrchestrator {
// Listen for continue button click
window.addEventListener('loadingContinue', () => {
// console.log('🔄 User chose to continue despite errors');
this.proceedToApplicationWithErrors();
}, { once: true }); // Remove listener after first use
this.errorAlreadyHandled = true;
// console.log('🔍 Set errorAlreadyHandled = true, returning early');
return; // Prevent reaching the general error handling below
}
if (results.success) {
// console.log('✅ All systems passed health checks');
// Initialize all components (even if some health checks failed)
// console.log('🚨 BREAKPOINT: About to call initializeComponents()');
await this.systemLoader.initializeComponents();
// console.log('🚨 BREAKPOINT: initializeComponents() completed');
// Hide loading screen only if everything is successful
this.loadingUI.hide();
@ -180,17 +201,21 @@ class SystemOrchestrator {
this.initializeOriginalSPA();
this.proceedToApplication();
} else {
// console.log('🔍 General error handling - errorAlreadyHandled:', this.errorAlreadyHandled);
console.error('❌ Critical systems failed:', results.errors);
console.error('❌ Failed systems details:', JSON.stringify(results.errors, null, 2));
// Show errors in loading UI and keep it visible (only if not already handled)
if (!this.errorAlreadyHandled) {
// console.log('🔍 Showing general error (not already handled)');
this.loadingUI.showError(results.errors);
} else {
// console.log('🔍 Skipping general error display (already handled)');
}
// Listen for continue button click
window.addEventListener('loadingContinue', () => {
// console.log('🔄 User chose to continue despite errors');
this.proceedToApplicationWithErrors();
}, { once: true }); // Remove listener after first use
}
@ -198,6 +223,7 @@ class SystemOrchestrator {
// Proceed to main application
proceedToApplication() {
// console.log('🎯 Proceeding to main application...');
// Don't initialize SPA here - wait for component initialization to complete
// SPA will be initialized after all components are ready
@ -205,9 +231,12 @@ class SystemOrchestrator {
// Proceed with application despite errors (user choice)
async proceedToApplicationWithErrors() {
// console.log('🎯 Proceeding with application despite errors...');
// Initialize components even though some checks failed
// console.log('🚨 BREAKPOINT: About to call initializeComponents() with errors');
await this.systemLoader.initializeComponents();
// console.log('🚨 BREAKPOINT: initializeComponents() completed with errors');
// Hide loading screen and proceed
this.loadingUI.hide();
@ -220,19 +249,25 @@ class SystemOrchestrator {
// Initialize the original SPA system
initializeOriginalSPA() {
// console.log('🔄 Initializing original SPA...');
if (typeof window.spaClean !== 'undefined') {
// console.log('✅ SPA instance already initialized by SystemLoader');
// Now manually call the SPA's init method since we prevented auto-initialization
if (typeof window.spaClean.init === 'function') {
// console.log('🔄 Manually calling SPA init()...');
window.spaClean.init().then(() => {
// console.log('✅ SPA initialization completed');
}).catch(error => {
console.error('❌ SPA initialization failed:', error);
});
}
} else if (typeof LibrePortalSPAClean !== 'undefined') {
// console.log('🔄 Creating SPA instance manually...');
window.spaClean = new LibrePortalSPAClean();
// console.log('✅ SPA instance created');
} else {
console.error('❌ SPA class not available');
return;
@ -259,6 +294,7 @@ class SystemOrchestrator {
// Retry initialization
async retryInitialization() {
// console.log('🔄 Retrying initialization...');
// Reset UI
this.loadingUI.reset();
@ -311,6 +347,7 @@ class SystemOrchestrator {
// Force re-initialization (for testing)
async forceReinitialize() {
// console.log('🔄 Force re-initializing...');
// Clean up
if (this.loadingUI && this.loadingUI.isVisible) {
@ -331,12 +368,15 @@ window.SystemOrchestrator = SystemOrchestrator;
// Auto-initialize when DOM is ready
if (document.readyState === 'loading') {
// console.log('🔄 SystemOrchestrator: DOM not ready, adding listener');
document.addEventListener('DOMContentLoaded', async () => {
// console.log('🚀 SystemOrchestrator: DOM ready, initializing...');
window.systemOrchestrator = new SystemOrchestrator();
await window.systemOrchestrator.initialize();
});
} else {
// DOM already ready
// console.log('🚀 SystemOrchestrator: DOM already ready, initializing immediately...');
(async () => {
window.systemOrchestrator = new SystemOrchestrator();
await window.systemOrchestrator.initialize();

View File

@ -93,6 +93,7 @@ class ConfigShared {
// Auto-detect and create appropriate field type
static createSmartField(fieldId, key, value, title, description, options = {}) {
//console.log(`createSmartField: key=${key}, value=${value}, config=${!!options.config}`);
// Check if value is boolean (true/false strings)
const isBoolean = value === 'true' || value === 'false';
@ -114,6 +115,7 @@ class ConfigShared {
// Handle standard toggle changes
static handleToggleChange(checkbox, key, category) {
//console.log(`Toggle changed: ${key} = ${checkbox.checked} (category: ${category})`);
// Trigger custom event for other components to listen to
const event = new CustomEvent('configToggleChanged', {
@ -129,6 +131,7 @@ class ConfigShared {
// Handle master toggle changes (enables/disables multiple fields)
static handleMasterToggle(checkbox, sectionId, fieldIds) {
//console.log(`Master toggle changed: ${sectionId} = ${checkbox.checked}`);
// Enable/disable related fields
if (fieldIds && fieldIds.length > 0) {
@ -158,6 +161,7 @@ class ConfigShared {
// Handle section toggle changes (shows/hides sections)
static handleSectionToggle(checkbox, sectionId) {
//console.log(`Section toggle changed: ${sectionId} = ${checkbox.checked}`);
const content = document.getElementById(`${sectionId}-content`);
if (content) {
@ -175,6 +179,7 @@ class ConfigShared {
static async saveToggleValue(key, value) {
try {
// For now, just log the change - local implementation
//console.log('Toggle value changed:', key, value ? 'true' : 'false');
// TODO: Implement local config file update
// This would require backend integration to write to actual config files
@ -350,6 +355,7 @@ class ConfigShared {
const crontabValue = `${minute} ${cronHour} * * *`;
hiddenInput.value = crontabValue;
//console.log(`Updated crontab for ${key}: ${crontabValue}`);
}
// Toggle password visibility
@ -524,6 +530,7 @@ class ConfigShared {
// Generate appropriate field based on value type and key
static generateField(fieldId, key, value, title, description, options = {}, allConfig = {}) {
//console.log(`generateField: key=${key}, value=${value}, CFG_INSTALL_MODE=${allConfig.CFG_INSTALL_MODE?.value}`);
// Note: Git fields (CFG_GIT_*) are now handled by the toggle system in renderGitSection
// They don't need to be hidden here since the section itself is toggled
@ -723,11 +730,14 @@ class ConfigShared {
} 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 += `
<select id="${fieldId}" name="${key}" class="form-control">
${timezoneOptions.map(opt => `<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>${opt.label}</option>`).join('')}
</select>
`;
//console.log('Generated timezone dropdown HTML for', key, 'with value', value);
} else if (key === 'CFG_INSTALL_MODE') {
const selectOptions = ConfigOptions.getSelectOptions(key);
fieldHTML += `<select id="${fieldId}" name="${key}" class="form-control" onchange="handleInstallModeChange(this)">${selectOptions.map(opt => `<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>${opt.label}</option>`).join('')}</select>`;
@ -735,17 +745,25 @@ class ConfigShared {
const selectOptions = ConfigOptions.getSelectOptions(key);
fieldHTML += `<select id="${fieldId}" name="${key}" class="form-control">${selectOptions.map(opt => `<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>${opt.label}</option>`).join('')}</select>`;
} 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 += `
<select id="${fieldId}" name="${key}" class="form-control">
${selectOptions.map(opt => `<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>${opt.label}</option>`).join('')}
</select>
`;
//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 += `
<textarea id="${fieldId}" name="${key}" class="form-control" rows="4">${value}</textarea>
`;
} 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 || '';
@ -814,6 +832,7 @@ class ConfigShared {
fieldGroup.style.pointerEvents = 'auto';
}
});
//console.log(`Section ${sectionId} enabled`);
} else {
// Disable section
sectionContent.classList.add('disabled');
@ -825,6 +844,7 @@ class ConfigShared {
fieldGroup.style.pointerEvents = 'none';
}
});
//console.log(`Section ${sectionId} disabled`);
}
}
@ -840,9 +860,11 @@ class ConfigShared {
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`);
}
}
@ -909,15 +931,22 @@ class ConfigShared {
// 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) {
@ -926,8 +955,10 @@ class ConfigShared {
}
});
} 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) {
@ -938,34 +969,51 @@ class ConfigShared {
}
}
//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';
}
@ -973,6 +1021,7 @@ class ConfigShared {
}
}
//console.log('=== TOGGLE MAIL SECTION DEBUG END ===');
}
static toggleRemoteBackupSection(sectionId, isEnabled) {
@ -1439,6 +1488,7 @@ 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
};
@ -1450,10 +1500,12 @@ window.isReloadingConfig = false;
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;
@ -1463,9 +1515,11 @@ window.handleInstallModeChange = function(selectElement) {
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(() => {
@ -1475,6 +1529,7 @@ window.handleInstallModeChange = function(selectElement) {
} 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(() => {

View File

@ -184,6 +184,7 @@ class ConfigOptions {
// Get select options for specific config keys
static getSelectOptions(key) {
//console.log('=== getSelectOptions ENTRY === called with:', key);
const optionMaps = {
'CFG_DOCKER_INSTALL_TYPE': [
{ value: 'rooted', label: 'Rooted' },
@ -273,6 +274,7 @@ class ConfigOptions {
}
const result = optionMaps[key] || [];
//console.log('Final result for', key, ':', result);
return result;
}
@ -328,6 +330,7 @@ class ConfigOptions {
// Check if a config key should use dropdown
static isDropdownKey(key) {
//console.log('ConfigOptions.isDropdownKey called with:', key);
const result = key === 'CFG_DOCKER_INSTALL_TYPE' ||
key === 'CFG_UFW_LOGGING' ||
key === 'CFG_TEXT_EDITOR' ||
@ -342,6 +345,7 @@ class ConfigOptions {
key === 'CFG_GLUETUN_VPN_SERVICE_PROVIDER' ||
key === 'CFG_GLUETUN_VPN_TYPE' ||
key === 'CFG_TRAEFIK_DASHBOARD_ACCESS';
//console.log('ConfigOptions.isDropdownKey result:', result);
return result;
}
@ -353,9 +357,11 @@ class ConfigOptions {
// Fetch available domains from system configuration
static async getAvailableDomains() {
try {
//console.log('🔍 Starting domain fetch...');
// Try to load system config to get domain information
const response = await fetch('/data/config/generated/configs.json');
//console.log('📡 Config response status:', response.status);
if (!response.ok) {
console.warn('Could not load system config for domains, returning empty list');
@ -363,8 +369,12 @@ class ConfigOptions {
}
const configData = await response.json();
//console.log('📄 Full config data:', configData);
//console.log('🔧 Config keys available:', Object.keys(configData));
const config = configData.config || {};
//console.log('⚙️ Config object:', config);
//console.log('🔑 Config keys:', Object.keys(config));
const domains = [];
@ -372,6 +382,7 @@ class ConfigOptions {
for (let i = 1; i <= 9; i++) {
const domainKey = `CFG_DOMAIN_${i}`;
const domainValue = config[domainKey]?.value || config[domainKey] || '';
//console.log(`🌐 Checking ${domainKey}:`, domainValue);
if (domainValue.trim() !== '') {
domains.push({
@ -382,6 +393,7 @@ class ConfigOptions {
}
}
//console.log('✅ Found configured domains:', domains);
return domains;
} catch (error) {
@ -392,9 +404,12 @@ class ConfigOptions {
// Get domain options for DOMAIN field
static async getDomainOptions() {
//console.log('🎯 Getting domain options...');
const domains = await this.getAvailableDomains();
//console.log('📊 Domains returned:', domains);
if (domains.length === 0) {
//console.log('⚠️ No domains found, returning fallback option');
// No domains configured - return empty option
return [
{ value: '1', label: 'No domains configured - Configure domains in Network settings first' }
@ -407,6 +422,7 @@ class ConfigOptions {
label: domain.domain
}));
//console.log('✅ Generated domain options:', options);
return options;
}

View File

@ -56,6 +56,7 @@ class DataLoader {
static async getContainers() {
try {
// Local implementation - return empty array for now
//console.log('Loading containers locally...');
// TODO: Implement local container detection
// This would require backend integration to query Docker
@ -71,12 +72,14 @@ class DataLoader {
static async loadSystemConfigs() {
try {
//console.log('Loading system configs');
// Load unified config
try {
const response = await fetch('/data/config/generated/configs.json');
if (response.ok) {
const data = await response.json();
//console.log('✅ Loaded unified system config');
return data;
} else {
console.warn('Failed to load unified config:', response.status);
@ -94,6 +97,7 @@ class DataLoader {
static async loadConfig(configType) {
try {
//console.log(`Loading ${configType} config`);
const response = await fetch(`/data/config/generated/config_${configType}.json`);
if (!response.ok) {
@ -166,6 +170,7 @@ let lastSystemDataHash = null;
// Dashboard-specific data loading
async function loadDashboardData() {
// console.log('🔄 Loading dashboard data...');
// Load all apps with installation status
const allApps = await DataLoader.loadApps();
@ -210,6 +215,7 @@ async function loadDashboardData() {
// Show/refresh the "out of date" banner on the dashboard.
window.updateNotifier?.renderDashboardBanner();
// console.log('✅ Dashboard data loaded successfully');
}
// Countdown timer for next automatic update
@ -291,6 +297,7 @@ function getTimeAgo(date) {
// Apps page data loading
async function loadAppsPageData() {
//console.log('Loading apps page data (all apps + categories)...');
// Load apps and categories in parallel
try {
@ -300,11 +307,18 @@ async function loadAppsPageData() {
DataLoader.loadConfigCategories()
]);
//console.log('🔍 DataLoader Results:');
//console.log(' - appsData length:', appsData?.length || 0);
//console.log(' - sidebarCategoriesData type:', typeof sidebarCategoriesData);
//console.log(' - sidebarCategoriesData keys:', Object.keys(sidebarCategoriesData || {}));
//console.log(' - configCategoriesData type:', typeof configCategoriesData);
//console.log(' - configCategoriesData keys:', Object.keys(configCategoriesData || {}));
apps = appsData;
categories = sidebarCategoriesData; // For sidebar
systemConfigs = []; // Apps page doesn't need system configs
//console.log(`Apps page loaded: ${apps.length} apps, ${categories.length} categories`);
////console.log('Apps array:', apps);
////console.log('Categories array:', categories);
@ -367,6 +381,7 @@ async function loadConfigDetailData() {
// Load system information for dashboard
async function loadSystemInfo() {
try {
//console.log('Loading system data');
// Add timestamp to prevent browser caching
const timestamp = Date.now();
@ -384,6 +399,9 @@ async function loadSystemInfo() {
const diskData = await diskResponse.json();
const memoryData = await memoryResponse.json();
//console.log('System data:', systemData);
//console.log('Disk data:', diskData);
//console.log('Memory data:', memoryData);
// Prepare system data
const osInfo = systemData.os || 'Unknown';
@ -393,6 +411,7 @@ async function loadSystemInfo() {
osInfo.split(' ')[0]; // Take first part like "Linux"
const diskChartData = diskData.root || diskData;
//console.log('Disk chart data:', diskChartData);
// Wait for all dashboard elements to be available
await waitForDashboardElements();
@ -404,12 +423,14 @@ async function loadSystemInfo() {
if (systemInfoEl) {
systemInfoEl.textContent = cleanOs;
// console.log('✅ Updated OS:', cleanOs);
} else {
console.warn('⚠️ system-info element not found');
}
if (uptimeEl) {
uptimeEl.textContent = systemData.uptime || 'Unknown';
// console.log('✅ Updated uptime:', systemData.uptime);
} else {
console.warn('⚠️ uptime-info element not found');
}
@ -417,6 +438,7 @@ async function loadSystemInfo() {
// Update memory info - use the text field which should be properly formatted
if (memoryEl) {
memoryEl.textContent = memoryData.text || 'Unknown';
// console.log('✅ Updated memory:', memoryData.text);
} else {
console.warn('⚠️ memory-info element not found');
}
@ -614,6 +636,7 @@ function updateDiskChart(data) {
// Minimal data loading (fallback)
async function loadMinimalData() {
//console.log('Loading minimal data...');
apps = [];
categories = [];
@ -622,9 +645,11 @@ async function loadMinimalData() {
// Preload essential data for smooth navigation
async function preloadOtherPagesData(currentPage) {
//console.log('Preloading essential data for navigation...');
try {
// No preloading needed - data loads fresh each page
//console.log('Preloading complete (no caching)');
} catch (error) {
console.error('Error preloading navigation data:', error);

View File

@ -3,6 +3,7 @@
function safeGetElement(id) {
const element = document.getElementById(id);
if (!element) {
//console.log(`${id} element not found`);
}
return element;
}
@ -10,6 +11,7 @@ function safeGetElement(id) {
function safeQuerySelector(selector) {
const element = document.querySelector(selector);
if (!element) {
//console.log(`${selector} element not found`);
}
return element;
}
@ -17,6 +19,7 @@ function safeQuerySelector(selector) {
function safeQuerySelectorAll(selector) {
const elements = document.querySelectorAll(selector);
if (!elements || elements.length === 0) {
//console.log(`${selector} elements not found`);
}
return elements;
}

View File

@ -11,6 +11,7 @@ class LibrePortalSPAClean {
}
async init() {
//console.log('🚀 Clean SPA: Initializing...');
// Setup routes from the feature manifest (falls back to the built-in table)
await this.setupRoutesFromManifest();
@ -31,9 +32,11 @@ class LibrePortalSPAClean {
// Handle initial route
this.handleInitialRoute();
//console.log('✅ Clean SPA: Initialization complete');
}
async waitForTopbar() {
//console.log('⏳ Waiting for topbar to load...');
// Wait for topbar component to be available and loaded
let attempts = 0;
@ -43,6 +46,7 @@ class LibrePortalSPAClean {
if (typeof TopbarComponent !== 'undefined' && TopbarComponent.loadTopbar) {
try {
await TopbarComponent.loadTopbar();
//console.log('✅ Topbar loaded successfully');
return;
} catch (error) {
console.warn('⚠️ Topbar loading failed, retrying...', error);
@ -74,6 +78,7 @@ class LibrePortalSPAClean {
// Legacy /config, /peers, /ssh are handled by _legacyRedirect() at the top
// of navigate() (rewrites to the canonical /admin/* path).
//console.log('📍 Routes registered:', Array.from(this.routes.keys()));
}
// Build the route table from the feature manifest (window.LP.features) so
@ -131,6 +136,7 @@ class LibrePortalSPAClean {
}
}
this._routesFromManifest = true;
//console.log('📍 Routes registered from manifest:', Array.from(this.routes.keys()));
} catch (err) {
console.error('[spa] manifest routing failed, using built-in routes:', err);
this.setupRoutes();
@ -170,12 +176,14 @@ class LibrePortalSPAClean {
}
async loadCoreData() {
//console.log('📊 Loading core data...');
try {
// Load apps
if (typeof DataLoader !== 'undefined' && DataLoader.loadApps) {
this.apps = await DataLoader.loadApps();
window.apps = this.apps;
//console.log(`📱 Loaded ${this.apps.length} apps`);
}
// Load categories
@ -183,9 +191,11 @@ class LibrePortalSPAClean {
this.categories = await DataLoader.loadCategories();
window.categories = this.categories;
window.sidebarCategories = this.categories; // Ensure this is always available
//console.log(`📂 Loaded ${Object.keys(this.categories).length} categories`);
}
this.dataLoaded = true;
//console.log('✅ Core data loaded successfully');
} catch (error) {
console.error('❌ Failed to load core data:', error);
@ -195,16 +205,20 @@ class LibrePortalSPAClean {
handleInitialRoute() {
const path = window.location.pathname + window.location.search;
// console.log('🎯 SPA: Handling initial route:', path);
// Handle root path - redirect to dashboard
if (path === '/' || path === '') {
// console.log('🏠 SPA: Redirecting to dashboard');
this.navigate('/dashboard', false);
} else {
// console.log('🔀 SPA: Navigating to:', path);
this.navigate(path, false); // Don't add to history for initial load
}
}
async navigate(path, addToHistory = true) {
// console.log('🚀 SPA: navigate called with:', path, 'addToHistory:', addToHistory);
// Legacy URL redirects (the old /ssh, /peers, /config short URLs → the Admin
// area). Rewritten here, at the top of navigate() and BEFORE the isLoading
@ -219,10 +233,12 @@ class LibrePortalSPAClean {
}
if (this.isLoading) {
// console.log('⏳ Navigation already in progress, ignoring:', path);
return;
}
if (this.currentRoute === path && addToHistory) {
//console.log('🔄 Same route, skipping navigation:', path);
return;
}
@ -243,6 +259,7 @@ class LibrePortalSPAClean {
// navigation. See components/dashboard/index.js.)
this.isLoading = true;
//console.log('🧭 Navigating to:', path);
try {
// Update browser history
@ -286,9 +303,11 @@ class LibrePortalSPAClean {
}
findRouteHandler(path) {
//console.log('🔍 Finding handler for path:', path);
// Exact match first
if (this.routes.has(path)) {
//console.log('✅ Exact match found:', path);
return this.routes.get(path);
}
@ -298,9 +317,11 @@ class LibrePortalSPAClean {
basePath = path.split('?')[0];
}
//console.log('🔍 Checking base path:', basePath, 'for full path:', path);
// Check base path against routes
if (this.routes.has(basePath)) {
//console.log('✅ Base path match found:', basePath);
return this.routes.get(basePath);
}
@ -309,22 +330,31 @@ class LibrePortalSPAClean {
if (route.includes('*')) {
const pattern = route.replace('*', '');
if (basePath.startsWith(pattern)) {
//console.log('✅ Wildcard match:', pattern, 'for base path:', basePath);
return handler;
}
}
}
//console.log('❌ No handler found for:', path);
//console.log('❌ Base path:', basePath);
//console.log('❌ Available routes:', Array.from(this.routes.keys()));
return null;
}
async handleDashboard() {
// console.log('🏠 SPA: Loading dashboard...');
try {
// console.log('📄 SPA: Fetching dashboard content...');
const html = await this.fetchContent('/components/dashboard/html/dashboard-content.html');
// console.log('📄 SPA: Dashboard content fetched, loading...');
this.loadContent(html, 'Dashboard');
// console.log('📄 SPA: Dashboard content loaded');
// Dashboard should already be initialized by SystemLoader
if (typeof loadInstalledApps === 'function') {
// console.log('📱 SPA: Loading installed apps...');
loadInstalledApps();
}
} catch (error) {
@ -374,6 +404,7 @@ class LibrePortalSPAClean {
}
async handleApps() {
//console.log('📱 Loading apps...');
// Category from the path (/apps/<category>), else legacy ?=<cat> / ?apps=.
const seg = window.location.pathname.replace(/^\/apps\/?/, '').split('/')[0];
@ -391,15 +422,19 @@ class LibrePortalSPAClean {
try {
// Ensure unified layout is loaded (like the old SPA)
if (!document.querySelector('.apps-layout')) {
//console.log('📄 Loading apps layout HTML...');
const html = await this.fetchContent('/components/apps/core/html/apps-unified-layout.html');
this.loadContent(html, 'Applications');
} else {
//console.log('📄 Apps layout already exists, skipping HTML load');
}
// Apps manager should already be initialized by SystemLoader
if (window.appsManager) {
//console.log('✅ AppsManager already initialized by SystemLoader');
await window.appsManager.initialize();
//console.log('✅ Apps loaded successfully');
} else {
console.error('AppsManager not available - SystemLoader should have initialized it');
throw new Error('AppsManager not initialized by SystemLoader');
@ -411,6 +446,7 @@ class LibrePortalSPAClean {
}
async handleAppDetail() {
//console.log('🔍 Loading app detail...');
// Extract app name. Path-based /app/<name> first, then legacy ?app= / ?=name.
const url = new URL(window.location);
@ -431,6 +467,7 @@ class LibrePortalSPAClean {
}
}
//console.log('🔍 Parsed app name:', appName, 'from URL:', url.search);
if (!appName) {
this.navigate('/apps', false);
@ -459,12 +496,14 @@ class LibrePortalSPAClean {
// AppTabbedManager should already be initialized by SystemLoader
if (window.appTabbedManager) {
// console.log('✅ AppTabbedManager already initialized by SystemLoader');
await window.appTabbedManager.initialize();
} else {
console.error('AppTabbedManager not available - SystemLoader should have initialized it');
throw new Error('AppTabbedManager not initialized by SystemLoader');
}
//console.log('✅ App detail loaded:', appName);
} catch (error) {
console.error('❌ App detail load error:', error);
this.showError('Failed to load application details');
@ -496,6 +535,7 @@ class LibrePortalSPAClean {
// Legacy /config and /config?=<x> → the path-based /admin equivalent.
async handleTasks() {
//console.log('📋 Loading tasks...');
try {
const html = await this.fetchContent('/components/tasks/html/tasks-content.html');
@ -503,6 +543,7 @@ class LibrePortalSPAClean {
// Tasks manager should already be initialized by SystemLoader
if (window.tasksManager) {
//console.log('✅ TasksManager already initialized by SystemLoader');
await window.tasksManager.init();
} else {
console.warn('⚠️ TasksManager not available yet, task functionality will be limited');
@ -516,6 +557,7 @@ class LibrePortalSPAClean {
}
async fetchContent(url) {
//console.log('📥 Fetching:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
@ -553,6 +595,7 @@ class LibrePortalSPAClean {
}
updateNavigation() {
//console.log('🔗 SPA: Using fallback navigation logic');
// Remove ALL active classes
document.querySelectorAll('.nav-item').forEach(item => {
@ -579,6 +622,7 @@ class LibrePortalSPAClean {
activeId = 'nav-dashboard';
}
//console.log('🔗 SPA: Setting active nav:', activeId);
const activeElement = document.getElementById(activeId);
if (activeElement) {
activeElement.classList.add('nav-active');
@ -649,6 +693,7 @@ window.appPartsFromPath = function (pathname) {
// Global navigation function for click handlers
window.navigateToRoute = function(href) {
if (window.spaClean) {
//console.log('🔗 Converting href:', href);
// Convert href to clean path - handle various formats
let route = href.replace('.html', '').replace('./', '').replace(/^\//, '');
@ -668,6 +713,7 @@ window.navigateToRoute = function(href) {
route = '/' + route;
}
//console.log('🎯 Final route:', route);
window.spaClean.navigate(route);
}
};

View File

@ -261,6 +261,9 @@ class LoadingUI {
// Show error message
showError(errors) {
// console.log('🔍 LoadingUI.showError called with errors:', errors.length);
// console.log('🔍 Existing error details elements:', document.querySelectorAll('.error-details').length);
// console.log('🔍 Call stack:', new Error().stack);
const actionsContainer = document.getElementById('loading-actions');
@ -376,6 +379,7 @@ class LoadingUI {
attachEventListeners() {
if (this.retryButton) {
this.retryButton.addEventListener('click', () => {
// console.log('🔄 Retry button clicked - refreshing page');
window.location.reload();
});
}

View File

@ -29,9 +29,11 @@ class ConfirmationDialog {
this.overlay.addEventListener('click', () => this.hide());
this.dialog.addEventListener('click', (e) => e.stopPropagation());
//console.log('Confirmation dialog initialized');
}
show(title, message, onConfirm, confirmText = 'Confirm', cancelText = 'Cancel', confirmClass = 'primary', showDataLossCheckbox = false) {
//console.log('Showing confirmation dialog');
this.callback = onConfirm;
@ -65,6 +67,8 @@ class ConfirmationDialog {
this.overlay.classList.add('active');
// Debug: Check if class was added
//console.log('Active class added:', this.overlay.className);
//console.log('Has active class:', this.overlay.classList.contains('active'));
// Handle checkbox if present
if (showDataLossCheckbox) {
@ -83,6 +87,7 @@ class ConfirmationDialog {
};
document.addEventListener('keydown', handleEscape);
//console.log('Confirmation dialog shown');
}
updateConfirmButton(isChecked) {
@ -106,6 +111,7 @@ class ConfirmationDialog {
}
}
//console.log('Confirmation dialog confirmed');
if (this.callback) {
this.callback();
}
@ -113,9 +119,13 @@ class ConfirmationDialog {
}
hide() {
//console.log('Hiding confirmation dialog');
//console.log('Before remove - classes:', this.overlay.className);
this.overlay.classList.remove('active');
//console.log('After remove - classes:', this.overlay.className);
//console.log('Has active class after remove:', this.overlay.classList.contains('active'));
this.callback = null;
}

View File

@ -161,6 +161,8 @@ class TaskCommands {
const taskFileName = `${taskId}.json`;
const taskFilePath = `tasks/queue/${taskFileName}`;
// console.log('🔍 Creating task file:', taskFilePath);
// console.log('🔍 Task ID:', taskId);
const task = {
id: taskId,
@ -173,6 +175,7 @@ class TaskCommands {
updatedAt: new Date().toISOString()
};
// console.log('🔍 Task object:', task);
const response = await fetch('/write-file', {
method: 'POST',

View File

@ -1,14 +1,18 @@
// Preserve task parameter before SPA strips it
(function() {
// console.log('🔍 Task parameter preservation script loaded');
// Check current URL for task parameter
const currentUrl = window.location.href;
const urlParams = new URLSearchParams(window.location.search);
const taskId = urlParams.get('task');
// console.log('🔍 Current URL:', currentUrl);
// console.log('🔍 Task parameter detected:', taskId);
if (taskId) {
// Store task parameter in sessionStorage
sessionStorage.setItem('pendingTaskId', taskId);
// console.log('🔍 Stored task ID in sessionStorage:', taskId);
}
})();

View File

@ -16,6 +16,7 @@ class TaskRouter {
*/
async routeAction(action, params = {}) {
try {
//console.log(`🎯 Routing action: ${action}`, params);
switch (action) {
case 'install':
@ -101,6 +102,7 @@ class TaskRouter {
};
this.commandQueue.push(queuedTask);
//console.log(`📋 Queued action: ${action}`, params);
// Start processing if not already running
if (!this.isProcessing) {
@ -119,14 +121,17 @@ class TaskRouter {
}
this.isProcessing = true;
//console.log('🔄 Processing command queue...');
while (this.commandQueue.length > 0) {
const queuedTask = this.commandQueue.shift();
try {
//console.log(`⚡ Executing queued action: ${queuedTask.action}`);
await this.routeAction(queuedTask.action, queuedTask.params);
queuedTask.status = 'completed';
//console.log(`✅ Completed queued action: ${queuedTask.action}`);
} catch (error) {
queuedTask.status = 'failed';
@ -153,6 +158,7 @@ class TaskRouter {
}
this.isProcessing = false;
//console.log('✅ Command queue processing complete');
}
/**

View File

@ -304,6 +304,7 @@ class TopbarComponent {
if (href) {
// Special handling for tasks navigation
if (item.id === 'nav-tasks' && window.tasksManager) {
// console.log('🔄 Tasks button clicked - forcing clean state and reloading...');
// Force clean state
window.tasksManager.highlightedTaskId = null;