Skip to content

Commit cd101e3

Browse files
committed
feat: Enhance WebUI and boot package handling in version 2.1.1
- Fixed bashisms in the boot image generator script for improved compatibility. - Styled boot image generation buttons for better user experience. - Added an explicit button for boot image generation in the WebUI. - Resolved navigation hangs on AJAX pages to enhance responsiveness. - Updated boot package status handling to reflect accurate states and improve user feedback.
1 parent fd58354 commit cd101e3

File tree

3 files changed

+114
-7
lines changed

3 files changed

+114
-7
lines changed

debian/changelog

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
rpi-sb-provisioner (2.1.1) unstable; urgency=medium
2+
3+
* WebUI: Fix bootimg generator bashisms
4+
* WebUI: Style bootimg generator buttons
5+
* WebUI: Add explicit bootimg generation button
6+
* WebUI: Fix hangs on navigation on pages using AJAX
7+
8+
-- Tom Dewey <[email protected]> Fri, 10 Oct 2025 16:45:00 +0100
9+
110
rpi-sb-provisioner (2.1.0) unstable; urgency=medium
211

312
* Features:

provisioner-service/src/images.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,8 @@ class BootPackageWebSocketController : public drogon::WebSocketController<BootPa
263263
response["status"] = "available";
264264
LOG_INFO << "WebSocket: Broadcasting boot package available for " << imageName << ": " << packageName;
265265
} else {
266-
response["status"] = "generating";
267-
LOG_INFO << "WebSocket: Broadcasting boot package generating for " << imageName;
266+
response["status"] = "not_found";
267+
LOG_INFO << "WebSocket: Broadcasting boot package not found for " << imageName;
268268
}
269269

270270
std::string message = response.toStyledString();
@@ -910,6 +910,9 @@ namespace provisioner {
910910
} else {
911911
LOG_INFO << "Started boot.img generation service: " << serviceName;
912912
}
913+
914+
// Broadcast "generating" status to WebSocket clients
915+
BootPackageWebSocketController::broadcastUpdate(imageName, false, "", "generating");
913916
}
914917

915918
// Cleanup
@@ -1582,6 +1585,18 @@ namespace provisioner {
15821585
return;
15831586
}
15841587

1588+
// Check if provisioning style is secure-boot (boot packages only work in secure-boot mode)
1589+
auto provisioningStyle = provisioner::utils::getConfigValue("PROVISIONING_STYLE");
1590+
if (!provisioningStyle || *provisioningStyle != "secure-boot") {
1591+
Json::Value result;
1592+
result["exists"] = false;
1593+
result["image_name"] = imageName;
1594+
result["status"] = "unsupported";
1595+
auto resp = drogon::HttpResponse::newHttpJsonResponse(result);
1596+
callback(resp);
1597+
return;
1598+
}
1599+
15851600
// Remove file extension from image name to get base name
15861601
std::string imageBaseName = imageName;
15871602
size_t dotPos = imageBaseName.find_last_of('.');
@@ -1595,6 +1610,7 @@ namespace provisioner {
15951610
Json::Value result;
15961611
result["exists"] = false;
15971612
result["image_name"] = imageName;
1613+
result["status"] = "not_found";
15981614

15991615
try {
16001616
if (std::filesystem::exists(outputDir) && std::filesystem::is_directory(outputDir)) {
@@ -1611,6 +1627,7 @@ namespace provisioner {
16111627
result["exists"] = true;
16121628
result["package_name"] = filename;
16131629
result["package_path"] = entry.path().string();
1630+
result["status"] = "available";
16141631
break;
16151632
}
16161633
}

provisioner-service/src/views/images.csp

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@
605605
cell.innerHTML = `<span style="color: #666;" title="Boot packages are only available in secure-boot provisioning mode">Unsupported</span>`;
606606
pendingBootPackageRequests.delete(imageName);
607607
} else if (response.exists && response.package_name) {
608-
cell.innerHTML = `<a href="/download-boot-package?name=${encodeURIComponent(imageName)}" class="download-link" download>Download ${response.package_name}</a>`;
608+
cell.innerHTML = `<a href="/download-boot-package?name=${encodeURIComponent(imageName)}" class="download-link" download title="${response.package_name}">Download</a>`;
609609
pendingBootPackageRequests.delete(imageName);
610610

611611
// Clear any polling interval for this image
@@ -627,6 +627,24 @@
627627
}
628628
}
629629

