Add dismiss functionality for cloud-init build status</message>
<message>Implement a new API endpoint to clear the build status to idle, allowing users to dismiss messages after a build is cancelled, completed, or errored. Update the dashboard UI to include a dismiss link that appears under relevant conditions, enhancing user experience by providing a clearer interface for managing build statuses. Modify the JavaScript to handle the dismissal action and ensure proper status updates are reflected in the UI.
This commit is contained in:
@@ -1629,6 +1629,18 @@ def api_build_cloudinit_cancel():
|
|||||||
return jsonify({"ok": False, "error": str(e)}), 500
|
return jsonify({"ok": False, "error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/build-cloudinit-status-clear", methods=["POST"])
|
||||||
|
@require_admin
|
||||||
|
def api_build_cloudinit_status_clear():
|
||||||
|
"""Clear build status to idle (so message is cleared after cancel/done/error)."""
|
||||||
|
st = _build_status_read()
|
||||||
|
busy = st.get("phase") in ("downloading", "decompressing", "injecting", "finalizing", "resolving")
|
||||||
|
if busy:
|
||||||
|
return jsonify({"ok": False, "error": "Cannot clear while build is in progress"}), 409
|
||||||
|
_build_status_write("idle", "", None, None)
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/build-cloudinit", methods=["POST"])
|
@app.route("/api/build-cloudinit", methods=["POST"])
|
||||||
@require_admin
|
@require_admin
|
||||||
def api_build_cloudinit():
|
def api_build_cloudinit():
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<button type="button" id="buildCloudInitCancelBtn" class="btn btn-outline" style="display:none; margin-left:0.5rem;">Cancel build</button>
|
<button type="button" id="buildCloudInitCancelBtn" class="btn btn-outline" style="display:none; margin-left:0.5rem;">Cancel build</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="buildCloudInitStatus" class="mono" style="min-height:1.2em;"></div>
|
<div id="buildCloudInitStatus" class="mono" style="min-height:1.2em;"></div>
|
||||||
|
<a href="#" id="buildCloudInitDismiss" style="display:none;">Dismiss</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
@@ -140,14 +141,27 @@
|
|||||||
var el = document.getElementById('buildCloudInitStatus');
|
var el = document.getElementById('buildCloudInitStatus');
|
||||||
var btn = document.getElementById('buildCloudInitBtn');
|
var btn = document.getElementById('buildCloudInitBtn');
|
||||||
var cancelBtn = document.getElementById('buildCloudInitCancelBtn');
|
var cancelBtn = document.getElementById('buildCloudInitCancelBtn');
|
||||||
|
var dismissEl = document.getElementById('buildCloudInitDismiss');
|
||||||
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
|
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
|
||||||
if (btn) btn.disabled = busy;
|
if (btn) btn.disabled = busy;
|
||||||
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
|
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
|
||||||
|
if (dismissEl) dismissEl.style.display = (d.phase === 'done' || d.phase === 'error' || d.phase === 'cancelled') ? 'inline' : 'none';
|
||||||
if (d.phase === 'idle' && !d.message) el.textContent = '';
|
if (d.phase === 'idle' && !d.message) el.textContent = '';
|
||||||
else if (d.phase === 'done') { el.textContent = 'Done: ' + (d.output_name || '') + ' — see Admin page Cloud-init images.'; }
|
else if (d.phase === 'done') { el.textContent = 'Done: ' + (d.output_name || '') + ' — see Admin page Cloud-init images.'; }
|
||||||
else if (d.phase === 'error') el.textContent = 'Error: ' + (d.error || '');
|
else if (d.phase === 'error') el.textContent = 'Error: ' + (d.error || '');
|
||||||
else if (d.phase === 'cancelled') el.textContent = 'Build cancelled.';
|
else if (d.phase === 'cancelled') {
|
||||||
else el.textContent = (d.phase || '') + ': ' + (d.message || '');
|
el.textContent = 'Build cancelled.';
|
||||||
|
if (btn) btn.disabled = false;
|
||||||
|
if (!window._buildClearScheduled) {
|
||||||
|
window._buildClearScheduled = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
authFetch('/api/build-cloudinit-status-clear', { method: 'POST', headers: {'Content-Type':'application/json'} }).then(function() { fetchBuildStatus(); }).finally(function() { window._buildClearScheduled = false; });
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window._buildClearScheduled = false;
|
||||||
|
el.textContent = (d.phase || '') + ': ' + (d.message || '');
|
||||||
|
}
|
||||||
if (busy) setTimeout(fetchBuildStatus, 5000);
|
if (busy) setTimeout(fetchBuildStatus, 5000);
|
||||||
}).catch(function() {});
|
}).catch(function() {});
|
||||||
}
|
}
|
||||||
@@ -228,6 +242,8 @@
|
|||||||
document.getElementById('buildCloudInitBtn').onclick = startBuild;
|
document.getElementById('buildCloudInitBtn').onclick = startBuild;
|
||||||
var cancelBuildBtn = document.getElementById('buildCloudInitCancelBtn');
|
var cancelBuildBtn = document.getElementById('buildCloudInitCancelBtn');
|
||||||
if (cancelBuildBtn) cancelBuildBtn.onclick = cancelBuild;
|
if (cancelBuildBtn) cancelBuildBtn.onclick = cancelBuild;
|
||||||
|
var dismissBuildBtn = document.getElementById('buildCloudInitDismiss');
|
||||||
|
if (dismissBuildBtn) dismissBuildBtn.onclick = function(e) { e.preventDefault(); authFetch('/api/build-cloudinit-status-clear', { method: 'POST', headers: {'Content-Type':'application/json'} }).then(function() { fetchBuildStatus(); }); };
|
||||||
document.getElementById('buildVariant').onchange = function() {
|
document.getElementById('buildVariant').onchange = function() {
|
||||||
authFetch('/api/raspios-latest-url?variant=' + encodeURIComponent(document.getElementById('buildVariant').value)).then(function(r) { return r.json(); }).then(function(d) {
|
authFetch('/api/raspios-latest-url?variant=' + encodeURIComponent(document.getElementById('buildVariant').value)).then(function(r) { return r.json(); }).then(function(d) {
|
||||||
document.getElementById('buildRaspiosUrl').textContent = (d.ok && d.filename) ? d.filename : (d.error || '');
|
document.getElementById('buildRaspiosUrl').textContent = (d.ok && d.filename) ? d.filename : (d.error || '');
|
||||||
|
|||||||
@@ -451,6 +451,7 @@
|
|||||||
<button type="button" id="buildCloudInitCancelBtn" class="btn btn-outline" style="display:none; margin-left:0.5rem;">Cancel build</button>
|
<button type="button" id="buildCloudInitCancelBtn" class="btn btn-outline" style="display:none; margin-left:0.5rem;">Cancel build</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="buildCloudInitStatus" class="backups-mono" style="font-size:0.85rem; min-height:1.5em; margin-bottom:0.5rem;"></div>
|
<div id="buildCloudInitStatus" class="backups-mono" style="font-size:0.85rem; min-height:1.5em; margin-bottom:0.5rem;"></div>
|
||||||
|
<a href="#" id="buildCloudInitDismiss" style="display:none; font-size:0.85rem;">Dismiss</a>
|
||||||
<details style="margin-top:0.5rem;">
|
<details style="margin-top:0.5rem;">
|
||||||
<summary>Cloud-init templates & customize</summary>
|
<summary>Cloud-init templates & customize</summary>
|
||||||
<div class="inner" style="margin-top:0.5rem;">
|
<div class="inner" style="margin-top:0.5rem;">
|
||||||
@@ -895,10 +896,12 @@
|
|||||||
var el = document.getElementById('buildCloudInitStatus');
|
var el = document.getElementById('buildCloudInitStatus');
|
||||||
var btn = document.getElementById('buildCloudInitBtn');
|
var btn = document.getElementById('buildCloudInitBtn');
|
||||||
var cancelBtn = document.getElementById('buildCloudInitCancelBtn');
|
var cancelBtn = document.getElementById('buildCloudInitCancelBtn');
|
||||||
|
var dismissEl = document.getElementById('buildCloudInitDismiss');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
|
var busy = ['resolving','downloading','decompressing','injecting','finalizing'].indexOf(d.phase) >= 0;
|
||||||
if (btn) btn.disabled = busy;
|
if (btn) btn.disabled = busy;
|
||||||
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
|
if (cancelBtn) { cancelBtn.style.display = busy ? 'inline-block' : 'none'; cancelBtn.disabled = false; }
|
||||||
|
if (dismissEl) dismissEl.style.display = (d.phase === 'done' || d.phase === 'error' || d.phase === 'cancelled') ? 'inline' : 'none';
|
||||||
if (d.phase === 'idle' && !d.message) {
|
if (d.phase === 'idle' && !d.message) {
|
||||||
el.textContent = '';
|
el.textContent = '';
|
||||||
} else if (d.phase === 'done') {
|
} else if (d.phase === 'done') {
|
||||||
@@ -909,7 +912,15 @@
|
|||||||
el.textContent = 'Error: ' + (d.error || d.message || 'Unknown');
|
el.textContent = 'Error: ' + (d.error || d.message || 'Unknown');
|
||||||
} else if (d.phase === 'cancelled') {
|
} else if (d.phase === 'cancelled') {
|
||||||
el.textContent = 'Build cancelled.';
|
el.textContent = 'Build cancelled.';
|
||||||
|
if (btn) btn.disabled = false;
|
||||||
|
if (!window._buildClearScheduled) {
|
||||||
|
window._buildClearScheduled = true;
|
||||||
|
setTimeout(function() {
|
||||||
|
fetch('/api/build-cloudinit-status-clear', { method: 'POST', headers: { 'Content-Type': 'application/json' } }).then(function() { fetchBuildStatus(); }).finally(function() { window._buildClearScheduled = false; });
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
window._buildClearScheduled = false;
|
||||||
el.textContent = (d.phase || '') + ': ' + (d.message || '');
|
el.textContent = (d.phase || '') + ': ' + (d.message || '');
|
||||||
}
|
}
|
||||||
if (busy) setTimeout(fetchBuildStatus, 5000);
|
if (busy) setTimeout(fetchBuildStatus, 5000);
|
||||||
@@ -1012,6 +1023,8 @@
|
|||||||
if (buildBtn) buildBtn.onclick = startBuildCloudInit;
|
if (buildBtn) buildBtn.onclick = startBuildCloudInit;
|
||||||
var cancelBuildBtn = document.getElementById('buildCloudInitCancelBtn');
|
var cancelBuildBtn = document.getElementById('buildCloudInitCancelBtn');
|
||||||
if (cancelBuildBtn) cancelBuildBtn.onclick = cancelBuildCloudInit;
|
if (cancelBuildBtn) cancelBuildBtn.onclick = cancelBuildCloudInit;
|
||||||
|
var dismissBuildBtn = document.getElementById('buildCloudInitDismiss');
|
||||||
|
if (dismissBuildBtn) dismissBuildBtn.onclick = function(e) { e.preventDefault(); fetch('/api/build-cloudinit-status-clear', { method: 'POST', headers: { 'Content-Type': 'application/json' } }).then(function() { fetchBuildStatus(); }); };
|
||||||
var variantSel = document.getElementById('buildVariant');
|
var variantSel = document.getElementById('buildVariant');
|
||||||
if (variantSel) variantSel.onchange = fetchRaspiosUrl;
|
if (variantSel) variantSel.onchange = fetchRaspiosUrl;
|
||||||
var templateLoadBtn = document.getElementById('buildTemplateLoad');
|
var templateLoadBtn = document.getElementById('buildTemplateLoad');
|
||||||
|
|||||||
@@ -190,10 +190,23 @@ mkdir -p "$OUTPUT_DIR"
|
|||||||
cp "$IMG_FILE" "$OUT_PATH"
|
cp "$IMG_FILE" "$OUT_PATH"
|
||||||
|
|
||||||
# Compress to .img.xz to reduce size (deploy supports decompress on the fly)
|
# Compress to .img.xz to reduce size (deploy supports decompress on the fly)
|
||||||
|
check_cancel
|
||||||
write_status "finalizing" "Compressing image (xz)…" "" ""
|
write_status "finalizing" "Compressing image (xz)…" "" ""
|
||||||
OUT_XZ="${OUT_PATH}.xz"
|
OUT_XZ="${OUT_PATH}.xz"
|
||||||
# -T 0 = use all cores; fallback to -T 1 if old xz
|
xz -T 0 -z -k -f "$OUT_PATH" 2>/dev/null &
|
||||||
if ! xz -T 0 -z -k -f "$OUT_PATH" 2>/dev/null; then
|
XZ_PID=$!
|
||||||
|
while kill -0 "$XZ_PID" 2>/dev/null; do
|
||||||
|
if [[ -f "$CANCEL_FILE" ]]; then
|
||||||
|
kill "$XZ_PID" 2>/dev/null
|
||||||
|
wait "$XZ_PID" 2>/dev/null
|
||||||
|
write_status "cancelled" "Build cancelled." "" ""
|
||||||
|
rm -f "$REQUEST_FILE" "$CANCEL_FILE"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
wait "$XZ_PID" 2>/dev/null || true
|
||||||
|
if [[ ! -f "$OUT_XZ" ]]; then
|
||||||
xz -T 1 -z -k -f "$OUT_PATH" 2>/dev/null || true
|
xz -T 1 -z -k -f "$OUT_PATH" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
if [[ -f "$OUT_XZ" ]]; then
|
if [[ -f "$OUT_XZ" ]]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user