// Cluster View Component class ClusterViewComponent extends Component { constructor(container, viewModel, eventBus) { super(container, viewModel, eventBus); logger.debug('ClusterViewComponent: Constructor called'); logger.debug('ClusterViewComponent: Container:', container); logger.debug('ClusterViewComponent: Container ID:', container?.id); // Find elements for sub-components const primaryNodeContainer = this.findElement('.primary-node-info'); const clusterMembersContainer = this.findElement('#cluster-members-container'); logger.debug('ClusterViewComponent: Primary node container:', primaryNodeContainer); logger.debug('ClusterViewComponent: Cluster members container:', clusterMembersContainer); logger.debug('ClusterViewComponent: Cluster members container ID:', clusterMembersContainer?.id); logger.debug('ClusterViewComponent: Cluster members container innerHTML:', clusterMembersContainer?.innerHTML); // Create sub-components this.primaryNodeComponent = new PrimaryNodeComponent( primaryNodeContainer, viewModel, eventBus ); this.clusterMembersComponent = new ClusterMembersComponent( clusterMembersContainer, viewModel, eventBus ); logger.debug('ClusterViewComponent: Sub-components created'); // Track if we've already loaded data to prevent unnecessary reloads this.dataLoaded = false; // Initialize overlay dialog this.overlayDialog = null; } mount() { logger.debug('ClusterViewComponent: Mounting...'); super.mount(); logger.debug('ClusterViewComponent: Mounting sub-components...'); // Mount sub-components this.primaryNodeComponent.mount(); this.clusterMembersComponent.mount(); // Set up refresh button event listener (since it's in the cluster header, not in the members container) this.setupRefreshButton(); // Set up deploy button event listener this.setupDeployButton(); // Initialize overlay dialog this.initializeOverlayDialog(); // Only load data if we haven't already or if the view model is empty const members = this.viewModel.get('members'); const shouldLoadData = true; // always perform initial refresh quickly if (shouldLoadData) { logger.debug('ClusterViewComponent: Starting initial data load...'); // Initial data load - ensure it happens after mounting // Trigger immediately to reduce perceived startup latency this.viewModel.updateClusterMembers().then(() => { this.dataLoaded = true; }).catch(error => { logger.error('ClusterViewComponent: Failed to load initial data:', error); }); } else { logger.debug('ClusterViewComponent: Data already loaded, skipping initial load'); } // Set up periodic updates // this.setupPeriodicUpdates(); // Disabled automatic refresh logger.debug('ClusterViewComponent: Mounted successfully'); } setupRefreshButton() { logger.debug('ClusterViewComponent: Setting up refresh button...'); const refreshBtn = this.findElement('.refresh-btn'); logger.debug('ClusterViewComponent: Found refresh button:', !!refreshBtn, refreshBtn); if (refreshBtn) { logger.debug('ClusterViewComponent: Adding click event listener to refresh button'); this.addEventListener(refreshBtn, 'click', this.handleRefresh.bind(this)); logger.debug('ClusterViewComponent: Event listener added successfully'); } else { logger.error('ClusterViewComponent: Refresh button not found!'); logger.debug('ClusterViewComponent: Container HTML:', this.container.innerHTML); logger.debug('ClusterViewComponent: All buttons in container:', this.container.querySelectorAll('button')); } } setupDeployButton() { logger.debug('ClusterViewComponent: Setting up deploy button...'); const deployBtn = this.findElement('#deploy-firmware-btn'); logger.debug('ClusterViewComponent: Found deploy button:', !!deployBtn, deployBtn); if (deployBtn) { logger.debug('ClusterViewComponent: Adding click event listener to deploy button'); this.addEventListener(deployBtn, 'click', this.handleDeploy.bind(this)); logger.debug('ClusterViewComponent: Event listener added successfully'); } else { logger.error('ClusterViewComponent: Deploy button not found!'); logger.debug('ClusterViewComponent: Container HTML:', this.container.innerHTML); logger.debug('ClusterViewComponent: All buttons in container:', this.container.querySelectorAll('button')); } } initializeOverlayDialog() { // Create overlay container if it doesn't exist let overlayContainer = document.getElementById('cluster-overlay-dialog'); if (!overlayContainer) { overlayContainer = document.createElement('div'); overlayContainer.id = 'cluster-overlay-dialog'; overlayContainer.className = 'overlay-dialog'; document.body.appendChild(overlayContainer); } // Create and initialize the overlay dialog component if (!this.overlayDialog) { const overlayVM = new ViewModel(); this.overlayDialog = new OverlayDialogComponent(overlayContainer, overlayVM, this.eventBus); this.overlayDialog.mount(); } } showConfirmationDialog(options) { if (!this.overlayDialog) { this.initializeOverlayDialog(); } this.overlayDialog.show(options); } async handleDeploy() { logger.debug('ClusterViewComponent: Deploy button clicked, opening firmware upload drawer...'); // Get current filtered members from cluster members component const filteredMembers = this.clusterMembersComponent ? this.clusterMembersComponent.getFilteredMembers() : []; if (!filteredMembers || filteredMembers.length === 0) { this.showConfirmationDialog({ title: 'No Nodes Available', message: 'No nodes available for firmware deployment. Please ensure cluster members are loaded and visible.', confirmText: 'OK', cancelText: null, onConfirm: () => {}, onCancel: null }); return; } // Open drawer with firmware upload interface this.openFirmwareUploadDrawer(filteredMembers); } openFirmwareUploadDrawer(targetNodes) { logger.debug('ClusterViewComponent: Opening firmware upload drawer for', targetNodes.length, 'nodes'); // Get display name for drawer title const nodeCount = targetNodes.length; const displayName = `Firmware Deployment - ${nodeCount} node${nodeCount !== 1 ? 's' : ''}`; // Open drawer with content callback (hide terminal button for firmware upload) this.clusterMembersComponent.drawer.openDrawer(displayName, (contentContainer, setActiveComponent) => { // Create firmware upload view model and component const firmwareUploadVM = new ClusterFirmwareViewModel(); firmwareUploadVM.setTargetNodes(targetNodes); // Create HTML for firmware upload interface contentContainer.innerHTML = `