v0.249.0: i18n slice 1 release B — backup pages in English, Hungarian byte-identical (R-556)
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
This commit is contained in:
2026-09-17 18:01:09 +02:00
parent f8ebc47728
commit 11b790e314
24 changed files with 2921 additions and 433 deletions
@@ -2,7 +2,7 @@
{{template "layout_start" .}}
<div class="page-header">
<h2>Biztonsági mentés — Visszaállítás</h2>
<h2>{{T "backups_restore.biztonsagi_mentes_visszaallitas"}}</h2>
<span class="domain-badge">{{.Domain}}</span>
</div>
@@ -12,11 +12,11 @@
<!-- R-550: a restore the box was running when it stopped. Persisted, so it survives the restart that
interrupted it; shown until that app's next restore. -->
<div class="settings-card">
<h3>Megszakadt visszaállítás</h3>
<h3>{{T "backups_restore.megszakadt_visszaallitas"}}</h3>
{{range .}}
<div class="alert alert-error"><strong>{{.Stack}}</strong>: {{.Message}}</div>
{{end}}
<p class="form-hint">A doboz újraindult, miközben a visszaállítás futott, ezért nem tudjuk, hogy befejeződött-e. Indítsd el újra lent, ugyanabból a mentésből.</p>
<p class="form-hint">{{T "backups_restore.a_doboz_ujraindult_mikozben_a"}}</p>
</div>
{{end}}
{{template "restore_banner" .}}
@@ -28,51 +28,50 @@
<!-- Section 7: Restore -->
{{if .Backup.AppDataInfo}}
<div class="backup-section-card">
<h3>Visszaállítás</h3>
<h3>{{T "backups_restore.visszaallitas"}}</h3>
<div class="restore-section">
<div class="restore-form-row">
<label class="restore-label">Alkalmazás:</label>
<label class="restore-label">{{T "backups_restore.alkalmazas"}}</label>
<select id="restore-app" class="restore-select" onchange="onRestoreAppChange()">
<option value="">— Válasszon —</option>
<option value="">{{T "backups_restore.valasszon"}}</option>
{{range .Backup.AppDataInfo}}
<option value="{{.StackName}}" data-has-hdd="{{.HasHDDData}}" data-has-db="{{.HasDBDump}}" data-has-volumes="{{.HasVolumeData}}">{{.DisplayName}}</option>
{{end}}
{{/* R-487: removed apps whose unit was kept — the restore reinstalls them (R-253). */}}
{{if .RemovedApps}}
<optgroup label="Eltávolított alkalmazások (mentés megtartva)">
<optgroup label="{{T "backups_restore.eltavolitott_alkalmazasok_mentes_megtart"}}">
{{range .RemovedApps}}
<option value="{{.StackName}}" data-removed="true" data-has-hdd="false" data-has-db="false" data-has-volumes="true">{{.DisplayName}} — eltávolítva</option>
<option value="{{.StackName}}" data-removed="true" data-has-hdd="false" data-has-db="false" data-has-volumes="true">{{T "backups_restore.eltavolitva"}}</option>
{{end}}
</optgroup>
{{end}}
</select>
</div>
<div class="restore-form-row">
<label class="restore-label">Pillanatkép:</label>
<label class="restore-label">{{T "backups_restore.pillanatkep"}}</label>
<select id="restore-snapshot" class="restore-select" onchange="onRestoreConfirmChange()">
<option value="">— Válasszon alkalmazást —</option>
<option value="">{{T "backups_restore.valasszon_alkalmazast"}}</option>
</select>
</div>
<div id="restore-type-info" class="restore-info" style="display:none;margin-bottom:0.5rem">
</div>
<div id="restore-no-snapshots" class="restore-warning" style="display:none;">
Még nincs mentés felhasználói adattal.
{{T "backups_restore.meg_nincs_mentes_felhasznaloi_adattal"}}
</div>
<div class="restore-warning">
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> A visszaállítás felülírja az alkalmazás jelenlegi adatait a kiválasztott mentés állapotával.
Az alkalmazás a folyamat során automatikusan leáll és újraindul.
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "backups_restore.a_visszaallitas_felulirja_az_alkalmazas"}}
</div>
<div class="restore-confirm">
<label>
<input type="checkbox" id="restore-confirm-cb" onchange="onRestoreConfirmChange()">
Megértettem, visszaállítás indítása.
{{T "backups_restore.megertettem_visszaallitas_inditasa"}}
</label>
</div>
<div class="restore-actions">
<button type="button" class="btn btn-sm btn-danger" id="restore-btn" disabled onclick="submitRestore()">Visszaállítás indítása</button>
<button type="button" class="btn btn-sm btn-danger" id="restore-btn" disabled onclick="submitRestore()">{{T "backups_restore.visszaallitas_inditasa"}}</button>
</div>
<div style="margin-top: 1rem; text-align: center; border-top: 1px solid var(--line); padding-top: 1rem;">
<a href="/import" class="btn btn-sm btn-outline">Importálás mentett csomagból (.fab)</a>
<a href="/import" class="btn btn-sm btn-outline">{{T "backups_restore.importalas_mentett_csomagbol_fab"}}</a>
</div>
</div>
</div>
@@ -84,8 +83,8 @@
the wizard at /backups/restore/app. -->
{{if .OffboxConfigured}}
<div class="backup-section-card">
<h3>Visszaállítás a távoli tárolóból</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Válaszd ki az alkalmazást, és a következő oldalon döntsd el, mit szeretnél: ellenőrzést külön mappába, csak a hiányzó fájlok visszahozását, vagy teljes visszaállítást. Egyik sem indul el kérdés nélkül.</p>
<h3>{{T "backups_restore.visszaallitas_a_tavoli_tarolobol"}}</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">{{T "backups_restore.valaszd_ki_az_alkalmazast_es"}}</p>
<!-- R-237: driven by what is IN THE STORE, not by what is deployed and toggled. A rebuilt box has
neither and used to be told there was nothing to restore, while its snapshots sat in the
repository the whole time. Installed-ness is a property OF a row, never a filter on it. -->
@@ -97,24 +96,18 @@
says what is true and names a route, rather than promising a click that does not exist. -->
{{if .NoRestoreDestination}}
<p class="form-hint" style="border-left:2px solid var(--amber);padding-left:.75rem">
<strong>Előbb csatold vissza az adatmeghajtót.</strong> A mentéseid megvannak, és a meghajtók is
megvannak — újratelepítés után viszont a gép még nem ismeri őket, ezért most nincs hová
visszaállítani.
{{T "backups_restore.elobb_csatold_vissza_az_adatmeghajtot"}}
{{if .HasAttachDestination}}
Ez két kattintás: <a href="/storage" style="color:var(--blue)">Tárhely →
Meghajtók</a>, „Meglévő meghajtó csatolása". Utána gyere vissza ide.
{{T "backups_restore.ez_ket_kattintas_tarhely_meghajtok"}}
{{else}}
Csatolható meghajtót viszont most nem látunk ezen a gépen, ezért ezt innen nem tudod
elindítani. Ha a meghajtó be van kötve, de nincs csatlakoztatva, az üzemeltető tudja
csatlakoztatni — jelezd neki. A <a href="/storage" style="color:var(--blue)">Tárhely →
Meghajtók</a> oldalon látod, mit ismer most a gép.
{{T "backups_restore.csatolhato_meghajtot_viszont_most_nem"}}
{{end}}
</p>
{{end}}
{{if eq .OffsiteStoreState "unreadable"}}
<p class="form-hint">Nem tudjuk elolvasni a távoli tárolót, ezért <strong>nem tudjuk, mi van benne</strong>. Ez nem azt jelenti, hogy üres — próbáld újra később, vagy jelezd az üzemeltetőnek.</p>
<p class="form-hint">{{T "backups_restore.nem_tudjuk_elolvasni_a_tavoli"}}</p>
{{else if eq .OffsiteStoreState "no-target"}}
<p class="form-hint">A távoli tároló kapcsolódási adatai még nem érkeztek meg ehhez a géphez, ezért még nem tudjuk megmutatni, mi van benne. Ez magától rendeződik.</p>
<p class="form-hint">{{T "backups_restore.a_tavoli_tarolo_kapcsolodasi_adatai"}}</p>
{{end}}
{{if .OffsiteRestoreRows}}
<div class="app-row-list">
@@ -130,26 +123,26 @@
body, so quoting it would keep the contradiction on the page and would make the
test that forbids it unfailable.) -->
{{if and .InStore (not .Installed)}}
<span class="form-hint" style="margin-right:.5rem">Nincs telepítve — előbb <a href="/stacks/{{.App}}/deploy" style="color:var(--blue)">telepítsd újra</a>, utána hozhatod vissza az adatait.</span>
<span class="form-hint" style="margin-right:.5rem">{{T "backups_restore.nincs_telepitve_elobb_telepitsd_ujra"}}</span>
{{end}}
{{if .StoreUnknown}}
<span class="form-hint" style="margin-right:.5rem">Nem tudjuk, van-e mentése — a tárolót nem sikerült elolvasni.</span>
<span class="form-hint" style="margin-right:.5rem">{{T "backups_restore.nem_tudjuk_van_e_mentese"}}</span>
{{end}}
<!-- R-48: ONE entry per app. The five inline forms that used to live here — verify,
prepare, the revealed commit, the missing-only merge and the true reconstitution —
were separable only by layout, and two of them differed by whether the customer's
data comes back at all. They are now described intents inside the wizard. -->
<a href="/backups/restore/app?name={{.App}}" class="btn btn-xs btn-outline">Visszaállítás…</a>
<a href="/backups/restore/app?name={{.App}}" class="btn btn-xs btn-outline">{{T "backups_restore.visszaallitas_2"}}</a>
{{else}}
<!-- Shown, not hidden: "installed but nothing in the store" is an answer. Silently
dropping the row is what made R-220's empty list unreadable, one screen over. -->
<span class="form-hint">Nincs mentése a távoli tárolóban — nincs mit visszaállítani.</span>
<span class="form-hint">{{T "backups_restore.nincs_mentese_a_tavoli_taroloban"}}</span>
{{end}}
{{template "app_list_row_end"}}
{{end}}
</div>
{{else if eq .OffsiteStoreState "known"}}
<p class="form-hint">A távoli tároló üres — nincs mit visszaállítani.</p>
<p class="form-hint">{{T "backups_restore.a_tavoli_tarolo_ures_nincs"}}</p>
{{end}}
<!-- R-7b: the shares source. Not an app row — it has no per-app toggle and no recovery unit —
@@ -159,13 +152,13 @@
<div class="app-row-list" style="margin-top:1rem">
{{template "app_list_row" dict "Slug" "" "Name" .SharesDisplayName}}
<form method="POST" action="/backup/shares/restore" style="display:inline">{{$.CSRFField}}
<button type="submit" class="btn btn-xs btn-outline">Megosztások visszaállítása</button>
<button type="submit" class="btn btn-xs btn-outline">{{T "backups_restore.megosztasok_visszaallitasa"}}</button>
</form>
{{if .SharesScratchReady}}
<form method="POST" action="/backup/shares/place" style="display:inline">{{$.CSRFField}}
<button type="submit" class="btn btn-xs btn-outline">Helyreállítás az élő adatok közé (csak a hiányzó fájlok)</button>
<button type="submit" class="btn btn-xs btn-outline">{{T "backups_restore.helyreallitas_az_elo_adatok_koze"}}</button>
</form>
<span class="form-hint" style="display:block;margin-top:.25rem">A meglévő fájlokat nem írja felül. A már létező megosztás-beállítások változatlanok maradnak.</span>
<span class="form-hint" style="display:block;margin-top:.25rem">{{T "backups_restore.a_meglevo_fajlokat_nem_irja"}}</span>
{{end}}
{{template "app_list_row_end"}}
</div>
@@ -176,9 +169,9 @@
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>
<h3 style="margin-bottom:.5rem">{{T "backups_restore.meglevo_ellenorzo_masolatok"}}</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>
<p class="form-hint" style="margin-bottom:.75rem">{{T "backups_restore.ezek_a_visszaallitott_masolatok_helyet"}}</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)}}
@@ -188,13 +181,13 @@
<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>
onclick="confirmDeleteVerifyCopy(this)">{{T "backups_restore.masolat_torlese"}}</button>
</form>
{{template "app_list_row_end"}}
{{end}}
</div>
{{else}}
<p class="form-hint">Nincs ellenőrző másolat a meghajtón.</p>
<p class="form-hint">{{T "backups_restore.nincs_ellenorzo_masolat_a_meghajton"}}</p>
{{end}}
</div>
{{end}}
@@ -205,27 +198,27 @@
EXISTING export pipeline (same producer as a drive export → byte-identical bundle) staged
under the data dir, then streams through a guarded endpoint. Import stays drive-scan. -->
<div class="backup-section-card">
<h3>Hordozható mentéscsomag (.fab)</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Hordozható pillanatfelvétel — bárhol tárolhatod, és bármikor visszatöltheted egy meghajtóról. A folyamatos védelmet az 1–3. szintű mentés adja.</p>
<h3>{{T "backups_restore.hordozhato_mentescsomag_fab"}}</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">{{T "backups_restore.hordozhato_pillanatfelvetel_barhol_tarol"}}</p>
{{if .OffboxApps}}
<div class="form-row" style="max-width:420px"><label>Jelszavas titkosítás (opcionális)</label>
<input type="password" id="fab-dl-password" class="form-input" autocomplete="new-password" placeholder="Opcionális jelszó">
<span class="form-hint">Üresen hagyva a csomag titkosítás nélkül készül.</span>
<div class="form-row" style="max-width:420px"><label>{{T "backups_restore.jelszavas_titkositas_opcionalis"}}</label>
<input type="password" id="fab-dl-password" class="form-input" autocomplete="new-password" placeholder="{{T "backups_restore.opcionalis_jelszo"}}">
<span class="form-hint">{{T "backups_restore.uresen_hagyva_a_csomag_titkositas"}}</span>
</div>
<div class="app-row-list">
{{range .OffboxApps}}
{{template "app_list_row" dict "Slug" .Slug "Name" .DisplayName}}
<button type="button" class="btn btn-xs btn-outline fab-dl-btn" data-stack="{{.Name}}" onclick="fabDownload(this)">Letöltés (.fab)</button>
<button type="button" class="btn btn-xs btn-outline fab-dl-btn" data-stack="{{.Name}}" onclick="fabDownload(this)">{{T "backups_restore.letoltes_fab"}}</button>
{{template "app_list_row_end"}}
{{end}}
</div>
<div class="schedule-actions" style="margin-top:.75rem">
<button type="button" class="btn btn-xs btn-outline" id="fab-dl-all" onclick="fabDownloadAll()">Összes letöltése (egyenként)</button>
<span class="form-hint" style="margin-left:.5rem">A csomagok egyenként készülnek és töltődnek le — pillanatfelvétel a mostani állapotról.</span>
<button type="button" class="btn btn-xs btn-outline" id="fab-dl-all" onclick="fabDownloadAll()">{{T "backups_restore.osszes_letoltese_egyenkent"}}</button>
<span class="form-hint" style="margin-left:.5rem">{{T "backups_restore.a_csomagok_egyenkent_keszulnek_es"}}</span>
</div>
<div id="fab-dl-status" class="form-hint" style="margin-top:.5rem"></div>
{{else}}
<p class="form-hint">Nincs telepített alkalmazás.</p>
<p class="form-hint">{{T "backups_restore.nincs_telepitett_alkalmazas"}}</p>
{{end}}
</div>
@@ -245,13 +238,13 @@ function fabDownload(btn){
fetch('/api/export/download/estimate?stack=' + encodeURIComponent(stack))
.then(function(r){ return r.json(); })
.then(function(j){
if (!j.ok) { fabSetStatus('Hiba: ' + (j.error || 'a becslés sikertelen')); return; }
if (!j.ok) { fabSetStatus('{{T "backups_restore.hiba"}} ' + (j.error || '{{T "backups_restore.a_becsles_sikertelen"}}')); return; }
var size = (j.data && j.data.total_size_human) || '?';
felhomConfirm(btn, 'A csomag becsült mérete: ' + size + '. Elindítod a letöltést? (Pillanatfelvétel a mostani állapotról.)', function(){
felhomConfirm(btn, '{{T "backups_restore.a_csomag_becsult_merete"}} ' + size + '{{T "backups_restore.elinditod_a_letoltest_pillanatfelvetel_a"}}', function(){
fabStart(stack, null);
});
})
.catch(function(){ fabSetStatus('Hiba: a becslés nem érhető el.'); });
.catch(function(){ fabSetStatus('{{T "backups_restore.hiba_a_becsles_nem_erheto"}}'); });
}
// 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
@@ -259,8 +252,8 @@ function fabDownload(btn){
// 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(){
felhomConfirm(btn, '{{T "backups_restore.biztosan_torlod_ezt_az_ellenorzo"}} ' + path + ' {{T "backups_restore.a_tenyleges_adataid_valtozatlanok_maradn"}}', function(){
felhomConfirm(btn, '{{T "backups_restore.utolso_megerosites_a_masolat_veglegesen"}}', function(){
var f = btn.closest('form');
if (f) { if (f.requestSubmit) f.requestSubmit(); else f.submit(); }
});
@@ -270,21 +263,21 @@ 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(); })
.then(function(j){
if (!j.ok) { fabSetStatus('Hiba (' + stack + '): ' + (j.error || 'az export nem indult el')); if (next) next(); return; }
fabSetStatus('Csomag készítése: ' + stack + '…');
if (!j.ok) { fabSetStatus('{{T "backups_restore.hiba_2"}}' + stack + '): ' + (j.error || '{{T "backups_restore.az_export_nem_indult_el"}}')); if (next) next(); return; }
fabSetStatus('{{T "backups_restore.csomag_keszitese"}} ' + stack + '…');
fabPoll(stack, next);
})
.catch(function(){ fabSetStatus('Hiba: az export nem indult el.'); if (next) next(); });
.catch(function(){ fabSetStatus('{{T "backups_restore.hiba_az_export_nem_indult"}}'); if (next) next(); });
}
function fabPoll(stack, next){
var iv = setInterval(function(){
fetch('/api/export/status').then(function(r){ return r.json(); }).then(function(j){
if (!j || j.running || !j.done) return; // keep polling until the job reports done
clearInterval(iv);
if (j.error) { fabSetStatus('Hiba (' + stack + '): ' + j.error); if (next) next(); return; }
if (j.error) { fabSetStatus('{{T "backups_restore.hiba_2"}}' + stack + '): ' + j.error); if (next) next(); return; }
var base = (j.output_path || '').split('/').pop();
if (!base) { fabSetStatus('Hiba: a csomag útvonala hiányzik.'); if (next) next(); return; }
fabSetStatus('Letöltés: ' + base + (j.output_size ? ' (' + j.output_size + ')' : ''));
if (!base) { fabSetStatus('{{T "backups_restore.hiba_a_csomag_utvonala_hianyzik"}}'); if (next) next(); return; }
fabSetStatus('{{T "backups_restore.letoltes"}} ' + base + (j.output_size ? ' (' + j.output_size + ')' : ''));
window.location.href = '/api/export/download?file=' + encodeURIComponent(base);
if (next) setTimeout(next, 3000); // let the stream begin before the next export starts
}).catch(function(){});
@@ -297,19 +290,19 @@ function fabDownloadAll(){
}
function fabRunQueue(){
var stack = fabQueue.shift();
if (!stack) { fabSetStatus('Minden csomag elkészült.'); return; }
if (!stack) { fabSetStatus('{{T "backups_restore.minden_csomag_elkeszult"}}'); return; }
fabStart(stack, fabRunQueue);
}
// Restore section
var huDays = ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', 'szombat'];
var huDays = ['{{T "backups_restore.vasarnap"}}', '{{T "backups_restore.hetfo"}}', '{{T "backups_restore.kedd"}}', '{{T "backups_restore.szerda"}}', '{{T "backups_restore.csutortok"}}', '{{T "backups_restore.pentek"}}', '{{T "backups_restore.szombat"}}'];
function formatSnapshot(s) {
var t = new Date(s.time);
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
var label = t.getFullYear() + '-' + pad(t.getMonth()+1) + '-' + pad(t.getDate()) +
' ' + huDays[t.getDay()] + ' ' + pad(t.getHours()) + ':' + pad(t.getMinutes()) +
' (' + s.short_id + ')';
var tierLabel = s.tier === 2 ? '2. szint' : '1. szint';
var tierLabel = s.tier === 2 ? '{{T "backups_restore.2_szint"}}' : '{{T "backups_restore.1_szint"}}';
if (s.drive_label) {
label += ' — ' + tierLabel + ', ' + s.drive_label;
} else {
@@ -331,7 +324,7 @@ function onRestoreAppChange() {
typeInfo.style.display = 'none';
if (!appName) {
snapSel.innerHTML = '<option value="">— Válasszon alkalmazást —</option>';
snapSel.innerHTML = '<option value="">{{T "backups_restore.valasszon_alkalmazast"}}</option>';
return;
}
@@ -342,23 +335,23 @@ function onRestoreAppChange() {
var hasVolumes = opt.getAttribute('data-has-volumes') === 'true';
if (hasHDD || hasVolumes) {
typeInfo.innerHTML = 'Teljes visszaállítás: adatbázis + konfiguráció + felhasználói adatok a kiválasztott pillanatképből.';
typeInfo.innerHTML = '{{T "backups_restore.teljes_visszaallitas_adatbazis_konfigura"}}';
typeInfo.className = 'restore-info';
} else if (hasDB) {
typeInfo.innerHTML = 'Adatbázis és konfiguráció visszaállítása — az alkalmazásnak nincs külön felhasználói adata.';
typeInfo.innerHTML = '{{T "backups_restore.adatbazis_es_konfiguracio_visszaallitasa"}}';
typeInfo.className = 'restore-info restore-info-partial';
} else {
typeInfo.innerHTML = 'Csak konfiguráció visszaállítása (compose fájlok, beállítások).';
typeInfo.innerHTML = '{{T "backups_restore.csak_konfiguracio_visszaallitasa_compose"}}';
typeInfo.className = 'restore-info restore-info-partial';
}
typeInfo.style.display = 'block';
snapSel.innerHTML = '<option value="">— Betöltés... —</option>';
snapSel.innerHTML = '<option value="">{{T "backups_restore.betoltes"}}</option>';
fetch('/api/backup/snapshots?stack=' + encodeURIComponent(appName))
.then(function(r) { return r.json(); })
.then(function(data) {
snapSel.innerHTML = '<option value="">— Válasszon —</option>';
snapSel.innerHTML = '<option value="">{{T "backups_restore.valasszon"}}</option>';
if (data.ok && data.data && data.data.length > 0) {
// Group by tier
var tier1 = data.data.filter(function(s) { return s.tier !== 2; });
@@ -366,7 +359,7 @@ function onRestoreAppChange() {
if (tier1.length > 0) {
var grp1 = document.createElement('optgroup');
grp1.label = '1. szint — Helyi mentés (ajánlott)';
grp1.label = '{{T "backups_restore.1_szint_helyi_mentes_ajanlott"}}';
tier1.forEach(function(s) {
var o = document.createElement('option');
o.value = s.short_id;
@@ -377,7 +370,7 @@ function onRestoreAppChange() {
}
if (tier2.length > 0) {
var grp2 = document.createElement('optgroup');
grp2.label = '2. szint — Másodlagos másolat';
grp2.label = '{{T "backups_restore.2_szint_masodlagos_masolat"}}';
tier2.forEach(function(s) {
var o = document.createElement('option');
o.value = s.short_id;
@@ -387,7 +380,7 @@ function onRestoreAppChange() {
snapSel.appendChild(grp2);
}
} else {
snapSel.innerHTML = '<option value="">— Nincs elérhető mentés —</option>';
snapSel.innerHTML = '<option value="">{{T "backups_restore.nincs_elerheto_mentes"}}</option>';
noSnaps.style.display = 'block';
}
});
@@ -407,7 +400,7 @@ function submitRestore() {
var btn = document.getElementById('restore-btn');
btn.disabled = true;
btn.textContent = 'Visszaállítás folyamatban...';
btn.textContent = '{{T "backups_restore.visszaallitas_folyamatban"}}';
var form = document.createElement('form');
form.method = 'POST';