fix: firmware page styling
This commit is contained in:
@@ -1274,6 +1274,25 @@ class FirmwareComponent extends Component {
|
||||
console.warn('FirmwareComponent: specificNodeSelect element not found during setupEventListeners');
|
||||
}
|
||||
|
||||
// Setup label select change handler (single-select add-to-chips)
|
||||
const labelSelect = this.findElement('#label-select');
|
||||
if (labelSelect) {
|
||||
this._boundLabelSelectHandler = (e) => {
|
||||
const value = e.target.value;
|
||||
if (!value) return;
|
||||
const current = this.viewModel.get('selectedLabels') || [];
|
||||
if (!current.includes(value)) {
|
||||
this.viewModel.setSelectedLabels([...current, value]);
|
||||
}
|
||||
// Reset select back to placeholder
|
||||
e.target.value = '';
|
||||
this.renderSelectedLabelChips();
|
||||
this.updateAffectedNodesPreview();
|
||||
this.updateDeployButton();
|
||||
};
|
||||
this.addEventListener(labelSelect, 'change', this._boundLabelSelectHandler);
|
||||
}
|
||||
|
||||
// Setup deploy button
|
||||
const deployBtn = this.findElement('#deploy-btn');
|
||||
if (deployBtn) {
|
||||
@@ -1289,15 +1308,23 @@ class FirmwareComponent extends Component {
|
||||
this.subscribeToProperty('targetType', () => {
|
||||
this.updateTargetVisibility();
|
||||
this.updateDeployButton();
|
||||
this.updateAffectedNodesPreview();
|
||||
});
|
||||
this.subscribeToProperty('specificNode', this.updateDeployButton.bind(this));
|
||||
this.subscribeToProperty('availableNodes', () => {
|
||||
this.populateNodeSelect();
|
||||
this.populateLabelSelect();
|
||||
this.updateDeployButton();
|
||||
this.updateAffectedNodesPreview();
|
||||
});
|
||||
this.subscribeToProperty('uploadProgress', this.updateUploadProgress.bind(this));
|
||||
this.subscribeToProperty('uploadResults', this.updateUploadResults.bind(this));
|
||||
this.subscribeToProperty('isUploading', this.updateUploadState.bind(this));
|
||||
this.subscribeToProperty('selectedLabels', () => {
|
||||
this.populateLabelSelect();
|
||||
this.updateAffectedNodesPreview();
|
||||
this.updateDeployButton();
|
||||
});
|
||||
}
|
||||
|
||||
mount() {
|
||||
@@ -1314,6 +1341,15 @@ class FirmwareComponent extends Component {
|
||||
console.log('FirmwareComponent: Mount - dropdown innerHTML:', dropdown.innerHTML);
|
||||
}
|
||||
|
||||
// Initialize target visibility and label list on first mount
|
||||
try {
|
||||
this.updateTargetVisibility();
|
||||
this.populateLabelSelect();
|
||||
this.updateAffectedNodesPreview();
|
||||
} catch (e) {
|
||||
console.warn('FirmwareComponent: Initialization after mount failed:', e);
|
||||
}
|
||||
|
||||
console.log('FirmwareComponent: Mounted successfully');
|
||||
}
|
||||
|
||||
@@ -1365,8 +1401,10 @@ class FirmwareComponent extends Component {
|
||||
|
||||
if (targetType === 'all') {
|
||||
await this.uploadToAllNodes(file);
|
||||
} else {
|
||||
} else if (targetType === 'specific') {
|
||||
await this.uploadToSpecificNode(file, specificNode);
|
||||
} else if (targetType === 'labels') {
|
||||
await this.uploadToLabelFilteredNodes(file);
|
||||
}
|
||||
|
||||
// Reset interface after successful upload
|
||||
@@ -1451,6 +1489,31 @@ class FirmwareComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadToLabelFilteredNodes(file) {
|
||||
try {
|
||||
const nodes = this.viewModel.getAffectedNodesByLabels();
|
||||
if (!nodes || nodes.length === 0) {
|
||||
alert('No nodes match the selected labels.');
|
||||
return;
|
||||
}
|
||||
const labels = this.viewModel.get('selectedLabels') || [];
|
||||
const confirmed = confirm(`Upload firmware to ${nodes.length} node(s) matching labels (${labels.join(', ')})?`);
|
||||
if (!confirmed) return;
|
||||
|
||||
// Show upload progress area
|
||||
this.showUploadProgress(file, nodes);
|
||||
|
||||
// Start batch upload
|
||||
const results = await this.performBatchUpload(file, nodes);
|
||||
|
||||
// Display results
|
||||
this.displayUploadResults(results);
|
||||
} catch (error) {
|
||||
console.error('Failed to upload firmware to label-filtered nodes:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async performBatchUpload(file, nodes) {
|
||||
const results = [];
|
||||
const totalNodes = nodes.length;
|
||||
@@ -1671,31 +1734,24 @@ class FirmwareComponent extends Component {
|
||||
updateTargetVisibility() {
|
||||
const targetType = this.viewModel.get('targetType');
|
||||
const specificNodeSelect = this.findElement('#specific-node-select');
|
||||
const labelSelect = this.findElement('#label-select');
|
||||
|
||||
console.log('FirmwareComponent: updateTargetVisibility called with targetType:', targetType);
|
||||
|
||||
if (targetType === 'specific') {
|
||||
specificNodeSelect.style.visibility = 'visible';
|
||||
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();
|
||||
} else {
|
||||
console.log('FirmwareComponent: Dropdown not ready, delaying population');
|
||||
// Small delay to ensure the dropdown is visible before populating
|
||||
setTimeout(() => {
|
||||
this.populateNodeSelect();
|
||||
}, 100);
|
||||
if (specificNodeSelect) { specificNodeSelect.style.display = 'inline-block'; }
|
||||
if (labelSelect) { labelSelect.style.display = 'none'; }
|
||||
this.populateNodeSelect();
|
||||
} else if (targetType === 'labels') {
|
||||
if (specificNodeSelect) { specificNodeSelect.style.display = 'none'; }
|
||||
if (labelSelect) {
|
||||
labelSelect.style.display = 'inline-block';
|
||||
this.populateLabelSelect();
|
||||
}
|
||||
} else {
|
||||
specificNodeSelect.style.visibility = 'hidden';
|
||||
specificNodeSelect.style.opacity = '0';
|
||||
console.log('FirmwareComponent: Hiding specific node select');
|
||||
if (specificNodeSelect) { specificNodeSelect.style.display = 'none'; }
|
||||
if (labelSelect) { labelSelect.style.display = 'none'; }
|
||||
}
|
||||
|
||||
this.updateDeployButton();
|
||||
}
|
||||
|
||||
@@ -1795,6 +1851,75 @@ class FirmwareComponent extends Component {
|
||||
|
||||
this.updateDeployButton();
|
||||
}
|
||||
|
||||
populateLabelSelect() {
|
||||
const select = this.findElement('#label-select');
|
||||
if (!select) return;
|
||||
const labels = this.viewModel.get('availableLabels') || [];
|
||||
const selected = new Set(this.viewModel.get('selectedLabels') || []);
|
||||
const options = ['<option value="">Select a label...</option>']
|
||||
.concat(labels.filter(l => !selected.has(l)).map(l => `<option value="${l}">${l}</option>`));
|
||||
select.innerHTML = options.join('');
|
||||
// Ensure change listener remains bound
|
||||
if (this._boundLabelSelectHandler) {
|
||||
select.removeEventListener('change', this._boundLabelSelectHandler);
|
||||
select.addEventListener('change', this._boundLabelSelectHandler);
|
||||
}
|
||||
this.renderSelectedLabelChips();
|
||||
}
|
||||
|
||||
renderSelectedLabelChips() {
|
||||
const container = this.findElement('#selected-labels-container');
|
||||
if (!container) return;
|
||||
const selected = this.viewModel.get('selectedLabels') || [];
|
||||
if (selected.length === 0) {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = selected.map(l => `
|
||||
<span class="label-chip removable" data-label="${l}">
|
||||
${l}
|
||||
<button class="chip-remove" data-label="${l}" title="Remove">×</button>
|
||||
</span>
|
||||
`).join('');
|
||||
Array.from(container.querySelectorAll('.chip-remove')).forEach(btn => {
|
||||
this.addEventListener(btn, 'click', (e) => {
|
||||
e.stopPropagation();
|
||||
const label = btn.getAttribute('data-label');
|
||||
const current = this.viewModel.get('selectedLabels') || [];
|
||||
this.viewModel.setSelectedLabels(current.filter(x => x !== label));
|
||||
this.populateLabelSelect();
|
||||
this.updateAffectedNodesPreview();
|
||||
this.updateDeployButton();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
updateAffectedNodesPreview() {
|
||||
const container = this.findElement('#firmware-nodes-list');
|
||||
if (!container) return;
|
||||
if (this.viewModel.get('targetType') !== 'labels') {
|
||||
container.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const nodes = this.viewModel.getAffectedNodesByLabels();
|
||||
if (!nodes.length) {
|
||||
container.innerHTML = `<div class="empty-state"><div>No nodes match the selected labels</div></div>`;
|
||||
return;
|
||||
}
|
||||
const html = `
|
||||
<div class="affected-nodes">
|
||||
<div class="progress-header"><h3>🎯 Affected Nodes (${nodes.length})</h3></div>
|
||||
<div class="progress-list">
|
||||
${nodes.map(n => `
|
||||
<div class="progress-item" data-node-ip="${n.ip}">
|
||||
<div class="progress-node-info"><span class="node-name">${n.hostname || n.ip}</span><span class="node-ip">${n.ip}</span></div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
container.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
// Cluster View Component
|
||||
|
||||
Reference in New Issue
Block a user