Files
felhom-controller/controller/internal/web/templates/recovery.html
T
admin 11b790e314
gates / gates (push) Successful in 20s
v0.249.0: i18n slice 1 release B — backup pages in English, Hungarian byte-identical (R-556)
Seven backup pages + the restore-progress JS converted against fixtures captured unconverted
(f8ebc47); recovery renders through executeTemplateLang; English retrieval claims registered;
the Fut comparison left unconverted (R-563).

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

195 lines
9.3 KiB
HTML

{{define "recovery"}}
<!DOCTYPE html>
<html lang="{{T "layout.html_lang"}}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>{{T "recovery.adatok_visszaszerzese_felhom"}}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="login-body">
<div class="login-card" style="max-width:46rem">
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="login-logo">
{{if .Unlocked}}
<!-- ── AFTER THE UNLOCK: what is in there. Read-only — nothing was restored. ────────────── -->
<h1 class="login-title">{{T "recovery.a_menteseid"}} <span class="title-accent">{{T "recovery.elerhetok"}}</span></h1>
{{if .Flash}}<div class="alert alert-info">{{.Flash}}</div>{{end}}
{{if .Error}}<div class="alert alert-error">{{.Error}}</div>{{end}}
{{if .InvUnavailable}}
<!-- R-217: the unlock SUCCEEDED but the repository could not be read. It renders NO listing and
claims nothing about the contents — the .Error above already says what is pending. This
branch exists because the previous code passed a zero-value OffsiteInventory here, whose
Empty=false fell through to .InvUntagged and asserted the store had opened with content. -->
{{else if .InvEmpty}}
<div class="alert alert-warning">
{{T "recovery.a_tarolo_megnyilt_de_nincs"}}
</div>
{{else if .InvUntagged}}
<div class="alert alert-warning">
{{T "recovery.a_tarolo_megnyilt_es_van"}}
</div>
{{else}}
<p class="login-subtitle" style="margin-bottom:1rem">
{{T "recovery.ezek_a_te_menteseid_a"}}
</p>
<table class="data-table" style="width:100%;margin-bottom:1rem">
<thead><tr><th>{{T "recovery.alkalmazas"}}</th><th>{{T "recovery.legutobbi_mentes"}}</th><th>{{T "recovery.meret"}}</th></tr></thead>
<tbody>
{{range .InvApps}}
<tr>
<td>{{.App}}</td>
<td>{{fmtTime .LatestAt}}</td>
<td>{{if gt .SizeBytes 0}}{{humanBytes .SizeBytes}}{{else}}—{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
<p class="form-hint">
{{T "recovery.a_visszaallitas_alkalmazasonkent_torteni"}}
</p>
<div class="form-actions">
<a href="/backups/restore" class="btn btn-primary">{{T "recovery.tovabb_a_visszaallitashoz"}}</a>
<a href="/launcher" class="btn btn-outline">{{T "recovery.vissza_a_kezdolapra"}}</a>
</div>
{{else}}
<!-- ── BEFORE ANY CODE: explain, then take the code. ───────────────────────────────────── -->
<h1 class="login-title">{{T "recovery.adatok"}} <span class="title-accent">{{T "recovery.visszaszerzese"}}</span></h1>
<p class="login-subtitle">{{.CustomerName}}</p>
{{if .Flash}}<div class="alert alert-info">{{.Flash}}</div>{{end}}
{{if .Error}}<div class="alert alert-error">{{.Error}}</div>{{end}}
<p>
{{T "recovery.ezt_a_gepet_ujratelepitettek_a"}}{{with .SealedAt}}{{T "recovery.amelyet_zartunk_le"}}{{end}}{{T "recovery.a_csomagot_csak_a_te"}}
</p>
<div class="alert alert-warning">
{{T "recovery.a_helyreallitasi_kodot_senki_nem"}}
</div>
<p>
{{T "recovery.ha_megadod_a_kodot_feloldjuk"}}
</p>
<div id="unlock-gateway-error" class="alert alert-error" style="display:none" role="alert"></div>
<form id="unlock-form" method="POST" action="/recovery/unlock" autocomplete="off">
{{.CSRFField}}
<label for="recovery_code">{{T "recovery.helyreallitasi_kod_tiz_szo"}}</label>
<input type="password" id="recovery_code" name="recovery_code"
autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"
placeholder="{{T "recovery.tiz_szo_szokozokkel_elvalasztva"}}" required>
<div class="form-actions">
<button type="submit" class="btn btn-primary">{{T "recovery.mentesek_feloldasa"}}</button>
<form method="POST" action="/recovery/postpone" style="display:inline">
{{.CSRFField}}
<button type="submit" class="btn btn-outline">{{T "recovery.most_nem"}}</button>
</form>
</div>
</form>
{{/* R-227 — A RESTART MID-UNLOCK MUST NOT SHOW A RAW ENGLISH GATEWAY ERROR.
Measured 2026-08-05 (CAMPAIGN-11 F8): the controller was restarted 0.7 s into an unlock and
the customer got traefik's `Bad Gateway` — a raw upstream error, in English, naming no reason
and saying nothing about whether the key was installed. The state was clean; only the page
was not. It breaches I3 (every refusal names a reason a person can act on, in Hungarian, with
no raw error).
WHICH LAYER ANSWERS: traefik, and its config IS generated by this repo
(internal/infra/templates/traefik*.tmpl). A fully branded proxy error page is therefore
possible here — but traefik v3 serves no static files itself, so it would need a new
always-up container purely to hold an error page, for every 502 on the box. That is out of
proportion to this finding and is scoped in the report rather than built.
What ships instead is the second sanctioned option: the unlock posts via fetch, so a gateway
error or a dropped connection is caught in the page and answered in Hungarian, without
leaving it. PROGRESSIVE ENHANCEMENT — with no JS the plain POST is unchanged, and that path
still shows the proxy's own error. Said plainly rather than implied. */}}
<script>
(function () {
var form = document.getElementById('unlock-form');
var box = document.getElementById('unlock-gateway-error');
if (!form || !box || !window.fetch) { return; }
form.addEventListener('submit', function (ev) {
ev.preventDefault();
box.style.display = 'none';
var btn = form.querySelector('button[type=submit]');
if (btn) { btn.disabled = true; btn.textContent = '{{T "recovery.feloldas_folyamatban"}}'; }
fetch(form.action, {
method: 'POST',
body: new FormData(form),
credentials: 'same-origin',
redirect: 'follow'
}).then(function (resp) {
if (resp.status >= 500) { throw new Error('gateway'); }
return resp.text().then(function (html) {
document.open(); document.write(html); document.close();
});
}).catch(function () {
// A 5xx from the proxy, or no response at all: the machine is very likely restarting.
// NOTHING is claimed about the code — we do not know whether it was used.
if (btn) { btn.disabled = false; btn.textContent = '{{T "recovery.mentesek_feloldasa"}}'; }
box.textContent = '{{T "recovery.a_gep_eppen_ujraindul_ezert"}} '
+ '{{T "recovery.semmi_nem_valtozott_varj_nehany"}} '
+ '{{T "recovery.ugyhogy_tartsd_keznel"}}';
box.style.display = '';
});
});
})();
</script>
<p class="form-hint">
{{T "recovery.a_most_nem_csak_azt"}}
</p>
<!-- ── THE EXCEPTIONAL PATH. Deliberately not an equal third button. ───────────────────── -->
<hr style="margin:1.5rem 0;border:none;border-top:1px solid var(--border,#2a3142)">
{{if .ConfirmSetAside}}
{{/* §7.3 / §2.4 — THE COPY CHANGES WITH THE BEHAVIOUR (v0.206.0, R-241).
It used to promise "félretesszük — nem töröljük". After this change the set-aside history IS
deleted, on a date, together with the sealed package that protects it — which is what lets
the question end instead of returning at every login. A confirmation that still said "we do
not delete" would be the most consequential false sentence on the whole surface. */}}
<div class="alert alert-error">
<p><strong>{{T "recovery.biztosan_nem_kered_vissza_a"}}</strong></p>
<p>{{T "recovery.ha_megerosited"}}</p>
<ul>
<li>{{T "recovery.a_korabbi_menteseket_most_felretesszuk"}}</li>
<li>{{T "recovery.a_nap_alatt_meggondolhatod_magad"}}</li>
<li>{{T "recovery.a_pontos_datumot_a_tavoli"}}</li>
<li>{{T "recovery.a_gep_uj_ures_mentesi"}}</li>
<li>{{T "recovery.a_torles_utan_ez_a"}}</li>
</ul>
<p>{{T "recovery.ha_csak_most_nincs_keznel"}}</p>
</div>
<div class="form-actions">
<form method="POST" action="/backup/offbox/reset">
{{.CSRFField}}
<input type="hidden" name="confirm" value="1">
<button type="submit" class="btn btn-danger">{{T "recovery.igen_felretehetitek_a_korabbi_menteseket"}}</button>
</form>
<a href="/recovery" class="btn btn-outline">{{T "recovery.megsem"}}</a>
</div>
{{else}}
<p class="form-hint">
{{T "recovery.ha_a_helyreallitasi_kodod_veglegesen"}}
{{if .CanSetAside}}
{{T "recovery.nem_kerem_vissza_a_korabbi"}}
{{else}}
{{T "recovery.ez_a_lehetoseg_akkor_valik"}}
{{end}}
</p>
{{end}}
{{end}}
</div>
</body>
</html>
{{end}}