11b790e314
gates / gates (push) Successful in 20s
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
195 lines
9.3 KiB
HTML
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}}
|