// Main SPORE UI Application // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', function() { console.log('=== SPORE UI Application Initialization ==='); // Initialize the framework (but don't navigate yet) console.log('App: Creating framework instance...'); const app = window.app; // Create view models console.log('App: Creating view models...'); const clusterViewModel = new ClusterViewModel(); const firmwareViewModel = new FirmwareViewModel(); console.log('App: View models created:', { clusterViewModel, firmwareViewModel }); // Connect firmware view model to cluster data clusterViewModel.subscribe('members', (members) => { console.log('App: Members subscription triggered:', members); if (members && members.length > 0) { // Extract node information for firmware view const nodes = members.map(member => ({ ip: member.ip, hostname: member.hostname || member.ip })); firmwareViewModel.updateAvailableNodes(nodes); console.log('App: Updated firmware view model with nodes:', nodes); } else { firmwareViewModel.updateAvailableNodes([]); console.log('App: Cleared firmware view model nodes'); } }); // Register routes with their view models console.log('App: Registering routes...'); app.registerRoute('cluster', ClusterViewComponent, 'cluster-view', clusterViewModel); app.registerRoute('firmware', FirmwareViewComponent, 'firmware-view', firmwareViewModel); console.log('App: Routes registered and components pre-initialized'); // Initialize cluster status component for header badge AFTER main components // DISABLED - causes interference with main cluster functionality /* console.log('App: Initializing cluster status component...'); const clusterStatusComponent = new ClusterStatusComponent( document.querySelector('.cluster-status'), clusterViewModel, app.eventBus ); clusterStatusComponent.initialize(); console.log('App: Cluster status component initialized'); */ // Set up navigation event listeners console.log('App: Setting up navigation...'); app.setupNavigation(); // Set up cluster status updates (simple approach without component interference) setupClusterStatusUpdates(clusterViewModel); // Set up periodic updates for cluster view with state preservation // setupPeriodicUpdates(); // Disabled automatic refresh // Now navigate to the default route console.log('App: Navigating to default route...'); app.navigateTo('cluster'); console.log('=== SPORE UI Application initialization completed ==='); }); // Set up periodic updates with state preservation function setupPeriodicUpdates() { // Auto-refresh cluster members every 30 seconds using smart update setInterval(() => { if (window.app.currentView && window.app.currentView.viewModel) { const viewModel = window.app.currentView.viewModel; // Use smart update if available, otherwise fall back to regular update if (viewModel.smartUpdate && typeof viewModel.smartUpdate === 'function') { console.log('App: Performing smart update to preserve UI state...'); viewModel.smartUpdate(); } else if (viewModel.updateClusterMembers && typeof viewModel.updateClusterMembers === 'function') { console.log('App: Performing regular update...'); viewModel.updateClusterMembers(); } } }, 30000); // Update primary node display every 10 seconds (this is lightweight and doesn't affect UI state) setInterval(() => { if (window.app.currentView && window.app.currentView.viewModel) { const viewModel = window.app.currentView.viewModel; if (viewModel.updatePrimaryNodeDisplay && typeof viewModel.updatePrimaryNodeDisplay === 'function') { viewModel.updatePrimaryNodeDisplay(); } } }, 10000); } // Set up cluster status updates (simple approach without component interference) function setupClusterStatusUpdates(clusterViewModel) { // Set initial "discovering" state immediately updateClusterStatusBadge(undefined, undefined, undefined); // Force a fresh fetch and keep showing "discovering" until we get real data let hasReceivedRealData = false; // Subscribe to view model changes to update cluster status clusterViewModel.subscribe('totalNodes', (totalNodes) => { if (hasReceivedRealData) { updateClusterStatusBadge(totalNodes, clusterViewModel.get('clientInitialized'), clusterViewModel.get('error')); } }); clusterViewModel.subscribe('clientInitialized', (clientInitialized) => { if (hasReceivedRealData) { updateClusterStatusBadge(clusterViewModel.get('totalNodes'), clientInitialized, clusterViewModel.get('error')); } }); clusterViewModel.subscribe('error', (error) => { if (hasReceivedRealData) { updateClusterStatusBadge(clusterViewModel.get('totalNodes'), clusterViewModel.get('clientInitialized'), error); } }); // Force a fresh fetch and only update status after we get real data setTimeout(async () => { try { console.log('Cluster Status: Forcing fresh fetch from backend...'); const discoveryInfo = await window.apiClient.getDiscoveryInfo(); console.log('Cluster Status: Got fresh data:', discoveryInfo); // Now we have real data, mark it and update the status hasReceivedRealData = true; updateClusterStatusBadge(discoveryInfo.totalNodes, discoveryInfo.clientInitialized, null); } catch (error) { console.error('Cluster Status: Failed to fetch fresh data:', error); hasReceivedRealData = true; updateClusterStatusBadge(0, false, error.message); } }, 100); // Small delay to ensure view model is ready } function updateClusterStatusBadge(totalNodes, clientInitialized, error) { const clusterStatusBadge = document.querySelector('.cluster-status'); if (!clusterStatusBadge) return; let statusText, statusIcon, statusClass; // Check if we're still in initial state (no real data yet) const hasRealData = totalNodes !== undefined && clientInitialized !== undefined; if (!hasRealData) { statusText = 'Cluster Discovering...'; statusIcon = '🔍'; statusClass = 'cluster-status-discovering'; } else if (error) { statusText = 'Cluster Error'; statusIcon = '❌'; statusClass = 'cluster-status-error'; } else if (totalNodes === 0) { statusText = 'Cluster Offline'; statusIcon = '🔴'; statusClass = 'cluster-status-offline'; } else if (clientInitialized) { statusText = 'Cluster Online'; statusIcon = '🟢'; statusClass = 'cluster-status-online'; } else { statusText = 'Cluster Connecting'; statusIcon = '🟡'; statusClass = 'cluster-status-connecting'; } // Update the badge clusterStatusBadge.innerHTML = `${statusIcon} ${statusText}`; // Remove all existing status classes clusterStatusBadge.classList.remove('cluster-status-online', 'cluster-status-offline', 'cluster-status-connecting', 'cluster-status-error', 'cluster-status-discovering'); // Add the appropriate status class clusterStatusBadge.classList.add(statusClass); } // Global error handler window.addEventListener('error', function(event) { console.error('Global error:', event.error); }); // Global unhandled promise rejection handler window.addEventListener('unhandledrejection', function(event) { console.error('Unhandled promise rejection:', event.reason); }); // Clean up on page unload window.addEventListener('beforeunload', function() { if (window.app) { console.log('App: Cleaning up cached components...'); window.app.cleanup(); } });