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);
}