fix: specific node firmware update

This commit is contained in:
2025-08-27 13:42:57 +02:00
parent 9adaf80a2b
commit 50544a6e5e

View File

@@ -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 {
<div class="progress-bar-container">
<div class="progress-bar" id="overall-progress-bar" style="width: 0%; background-color: #fbbf24;"></div>
</div>
<span class="progress-text">0/0 Successful (0%)</span>
<span class="progress-text">0/${nodes.length} Successful (0%)</span>
</div>
<div class="progress-summary" id="progress-summary">
<span>Status: Preparing upload...</span>
@@ -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 = '<span>Status: Upload completed successfully</span>';
} else if (successfulUploads === 0) {
progressSummary.innerHTML = '<span>Status: Upload failed</span>';
}
}
}
}
@@ -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 = `<span>✅ Upload to ${results[0].hostname || results[0].nodeIp} completed successfully at ${new Date().toLocaleTimeString()}</span>`;
} else {
progressHeader.textContent = `📤 Firmware Upload Failed`;
progressSummary.innerHTML = `<span>❌ Upload to ${results[0].hostname || results[0].nodeIp} failed at ${new Date().toLocaleTimeString()}</span>`;
}
} else if (successCount === totalCount) {
// Multi-node upload - all successful
progressHeader.textContent = `📤 Firmware Upload Complete (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span>✅ All uploads completed successfully at ${new Date().toLocaleTimeString()}</span>`;
} else {
// Multi-node upload - some failed
progressHeader.textContent = `📤 Firmware Upload Results (${successCount}/${totalCount} Successful)`;
progressSummary.innerHTML = `<span>⚠️ Upload completed with ${totalCount - successCount} failure(s) at ${new Date().toLocaleTimeString()}</span>`;
}
@@ -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 = '<option value="">Select a node...</option>';
@@ -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);
}