feat: half baked fw upload

This commit is contained in:
2025-08-25 09:41:56 +02:00
parent 2938b5f5fe
commit 9514bf7ac5
4 changed files with 251 additions and 0 deletions

View File

@@ -187,6 +187,7 @@ function displayNodeDetails(container, nodeStatus) {
📁 Choose Firmware File
</button>
<div class="upload-info">Select a .bin or .hex file to upload</div>
<div id="upload-status" style="display: none;"></div>
</div>
</div>
</div>
@@ -244,6 +245,18 @@ function setupTabs(container) {
fileInput.click();
}
});
// Set up file input change handler
const fileInput = container.querySelector('#firmware-file');
if (fileInput) {
fileInput.addEventListener('change', async (e) => {
e.stopPropagation();
const file = e.target.files[0];
if (file) {
await uploadFirmware(file, container);
}
});
}
}
}
@@ -297,6 +310,84 @@ async function loadTasksData(container, nodeStatus) {
}
}
// Function to upload firmware
async function uploadFirmware(file, container) {
const uploadStatus = container.querySelector('#upload-status');
const uploadBtn = container.querySelector('.upload-btn');
const originalText = uploadBtn.textContent;
try {
// Show upload status
uploadStatus.style.display = 'block';
uploadStatus.innerHTML = `
<div class="upload-progress">
<div>📤 Uploading ${file.name}...</div>
<div style="font-size: 0.8rem; opacity: 0.7;">Size: ${(file.size / 1024).toFixed(1)}KB</div>
</div>
`;
// Disable upload button
uploadBtn.disabled = true;
uploadBtn.textContent = '⏳ Uploading...';
// Get the member IP from the card
const memberCard = container.closest('.member-card');
const memberIp = memberCard.dataset.memberIp;
if (!memberIp) {
throw new Error('Could not determine target node IP address');
}
// Create FormData for multipart upload
const formData = new FormData();
formData.append('file', file);
// Upload to backend
const response = await fetch('/api/node/update?ip=' + encodeURIComponent(memberIp), {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
// Show success
uploadStatus.innerHTML = `
<div class="upload-success">
<div>✅ Firmware uploaded successfully!</div>
<div style="font-size: 0.8rem; opacity: 0.7;">Node: ${memberIp}</div>
<div style="font-size: 0.8rem; opacity: 0.7;">Size: ${(file.size / 1024).toFixed(1)}KB</div>
</div>
`;
console.log('Firmware upload successful:', result);
} catch (error) {
console.error('Firmware upload failed:', error);
// Show error
uploadStatus.innerHTML = `
<div class="upload-error">
<div>❌ Upload failed: ${error.message}</div>
</div>
`;
} finally {
// Re-enable upload button
uploadBtn.disabled = false;
uploadBtn.textContent = originalText;
// Clear file input
const fileInput = container.querySelector('#firmware-file');
if (fileInput) {
fileInput.value = '';
}
}
}
// Function to display cluster members
function displayClusterMembers(members, expandedCards = new Map()) {
const container = document.getElementById('cluster-members-container');

View File

@@ -422,6 +422,43 @@ p {
color: rgba(255, 255, 255, 0.8);
}
/* Upload Status Styles */
#upload-status {
margin-top: 1rem;
padding: 1rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.upload-progress {
text-align: center;
color: #ffa726;
}
.upload-success {
text-align: center;
color: #4caf50;
background: rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.2);
padding: 0.75rem;
border-radius: 6px;
}
.upload-error {
text-align: center;
color: #f44336;
background: rgba(244, 67, 54, 0.1);
border: 1px solid rgba(244, 67, 54, 0.2);
padding: 0.75rem;
border-radius: 6px;
}
.upload-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
.no-tasks {
text-align: center;
padding: 2rem;