Compare commits
No commits in common. "5414b3d407c1561f0bb3ebc836b604abe0220289" and "7a5f4cdc33359e014a1aec238c377e1dcb10b3fb" have entirely different histories.
5414b3d407
...
7a5f4cdc33
@ -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) {
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ window.ConfigValidator = function() {
|
||||
continue;
|
||||
}
|
||||
|
||||
//console.log(`Config file exists: ${config.name}`);
|
||||
|
||||
} catch (error) {
|
||||
results.valid = false;
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
}
|
||||
})();
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user