From e0dedc1c238d94a2e06ecde19ddb449f69b4bd88 Mon Sep 17 00:00:00 2001 From: Patrick Balsiger Date: Tue, 26 Aug 2025 12:50:44 +0200 Subject: [PATCH] fix: upload progress bars --- public/script.js | 188 +++++++++++++++++++++++++++++++++++++++++----- public/styles.css | 14 ++++ 2 files changed, 183 insertions(+), 19 deletions(-) diff --git a/public/script.js b/public/script.js index 2956289..dbd79dc 100644 --- a/public/script.js +++ b/public/script.js @@ -791,8 +791,8 @@ async function uploadFirmwareToSpecificNode(file, nodeIp) { // Show upload progress area showFirmwareUploadProgress(file, [{ ip: nodeIp, hostname: nodeIp }]); - // Perform single node upload - const result = await performSingleFirmwareUpload(file, nodeIp); + // Perform single node upload with progress tracking + const result = await performSingleFirmwareUploadWithProgress(file, nodeIp); // Display results displayFirmwareUploadResults([result]); @@ -807,21 +807,24 @@ async function uploadFirmwareToSpecificNode(file, nodeIp) { async function performBatchFirmwareUpload(file, nodes) { const results = []; const totalNodes = nodes.length; + let successfulUploads = 0; for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; const nodeIp = node.ip; try { - // Update progress + // Update progress - show current node being processed updateFirmwareUploadProgress(i + 1, totalNodes, nodeIp, 'Uploading...'); // Upload to this node const result = await performSingleFirmwareUpload(file, nodeIp); results.push(result); + successfulUploads++; - // Update progress + // Update progress - show completion and update progress bar with actual success rate updateFirmwareUploadProgress(i + 1, totalNodes, nodeIp, 'Completed'); + updateMultiNodeProgress(successfulUploads, totalNodes); } catch (error) { console.error(`Failed to upload to node ${nodeIp}:`, error); @@ -834,8 +837,9 @@ async function performBatchFirmwareUpload(file, nodes) { }; results.push(errorResult); - // Update progress + // Update progress - show failure and update progress bar with actual success rate updateFirmwareUploadProgress(i + 1, totalNodes, nodeIp, 'Failed'); + updateMultiNodeProgress(successfulUploads, totalNodes); } // Small delay between uploads to avoid overwhelming the network @@ -844,6 +848,9 @@ async function performBatchFirmwareUpload(file, nodes) { } } + // Update final progress based on successful uploads + updateFinalProgress(successfulUploads, totalNodes); + return results; } @@ -880,6 +887,102 @@ async function performSingleFirmwareUpload(file, nodeIp) { } } +// Perform single firmware upload to a specific node with progress tracking +async function performSingleFirmwareUploadWithProgress(file, nodeIp) { + try { + // Simulate upload progress for single node + await simulateUploadProgress(nodeIp); + + // Create FormData for the upload + const formData = new FormData(); + formData.append('file', file); + + // Upload to backend + const response = await fetch(`/api/node/update?ip=${encodeURIComponent(nodeIp)}`, { + 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(); + + return { + nodeIp: nodeIp, + hostname: nodeIp, // Will be updated if we have more info + success: true, + result: result, + timestamp: new Date().toISOString() + }; + + } catch (error) { + throw new Error(`Upload to ${nodeIp} failed: ${error.message}`); + } +} + +// Simulate upload progress for single node uploads +async function simulateUploadProgress(nodeIp) { + const progressSteps = [10, 25, 50, 75, 90, 100]; + const totalSteps = progressSteps.length; + + for (let i = 0; i < totalSteps; i++) { + const progress = progressSteps[i]; + updateSingleNodeProgress(progress, nodeIp); + + // Wait a bit between progress updates (simulating actual upload time) + if (i < totalSteps - 1) { + await new Promise(resolve => setTimeout(resolve, 300)); + } + } +} + +// Update progress for single node uploads +function updateSingleNodeProgress(percentage, nodeIp) { + const progressBar = document.getElementById('overall-progress-bar'); + const progressText = document.querySelector('.progress-text'); + + if (progressBar && progressText) { + progressBar.style.width = `${percentage}%`; + progressText.textContent = `${percentage}% Complete`; + + // Update progress bar color based on completion + if (percentage === 100) { + progressBar.style.backgroundColor = '#4ade80'; + } else if (percentage > 50) { + progressBar.style.backgroundColor = '#60a5fa'; + } else { + progressBar.style.backgroundColor = '#fbbf24'; + } + } +} + +// Update final progress based on successful vs total uploads +function updateFinalProgress(successfulUploads, totalNodes) { + const progressBar = document.getElementById('overall-progress-bar'); + const progressText = document.querySelector('.progress-text'); + const progressHeader = document.querySelector('.progress-header h3'); + + if (progressBar && progressText) { + const successPercentage = Math.round((successfulUploads / totalNodes) * 100); + progressBar.style.width = `${successPercentage}%`; + + if (successfulUploads === totalNodes) { + progressText.textContent = '100% Complete'; + progressBar.style.backgroundColor = '#4ade80'; + } else { + progressText.textContent = `${successfulUploads}/${totalNodes} Successful`; + progressBar.style.backgroundColor = '#f87171'; + } + } + + if (progressHeader) { + progressHeader.textContent = `📤 Firmware Upload Results (${successfulUploads}/${totalNodes} Successful)`; + } +} + // Show firmware upload progress area function showFirmwareUploadProgress(file, nodes) { const container = document.getElementById('firmware-nodes-list'); @@ -895,9 +998,12 @@ function showFirmwareUploadProgress(file, nodes) {
-
+
- 0% Complete + 0/0 Successful (0%) +
+
+ Status: Preparing upload...
@@ -949,24 +1055,58 @@ function updateFirmwareUploadProgress(current, total, nodeIp, status) { } } - // Update overall progress + // Update progress header to show current node being processed const progressHeader = document.querySelector('.progress-header h3'); - const progressBar = document.getElementById('overall-progress-bar'); - const progressText = document.querySelector('.progress-text'); - if (progressHeader) { progressHeader.textContent = `📤 Firmware Upload Progress (${current}/${total})`; } + // Update progress summary + const progressSummary = document.getElementById('progress-summary'); + if (progressSummary) { + if (status === 'Uploading...') { + progressSummary.innerHTML = `Status: Uploading to ${nodeIp} (${current}/${total})`; + } else if (status === 'Completed') { + // For multi-node uploads, show success rate + if (total > 1) { + const successfulNodes = document.querySelectorAll('.progress-status.success').length; + const totalNodes = total; + const successRate = Math.round((successfulNodes / totalNodes) * 100); + progressSummary.innerHTML = `Status: Completed upload to ${nodeIp}. Overall: ${successfulNodes}/${totalNodes} successful (${successRate}%)`; + } else { + progressSummary.innerHTML = `Status: Completed upload to ${nodeIp} (${current}/${total})`; + } + } else if (status === 'Failed') { + // For multi-node uploads, show success rate + if (total > 1) { + const successfulNodes = document.querySelectorAll('.progress-status.success').length; + const totalNodes = total; + const successRate = Math.round((successfulNodes / totalNodes) * 100); + progressSummary.innerHTML = `Status: Failed upload to ${nodeIp}. Overall: ${successfulNodes}/${totalNodes} successful (${successRate}%)`; + } else { + progressSummary.innerHTML = `Status: Failed upload to ${nodeIp} (${current}/${total})`; + } + } + } + + // IMPORTANT: Do NOT update the progress bar here - let updateMultiNodeProgress handle it + // The progress bar should only reflect actual successful uploads, not nodes processed +} + +// Update progress for multi-node uploads based on actual success rate +function updateMultiNodeProgress(successfulUploads, totalNodes) { + const progressBar = document.getElementById('overall-progress-bar'); + const progressText = document.querySelector('.progress-text'); + if (progressBar && progressText) { - const percentage = Math.round((current / total) * 100); - progressBar.style.width = `${percentage}%`; - progressText.textContent = `${percentage}% Complete`; + const successPercentage = Math.round((successfulUploads / totalNodes) * 100); + progressBar.style.width = `${successPercentage}%`; + progressText.textContent = `${successfulUploads}/${totalNodes} Successful (${successPercentage}%)`; // Update progress bar color based on completion - if (percentage === 100) { + if (successPercentage === 100) { progressBar.style.backgroundColor = '#4ade80'; - } else if (percentage > 50) { + } else if (successPercentage > 50) { progressBar.style.backgroundColor = '#60a5fa'; } else { progressBar.style.backgroundColor = '#fbbf24'; @@ -1017,13 +1157,23 @@ function displayFirmwareUploadResults(results) {
`; - container.innerHTML = resultsHTML; + // Append results below the progress area instead of replacing everything + // First, check if there's already a results section and remove it + const existingResults = container.querySelector('#firmware-upload-results'); + if (existingResults) { + existingResults.remove(); + } + + // Append the new results below the progress area + container.insertAdjacentHTML('beforeend', resultsHTML); } // Clear firmware upload results function clearFirmwareResults() { - const container = document.getElementById('firmware-nodes-list'); - container.innerHTML = ''; + const resultsSection = document.getElementById('firmware-upload-results'); + if (resultsSection) { + resultsSection.remove(); + } } // Populate node select dropdown diff --git a/public/styles.css b/public/styles.css index 41d5b3b..fd6c68c 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1180,6 +1180,20 @@ p { text-align: right; } +.progress-summary { + margin-top: 0.75rem; + padding: 0.5rem 0.75rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.progress-summary span { + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.8); + font-weight: 500; +} + .success-count { color: #4ade80 !important; border-color: rgba(74, 222, 128, 0.3) !important;