feature/framework #1
@@ -1067,6 +1067,20 @@ class NodeDetailsComponent extends Component {
|
|||||||
class FirmwareComponent extends Component {
|
class FirmwareComponent extends Component {
|
||||||
constructor(container, viewModel, eventBus) {
|
constructor(container, viewModel, eventBus) {
|
||||||
super(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() {
|
setupEventListeners() {
|
||||||
@@ -1084,8 +1098,18 @@ class FirmwareComponent extends Component {
|
|||||||
|
|
||||||
// Setup specific node select change handler
|
// Setup specific node select change handler
|
||||||
const specificNodeSelect = this.findElement('#specific-node-select');
|
const specificNodeSelect = this.findElement('#specific-node-select');
|
||||||
|
console.log('FirmwareComponent: setupEventListeners - specificNodeSelect found:', !!specificNodeSelect);
|
||||||
if (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
|
// Setup deploy button
|
||||||
@@ -1114,6 +1138,23 @@ class FirmwareComponent extends Component {
|
|||||||
this.subscribeToProperty('isUploading', this.updateUploadState.bind(this));
|
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() {
|
render() {
|
||||||
// Initial render is handled by the HTML template
|
// Initial render is handled by the HTML template
|
||||||
this.updateDeployButton();
|
this.updateDeployButton();
|
||||||
@@ -1131,7 +1172,15 @@ class FirmwareComponent extends Component {
|
|||||||
|
|
||||||
handleNodeSelect(event) {
|
handleNodeSelect(event) {
|
||||||
const nodeIp = event.target.value;
|
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);
|
this.viewModel.setSpecificNode(nodeIp);
|
||||||
|
|
||||||
|
// Also update the deploy button state
|
||||||
|
this.updateDeployButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeploy() {
|
async handleDeploy() {
|
||||||
@@ -1206,14 +1255,36 @@ class FirmwareComponent extends Component {
|
|||||||
// Show upload progress area
|
// Show upload progress area
|
||||||
this.showUploadProgress(file, [{ ip: nodeIp, hostname: nodeIp }]);
|
this.showUploadProgress(file, [{ ip: nodeIp, hostname: nodeIp }]);
|
||||||
|
|
||||||
|
// Update progress to show starting
|
||||||
|
this.updateNodeProgress(1, 1, nodeIp, 'Uploading...');
|
||||||
|
|
||||||
// Perform single node upload
|
// Perform single node upload
|
||||||
const result = await this.performSingleUpload(file, nodeIp);
|
const result = await this.performSingleUpload(file, nodeIp);
|
||||||
|
|
||||||
|
// Update progress to show completion
|
||||||
|
this.updateNodeProgress(1, 1, nodeIp, 'Completed');
|
||||||
|
this.updateOverallProgress(1, 1);
|
||||||
|
|
||||||
// Display results
|
// Display results
|
||||||
this.displayUploadResults([result]);
|
this.displayUploadResults([result]);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to upload firmware to node ${nodeIp}:`, 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;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1298,7 +1369,7 @@ class FirmwareComponent extends Component {
|
|||||||
<div class="progress-bar-container">
|
<div class="progress-bar-container">
|
||||||
<div class="progress-bar" id="overall-progress-bar" style="width: 0%; background-color: #fbbf24;"></div>
|
<div class="progress-bar" id="overall-progress-bar" style="width: 0%; background-color: #fbbf24;"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress-text">0/0 Successful (0%)</span>
|
<span class="progress-text">0/${nodes.length} Successful (0%)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-summary" id="progress-summary">
|
<div class="progress-summary" id="progress-summary">
|
||||||
<span>Status: Preparing upload...</span>
|
<span>Status: Preparing upload...</span>
|
||||||
@@ -1320,6 +1391,12 @@ class FirmwareComponent extends Component {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
container.innerHTML = progressHTML;
|
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) {
|
updateNodeProgress(current, total, nodeIp, status) {
|
||||||
@@ -1370,6 +1447,16 @@ class FirmwareComponent extends Component {
|
|||||||
} else {
|
} else {
|
||||||
progressBar.style.backgroundColor = '#fbbf24';
|
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 totalCount = results.length;
|
||||||
const successRate = Math.round((successCount / totalCount) * 100);
|
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)`;
|
progressHeader.textContent = `📤 Firmware Upload Complete (${successCount}/${totalCount} Successful)`;
|
||||||
progressSummary.innerHTML = `<span>✅ All uploads completed successfully at ${new Date().toLocaleTimeString()}</span>`;
|
progressSummary.innerHTML = `<span>✅ All uploads completed successfully at ${new Date().toLocaleTimeString()}</span>`;
|
||||||
} else {
|
} else {
|
||||||
|
// Multi-node upload - some failed
|
||||||
progressHeader.textContent = `📤 Firmware Upload Results (${successCount}/${totalCount} Successful)`;
|
progressHeader.textContent = `📤 Firmware Upload Results (${successCount}/${totalCount} Successful)`;
|
||||||
progressSummary.innerHTML = `<span>⚠️ Upload completed with ${totalCount - successCount} failure(s) at ${new Date().toLocaleTimeString()}</span>`;
|
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 targetType = this.viewModel.get('targetType');
|
||||||
const specificNodeSelect = this.findElement('#specific-node-select');
|
const specificNodeSelect = this.findElement('#specific-node-select');
|
||||||
|
|
||||||
|
console.log('FirmwareComponent: updateTargetVisibility called with targetType:', targetType);
|
||||||
|
|
||||||
if (targetType === 'specific') {
|
if (targetType === 'specific') {
|
||||||
specificNodeSelect.style.visibility = 'visible';
|
specificNodeSelect.style.visibility = 'visible';
|
||||||
specificNodeSelect.style.opacity = '1';
|
specificNodeSelect.style.opacity = '1';
|
||||||
|
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();
|
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 {
|
} else {
|
||||||
specificNodeSelect.style.visibility = 'hidden';
|
specificNodeSelect.style.visibility = 'hidden';
|
||||||
specificNodeSelect.style.opacity = '0';
|
specificNodeSelect.style.opacity = '0';
|
||||||
|
console.log('FirmwareComponent: Hiding specific node select');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateDeployButton();
|
this.updateDeployButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNodeSelect() {
|
// Note: handleNodeSelect is already defined above and handles the actual node selection
|
||||||
this.updateDeployButton();
|
// This duplicate method was causing the issue - removing it
|
||||||
}
|
|
||||||
|
|
||||||
updateDeployButton() {
|
updateDeployButton() {
|
||||||
const deployBtn = this.findElement('#deploy-btn');
|
const deployBtn = this.findElement('#deploy-btn');
|
||||||
@@ -1437,7 +1549,19 @@ class FirmwareComponent extends Component {
|
|||||||
|
|
||||||
populateNodeSelect() {
|
populateNodeSelect() {
|
||||||
const select = this.findElement('#specific-node-select');
|
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
|
// Clear existing options
|
||||||
select.innerHTML = '<option value="">Select a node...</option>';
|
select.innerHTML = '<option value="">Select a node...</option>';
|
||||||
@@ -1461,6 +1585,27 @@ class FirmwareComponent extends Component {
|
|||||||
option.textContent = `${node.hostname} (${node.ip})`;
|
option.textContent = `${node.hostname} (${node.ip})`;
|
||||||
select.appendChild(option);
|
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() {
|
updateUploadProgress() {
|
||||||
@@ -1704,21 +1849,33 @@ class FirmwareViewComponent extends Component {
|
|||||||
constructor(container, viewModel, eventBus) {
|
constructor(container, viewModel, eventBus) {
|
||||||
super(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.firmwareComponent = new FirmwareComponent(
|
||||||
this.findElement('#firmware-container'),
|
firmwareContainer,
|
||||||
viewModel,
|
viewModel,
|
||||||
eventBus
|
eventBus
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('FirmwareViewComponent: FirmwareComponent created');
|
||||||
}
|
}
|
||||||
|
|
||||||
mount() {
|
mount() {
|
||||||
super.mount();
|
super.mount();
|
||||||
|
|
||||||
|
console.log('FirmwareViewComponent: Mounting...');
|
||||||
|
|
||||||
// Mount sub-component
|
// Mount sub-component
|
||||||
this.firmwareComponent.mount();
|
this.firmwareComponent.mount();
|
||||||
|
|
||||||
// Update available nodes
|
// Update available nodes
|
||||||
this.updateAvailableNodes();
|
this.updateAvailableNodes();
|
||||||
|
|
||||||
|
console.log('FirmwareViewComponent: Mounted successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
unmount() {
|
unmount() {
|
||||||
@@ -1758,9 +1915,12 @@ class FirmwareViewComponent extends Component {
|
|||||||
|
|
||||||
async updateAvailableNodes() {
|
async updateAvailableNodes() {
|
||||||
try {
|
try {
|
||||||
|
console.log('FirmwareViewComponent: updateAvailableNodes called');
|
||||||
const response = await window.apiClient.getClusterMembers();
|
const response = await window.apiClient.getClusterMembers();
|
||||||
const nodes = response.members || [];
|
const nodes = response.members || [];
|
||||||
|
console.log('FirmwareViewComponent: Got nodes:', nodes);
|
||||||
this.viewModel.updateAvailableNodes(nodes);
|
this.viewModel.updateAvailableNodes(nodes);
|
||||||
|
console.log('FirmwareViewComponent: Available nodes updated in view model');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to update available nodes:', error);
|
console.error('Failed to update available nodes:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user