630+
// Fetch boot package status via HTTP (explicit status fetch)
631+
async function fetchBootPackageStatus(imageName) {
632+
try {
633+
const response = await fetch(`/get-boot-package-info?name=${encodeURIComponent(imageName)}`);
634+
if (response.ok) {
635+
const data = await response.json();
636+
updateBootPackageCell(data);
637+
return data;
638+
} else {
639+
console.error('Failed to fetch boot package status for:', imageName);
640+
return null;
641+
}
642+
} catch (error) {
643+
console.error('Error fetching boot package status:', error);
644+
return null;
645+
}
646+
}
647+
630648
// Request boot package status via WebSocket
631649
function requestBootPackage(imageName) {
632650
if (!wsBootPackage || wsBootPackage.readyState !== WebSocket.OPEN) {
@@ -660,12 +678,65 @@
660678
});
661679
}
662680

681+
// Setup long-interval polling for boot packages (fallback mechanism)
682+
function setupBootPackagePolling() {
683+
// Clear any existing polling interval
684+
if (window.bootPackageLongPoll) {
685+
clearInterval(window.bootPackageLongPoll);
686+
}
687+
688+
// Poll every 30 seconds as a fallback
689+
window.bootPackageLongPoll = setInterval(() => {
690+
const bootPackageCells = document.querySelectorAll('.boot-package-cell');
691+
bootPackageCells.forEach(cell => {
692+
const imageName = cell.getAttribute('data-image');
693+
if (imageName) {
694+
fetchBootPackageStatus(imageName);
695+
}
696+
});
697+
}, 30000); // 30 seconds
698+
}
699+
663700
// Check all boot packages on page load
664701
function checkAllBootPackages() {
665-
// WebSocket will handle this when connected
702+
// Explicitly fetch status for all images on load
703+
const bootPackageCells = document.querySelectorAll('.boot-package-cell');
704+
bootPackageCells.forEach(cell => {
705+
const imageName = cell.getAttribute('data-image');
706+
if (imageName) {
707+
fetchBootPackageStatus(imageName);
708+
}
709+
});
710+
711+
// Setup long-interval polling as fallback
712+
setupBootPackagePolling();
713+
714+
// Also initialize WebSocket for real-time updates
666715
initBootPackageWebSocket();
667716
}
668717

718+
// Cleanup function to be called when navigating away
719+
function cleanupBootPackagePolling() {
720+
// Clear long-interval polling
721+
if (window.bootPackageLongPoll) {
722+
clearInterval(window.bootPackageLongPoll);
723+
window.bootPackageLongPoll = null;
724+
}
725+
726+
// Clear all per-image intervals
727+
if (window.bootPackageIntervals) {
728+
Object.values(window.bootPackageIntervals).forEach(interval => {
729+
clearInterval(interval);
730+
});
731+
window.bootPackageIntervals = {};
732+
}
733+
734+
// Close WebSocket
735+
if (wsBootPackage && wsBootPackage.readyState === WebSocket.OPEN) {
736+
wsBootPackage.close();
737+
}
738+
}
739+
669740
// Initialize SHA256 requests for all images
670741
function initializeSha256Requests() {
671742
if (!ws || ws.readyState !== WebSocket.OPEN) {
@@ -761,6 +832,7 @@
761832
pendingBootPackageRequests.clear();
762833
// Stop all polling
763834
stopAllPolling();
835+
cleanupBootPackagePolling();
764836
// Force close WebSockets with timeout
765837
forceCloseWebSocket();
766838
if (wsBootPackage) {
@@ -773,8 +845,10 @@
773845
console.log('Page hidden - cleaning up WebSocket and polling');
774846
// Clear all pending requests first
775847
pendingSha256Requests.clear();
848+
pendingBootPackageRequests.clear();
776849
// Stop all polling
777850
stopAllPolling();
851+
cleanupBootPackagePolling();
778852
// Force close WebSocket with timeout
779853
forceCloseWebSocket();
780854
});
@@ -997,14 +1071,21 @@
9971071

9981072
/* Boot package download link styles */
9991073
.download-link {
1000-
color: #0066cc;
1074+
display: inline-block;
1075+
padding: 6px 12px;
1076+
background-color: #2196F3;
1077+
color: white;
10011078
text-decoration: none;
10021079
font-weight: 500;
1080+
border-radius: 3px;
1081+
font-size: 14px;
1082+
cursor: pointer;
1083+
border: none;
10031084
}
10041085

10051086
.download-link:hover {
1006-
text-decoration: underline;
1007-
color: #0052a3;
1087+
background-color: #0b7dda;
1088+
text-decoration: none;
10081089
}
10091090

10101091
.boot-package-cell {

0 commit comments

Comments
 (0)