Files
felhom-controller/controller/internal/web/templates/backups_restore_wizard.html
T
admin 0fe315b759
gates / gates (push) Successful in 15s
v0.246.0: an interrupted restore is told; the recovery-code reminder waits until the box can take it
MinAgent: 0.131.0 (unchanged). Requires hub v0.117.0 for restore_interrupted.

R-550 (operator ruling: fix). A design reversed and recorded: the restore
op-status was in memory by choice. Now restore-status.json in DataDir, written
atomically at both ends of an op. At startup a record still marked running
becomes a failed, interrupted result kept per app until that app's next
restore, shown on /backups/restore and the off-site wizard, and raised once as
restore_interrupted. Cooldowns stay in memory.

R-546. The R-543 reminder bar consults the agent's own preflight ok (every
blocking item, not a copy of pbs_storage_id), cached 60 s, probed only while
paused. /backup/escrow shows a waiting card that polls and reloads instead of
red crosses and English diagnostics. POST /api/escrow/start refuses 409 before
staging or starting - the direct path chaos night used. Unknown readiness keeps
the bar.

Red-proofs (each seen failing): restore record across restart; main() calls
both startup functions; startup helper with loading skipped; restore page card;
bar held back; waiting card; start refusal. go build/vet/test ./... green, 28
packages; controller_gates --fast all OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-17 10:45:29 +02:00

180 lines
11 KiB
HTML

