|
605 | 605 | cell.innerHTML = `<span style="color: #666;" title="Boot packages are only available in secure-boot provisioning mode">Unsupported</span>`; |
606 | 606 | pendingBootPackageRequests.delete(imageName); |
607 | 607 | } 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>`; |
609 | 609 | pendingBootPackageRequests.delete(imageName); |
610 | 610 |
|
611 | 611 | // Clear any polling interval for this image |
|
627 | 627 | } |
628 | 628 | } |
629 | 629 |
|
| 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 | + |
630 | 648 | // Request boot package status via WebSocket |
631 | 649 | function requestBootPackage(imageName) { |
632 | 650 | if (!wsBootPackage || wsBootPackage.readyState !== WebSocket.OPEN) { |
|
660 | 678 | }); |
661 | 679 | } |
662 | 680 |
|
| 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 | + |
663 | 700 | // Check all boot packages on page load |
664 | 701 | 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 |
666 | 715 | initBootPackageWebSocket(); |
667 | 716 | } |
668 | 717 |
|
| 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 | + |
669 | 740 | // Initialize SHA256 requests for all images |
670 | 741 | function initializeSha256Requests() { |
671 | 742 | if (!ws || ws.readyState !== WebSocket.OPEN) { |
|
761 | 832 | pendingBootPackageRequests.clear(); |
762 | 833 | // Stop all polling |
763 | 834 | stopAllPolling(); |
| 835 | + cleanupBootPackagePolling(); |
764 | 836 | // Force close WebSockets with timeout |
765 | 837 | forceCloseWebSocket(); |
766 | 838 | if (wsBootPackage) { |
|
773 | 845 | console.log('Page hidden - cleaning up WebSocket and polling'); |
774 | 846 | // Clear all pending requests first |
775 | 847 | pendingSha256Requests.clear(); |
| 848 | + pendingBootPackageRequests.clear(); |
776 | 849 | // Stop all polling |
777 | 850 | stopAllPolling(); |
| 851 | + cleanupBootPackagePolling(); |
778 | 852 | // Force close WebSocket with timeout |
779 | 853 | forceCloseWebSocket(); |
780 | 854 | }); |
|
997 | 1071 |
|
998 | 1072 | /* Boot package download link styles */ |
999 | 1073 | .download-link { |
1000 | | - color: #0066cc; |
| 1074 | + display: inline-block; |
| 1075 | + padding: 6px 12px; |
| 1076 | + background-color: #2196F3; |
| 1077 | + color: white; |
1001 | 1078 | text-decoration: none; |
1002 | 1079 | font-weight: 500; |
| 1080 | + border-radius: 3px; |
| 1081 | + font-size: 14px; |
| 1082 | + cursor: pointer; |
| 1083 | + border: none; |
1003 | 1084 | } |
1004 | 1085 |
|
1005 | 1086 | .download-link:hover { |
1006 | | - text-decoration: underline; |
1007 | | - color: #0052a3; |
| 1087 | + background-color: #0b7dda; |
| 1088 | + text-decoration: none; |
1008 | 1089 | } |
1009 | 1090 |
|
1010 | 1091 | .boot-package-cell { |
|
0 commit comments