feat: Show progress while extracting DSM image (#1362)

This commit is contained in:
Kroese
2026-08-09 13:33:33 +02:00
committed by GitHub
parent c64046411b
commit 04cd929c9c
3 changed files with 282 additions and 0 deletions
+5
View File
@@ -227,6 +227,8 @@ fi
MSG="Extracting installation image..."
info "Install: $MSG" && html "$MSG"
/run/progress.sh "$TMP" "$SIZE" "$MSG ([P])..." &
# Newer PAT files are normal tar archives; older encrypted/proprietary forms
# require the bundled extractor as a compatibility fallback.
if { tar tf "$PAT"; } >/dev/null 2>&1; then
@@ -238,12 +240,15 @@ else
{ (cd "$TMP" && python3 /run/extract.py -i "$PAT" -d 2>/run/extract.log); rc=$?; } || :
if (( rc != 0 )); then
fKill "progress.sh"
cat /run/extract.log
error "Failed to extract PAT file, reason $rc" && exit 63
fi
fi
fKill "progress.sh"
MSG="Preparing system partition..."
info "Install: $MSG" && html "$MSG"
+62
View File
@@ -17,6 +17,68 @@ body {
margin-top: 50px;
}
#progress {
width: 0;
min-width: 250px;
max-width: 80vw;
height: 15px;
overflow: hidden;
margin: 20px auto 0;
border-radius: 999px;
background: rgba(0, 0, 0, 0.17);
box-shadow:
inset 0 1px 2px rgba(0, 0, 0, 0.22),
0 1px 0 rgba(255, 255, 255, 0.08);
}
#progress[hidden] {
display: none;
}
#progress-fill {
position: relative;
width: 0;
height: 100%;
overflow: hidden;
background: rgba(255, 255, 255, 0.94);
transition: width 0.25s ease;
}
#progress-fill:after {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -40%;
width: 34%;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0),
rgba(189, 227, 255, 0.15) 25%,
rgba(255, 255, 255, 0.70) 50%,
rgba(189, 227, 255, 0.15) 75%,
rgba(255, 255, 255, 0)
);
animation: progress-sheen 1.9s ease-in-out infinite;
}
@keyframes progress-sheen {
0% {
left: -40%;
opacity: 0;
}
12% {
opacity: 1;
}
60%,
100% {
left: 112%;
opacity: 1;
}
}
footer {
width: 98%;
position: fixed;
+215
View File
@@ -259,6 +259,210 @@ function rememberStatus(msg) {
return true;
}
function parseSize(value, unit) {
var powers = {
"B": 0,
"KB": 1,
"KIB": 1,
"MB": 2,
"MIB": 2,
"GB": 3,
"GIB": 3,
"TB": 4,
"TIB": 4,
"PB": 5,
"PIB": 5,
"EB": 6,
"EIB": 6
};
unit = unit.toUpperCase();
if (!Object.prototype.hasOwnProperty.call(powers, unit)) {
return null;
}
var bytes = Number(value) * Math.pow(1024, powers[unit]);
if (!Number.isFinite(bytes) || bytes < 0) {
return null;
}
return bytes;
}
function estimateProgress(bytes) {
var boundary = 512 * 1024 * 1024;
var previousBoundary = 0;
while (bytes > boundary) {
previousBoundary = boundary;
boundary *= 2;
}
if (previousBoundary === 0) {
return Math.min(bytes / boundary * 100, 100);
}
var rangeProgress =
(bytes - previousBoundary) /
(boundary - previousBoundary);
return 30 + Math.pow(rangeProgress, 2) * 70;
}
function parseProgress(msg) {
var container = document.createElement("div");
container.innerHTML = msg;
var walker = document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT
);
var node;
var lastNode = null;
while ((node = walker.nextNode())) {
if (node.nodeValue.trim() !== "") {
lastNode = node;
}
}
if (!lastNode) {
return {
message: msg,
progress: null,
size: null
};
}
var percentMatch = lastNode.nodeValue.match(
/\s+\((\d+(?:\.\d+)?)%\)\s*$/
);
if (percentMatch) {
var progress = Number(percentMatch[1]);
if (Number.isFinite(progress) && progress >= 0 && progress <= 100) {
lastNode.nodeValue = lastNode.nodeValue.slice(
0,
percentMatch.index
);
return {
message: container.innerHTML,
progress: progress,
size: null
};
}
}
var sizeMatch = lastNode.nodeValue.match(
/\s+\((\d+(?:\.\d+)?)\s+([KMGTPE]?i?B)\)\s*$/i
);
if (sizeMatch) {
var bytes = parseSize(sizeMatch[1], sizeMatch[2]);
if (bytes != null) {
var size = sizeMatch[1] + " " + sizeMatch[2];
return {
message: msg,
progress: estimateProgress(bytes),
size: size
};
}
}
return {
message: msg,
progress: null,
size: null
};
}
function resizeProgress() {
var info = document.getElementById("info");
var progress = document.getElementById("progress");
if (!info || !progress || progress.hidden) {
return false;
}
var style = window.getComputedStyle(info);
var measurement = document.createElement("span");
measurement.textContent = info.innerText;
measurement.style.position = "fixed";
measurement.style.left = "-9999px";
measurement.style.top = "-9999px";
measurement.style.visibility = "hidden";
measurement.style.whiteSpace = "nowrap";
measurement.style.fontFamily = style.fontFamily;
measurement.style.fontSize = style.fontSize;
measurement.style.fontWeight = style.fontWeight;
measurement.style.fontStyle = style.fontStyle;
measurement.style.letterSpacing = style.letterSpacing;
measurement.style.textTransform = style.textTransform;
document.body.appendChild(measurement);
var textWidth = measurement.getBoundingClientRect().width;
var loading = info.getElementsByClassName("loading").length > 0;
var dotsWidth = 0;
if (loading) {
dotsWidth = parseFloat(style.fontSize) * 0.75;
}
measurement.remove();
var maximumWidth = window.innerWidth * 0.8;
var width = Math.min(textWidth + dotsWidth + 100, maximumWidth);
progress.style.width = width + "px";
progress.style.transform = loading
? "translateX(" + (dotsWidth / 2) + "px)"
: "";
return true;
}
function setProgress(value, size) {
var progress = document.getElementById("progress");
var fill = document.getElementById("progress-fill");
if (value == null) {
progress.hidden = true;
progress.removeAttribute("title");
progress.removeAttribute("aria-valuenow");
progress.removeAttribute("aria-valuetext");
fill.style.width = "0%";
return true;
}
progress.hidden = false;
progress.title = size != null ? size : value + "%";
progress.setAttribute("aria-valuenow", value);
if (size != null) {
progress.setAttribute("aria-valuetext", size);
} else {
progress.removeAttribute("aria-valuetext");
}
fill.style.width = value + "%";
return true;
}
function setInfo(msg, loading, error) {
try {
@@ -266,9 +470,17 @@ function setInfo(msg, loading, error) {
return false;
}
var parsed = parseProgress(msg);
msg = parsed.message;
setProgress(parsed.progress, parsed.size);
var el = document.getElementById("info");
if (el.innerText == msg || el.innerHTML == msg) {
var progressEl = document.getElementById("progress");
if (progressEl && !progressEl.hidden && !progressEl.style.width) {
resizeProgress();
}
return true;
}
@@ -290,11 +502,13 @@ function setInfo(msg, loading, error) {
if (msg.includes(p)) {
if (el.innerHTML.includes(p)) {
el.getElementsByClassName('loading')[0].textContent = extractContent(msg);
resizeProgress();
return true;
}
}
el.innerHTML = msg;
resizeProgress();
return true;
} catch (e) {
@@ -413,6 +627,7 @@ function connect() {
};
}
window.addEventListener("resize", resizeProgress);
document.addEventListener("visibilitychange", visibilityChanged);
rememberStatus(document.getElementById("info").innerHTML);