{{define "backups_restore_wizard"}}
{{template "layout_start" .}}
<!-- R-48: the offsite restore wizard. One entry per app on /backups/restore leads here, and the
three intents are separately-described CARDS with a consequence sentence each — never sibling
buttons whose difference is whether the data comes back. Server-rendered throughout: every step
is a real form POST to the EXISTING endpoint, so the page works with JavaScript disabled. -->
<div class="page-header">
<div style="display:flex;align-items:center;gap:.5rem">
<a href="/backups/restore" class="btn btn-sm btn-outline">← Vissza</a>
<h2>Visszaállítás — {{.AppDisplayName}}</h2>
</div>
<span class="domain-badge">{{.Domain}}</span>
</div>
{{template "backups_flash" .}}
{{template "restore_banner" .}}
<!-- Phase strip: the customer can see there IS a sequence, and where they are in it. The round-2
incident's second half was that the decisive step appeared only after the first was pressed,
with nothing signposting that a second step existed at all. -->
{{$phase := printf "%s" .Wizard.Phase}}
<div class="restore-wizard-phases">
<span class="restore-wizard-phase{{if eq $phase "elokeszites"}} is-current{{end}}">Előkészítés</span>
<span class="restore-wizard-phase{{if eq $phase "megerosites"}} is-current{{end}}">Megerősítés</span>
<span class="restore-wizard-phase{{if eq $phase "vegrehajtas"}} is-current{{end}}">Végrehajtás</span>
<span class="restore-wizard-phase{{if eq $phase "eredmeny"}} is-current{{end}}">Eredmény</span>
</div>
{{with .LastResult}}
<!-- „Eredmény": the outcome of the restore that just finished, for THIS app. The redirect flash says
the same thing but does not survive a reload; this does, for restoreResultWindow. -->
<div class="settings-card">
<h3>Eredmény</h3>
<div class="alert {{if .OK}}alert-info{{else}}alert-error{{end}}">{{.Message}}</div>
<p class="form-hint">{{if .Interrupted}}Észlelve{{else}}Befejezve{{end}}: {{fmtTime .FinishedAt}}. Ha szeretnéd, alább újra indíthatsz egy visszaállítást.</p>
</div>
{{end}}
{{if eq (printf "%s" .Wizard.Step) "execution"}}
<!-- EXECUTION — every mutation form is suppressed server-side. The manager's single-flight would
refuse them anyway; offering a control guaranteed to fail is the same dishonesty class R-48
addresses. The live progress comes from the existing restore-status poll (the banner above). -->
<div class="settings-card">
<h3>Végrehajtás</h3>
<p>Jelenleg egy mentési vagy visszaállítási művelet fut{{with .RunningStack}} ({{.}}){{end}}. Amíg ez tart, új visszaállítás nem indítható.</p>
<!-- R-351: this used to say the state refreshes automatically AND that you must refresh the page
when it finishes. Both cannot be true, and the second half was the one people believed. The
banner now carries the terminal result too (RestoreOpStatus.LastRecent), so the sentence can
describe what the page actually does. -->
<p class="form-hint">Az állapot fent automatikusan frissül, és a művelet eredménye is ott jelenik meg, amint elkészült.</p>
<div class="form-actions">
<a href="/backups/restore/app?name={{.App}}" class="btn btn-sm btn-outline">Állapot frissítése</a>
</div>
</div>
{{else if eq (printf "%s" .Wizard.Step) "prepare-confirm"}}
<!-- MEGERŐSÍTÉS — the size gate. The preparation has measured what needs downloading; the customer
confirms with the size in front of them, before any transfer starts. -->
<div class="settings-card">
<h3>Megerősítés — teljes visszaállítás előkészítése</h3>
<p>A teljes visszaállításhoz a mentés teljes tartalmát le kell tölteni a távoli tárolóból{{with .FullPrepSize}} — a becsült méret: <strong>{{.}}</strong>{{end}}. A letöltés a meghajtón egy külön előkészítő mappába kerül; az élő adataid ebben a lépésben még nem változnak.</p>
<p class="form-hint">A letöltés a mérettől és a kapcsolat sebességétől függően hosszabb ideig is tarthat. Ha elkészült, ezen az oldalon választhatod ki, hogy csak a hiányzó fájlokat hozod vissza, vagy teljes visszaállítást kérsz.</p>
<div class="form-actions">
<form method="POST" action="/backup/offbox/restore">{{.CSRFField}}
<input type="hidden" name="app" value="{{.App}}">
<input type="hidden" name="mode" value="full">
<input type="hidden" name="confirm" value="1">
<button type="submit" class="btn btn-primary">Előkészítés indítása{{with .FullPrepSize}} (~{{.}}){{end}}</button>
</form>
<a href="/backups/restore/app?name={{.App}}" class="btn btn-outline">Mégsem</a>
</div>
</div>
{{else}}
<!-- INTENT — three cards, each with its own consequence sentence. Card order is deliberate:
harmless first, irreversible-looking last. -->
<div class="settings-card">
<h3>1. Ellenőrzés külön mappába (beállítások és adatbázis)</h3>
<!-- R-204 item 3: this card's scope is stated BEFORE the choice, not only in the outcome. It
restores the recovery unit only; the customer's own files stay in the backup. Saying „a
mentés tartalma" here was how a disaster-recovery customer chose the one intent that does
not return their documents. -->
<p>Az alkalmazás beállításait és adatbázisát hozza vissza egy külön ellenőrző mappába. A saját fájljaidat (dokumentumok, képek, feltöltések) <strong>nem</strong> hozza vissza — azokhoz a 3. pont teljes visszaállítása kell. Az élő adataid nem változnak.</p>
<div class="form-actions">
<form method="POST" action="/backup/offbox/restore">{{.CSRFField}}
<input type="hidden" name="app" value="{{.App}}">
<input type="hidden" name="mode" value="unit">
<button type="submit" class="btn btn-outline"{{if not .Wizard.VerifyEnabled}} disabled{{end}}>Ellenőrzés indítása</button>
</form>
</div>
</div>
<div class="settings-card">
<h3>2. Hiányzó fájlok visszahozása</h3>
<p>Csak a hiányzó fájlokat másolja vissza a meglévők közé. A meglévő fájlokat nem írja felül, adatbázist nem állít vissza — törölt tartalom ettől nem jelenik meg újra.</p>
{{if .Wizard.PlaceEnabled}}
<div class="form-actions">
<form method="POST" action="/backup/offbox/place">{{.CSRFField}}
<input type="hidden" name="app" value="{{.App}}">
<button type="submit" class="btn btn-outline">Hiányzó fájlok visszahozása</button>
</form>
</div>
{{else}}
<p class="form-hint">Ehhez előbb elő kell készíteni a teljes mentést — lásd a 3. pontot.</p>
{{end}}
</div>
<div class="settings-card restore-danger-card">
<h3>3. Teljes visszaállítás (fájlok + adatbázis)</h3>
<p>A fájlokat a mentés szerinti változatra állítja vissza és az adatbázist is visszatölti. Semmit nem töröl: a mentés óta létrejött fájlok megmaradnak. A jelenlegi adatbázisról előtte biztonsági mentés készül.</p>
{{if .Wizard.RestoreEnabled}}
<!-- Pair honesty (R-43): what the two halves of this restore actually ARE. A restore is the one
operation whose result cannot be inspected before committing to it. -->
<div class="alert alert-info" style="margin-bottom:.75rem">
<strong>Az előkészített mentés:</strong>
{{if not .Pair.DumpsAt.IsZero}}adatbázis-mentés ideje: {{fmtTime .Pair.DumpsAt}}.{{else}}az adatbázis-mentés ideje nem állapítható meg.{{end}}
</div>
{{if .Pair.Skewed}}
<div class="alert alert-warning" style="margin-bottom:.75rem">Az adatbázis-mentés régebbi{{if not .Pair.DumpsAt.IsZero}} ({{fmtTime .Pair.DumpsAt}}){{end}} — a fájlok és az adatbázis eltérő időpontból származnak.</div>
{{end}}
{{if .Pair.LooksEmpty}}
<div class="alert alert-warning" style="margin-bottom:.75rem">A mentett adatbázis üresnek tűnik (nincs benne felhasználói fiók) — elképzelhető, hogy a mentés korábbi, mint az adataid.</div>
{{end}}
<div class="form-actions">
<form method="POST" action="/backup/offbox/reconstitute">{{.CSRFField}}
<input type="hidden" name="app" value="{{.App}}">
<input type="hidden" name="confirm" value="1">
<button type="button" class="btn btn-danger"
data-restore-app="{{.App}}"
data-restore-when="{{if not .Pair.DumpsAt.IsZero}}{{fmtTime .Pair.DumpsAt}}{{end}}"
data-restore-skewed="{{if .Pair.Skewed}}1{{end}}"
data-restore-empty="{{if .Pair.LooksEmpty}}1{{end}}"
onclick="confirmFullRestore(this)">Teljes visszaállítás indítása</button>
</form>
</div>
{{else}}
<p class="form-hint">A teljes visszaállításhoz először le kell tölteni a mentés teljes tartalmát. Az előkészítés megmutatja a méretet, mielőtt bármi elindulna — az élő adataid az előkészítés alatt nem változnak.</p>
<div class="form-actions">
<form method="POST" action="/backup/offbox/restore">{{.CSRFField}}
<input type="hidden" name="app" value="{{.App}}">
<input type="hidden" name="mode" value="full">
<button type="submit" class="btn btn-outline"{{if not .Wizard.PrepareEnabled}} disabled{{end}}>Teljes visszaállítás előkészítése</button>
</form>
</div>
{{end}}
</div>
{{end}}
<script>
{{template "restore_banner_js"}}
/* Carried VERBATIM from backups_restore.html (R-43). The double-confirm is the good part of the old
surface — it states the DB half's age and any warning before the customer commits — so it moves
with the action rather than being rewritten. felhomConfirm is the house inline idiom, never the
OS-modal confirm() (F-11). */
function confirmFullRestore(btn){
var app = btn.getAttribute('data-restore-app') || '';
var when = btn.getAttribute('data-restore-when') || '';
var skewed = btn.getAttribute('data-restore-skewed') === '1';
var empty = btn.getAttribute('data-restore-empty') === '1';
var q = 'Teljes visszaállítás: ' + app + (when ? ' — a mentés ideje: ' + when : '') + '.';
if (skewed) { q += ' FIGYELEM: a fájlok és az adatbázis eltérő időpontból származnak.'; }
if (empty) { q += ' FIGYELEM: a mentett adatbázis üresnek tűnik.'; }
q += ' A fájlok a mentés szerinti változatra állnak vissza, semmi nem törlődik.';
felhomConfirm(btn, q, function(){
felhomConfirm(btn, 'UTOLSÓ MEGERŐSÍTÉS: az alkalmazás leáll, az adatbázis visszatöltődik, majd újraindul. A jelenlegi adatbázisról biztonsági mentés készül.', function(){
var f = btn.closest('form');
if (f) { if (f.requestSubmit) f.requestSubmit(); else f.submit(); }
});
});
}
</script>
{{template "layout_end" .}}
{{end}}