Add backup shrinking functionality to eMMC provisioning dashboard: implement API for shrinking raw .img backups using PiShrink, update UI to support shrink option after backup, and enhance documentation for backup image handling and storage options on Proxmox host.
This commit is contained in:
@@ -382,6 +382,9 @@
|
||||
<section class="section">
|
||||
<h2 class="section-title">Capture image or deploy</h2>
|
||||
<p class="backup-deploy-hint">To <strong>capture (backup)</strong> an image from a device: connect it in USB boot mode or register over network. When the device appears below, click <strong>Backup</strong> to save its eMMC to a file. To write an image to a device, click <strong>Deploy</strong> (requires a golden image).</p>
|
||||
<p id="shrinkOptionWrap" class="backup-deploy-hint" style="display:none; margin-top:0.5rem;">
|
||||
<label><input type="checkbox" id="shrinkAfterBackup" /> Shrink after backup</label> <span class="backups-mono" style="font-size:0.8rem;">(reduces size; requires PiShrink on host)</span>
|
||||
</p>
|
||||
<div id="pendingDevices"></div>
|
||||
<div id="noPendingPlaceholder" class="placeholder-actions" style="display:none;">
|
||||
<span>Connect a device to see:</span>
|
||||
@@ -506,12 +509,16 @@
|
||||
container.innerHTML = '';
|
||||
let hasAny = false;
|
||||
|
||||
const shrinkWrap = document.getElementById('shrinkOptionWrap');
|
||||
if (usb) {
|
||||
hasAny = true;
|
||||
if (shrinkWrap) shrinkWrap.style.display = 'block';
|
||||
const el = document.createElement('div');
|
||||
el.className = 'device-item';
|
||||
el.innerHTML = '<div class="device-info"><div class="device-type">USB boot</div><div class="device-desc">Device connected — choose Backup or Deploy</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="usb" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="usb" data-action="deploy">Deploy</button></div>';
|
||||
container.appendChild(el);
|
||||
} else {
|
||||
if (shrinkWrap) shrinkWrap.style.display = 'none';
|
||||
}
|
||||
(network || []).forEach(function(d) {
|
||||
hasAny = true;
|
||||
@@ -532,6 +539,8 @@
|
||||
const mac = btn.getAttribute('data-mac');
|
||||
const body = { source: source, action: action };
|
||||
if (mac) body.mac = mac;
|
||||
const shrinkCb = document.getElementById('shrinkAfterBackup');
|
||||
if (action === 'backup' && shrinkCb && shrinkCb.checked) body.shrink = true;
|
||||
fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
@@ -557,12 +566,15 @@
|
||||
tr.dataset.name = b.name;
|
||||
const displayName = (b.display_name || b.name);
|
||||
const desc = (b.description || '');
|
||||
const isRawImg = b.name.endsWith('.img') && !b.name.endsWith('.img.gz') && !b.name.endsWith('.img.xz');
|
||||
const shrinkBtn = isRawImg ? '<button type="button" class="btn btn-outline btn-sm shrink-btn" data-name="' + escapeHtml(b.name) + '" title="Shrink image (PiShrink)">Shrink</button> ' : '';
|
||||
tr.innerHTML =
|
||||
'<td class="backup-name-edit" data-field="name" title="Click to rename">' + escapeHtml(displayName) + '</td>' +
|
||||
'<td class="backup-desc-edit" data-field="description" title="Click to add description">' + escapeHtml(desc) + '</td>' +
|
||||
'<td class="backups-mono">' + fmtSize(b.size) + '</td>' +
|
||||
'<td class="backups-mono">' + fmtDate(b.mtime) + '</td>' +
|
||||
'<td class="actions-cell">' +
|
||||
shrinkBtn +
|
||||
'<button type="button" class="btn btn-primary btn-sm set-golden-btn" data-name="' + escapeHtml(b.name) + '">Set as golden</button> ' +
|
||||
'<button type="button" class="btn btn-outline btn-sm rename-file-btn" data-name="' + escapeHtml(b.name) + '" title="Rename file">Rename file</button> ' +
|
||||
'<a href="/api/backups/' + encodeURIComponent(b.name) + '" download class="btn btn-outline btn-sm download-link">Download</a>' +
|
||||
@@ -572,6 +584,7 @@
|
||||
bindBackupEdits();
|
||||
bindSetGolden();
|
||||
bindRenameFile();
|
||||
bindShrink();
|
||||
}
|
||||
|
||||
function bindRenameFile() {
|
||||
@@ -648,6 +661,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
function bindShrink() {
|
||||
document.querySelectorAll('.shrink-btn').forEach(function(btn) {
|
||||
btn.onclick = function() {
|
||||
const name = btn.getAttribute('data-name');
|
||||
if (!confirm('Shrink this image with PiShrink? This reduces file size and may take a few minutes.\n\n' + name)) return;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Shrinking…';
|
||||
fetch('/api/backups/' + encodeURIComponent(name) + '/shrink', { method: 'POST' })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.ok) { fetchBackups(); }
|
||||
else alert(data.error || data.detail || 'Failed');
|
||||
})
|
||||
.catch(function() { alert('Request failed'); })
|
||||
.finally(function() { btn.disabled = false; btn.textContent = 'Shrink'; });
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function fetchGoldenInfo() {
|
||||
fetch('/api/golden-info').then(function(r) { return r.json(); }).then(function(d) {
|
||||
const el = document.getElementById('goldenHint');
|
||||
|
||||
Reference in New Issue
Block a user