v0.147.0 — feedback slice 1: pressing a button says something
The systemic complaint, twice in one evening: you press a button and nothing happens. No progress, no ETA, no named result. Three worst offenders, fixed on the two patterns already here (deploy 3-step panel, storage-init status poll). No new framework — that is a ROADMAP item; three targeted cards ship tonight. 4a — a verification restore names its result. The flash said the app had been restored "to a verification folder on the drive"; which folder, on which drive, was invisible, so the customer could not go and look at what they had just asked for. Full path now. The restore page gained a listing of existing verification copies (app, size, date, path) — nothing anywhere showed these, so they piled up and the only way to find them was SSH — each with a double-confirmed delete. That delete is the only one this release adds, so it names a STACK, never a path: the Manager resolves the name inside a backups/offsite-restore root it computed itself and refuses anything landing outside. Red-proofed — neutralise the name guard and stack:"" resolves to the offsite-restore ROOT and takes every copy with it. Refusals are asserted as non-effects. 4b — Megosztás enable shows what it is waiting for. Enabling ran ReconcileSamba synchronously inside the POST handler; on a golden without felhom-samba baked that is compose pulling ~100MB, i.e. minutes of an apparently-hung form post followed by "Beállítás mentve." whether or not anything came up. Detached + polled now, distinguishing "képfájl letöltése" from "indítás" — decided BEFORE the work starts, since afterwards the image is always present. Success is probed, not inferred (compose up -d exits 0 on a crash-loop). The password form starts the same job: with UserSet false reconcile deploys nothing, so on a fresh box that is where the pull actually happens. 4c — "Távoli mentés most" streams real progress. restic was already reporting bytes and percent; the runner seam used CombinedOutput() and discarded them. The manual run now passes --json and scans stdout line-by-line: total bytes, percent, current app. Manual only — the nightly stays silent, pinned by a test that fails if it ever passes --json. The poll now arms unconditionally, closing a race the manual trigger always ran: the redirect rendered before the goroutine wrote LastStatus=running, so the poll never armed and the page sat static during the very run just started. Red-proofed twice. Also closes the golden/controller infra-image drift at the source: infra.Images() derives from the existing pins and --print-infra-images exposes it, so the golden bake can stop carrying its own copy. That copy had already drifted — felhom-samba was never added, so the golden baked 3 of 4, which is why enabling Megosztás pulled at runtime in the first place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
This commit is contained in:
@@ -126,6 +126,15 @@
|
||||
<button type="submit" class="btn btn-sm btn-primary">Távoli mentés most</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- v0.147.0 (4c): live progress for a MANUAL run — total bytes, percent and which app is being
|
||||
pushed, parsed from restic's --json status stream. Hidden until a run reports progress; the
|
||||
nightly run never populates it. -->
|
||||
<div id="offbox-progress" class="alert alert-info" style="display:none;margin-top:.75rem">
|
||||
<span id="offbox-progress-text"></span>
|
||||
<div class="progress-bar-task" style="margin-top:.5rem">
|
||||
<div id="offbox-progress-bar" class="progress-fill" style="width:0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 style="margin-top:1.25rem">Mely alkalmazások mentődnek a távoli tárolóra?</h4>
|
||||
{{if and .OffboxApps (eq .Offbox.EscrowState "escrowed") (eq .OffboxToggledCount 0)}}
|
||||
<p class="form-hint">Nincs távoli mentésre jelölt alkalmazás — jelölj ki legalább egyet.</p>
|
||||
@@ -172,19 +181,55 @@
|
||||
{{end}}
|
||||
|
||||
<script>
|
||||
/* Part C (v0.142.0): while a remote backup is in flight the status card shows "Fut…" — poll the run
|
||||
status and reload once it reaches a terminal state, so the customer sees Rendben/Hiba + fresh
|
||||
numbers WITHOUT a manual reload. Polls only when a run is running; stops (page reload) at terminal. */
|
||||
/* Part C (v0.142.0) + 4c (v0.147.0): poll the run status, render live progress for a manual run, and
|
||||
reload once it reaches a terminal state so the customer sees Rendben/Hiba + fresh numbers WITHOUT a
|
||||
manual reload.
|
||||
|
||||
v0.147.0 changed WHEN polling starts. It used to begin only if the page already rendered "Fut…",
|
||||
which loses a race the manual trigger always runs: the POST redirects and this page renders before
|
||||
the detached goroutine has written LastStatus=running, so the poll never armed and the customer
|
||||
watched a static page during the very run they had just started. Now it always arms and gives up
|
||||
after GRACE quiet ticks if nothing is (or ever was) in flight. */
|
||||
(function(){
|
||||
var v=document.getElementById('offbox-status-value');
|
||||
if(!v || v.textContent.indexOf('Fut')<0) return; // not running → nothing to poll
|
||||
var timer=setInterval(function(){
|
||||
var v = document.getElementById('offbox-status-value');
|
||||
var box = document.getElementById('offbox-progress');
|
||||
var text = document.getElementById('offbox-progress-text');
|
||||
var bar = document.getElementById('offbox-progress-bar');
|
||||
var GRACE = 5; // ~15s of quiet before concluding nothing is running
|
||||
var quiet = 0;
|
||||
var sawRunning = (v && v.textContent.indexOf('Fut') >= 0);
|
||||
|
||||
var timer = setInterval(function(){
|
||||
fetch('/backup/offbox/status',{headers:{'Accept':'application/json'}})
|
||||
.then(function(r){return r.json();})
|
||||
.then(function(d){
|
||||
if(d && d.status==='running') return; // still running → keep polling
|
||||
clearInterval(timer);
|
||||
location.reload(); // terminal → re-render (fresh numbers, warnings, or the orphan card)
|
||||
if(!d) return;
|
||||
var p = d.progress || {};
|
||||
var running = (d.status === 'running') || p.active;
|
||||
|
||||
if(running){
|
||||
sawRunning = true; quiet = 0;
|
||||
if(p.active && box){
|
||||
/* Only claim a percentage once restic has told us a total — before the scan finishes,
|
||||
percent is 0 of 0, and a bar pinned at 0% reads as "stuck" rather than "measuring". */
|
||||
var label = p.current_app ? ('Mentés: ' + p.current_app) : 'Mentés folyamatban';
|
||||
if(p.total_bytes > 0){
|
||||
label += ' — ' + Math.round(p.percent) + '% (' + p.done_human + ' / ' + p.total_human + ')';
|
||||
bar.style.width = Math.max(0, Math.min(100, p.percent)) + '%';
|
||||
} else {
|
||||
label += ' — a mentendő adatok felmérése…';
|
||||
bar.style.width = '0%';
|
||||
}
|
||||
text.textContent = label;
|
||||
box.style.display = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Not running. Terminal only if we ever saw it running — otherwise this is the pre-start
|
||||
race (or a page nobody triggered anything from). */
|
||||
if(sawRunning){ clearInterval(timer); location.reload(); return; }
|
||||
if(++quiet >= GRACE){ clearInterval(timer); }
|
||||
})
|
||||
.catch(function(){ /* transient — keep polling */ });
|
||||
}, 3000);
|
||||
|
||||
@@ -121,6 +121,32 @@
|
||||
{{template "app_list_row_end"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- v0.147.0 (4a): what the verification restores actually LEFT on disk. Until now the result of
|
||||
an ellenőrző visszaállítás was invisible — the flash said "a verification folder on the
|
||||
drive" without naming it, and nothing listed what had accumulated. Full path, size and date,
|
||||
so the copy can be found, opened, and cleaned up. -->
|
||||
<div class="backup-tier-divider" style="margin-top:1.5rem"></div>
|
||||
<h3 style="margin-bottom:.5rem">Meglévő ellenőrző másolatok</h3>
|
||||
{{if .OffsiteRestoreCopies}}
|
||||
<p class="form-hint" style="margin-bottom:.75rem">Ezek a visszaállított másolatok helyet foglalnak a meghajtón. A tényleges adataidat nem érintik, bármikor törölhetők.</p>
|
||||
<div class="app-row-list">
|
||||
{{range .OffsiteRestoreCopies}}
|
||||
{{template "app_list_row" dict "Slug" .Stack "Name" .Stack "Secondary" (printf "%s · %s · %s" .SizeHuman (.Created.Format "2006. 01. 02. 15:04") .Path)}}
|
||||
<form method="POST" action="/backup/offbox/verify-copy/delete" style="display:inline">
|
||||
{{$.CSRFField}}
|
||||
<input type="hidden" name="stack" value="{{.Stack}}">
|
||||
<input type="hidden" name="confirm" value="1">
|
||||
<button type="button" class="btn btn-xs btn-danger-outline"
|
||||
data-copy-path="{{.Path}}"
|
||||
onclick="confirmDeleteVerifyCopy(this)">Másolat törlése</button>
|
||||
</form>
|
||||
{{template "app_list_row_end"}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="form-hint">Nincs ellenőrző másolat a meghajtón.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -178,6 +204,19 @@ function fabDownload(btn){
|
||||
})
|
||||
.catch(function(){ fabSetStatus('Hiba: a becslés nem érhető el.'); });
|
||||
}
|
||||
// v0.147.0 (4a): the ONLY delete this page offers. Two inline acknowledgements — the house
|
||||
// double-confirm idiom (deploy.html's stale-data delete), never native confirm(), which is an
|
||||
// OS-modal that blocks browser automation (F-11). The first step names the exact path so the
|
||||
// customer is agreeing to a specific directory, not to the word "delete".
|
||||
function confirmDeleteVerifyCopy(btn){
|
||||
var path = btn.getAttribute('data-copy-path') || '';
|
||||
felhomConfirm(btn, 'Biztosan törlöd ezt az ellenőrző másolatot? ' + path + ' — a tényleges adataid változatlanok maradnak.', function(){
|
||||
felhomConfirm(btn, 'UTOLSÓ MEGERŐSÍTÉS: a másolat véglegesen törlődik.', function(){
|
||||
var f = btn.closest('form');
|
||||
if (f) { if (f.requestSubmit) f.requestSubmit(); else f.submit(); }
|
||||
});
|
||||
});
|
||||
}
|
||||
function fabStart(stack, next){
|
||||
fetch('/api/export/download/start', {method:'POST', headers:Object.assign({'Content-Type':'application/json'}, csrfHeaders()), body: JSON.stringify({stack_name: stack, password: fabPassword()})})
|
||||
.then(function(r){ return r.json(); })
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- v0.147.0 (4b): bring-up progress. Hidden until the poll reports a non-idle phase, so a page
|
||||
with nothing in flight looks exactly as it did before. This is what makes the first-enable
|
||||
wait survivable on a golden that has not baked felhom-samba: the pull is named, not silent. -->
|
||||
<div id="samba-progress" class="alert alert-info" style="display:none">
|
||||
<span id="samba-progress-text"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<span>Állapot:</span>
|
||||
{{if .SMBRunning}}
|
||||
@@ -282,6 +289,54 @@ function shareBrowseLoad(p){
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* v0.147.0 (4b) — Megosztás bring-up poll. Same shape as the storage-init status poll
|
||||
(storage_init.html): 1.5s tick, phase string mapped to Hungarian, terminal states stop the timer.
|
||||
The card only ever appears while something is genuinely in flight. */
|
||||
(function(){
|
||||
var box = document.getElementById('samba-progress');
|
||||
var txt = document.getElementById('samba-progress-text');
|
||||
if (!box || !txt) return;
|
||||
var PHASES = {
|
||||
pulling: 'A megosztási szolgáltatás előkészítése… (képfájl letöltése — ez több percig tarthat)',
|
||||
starting: 'A megosztási szolgáltatás indítása…',
|
||||
needs_password: 'A megosztás be van kapcsolva, de még nincs megosztási jelszó — add meg alább.'
|
||||
};
|
||||
var timer = null;
|
||||
function stop(){ if (timer) { clearInterval(timer); timer = null; } }
|
||||
function show(cls, text){
|
||||
box.className = 'alert ' + cls;
|
||||
txt.textContent = text;
|
||||
box.style.display = '';
|
||||
}
|
||||
function tick(){
|
||||
fetch('/sharing/status', {credentials:'same-origin'})
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(j){
|
||||
if (!j || !j.ok || !j.data) return; // transient — keep polling
|
||||
var ph = j.data.phase;
|
||||
if (PHASES[ph]) { show('alert-info', PHASES[ph]); return; }
|
||||
if (ph === 'running') {
|
||||
stop();
|
||||
show('alert-success', 'A megosztási szolgáltatás fut.');
|
||||
/* Repaint the „Állapot" badge, which was rendered server-side as „áll". */
|
||||
setTimeout(function(){ location.reload(); }, 1200);
|
||||
return;
|
||||
}
|
||||
if (ph === 'failed') {
|
||||
stop();
|
||||
show('alert-error', 'A megosztási szolgáltatás nem indult el' + (j.data.error ? ': ' + j.data.error : '.'));
|
||||
return;
|
||||
}
|
||||
/* idle and nothing running: nothing to report. */
|
||||
stop();
|
||||
box.style.display = 'none';
|
||||
})
|
||||
.catch(function(){ /* transient — keep polling */ });
|
||||
}
|
||||
tick();
|
||||
timer = setInterval(tick, 1500);
|
||||
})();
|
||||
</script>
|
||||
|
||||
{{template "layout_end" .}}
|
||||
|
||||
Reference in New Issue
Block a user