diff --git a/public/components.js b/public/components.js index a572e83..e84b126 100644 --- a/public/components.js +++ b/public/components.js @@ -1067,6 +1067,20 @@ class NodeDetailsComponent extends Component { class FirmwareComponent extends Component { constructor(container, viewModel, eventBus) { super(container, viewModel, eventBus); + + console.log('FirmwareComponent: Constructor called'); + console.log('FirmwareComponent: Container:', container); + console.log('FirmwareComponent: Container ID:', container?.id); + + // Check if the dropdown exists in the container + if (container) { + const dropdown = container.querySelector('#specific-node-select'); + console.log('FirmwareComponent: Dropdown found in constructor:', !!dropdown); + if (dropdown) { + console.log('FirmwareComponent: Dropdown tagName:', dropdown.tagName); + console.log('FirmwareComponent: Dropdown id:', dropdown.id); + } + } } setupEventListeners() { @@ -1084,8 +1098,18 @@ class FirmwareComponent extends Component { // Setup specific node select change handler const specificNodeSelect = this.findElement('#specific-node-select'); + console.log('FirmwareComponent: setupEventListeners - specificNodeSelect found:', !!specificNodeSelect); if (specificNodeSelect) { - this.addEventListener(specificNodeSelect, 'change', this.handleNodeSelect.bind(this)); + console.log('FirmwareComponent: specificNodeSelect element:', specificNodeSelect); + console.log('FirmwareComponent: specificNodeSelect tagName:', specificNodeSelect.tagName); + console.log('FirmwareComponent: specificNodeSelect id:', specificNodeSelect.id); + + // Store the bound handler as an instance property + this._boundNodeSelectHandler = this.handleNodeSelect.bind(this); + this.addEventListener(specificNodeSelect, 'change', this._boundNodeSelectHandler); + console.log('FirmwareComponent: Event listener added to specificNodeSelect'); + } else { + console.warn('FirmwareComponent: specificNodeSelect element not found during setupEventListeners'); } // Setup deploy button @@ -1114,6 +1138,23 @@ class FirmwareComponent extends Component { this.subscribeToProperty('isUploading', this.updateUploadState.bind(this)); } + mount() { + super.mount(); + + console.log('FirmwareComponent: Mounting...'); + + // Check if the dropdown exists when mounted + const dropdown = this.findElement('#specific-node-select'); + console.log('FirmwareComponent: Mount - dropdown found:', !!dropdown); + if (dropdown) { + console.log('FirmwareComponent: Mount - dropdown tagName:', dropdown.tagName); + console.log('FirmwareComponent: Mount - dropdown id:', dropdown.id); + console.log('FirmwareComponent: Mount - dropdown innerHTML:', dropdown.innerHTML); + } + + console.log('FirmwareComponent: Mounted successfully'); + } + render() { // Initial render is handled by the HTML template this.updateDeployButton(); @@ -1131,7 +1172,15 @@ class FirmwareComponent extends Component { handleNodeSelect(event) { const nodeIp = event.target.value; + console.log('FirmwareComponent: handleNodeSelect called with nodeIp:', nodeIp); + console.log('Event:', event); + console.log('Event target:', event.target); + console.log('Event target value:', event.target.value); + this.viewModel.setSpecificNode(nodeIp); + + // Also update the deploy button state + this.updateDeployButton(); } async handleDeploy() { @@ -1206,14 +1255,36 @@ class FirmwareComponent extends Component { // Show upload progress area this.showUploadProgress(file, [{ ip: nodeIp, hostname: nodeIp }]); + // Update progress to show starting + this.updateNodeProgress(1, 1, nodeIp, 'Uploading...'); + // Perform single node upload const result = await this.performSingleUpload(file, nodeIp); + // Update progress to show completion + this.updateNodeProgress(1, 1, nodeIp, 'Completed'); + this.updateOverallProgress(1, 1); + // Display results this.displayUploadResults([result]); } catch (error) { console.error(`Failed to upload firmware to node ${nodeIp}:`, error); + + // Update progress to show failure + this.updateNodeProgress(1, 1, nodeIp, 'Failed'); + this.updateOverallProgress(0, 1); + + // Display error results + const errorResult = { + nodeIp: nodeIp, + hostname: nodeIp, + success: false, + error: error.message, + timestamp: new Date().toISOString() + }; + this.displayUploadResults([errorResult]); + throw error; } } @@ -1298,7 +1369,7 @@ class FirmwareComponent extends Component {
- 0/0 Successful (0%) + 0/${nodes.length} Successful (0%)
Status: Preparing upload... @@ -1320,6 +1391,12 @@ class FirmwareComponent extends Component { `; container.innerHTML = progressHTML; + + // Initialize progress for single-node uploads + if (nodes.length === 1) { + const node = nodes[0]; + this.updateNodeProgress(1, 1, node.ip, 'Pending...'); + } } updateNodeProgress(current, total, nodeIp, status) { @@ -1370,6 +1447,16 @@ class FirmwareComponent extends Component { } else { progressBar.style.backgroundColor = '#fbbf24'; } + + // Update progress summary for single-node uploads + const progressSummary = this.findElement('#progress-summary'); + if (progressSummary && totalNodes === 1) { + if (successfulUploads === 1) { + progressSummary.innerHTML = 'Status: Upload completed successfully'; + } else if (successfulUploads === 0) { + progressSummary.innerHTML = 'Status: Upload failed'; + } + } } } @@ -1382,10 +1469,21 @@ class FirmwareComponent extends Component { const totalCount = results.length; const successRate = Math.round((successCount / totalCount) * 100); - if (successCount === totalCount) { + if (totalCount === 1) { + // Single node upload + if (successCount === 1) { + progressHeader.textContent = `📤 Firmware Upload Complete`; + progressSummary.innerHTML = `✅ Upload to ${results[0].hostname || results[0].nodeIp} completed successfully at ${new Date().toLocaleTimeString()}`; + } else { + progressHeader.textContent = `📤 Firmware Upload Failed`; + progressSummary.innerHTML = `❌ Upload to ${results[0].hostname || results[0].nodeIp} failed at ${new Date().toLocaleTimeString()}`; + } + } else if (successCount === totalCount) { + // Multi-node upload - all successful progressHeader.textContent = `📤 Firmware Upload Complete (${successCount}/${totalCount} Successful)`; progressSummary.innerHTML = `✅ All uploads completed successfully at ${new Date().toLocaleTimeString()}`; } else { + // Multi-node upload - some failed progressHeader.textContent = `📤 Firmware Upload Results (${successCount}/${totalCount} Successful)`; progressSummary.innerHTML = `⚠️ Upload completed with ${totalCount - successCount} failure(s) at ${new Date().toLocaleTimeString()}`; } @@ -1412,21 +1510,35 @@ class FirmwareComponent extends Component { const targetType = this.viewModel.get('targetType'); const specificNodeSelect = this.findElement('#specific-node-select'); + console.log('FirmwareComponent: updateTargetVisibility called with targetType:', targetType); + if (targetType === 'specific') { specificNodeSelect.style.visibility = 'visible'; specificNodeSelect.style.opacity = '1'; - this.populateNodeSelect(); + console.log('FirmwareComponent: Showing specific node select'); + + // Check if the dropdown exists and is ready + if (specificNodeSelect && specificNodeSelect.tagName === 'SELECT') { + console.log('FirmwareComponent: Dropdown is ready, populating immediately'); + this.populateNodeSelect(); + } else { + console.log('FirmwareComponent: Dropdown not ready, delaying population'); + // Small delay to ensure the dropdown is visible before populating + setTimeout(() => { + this.populateNodeSelect(); + }, 100); + } } else { specificNodeSelect.style.visibility = 'hidden'; specificNodeSelect.style.opacity = '0'; + console.log('FirmwareComponent: Hiding specific node select'); } this.updateDeployButton(); } - handleNodeSelect() { - this.updateDeployButton(); - } + // Note: handleNodeSelect is already defined above and handles the actual node selection + // This duplicate method was causing the issue - removing it updateDeployButton() { const deployBtn = this.findElement('#deploy-btn'); @@ -1437,7 +1549,19 @@ class FirmwareComponent extends Component { populateNodeSelect() { const select = this.findElement('#specific-node-select'); - if (!select) return; + if (!select) { + console.warn('FirmwareComponent: populateNodeSelect - select element not found'); + return; + } + + if (select.tagName !== 'SELECT') { + console.warn('FirmwareComponent: populateNodeSelect - element is not a SELECT:', select.tagName); + return; + } + + console.log('FirmwareComponent: populateNodeSelect called'); + console.log('FirmwareComponent: Select element:', select); + console.log('FirmwareComponent: Available nodes:', this.viewModel.get('availableNodes')); // Clear existing options select.innerHTML = ''; @@ -1461,6 +1585,27 @@ class FirmwareComponent extends Component { option.textContent = `${node.hostname} (${node.ip})`; select.appendChild(option); }); + + // Ensure event listener is still bound after repopulating + this.ensureNodeSelectListener(select); + + console.log('FirmwareComponent: Node select populated with', availableNodes.length, 'nodes'); + } + + // Ensure the node select change listener is properly bound + ensureNodeSelectListener(select) { + if (!select) return; + + // Store the bound handler as an instance property to avoid binding issues + if (!this._boundNodeSelectHandler) { + this._boundNodeSelectHandler = this.handleNodeSelect.bind(this); + } + + // Remove any existing listeners and add the bound one + select.removeEventListener('change', this._boundNodeSelectHandler); + select.addEventListener('change', this._boundNodeSelectHandler); + + console.log('FirmwareComponent: Node select event listener ensured'); } updateUploadProgress() { @@ -1704,21 +1849,33 @@ class FirmwareViewComponent extends Component { constructor(container, viewModel, eventBus) { super(container, viewModel, eventBus); + console.log('FirmwareViewComponent: Constructor called'); + console.log('FirmwareViewComponent: Container:', container); + + const firmwareContainer = this.findElement('#firmware-container'); + console.log('FirmwareViewComponent: Firmware container found:', !!firmwareContainer); + this.firmwareComponent = new FirmwareComponent( - this.findElement('#firmware-container'), + firmwareContainer, viewModel, eventBus ); + + console.log('FirmwareViewComponent: FirmwareComponent created'); } mount() { super.mount(); + console.log('FirmwareViewComponent: Mounting...'); + // Mount sub-component this.firmwareComponent.mount(); // Update available nodes this.updateAvailableNodes(); + + console.log('FirmwareViewComponent: Mounted successfully'); } unmount() { @@ -1758,9 +1915,12 @@ class FirmwareViewComponent extends Component { async updateAvailableNodes() { try { + console.log('FirmwareViewComponent: updateAvailableNodes called'); const response = await window.apiClient.getClusterMembers(); const nodes = response.members || []; + console.log('FirmwareViewComponent: Got nodes:', nodes); this.viewModel.updateAvailableNodes(nodes); + console.log('FirmwareViewComponent: Available nodes updated in view model'); } catch (error) { console.error('Failed to update available nodes:', error); }