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
@@ -120,6 +120,7 @@ func (s *Server) escrowWizardPageHandler(w http.ResponseWriter, r *http.Request)
}
data := map[string]interface{}{
"Title": "Helyreállítási kód",
"TitleKey": "page.title.escrow", // i18n: the Hungarian title above is what hu renders
"CustomerName": s.cfg.Customer.Name,
"Domain": s.cfg.Customer.Domain,
"ActivePage": "backups-remote",
+3
View File
@@ -993,6 +993,7 @@ func offboxCeremonyWaitState(t *settings.OffboxTarget) (awaiting, timedOut bool)
// participation toggles and the manual-target form.
func (s *Server) backupsRemoteHandler(w http.ResponseWriter, r *http.Request) {
data := s.backupsCommonData("backups-remote", "Biztonsági mentés — Távoli mentés", r)
data["TitleKey"] = "page.title.backups_remote" // i18n: the Hungarian title above is what hu renders
s.backupsOffboxData(data)
// Escrow ceremony card states (v0.127.0): the Scenario-F stale flag + the agent version gate.
data["EscrowStale"] = s.escrowStale()
@@ -1040,6 +1041,7 @@ func (s *Server) backupsRemoteHandler(w http.ResponseWriter, r *http.Request) {
// 1./2./3. tier rows.
func (s *Server) backupsAppsHandler(w http.ResponseWriter, r *http.Request) {
data := s.backupsCommonData("backups-apps", "Biztonsági mentés — Alkalmazások", r)
data["TitleKey"] = "page.title.backups_apps" // i18n: the Hungarian title above is what hu renders
s.backupsOffboxData(data) // the tier-3 rows render $.Offbox status
if fullStatus, ok := data["Backup"].(*backup.FullBackupStatus); ok && fullStatus != nil {
@@ -1072,6 +1074,7 @@ func (s *Server) backupsAppsHandler(w http.ResponseWriter, r *http.Request) {
// restore-to-verify list and the .fab export/import loop.
func (s *Server) backupsRestoreHandler(w http.ResponseWriter, r *http.Request) {
data := s.backupsCommonData("backups-restore", "Biztonsági mentés — Visszaállítás", r)
data["TitleKey"] = "page.title.backups_restore" // i18n: the Hungarian title above is what hu renders
// R-550: restores the box was running when it stopped — shown until that app is restored again.
if s.backupMgr != nil {
data["InterruptedRestores"] = s.backupMgr.InterruptedRestores()
@@ -123,6 +123,16 @@ func i18nCasesB() []i18nCase {
d["Backup"].(m)["DiscoveredDBs"] = []m{}
return d
}},
{"backups_apps_stale_copy", "backups_apps", func() map[string]interface{} {
d := i18nAppsData("ok", false, true, "dumps")
rows := d["AppBackupRows"].([]m)
for _, r := range rows {
if r["StackName"] == "oksucc" {
r["Tier2StaleCopy"] = true
}
}
return d
}},
{"backups_apps_empty", "backups_apps", func() map[string]interface{} { return i18nLayoutData("backups-apps", "Alkalmazás mentések") }},
{"backups_remote_full", "backups_remote", func() map[string]interface{} {
return i18nRemoteData(func(d, o m) {
@@ -3,6 +3,8 @@ package web
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"net/http"
"net/http/httptest"
"net/url"
@@ -263,3 +265,68 @@ func huTemplateSource(name string) ([]byte, error) {
out, _ := b.Expand(i18n.Default, string(raw))
return []byte(out), nil
}
// ── Slice 1: pages rendered OUTSIDE executeTemplate follow the language (executeTemplateLang) ───────
//
// i18nDirectPages lists them with a parity case and a phrase that must appear in English. Release B adds
// recovery; release C adds login, claim, the two guest share pages and the catch-all.
var i18nDirectPages = []struct{ tmpl, caseName, enProbe string }{
{"recovery", "recovery_locked_can", "Unlock backups"},
}
// TestI18nDirectRenderPagesFollowLanguage — with the household language saved as English the page is
// English (`<html lang="en">` and a known English phrase); saved as Hungarian it is byte-identical to the
// fixture captured from the unconverted template.
func TestI18nDirectRenderPagesFollowLanguage(t *testing.T) {
cases := map[string]i18nCase{}
for _, c := range i18nCases() {
cases[c.name] = c
}
for _, p := range i18nDirectPages {
c, ok := cases[p.caseName]
if !ok {
t.Fatalf("%s: no parity case %q", p.tmpl, p.caseName)
}
for _, lang := range []string{"hu", "en"} {
s := i18nTestServer(t)
if err := s.settings.SetLanguage(lang); err != nil {
t.Fatal(err)
}
var buf strings.Builder
if err := s.executeTemplateLang(&buf, httptest.NewRequest(http.MethodGet, "/x", nil), p.tmpl, c.data()); err != nil {
t.Fatalf("%s [%s]: %v", p.tmpl, lang, err)
}
got := relativeAgeRe.ReplaceAllString(buf.String(), "# $1")
if lang == "hu" {
want, err := os.ReadFile(filepath.Join("testdata", "i18n_parity", p.caseName+".html"))
if err != nil {
t.Fatal(err)
}
if got != string(want) {
t.Errorf("%s: Hungarian render through executeTemplateLang differs from fixture %s", p.tmpl, p.caseName)
}
continue
}
if !strings.Contains(got, `<html lang="en"`) || !strings.Contains(got, p.enProbe) {
t.Errorf("%s: saved English but the page is not English (lang=en: %v, %q: %v)", p.tmpl,
strings.Contains(got, `<html lang="en"`), p.enProbe, strings.Contains(got, p.enProbe))
}
}
}
}
// TestRecoveryHandlerFollowsLanguage — the REAL render path (renderRecovery), not the helper: saved
// English reaches the recovery page. RED-PROOF: put `s.tmpl.ExecuteTemplate` back in renderRecoveryState.
func TestRecoveryHandlerFollowsLanguage(t *testing.T) {
s := newDashboardServer(t, time.Time{})
if err := s.settings.SetLanguage("en"); err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
s.renderRecovery(rec, httptest.NewRequest(http.MethodGet, "/recovery", nil), "", "", nil)
body := rec.Body.String()
if rec.Code != 200 || !strings.Contains(body, `<html lang="en"`) || !strings.Contains(body, "Unlock backups") {
t.Errorf("recovery page with English saved: code %d, lang=en %v, English phrase %v", rec.Code,
strings.Contains(body, `<html lang="en"`), strings.Contains(body, "Unlock backups"))
}
}
+1 -1
View File
@@ -183,7 +183,7 @@ func (s *Server) renderRecoveryState(w http.ResponseWriter, r *http.Request, err
data["InvUnavailable"] = true
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := s.tmpl.ExecuteTemplate(w, "recovery", data); err != nil {
if err := s.executeTemplateLang(w, r, "recovery", data); err != nil { // slice 1: the household's language
s.logger.Printf("[ERROR] [web] Template error (recovery): %v", err)
http.Error(w, "Internal error", http.StatusInternalServerError)
}
+14
View File
@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"html/template"
"io"
"io/fs"
"log"
"net/http"
@@ -869,6 +870,19 @@ func (s *Server) executeTemplate(w http.ResponseWriter, r *http.Request, name st
buf.WriteTo(w)
}
// executeTemplateLang renders a page that stands OUTSIDE the dashboard chrome — sign-in, claim,
// recovery, the guest share pages, the catch-all — in the request's language (slice 1, R-556).
// Deliberately NOT executeTemplate: that one injects the session CSRF fields and the escrow reminder bar
// (R-543), which a guest or a signed-out visitor must never receive. It only picks the language set and
// tells the page which language it is in. The caller keeps its own headers and error handling.
func (s *Server) executeTemplateLang(w io.Writer, r *http.Request, name string, data map[string]interface{}) error {
lang := s.langFor(r)
if data != nil {
data["Lang"] = lang
}
return s.templatesFor(lang).ExecuteTemplate(w, name, data)
}
// --- Static file / asset serving ---
func (s *Server) serveCSSHandler(w http.ResponseWriter, r *http.Request) {
@@ -2,7 +2,7 @@
{{template "layout_start" .}}
<div class="page-header">
<h2>Biztonsági mentés — Alkalmazások</h2>
<h2>{{T "backups_apps.biztonsagi_mentes_alkalmazasok"}}</h2>
<span class="domain-badge">{{.Domain}}</span>
</div>
@@ -13,54 +13,54 @@
{{template "backups_empty" .}}
{{else}}
<h3 class="backup-tier-divider">Alkalmazás-mentések (adatbázis + konfiguráció)</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az egyes alkalmazások részletes, granulált mentése — adatbázis-kiírások, beállítások és alkalmazás-fájlok. A fenti teljes mentéstől függetlenül, alkalmazásonként visszaállítható.</p>
<h3 class="backup-tier-divider">{{T "backups_apps.alkalmazas_mentesek_adatbazis_konfigurac"}}</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">{{T "backups_apps.az_egyes_alkalmazasok_reszletes_granulal"}}</p>
<!-- Section 2: Schedule -->
<div class="schedule-card">
<h3>Ütemezés</h3>
<h3>{{T "backups_apps.utemezes"}}</h3>
<div class="schedule-rows">
<div class="schedule-row">
<span class="schedule-task">Adatbázis mentés</span>
<span class="schedule-task">{{T "backups_apps.adatbazis_mentes"}}</span>
<span class="schedule-time">{{.Backup.DBDumpSchedule}}</span>
<span class="schedule-next">Következő: {{nextRunLabel .Backup.NextDBDump}}</span>
<span class="schedule-next">{{T "backups_apps.kovetkezo"}}</span>
</div>
</div>
<div class="schedule-summary">
{{if .Backup.LastDBDump}}
<div class="schedule-summary-row">
<span>Utolsó adatbázis mentés:</span>
<span>{{T "backups_apps.utolso_adatbazis_mentes"}}</span>
<span class="schedule-summary-value">{{fmtTime .Backup.LastDBDump.LastRun}} ({{timeAgo .Backup.LastDBDump.LastRun}})</span>
</div>
{{else}}
<div class="schedule-summary-row">
<span>Utolsó adatbázis mentés:</span>
<span class="schedule-summary-value relative-time">Még nem futott</span>
<span>{{T "backups_apps.utolso_adatbazis_mentes"}}</span>
<span class="schedule-summary-value relative-time">{{T "backups_apps.meg_nem_futott"}}</span>
</div>
{{end}}
</div>
<div class="schedule-actions">
<button class="btn btn-sm btn-primary" onclick="triggerBackupFromPage()" id="backup-page-btn"
{{if .Backup.Running}}disabled{{end}}>
{{if .Backup.Running}}Mentés folyamatban...{{else}}Mentés most{{end}}
{{if .Backup.Running}}{{T "backups_apps.mentes_folyamatban"}}{{else}}{{T "backups_apps.mentes_most"}}{{end}}
</button>
</div>
</div>
<!-- Section 3: Databases -->
<div class="backup-section-card">
<h3>Adatbázisok</h3>
<h3>{{T "backups_apps.adatbazisok"}}</h3>
{{if or .Backup.DumpFiles .Backup.DiscoveredDBs}}
<div class="backup-table-wrap">
<table class="db-table">
<thead>
<tr>
<th>Alkalmazás</th>
<th>Típus</th>
<th>Méret</th>
<th>Utolsó</th>
<th>Érvényesítés</th>
<th>Állapot</th>
<th>{{T "backups_apps.alkalmazas"}}</th>
<th>{{T "backups_apps.tipus"}}</th>
<th>{{T "backups_apps.meret"}}</th>
<th>{{T "backups_apps.utolso"}}</th>
<th>{{T "backups_apps.ervenyesites"}}</th>
<th>{{T "backups_apps.allapot"}}</th>
</tr>
</thead>
<tbody>
@@ -75,16 +75,16 @@
{{if .Error}}
<span class="validation-badge validation-na">–</span>
{{else if .Validation.Valid}}
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
<span class="validation-badge validation-ok">{{T "backups_apps.tabla"}}</span>
{{else if .Validation.Error}}
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">{{T "backups_apps.hiba"}}</span>
{{else}}
<span class="validation-badge validation-na" title="Az érvényesítés nem futott le">–</span>
<span class="validation-badge validation-na" title="{{T "backups_apps.az_ervenyesites_nem_futott_le"}}">–</span>
{{end}}
</td>
<td>
{{if .Error}}
<span class="validation-badge validation-fail" title="{{.Error}}">Hiba</span>
<span class="validation-badge validation-fail" title="{{.Error}}">{{T "backups_apps.hiba"}}</span>
{{else}}
<span class="validation-badge validation-ok">OK</span>
{{end}}
@@ -100,9 +100,9 @@
<td class="mono">{{fmtTimeShort .ModTime}}</td>
<td>
{{if .Validation.Valid}}
<span class="validation-badge validation-ok">{{.Validation.TableCount}} tábla</span>
<span class="validation-badge validation-ok">{{T "backups_apps.tabla"}}</span>
{{else if .Validation.Error}}
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">Hiba</span>
<span class="validation-badge validation-fail" title="{{.Validation.Error}}">{{T "backups_apps.hiba"}}</span>
{{else}}
<span class="validation-badge validation-na">–</span>
{{end}}
@@ -115,23 +115,20 @@
</table>
</div>
{{else if eq .DBSectionState "pending"}}
<div class="backup-table-empty">Még nem készült adatbázis-mentés — az első ütemezett mentés éjjel fut le.</div>
<div class="backup-table-empty">{{T "backups_apps.meg_nem_keszult_adatbazis_mentes"}}</div>
{{else}}
<div class="backup-table-empty">A telepített alkalmazások beágyazott adatbázist használnak (pl. SQLite) — adatbázisuk a fájl- és kötetmentés része, külön adatbázis-kiírás nem szükséges.</div>
<div class="backup-table-empty">{{T "backups_apps.a_telepitett_alkalmazasok_beagyazott_ada"}}</div>
{{end}}
</div>
<!-- Section 4: Unified per-app backup status -->
{{if .Backup.AppDataInfo}}
<div class="backup-section-card">
<h3>Alkalmazások mentési állapota</h3>
<h3>{{T "backups_apps.alkalmazasok_mentesi_allapota"}}</h3>
{{if .NoUserDataBackupWarning}}
<div class="alert alert-error" style="margin-bottom:1.5rem">
<strong>Felhasználói adatokról nincs biztonsági mentés.</strong><br>
A szerveren tárolt fotók, dokumentumok és egyéb fájlok jelenleg csak egy példányban léteznek.
Külső meghajtó csatlakoztatásával biztonsági másolat készíthető a 3-2-1 szabály szerint.
<a href="/settings" style="color:inherit;text-decoration:underline">Meghajtó beállítása →</a>
{{T "backups_apps.felhasznaloi_adatokrol_nincs_biztonsagi"}}
</div>
{{end}}
@@ -148,17 +145,17 @@
<span class="app-backup-row-name">{{.DisplayName}}</span>
<div class="app-backup-row-meta">
{{if .Removed}}
<span class="tag tag-warn">Eltávolítva — visszaállítható</span>
<span class="tag tag-warn">{{T "backups_apps.eltavolitva_visszaallithato"}}</span>
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
{{else if .DriveDisconnected}}
<span class="tag tag-warn">Meghajtó leválasztva</span>
<span class="tag tag-warn">{{T "backups_apps.meghajto_levalasztva"}}</span>
{{else if .HasHDDData}}
{{if .StorageLabel}}<span class="meta-badge meta-badge-storage">{{.StorageLabel}}</span>{{end}}
<span class="mono app-backup-size" style="font-size:.8rem">{{.HDDSizeHuman}}</span>
{{else if .HasVolumeData}}
<span class="meta-badge">Konfig{{if .HasDB}} + DB{{end}} + Adatok</span>
<span class="meta-badge">{{T "backups_apps.konfig"}}{{if .HasDB}} + DB{{end}} {{T "backups_apps.adatok"}}</span>
{{else}}
<span class="meta-badge">Konfig{{if .HasDB}} + DB{{end}}</span>
<span class="meta-badge">{{T "backups_apps.konfig"}}{{if .HasDB}} + DB{{end}}</span>
{{end}}
<span class="status-dot status-{{.Status}}" title="{{.StatusText}}"></span>
</div>
@@ -171,15 +168,15 @@
from the unit (R-253). No tier rows: nothing is scheduled for an app that is not here. */}}
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">Mentés</span>
<span class="layer-badge">Megtartva</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Az alkalmazást eltávolítottad, a mentése megmaradt. Legutóbbi mentés: {{fmtTimeStr .RemovedUnitTime}}.</span>
<span class="tier-label">{{T "backups_apps.mentes"}}</span>
<span class="layer-badge">{{T "backups_apps.megtartva"}}</span>
<span class="tier-location">{{T "backups_apps.helyi"}}</span>
<span class="tier-contents">{{T "backups_apps.az_alkalmazast_eltavolitottad_a_mentese"}}</span>
<div class="layer-actions">
<form method="POST" action="/backup/restore" style="display:inline">{{$.CSRFField}}
<input type="hidden" name="stack_name" value="{{.StackName}}">
<input type="hidden" name="snapshot_id" value="helyi">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Újratelepíti a(z) {{.DisplayName}} alkalmazást a megtartott mentésből ({{fmtTimeStr .RemovedUnitTime}})? Az adatok a mentésből állnak vissza.">Visszaállítás a mentésből</button>
<button type="submit" class="btn btn-xs btn-outline" data-confirm="{{T "backups_apps.ujratelepiti_a_z_alkalmazast_a"}}">{{T "backups_apps.visszaallitas_a_mentesbol"}}</button>
</form>
</div>
</div>
@@ -188,82 +185,82 @@
<div class="backup-layers">
<!-- Tier 1: Nightly backup (mandatory, same drive) -->
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="tier-label">{{T "backups_apps.1_mentes"}}</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-location">{{T "backups_apps.helyi"}}</span>
{{if .Tier1LastRun}}
{{/* R-258: three states. The tick is THIS app's own most recent dump result,
not the box's most recent run — and a third value renders NO icon, which is
where "we have no result for this backup" belongs. A tick standing for
"a file exists" is what this replaced. */}}
<span class="layer-last">Utolsó: {{timeAgoStr .Tier1LastRun}}
<span class="layer-last">{{T "backups_apps.utolso_2"}}
{{if eq .Tier1LastStatus "ok"}}<span class="text-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg></span>
{{else if eq .Tier1LastStatus "error"}}<span class="text-error"><svg class="ico ico-sm"><use href="#i-x"/></svg></span>
{{else}}<span class="state-text-neutral" title="Erről a mentésről nincs eredményünk.">—</span>{{end}}
{{else}}<span class="state-text-neutral" title="{{T "backups_apps.errol_a_mentesrol_nincs_eredmenyunk"}}">—</span>{{end}}
</span>
{{end}}
<span class="tier-contents">{{.Tier1Contents}}</span>
{{if .DriveFilesNote}}<span class="state-text-neutral" style="font-size:.8rem">{{.DriveFilesNote}}{{if .DriveFilesNoteLink}} <a href="{{.DriveFilesNoteLink}}">{{.DriveFilesNoteLinkText}} &rarr;</a>{{end}}</span>{{end}}
{{if and .HasDB (eq .Tier1DBStatus "error")}}
<span class="text-error" style="font-size:.8rem"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> DB dump hiba</span>
<span class="text-error" style="font-size:.8rem"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "backups_apps.db_dump_hiba"}}</span>
{{end}}
</div>
<!-- Tier 2: Cross-drive backup (opt-in, different device) -->
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="tier-label">{{T "backups_apps.2_mentes"}}</span>
{{if .Tier2UserDisabled}}
<span class="layer-unconfigured">2. mentés kikapcsolva</span>
<span class="layer-unconfigured">{{T "backups_apps.2_mentes_kikapcsolva"}}</span>
<div class="layer-actions">
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
{{else if and .Tier2Configured .Tier2DestDisconnected}}
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ {{.Tier2Dest}}</span>
<span class="tag tag-warn">Cél meghajtó leválasztva</span>
<span class="tag tag-warn">{{T "backups_apps.cel_meghajto_levalasztva"}}</span>
{{if not .Tier2SuccessTracked}}{{if .Tier2LastRun}}
<span class="layer-last" style="opacity:.6">Utolsó: {{timeAgoStr .Tier2LastRun}}</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.utolso_3"}}</span>
{{end}}{{else if .Tier2LastSuccess}}
<span class="layer-last" style="opacity:.6">Utolsó sikeres: {{timeAgoStr .Tier2LastSuccess}}</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.utolso_sikeres"}}</span>
{{else}}
<span class="layer-last" style="opacity:.6">Még nincs sikeres másolat</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.meg_nincs_sikeres_masolat"}}</span>
{{end}}
<span class="tier-contents" style="opacity:.6">{{.Tier23Contents}}</span>
<div class="layer-actions">
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
{{else if and .Tier2Configured .Tier2DestInactive}}
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ {{.Tier2Dest}}</span>
<span class="tag tag-warn">Cél meghajtó inaktív</span>
<span class="tag tag-warn">{{T "backups_apps.cel_meghajto_inaktiv"}}</span>
{{if not .Tier2SuccessTracked}}{{if .Tier2LastRun}}
<span class="layer-last" style="opacity:.6">Utolsó: {{timeAgoStr .Tier2LastRun}}</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.utolso_3"}}</span>
{{end}}{{else if .Tier2LastSuccess}}
<span class="layer-last" style="opacity:.6">Utolsó sikeres: {{timeAgoStr .Tier2LastSuccess}}</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.utolso_sikeres"}}</span>
{{else}}
<span class="layer-last" style="opacity:.6">Még nincs sikeres másolat</span>
<span class="layer-last" style="opacity:.6">{{T "backups_apps.meg_nincs_sikeres_masolat"}}</span>
{{end}}
<span class="tier-contents" style="opacity:.6">{{.Tier23Contents}}</span>
<div class="layer-actions">
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
{{else if .Tier2Configured}}
<span class="layer-method">rsync</span>
<span class="layer-dest">→ {{.Tier2Dest}}</span>
<span class="layer-schedule">{{.Tier2Schedule}}</span>
{{if not .Tier2SuccessTracked}}{{if .Tier2LastRun}}
<span class="layer-last">Utolsó: {{timeAgoStr .Tier2LastRun}}
<span class="layer-last">{{T "backups_apps.utolso_3"}}
<span class="{{if eq .Tier2LastStatus "ok"}}text-ok{{else if eq .Tier2LastStatus "error"}}text-error{{else if eq .Tier2LastStatus "running"}}text-muted{{end}}">
{{.Tier2StatusBadge}}
</span>
</span>
{{end}}{{else if .Tier2LastSuccess}}
<span class="layer-last">Utolsó sikeres: {{timeAgoStr .Tier2LastSuccess}}
<span class="layer-last">{{T "backups_apps.utolso_sikeres"}}
<span class="{{if eq .Tier2LastStatus "ok"}}text-ok{{else if eq .Tier2LastStatus "error"}}text-error{{else if eq .Tier2LastStatus "running"}}text-muted{{end}}">
{{.Tier2StatusBadge}}
</span>
</span>
{{else}}
<span class="layer-last">Még nincs sikeres másolat
<span class="layer-last">{{T "backups_apps.meg_nincs_sikeres_masolat"}}
<span class="{{if eq .Tier2LastStatus "error"}}text-error{{else}}text-muted{{end}}">{{.Tier2StatusBadge}}</span>
</span>
{{end}}
@@ -274,20 +271,20 @@
this one is about the PACKAGE, and after a preserved leg they differ. */}}
{{if .Tier2UnitStaleNotice}}<span class="layer-reason" style="color:var(--warn);opacity:.9">{{.Tier2UnitStaleNotice}}</span>{{end}}
<span class="tier-contents">{{.Tier23Contents}}</span>
<span class="tier-browsable" title="A mentés böngészhető fájlrendszerben"><svg class="ico ico-sm"><use href="#i-file-text"/></svg></span>
<span class="tier-browsable" title="{{T "backups_apps.a_mentes_bongeszheto_fajlrendszerben"}}"><svg class="ico ico-sm"><use href="#i-file-text"/></svg></span>
<div class="layer-actions">
{{if not .Tier2SuccessTracked}}{{if .Tier2LastRun}}
<form method="POST" action="/backup/tier2/restore" style="display:inline">{{$.CSRFField}}
<input type="hidden" name="stack_name" value="{{.StackName}}">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: {{fmtTimeStr .Tier2LastRun}}">Fájlok visszaállítása</button>
<button type="submit" class="btn btn-xs btn-outline" data-confirm="{{T "backups_apps.visszaallitja_a_hianyzo_fajlokat_a"}}">{{T "backups_apps.fajlok_visszaallitasa"}}</button>
</form>
{{end}}{{else if .Tier2LastSuccess}}
<form method="POST" action="/backup/tier2/restore" style="display:inline">{{$.CSRFField}}
<input type="hidden" name="stack_name" value="{{.StackName}}">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi sikeres másolat: {{fmtTimeStr .Tier2LastSuccess}}.{{if .Tier2StaleCopy}} Figyelem: a legutóbbi mentési kísérlet nem sikerült, ezért a visszaállított fájlok ennél régebbiek lehetnek.{{end}}">Fájlok visszaállítása</button>
<button type="submit" class="btn btn-xs btn-outline" data-confirm="{{T "backups_apps.visszaallitja_legutobbi_sikeres_masolat"}}{{if .Tier2StaleCopy}}{{T "backups_apps.figyelem_legutobbi_kiserlet_nem_sikerult"}}{{end}}">{{T "backups_apps.fajlok_visszaallitasa"}}</button>
</form>
{{else}}
<span class="layer-reason" style="opacity:.85">Még nincs sikeres másolat, amiből vissza lehetne állítani.</span>
<span class="layer-reason" style="opacity:.85">{{T "backups_apps.meg_nincs_sikeres_masolat_amibol"}}</span>
{{end}}
{{/* R-102/R-103: the DESTRUCTIVE twin of the button above. Offered only when the
copy holds an openable recovery unit (Tier2UnitRestorable — a second
@@ -297,54 +294,54 @@
{{if .Tier2UnitRestorable}}
<form method="POST" action="/backup/tier2/unit-restore" style="display:inline">{{$.CSRFField}}
<input type="hidden" name="stack_name" value="{{.StackName}}">
<button type="submit" class="btn btn-xs btn-danger-outline" data-confirm="{{.Tier2UnitConfirm}}">Teljes visszaállítás a másolatból</button>
<button type="submit" class="btn btn-xs btn-danger-outline" data-confirm="{{.Tier2UnitConfirm}}">{{T "backups_apps.teljes_visszaallitas_a_masolatbol"}}</button>
</form>
{{end}}
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
{{else}}
<span class="layer-auto-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg> 1. mentés auto</span>
<span class="layer-unconfigured"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Nincs 2. (off-drive) másolat</span>
<span class="layer-auto-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg> {{T "backups_apps.1_mentes_auto"}}</span>
<span class="layer-unconfigured"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "backups_apps.nincs_2_off_drive_masolat"}}</span>
{{if .Tier2LastError}}
<span class="layer-reason" style="opacity:.85" title="A 2. mentés automatikus — külön beállítás nem kell">{{.Tier2LastError}}</span>
<span class="layer-reason" style="opacity:.85" title="{{T "backups_apps.a_2_mentes_automatikus_kulon"}}">{{.Tier2LastError}}</span>
{{end}}
<div class="layer-actions">
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
{{end}}
</div>
<!-- Tier 3: Off-box (NAS) backup — real state (see tier3State). -->
{{if eq .Tier3State "active"}}
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge {{if eq $.Offbox.LastStatus "ok"}}text-ok{{else if eq $.Offbox.LastStatus "error"}}text-error{{else if eq $.Offbox.LastStatus "running"}}text-muted{{end}}">{{if eq $.Offbox.LastStatus "ok"}}Sikeres{{else if eq $.Offbox.LastStatus "error"}}Hiba{{else if eq $.Offbox.LastStatus "running"}}Fut...{{else}}—{{end}}</span>
<span class="tier-label">{{T "backups_apps.3_mentes"}}</span>
<span class="layer-badge {{if eq $.Offbox.LastStatus "ok"}}text-ok{{else if eq $.Offbox.LastStatus "error"}}text-error{{else if eq $.Offbox.LastStatus "running"}}text-muted{{end}}">{{if eq $.Offbox.LastStatus "ok"}}{{T "backups_apps.sikeres"}}{{else if eq $.Offbox.LastStatus "error"}}{{T "backups_apps.hiba"}}{{else if eq $.Offbox.LastStatus "running"}}{{T "backups_apps.fut"}}{{else}}—{{end}}</span>
<span class="tier-location">restic → {{$.Offbox.Host}}</span>
{{if $.Offbox.LastRun}}<span class="layer-last">Utolsó: {{timeAgoStr $.Offbox.LastRun}}</span>{{end}}
<span class="tier-contents">Helyreállítási egység, titkosítva</span>
{{if $.Offbox.LastRun}}<span class="layer-last">{{T "backups_apps.utolso_4"}}</span>{{end}}
<span class="tier-contents">{{T "backups_apps.helyreallitasi_egyseg_titkositva"}}</span>
</div>
{{else if eq .Tier3State "escrow_pending"}}
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge text-error">Kulcsletétre vár</span>
<span class="tier-contents">A távoli mentés a titkosítási kulcs letétbe helyezéséig szünetel</span>
<span class="tier-label">{{T "backups_apps.3_mentes"}}</span>
<span class="layer-badge text-error">{{T "backups_apps.kulcsletetre_var"}}</span>
<span class="tier-contents">{{T "backups_apps.a_tavoli_mentes_a_titkositasi"}}</span>
</div>
{{else if eq .Tier3State "off"}}
<div class="backup-layer-row" style="opacity:.5">
<span class="tier-label">3. mentés</span>
<span class="layer-badge">Kikapcsolva</span>
<span class="tier-contents">Ez az alkalmazás nincs kijelölve távoli mentésre</span>
<span class="tier-label">{{T "backups_apps.3_mentes"}}</span>
<span class="layer-badge">{{T "backups_apps.kikapcsolva"}}</span>
<span class="tier-contents">{{T "backups_apps.ez_az_alkalmazas_nincs_kijelolve"}}</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Bekapcsolás</a>
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">{{T "backups_apps.bekapcsolas"}}</a>
</div>
</div>
{{else}}
<div class="backup-layer-row" style="opacity:.5">
<span class="tier-label">3. mentés</span>
<span class="layer-badge" style="background:var(--bg-2);color:var(--text-3)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<span class="tier-label">{{T "backups_apps.3_mentes"}}</span>
<span class="layer-badge" style="background:var(--bg-2);color:var(--text-3)">{{T "backups_apps.nincs_beallitva"}}</span>
<span class="tier-location">{{T "backups_apps.tavoli_offsite"}}</span>
<span class="tier-contents">{{T "backups_apps.nincs_tavoli_mentesi_cel_beallitva"}}</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">{{T "backups_apps.beallitas"}}</a>
</div>
</div>
{{end}}
@@ -383,22 +380,22 @@ function toggleBackupDetail(header) {
function triggerBackupFromPage() {
const btn = document.getElementById('backup-page-btn');
btn.disabled = true;
btn.textContent = 'Mentés indítása...';
btn.textContent = '{{T "backups_apps.mentes_inditasa"}}';
fetch('/api/backup/run', { method: 'POST', headers: csrfHeaders() })
.then(r => r.json())
.then(data => {
if (data.ok) {
btn.textContent = 'Mentés folyamatban...';
btn.textContent = '{{T "backups_apps.mentes_folyamatban"}}';
btn.classList.add('loading');
startBackupPolling();
} else {
btn.textContent = data.error || 'Hiba';
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
btn.textContent = data.error || '{{T "backups_apps.hiba"}}';
setTimeout(() => { btn.textContent = '{{T "backups_apps.mentes_most"}}'; btn.disabled = false; }, 3000);
}
})
.catch(() => {
btn.textContent = 'Hiba';
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
btn.textContent = '{{T "backups_apps.hiba"}}';
setTimeout(() => { btn.textContent = '{{T "backups_apps.mentes_most"}}'; btn.disabled = false; }, 3000);
});
}
@@ -7,8 +7,8 @@
<div class="page-header">
<div style="display:flex;align-items:center;gap:.5rem">
<a href="/backups/remote" class="btn btn-sm btn-outline">← Vissza</a>
<h2>Helyreállítási kód</h2>
<a href="/backups/remote" class="btn btn-sm btn-outline">{{T "backups_escrow.vissza"}}</a>
<h2>{{T "backups_escrow.helyreallitasi_kod"}}</h2>
</div>
<span class="domain-badge">{{.Domain}}</span>
</div>
@@ -20,7 +20,7 @@
polls and reloads itself when the agent says ready. -->
<div class="settings-card" id="escrow-not-ready">
<div class="alert alert-info">{{.EscrowNotReadyMessage}}</div>
<p class="form-hint">Ehhez nem kell semmit tenned. Ha fél óra múlva is ezt látod, szólj az üzemeltetőnek.</p>
<p class="form-hint">{{T "backups_escrow.ehhez_nem_kell_semmit_tenned"}}</p>
</div>
<script>
(function(){
@@ -34,96 +34,91 @@
})();
</script>
{{else if not .AgentSupported}}
<div class="alert alert-info">A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik. Próbálja újra később.</div>
<div class="alert alert-info">{{T "backups_escrow.a_funkciohoz_az_ugynok_frissitese"}}</div>
{{else}}
<!-- 1. Preflight -->
<div class="settings-card" id="pf-card">
<h3>1. Előfeltételek ellenőrzése</h3>
<h3>{{T "backups_escrow.1_elofeltetelek_ellenorzese"}}</h3>
<div id="pf-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
<div id="pf-list"><p class="form-hint">Ellenőrzés folyamatban…</p></div>
<div id="pf-list"><p class="form-hint">{{T "backups_escrow.ellenorzes_folyamatban"}}</p></div>
</div>
<!-- 2. Warning + 3. Re-auth (revealed when preflight is green) -->
<div class="settings-card" id="warn-card" style="display:none">
<h3>2. Fontos tudnivalók</h3>
<h3>{{T "backups_escrow.2_fontos_tudnivalok"}}</h3>
<div class="alert alert-warning" style="margin-bottom:.75rem">
A helyreállítási kód a mentései utolsó kulcsa. Pontosan egyszer jelenik meg — a rendszer
sehol nem tárolja, és a Felhom sem ismeri. Ha a szerver megsemmisül, a távoli mentések CSAK
ezzel a kóddal állíthatók vissza. Írja fel papírra vagy mentse jelszókezelőbe — de NE ezen a
szerveren tárolja.
{{T "backups_escrow.a_helyreallitasi_kod_a_mentesei"}}
</div>
{{if .Receremony}}
<div class="alert alert-info" style="margin-bottom:.75rem">
Az új kód létrehozásával a korábbi kód a jövőbeli mentésekre érvényét veszti; a már meglévő
mentési előzményekhez érvényes marad.
{{T "backups_escrow.az_uj_kod_letrehozasaval_a"}}
</div>
{{end}}
<h3 style="margin-top:1.25rem">3. Megerősítés</h3>
<h3 style="margin-top:1.25rem">{{T "backups_escrow.3_megerosites"}}</h3>
<form id="start-form" onsubmit="return startCeremony(event)">
<div class="form-group">
<label for="reauth-password">A folytatáshoz adja meg a bejelentkezési jelszavát</label>
<label for="reauth-password">{{T "backups_escrow.a_folytatashoz_adja_meg_a"}}</label>
<input type="password" id="reauth-password" class="form-control" autocomplete="current-password" required style="max-width:280px">
</div>
<div id="start-error" class="alert alert-error" style="display:none;margin-bottom:.75rem"></div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="start-btn">Kód létrehozása</button>
<a href="/backups/remote" class="btn btn-outline">Mégsem</a>
<button type="submit" class="btn btn-primary" id="start-btn">{{T "backups_escrow.kod_letrehozasa"}}</button>
<a href="/backups/remote" class="btn btn-outline">{{T "backups_escrow.megsem"}}</a>
</div>
</form>
</div>
<!-- 4. Progress -->
<div class="settings-card" id="progress-card" style="display:none">
<h3>4. Létrehozás</h3>
<p class="form-hint" id="progress-line">A kód létrehozása folyamatban… (általában néhány másodperc)</p>
<h3>{{T "backups_escrow.4_letrehozas"}}</h3>
<p class="form-hint" id="progress-line">{{T "backups_escrow.a_kod_letrehozasa_folyamatban_altalaban"}}</p>
<div id="progress-error" class="alert alert-error" style="display:none"></div>
</div>
<!-- 5. Reveal + 6. Typed-back + 7. Finish -->
<div class="settings-card" id="reveal-card" style="display:none">
<h3>5. A helyreállítási kód</h3>
<h3>{{T "backups_escrow.5_a_helyreallitasi_kod"}}</h3>
<div id="reveal-gate">
<p class="form-hint">A kód pontosan egyszer jeleníthető meg. Készítsen elő papírt és tollat, mielőtt megnyomja a gombot.</p>
<button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">Kód megjelenítése</button>
<p class="form-hint">{{T "backups_escrow.a_kod_pontosan_egyszer_jelenitheto"}}</p>
<button class="btn btn-primary" id="reveal-btn" onclick="claimCode()">{{T "backups_escrow.kod_megjelenitese"}}</button>
<div id="reveal-error" class="alert alert-error" style="display:none;margin-top:.75rem"></div>
</div>
<div id="reveal-shown" style="display:none">
<div class="alert alert-warning" style="margin-bottom:.5rem">Ez mostantól az élő helyreállítási kód — a korábbi kód érvényét vesztette. Mentse el most: a kód többé nem jeleníthető meg.</div>
<div class="alert alert-warning" style="margin-bottom:.5rem">{{T "backups_escrow.ez_mostantol_az_elo_helyreallitasi"}}</div>
<pre class="mono" id="code-block" style="white-space:pre-wrap;word-break:break-all;background:var(--bg-0);border:1px solid var(--line);border-radius:var(--radius);padding:1rem;font-size:1.05rem"></pre>
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">Elrejtés</button>
<h3 style="margin-top:1.25rem">6. Ellenőrzés</h3>
<button type="button" class="btn btn-ghost btn-sm" id="code-blur-toggle" onclick="toggleCodeBlur()">{{T "backups_escrow.elrejtes"}}</button>
<h3 style="margin-top:1.25rem">{{T "backups_escrow.6_ellenorzes"}}</h3>
<div class="form-group">
<label id="verify-label" for="verify-w1">Megerősítés: írja be a kód szavait</label>
<label id="verify-label" for="verify-w1">{{T "backups_escrow.megerosites_irja_be_a_kod"}}</label>
<div style="display:flex;gap:.5rem">
<input type="text" id="verify-w1" class="form-control" autocomplete="off" style="max-width:180px">
<input type="text" id="verify-w2" class="form-control" autocomplete="off" style="max-width:180px">
</div>
<div id="verify-error" class="alert alert-error" style="display:none;margin-top:.5rem">A megadott szavak nem egyeznek — ellenőrizze a felírt kódot.</div>
<div id="verify-error" class="alert alert-error" style="display:none;margin-top:.5rem">{{T "backups_escrow.a_megadott_szavak_nem_egyeznek"}}</div>
</div>
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">Szavak ellenőrzése</button>
<button class="btn btn-sm" id="verify-btn" onclick="verifyWords()">{{T "backups_escrow.szavak_ellenorzese"}}</button>
<div id="finish-block" style="display:none;margin-top:1rem">
<h3>7. Befejezés</h3>
<h3>{{T "backups_escrow.7_befejezes"}}</h3>
{{/* v0.138.0: the "what happens next" note — the wizard finishes to /backups/remote,
where the interim "awaiting hub confirmation" card shows until the flip. Setting
expectations here avoids surprise at that card. */}}
<div class="alert alert-info" style="margin-bottom:.75rem">
<strong>Mi történik ezután?</strong> A helyreállítási kód elkészült, és a rendszer elküldi a központba megerősítésre — ez általában néhány másodperc, legfeljebb 15 perc. Addig a Távoli mentés oldalon a „megerősítésre vár" üzenet látható; a megerősítés után indítható az első távoli mentés.
{{T "backups_escrow.mi_tortenik_ezutan_a_helyreallitasi"}}
</div>
<label class="toggle" style="margin-bottom:.75rem">
<input type="checkbox" id="finish-check" onchange="document.getElementById('finish-btn').disabled=!this.checked">
<span class="toggle-label">Felírtam a kódot, és biztonságos helyen — nem ezen a szerveren — tárolom.</span>
<span class="toggle-label">{{T "backups_escrow.felirtam_a_kodot_es_biztonsagos"}}</span>
</label>
<div><button class="btn btn-primary" id="finish-btn" disabled onclick="finishWizard()">Befejezés</button></div>
<div><button class="btn btn-primary" id="finish-btn" disabled onclick="finishWizard()">{{T "backups_escrow.befejezes"}}</button></div>
</div>
</div>
</div>
<!-- Void / unclaimed state -->
<div class="settings-card" id="void-card" style="display:none">
<div class="alert alert-warning">A kód létrejött, de nem lett megjelenítve — biztonsági okból újra nem kérhető le.
Indítsa újra a folyamatot: az új kód a régit érvényteleníti.</div>
<a href="/backup/escrow" class="btn btn-sm btn-primary">Újraindítás</a>
<div class="alert alert-warning">{{T "backups_escrow.a_kod_letrejott_de_nem"}}</div>
<a href="/backup/escrow" class="btn btn-sm btn-primary">{{T "backups_escrow.ujrainditas"}}</a>
</div>
<script>
@@ -131,12 +126,12 @@ function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){retur
// Hungarian labels for the agent preflight row ids.
var PF_LABELS = {
pbs_storage_id: 'Mentési tároló (PBS) beállítva',
dr_tier: 'Helyreállítási (DR) szint aktív',
age_binary: 'Titkosító eszköz telepítve',
hub_upload: 'Központi feltöltés beállítva',
staged_secret: 'Távoli mentés jelszava előkészítve',
sudo_grant: 'Rendszerjogosultság engedélyezve'
pbs_storage_id: '{{T "backups_escrow.mentesi_tarolo_pbs_beallitva"}}',
dr_tier: '{{T "backups_escrow.helyreallitasi_dr_szint_aktiv"}}',
age_binary: '{{T "backups_escrow.titkosito_eszkoz_telepitve"}}',
hub_upload: '{{T "backups_escrow.kozponti_feltoltes_beallitva"}}',
staged_secret: '{{T "backups_escrow.tavoli_mentes_jelszava_elokeszitve"}}',
sudo_grant: '{{T "backups_escrow.rendszerjogosultsag_engedelyezve"}}'
};
// Hungarian DETAIL mapping (v0.127.1): the agent's `detail` strings are operator-English by
@@ -144,17 +139,17 @@ var PF_LABELS = {
// (storage id / binary path); boolean-OK rows render no detail at all. Not-OK rows get the
// Hungarian explanation; unknown/future ids fall back to the raw detail (never blank a failure).
var PF_FAIL_DETAILS = {
pbs_storage_id: 'nincs beállítva mentési tároló',
age_binary: 'a titkosító eszköz (age) nem található',
dr_tier: 'a DR-szint nincs alkalmazva ezen a gépen',
hub_upload: 'a központi feltöltés nincs beállítva',
sudo_grant: 'a rendszerjogosultság hiányzik — az ügynök frissítése szükséges',
staged_secret: 'nincs előkészített jelszó — a varázsló indításkor automatikusan előkészíti, ha a távoli mentés be van állítva'
pbs_storage_id: '{{T "backups_escrow.nincs_beallitva_mentesi_tarolo"}}',
age_binary: '{{T "backups_escrow.a_titkosito_eszkoz_age_nem"}}',
dr_tier: '{{T "backups_escrow.a_dr_szint_nincs_alkalmazva"}}',
hub_upload: '{{T "backups_escrow.a_kozponti_feltoltes_nincs_beallitva"}}',
sudo_grant: '{{T "backups_escrow.a_rendszerjogosultsag_hianyzik_az_ugynok"}}',
staged_secret: '{{T "backups_escrow.nincs_elokeszitett_jelszo_a_varazslo"}}'
};
function pfDetail(it){
if(it.ok){
if(it.id==='pbs_storage_id' || it.id==='age_binary'){ return it.detail||''; } // the VALUE
if(it.id==='staged_secret'){ return 'előkészítve'; }
if(it.id==='staged_secret'){ return '{{T "backups_escrow.elokeszitve"}}'; }
return ''; // dr_tier / hub_upload / sudo_grant: the check mark + label is complete
}
return PF_FAIL_DETAILS[it.id] || (it.detail||'');
@@ -163,7 +158,7 @@ function pfDetail(it){
async function loadPreflight(){
try{
var r = await fetch('/api/escrow/preflight'); var j = await r.json();
if(!j.ok && !j.data){ throw new Error(j.error||'Hiba'); }
if(!j.ok && !j.data){ throw new Error(j.error||'{{T "backups_escrow.hiba"}}'); }
var d = j.data||{};
var html='<ul style="list-style:none;padding:0;margin:0">';
(d.items||[]).forEach(function(it){
@@ -182,7 +177,7 @@ async function loadPreflight(){
html+='<li style="padding:.35rem 0;border-bottom:1px solid var(--line-soft)">'+line+'</li>';
});
if(!d.agent_supported){
html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</li>';
html+='<li style="padding:.35rem 0"><span style="color:var(--crit)">&#10007;</span> {{T "backups_escrow.a_funkciohoz_az_ugynok_frissitese_2"}}</li>';
}
html+='</ul>';
document.getElementById('pf-list').innerHTML=html;
@@ -190,11 +185,11 @@ async function loadPreflight(){
document.getElementById('warn-card').style.display='block';
} else {
document.getElementById('pf-list').insertAdjacentHTML('beforeend',
'<p class="form-hint" style="margin-top:.75rem">A folyamat a piros feltételek teljesüléséig nem indítható.</p>');
'<p class="form-hint" style="margin-top:.75rem">{{T "backups_escrow.a_folyamat_a_piros_feltetelek"}}</p>');
}
}catch(e){
var el=document.getElementById('pf-error'); el.style.display='block';
el.textContent='Az előfeltételek ellenőrzése nem sikerült: '+e.message;
el.textContent='{{T "backups_escrow.az_elofeltetelek_ellenorzese_nem_sikerul"}} '+e.message;
}
}
@@ -207,12 +202,12 @@ async function startCeremony(ev){
var r=await fetch('/api/escrow/start',{method:'POST',
headers:Object.assign({'Content-Type':'application/x-www-form-urlencoded'},csrfHeaders()),body:body});
var j=await r.json();
if(!j.ok){ errEl.textContent=j.error||'A folyamat indítása nem sikerült.'; errEl.style.display='block'; btn.disabled=false; return false; }
if(!j.ok){ errEl.textContent=j.error||'{{T "backups_escrow.a_folyamat_inditasa_nem_sikerult"}}'; errEl.style.display='block'; btn.disabled=false; return false; }
document.getElementById('reauth-password').value='';
document.getElementById('warn-card').style.display='none';
document.getElementById('progress-card').style.display='block';
pollStatus();
}catch(e){ errEl.textContent='Hiba: '+e.message; errEl.style.display='block'; btn.disabled=false; }
}catch(e){ errEl.textContent='{{T "backups_escrow.hiba_2"}} '+e.message; errEl.style.display='block'; btn.disabled=false; }
return false;
}
@@ -230,7 +225,7 @@ function pollStatus(){
} else if(d.phase==='failed'){
clearInterval(pollTimer);
var el=document.getElementById('progress-error'); el.style.display='block';
el.textContent='A létrehozás nem sikerült.'+(d.detail?(' Részletek: '+d.detail):'');
el.textContent='{{T "backups_escrow.a_letrehozas_nem_sikerult"}}'+(d.detail?(' {{T "backups_escrow.reszletek"}} '+d.detail):'');
} else if(d.phase==='unclaimed_void' || d.phase==='none'){
clearInterval(pollTimer);
document.getElementById('progress-card').style.display='none';
@@ -252,7 +247,7 @@ async function claimCode(){
var r=await fetch('/api/escrow/claim',{method:'POST',headers:csrfHeaders()});
var j=await r.json();
if(r.status===410){ document.getElementById('reveal-card').style.display='none'; document.getElementById('void-card').style.display='block'; return; }
if(!j.ok || !j.data || !j.data.recovery_code){ errEl.textContent=j.error||'A kód lekérése nem sikerült.'; errEl.style.display='block'; btn.disabled=false; return; }
if(!j.ok || !j.data || !j.data.recovery_code){ errEl.textContent=j.error||'{{T "backups_escrow.a_kod_lekerese_nem_sikerult"}}'; errEl.style.display='block'; btn.disabled=false; return; }
claimedR=j.data.recovery_code;
// Typed-back positions FIRST (v0.127.1: the rendering below highlights them): two random
// distinct 1-based word positions of the dash-separated code.
@@ -276,8 +271,8 @@ async function claimCode(){
});
document.getElementById('reveal-gate').style.display='none';
document.getElementById('reveal-shown').style.display='block';
document.getElementById('verify-label').textContent='Megerősítés: írja be a kód '+verifyIdx[0]+'. és '+verifyIdx[1]+'. szavát';
}catch(e){ errEl.textContent='Hiba: '+e.message; errEl.style.display='block'; btn.disabled=false; }
document.getElementById('verify-label').textContent='{{T "backups_escrow.megerosites_irja_be_a_kod_2"}} '+verifyIdx[0]+'{{T "backups_escrow.es"}} '+verifyIdx[1]+'{{T "backups_escrow.szavat"}}';
}catch(e){ errEl.textContent='{{T "backups_escrow.hiba_2"}} '+e.message; errEl.style.display='block'; btn.disabled=false; }
}
// Manual hide toggle (v0.127.2 — Viktor's live finding reversed the v0.127.1 auto-blur: blurring
@@ -289,7 +284,7 @@ var codeBlurred=false;
function setCodeBlur(on){
codeBlurred=on;
document.getElementById('code-block').style.filter=on?'blur(6px)':'';
document.getElementById('code-blur-toggle').textContent=on?'Megjelenítés':'Elrejtés';
document.getElementById('code-blur-toggle').textContent=on?'{{T "backups_escrow.megjelenites"}}':'{{T "backups_escrow.elrejtes"}}';
}
function toggleCodeBlur(){ setCodeBlur(!codeBlurred); }
@@ -2,7 +2,7 @@
{{template "layout_start" .}}
<div class="page-header">
<h2>Biztonsági mentés — Távoli mentés</h2>
<h2>{{T "backups_remote.biztonsagi_mentes_tavoli_mentes"}}</h2>
<span class="domain-badge">{{.Domain}}</span>
</div>
@@ -12,11 +12,9 @@
<!-- R-193: the PERMANENT entry point. Bound to RecoveryOffer, never to the postpone flag — a
customer who clicked "Most nem" once must not lose the route to their own data. -->
<div class="alert alert-warning">
<strong>A korábbi, házon kívüli mentéseid visszaszerezhetők.</strong>
Ezt a gépet újratelepítették, és a Felhom központi rendszere őriz hozzá egy lezárt csomagot. A
helyreállítási kódoddal feloldhatod a korábbi mentéseidet, és megnézheted, mi van bennük.
{{T "backups_remote.a_korabbi_hazon_kivuli_menteseid"}}
<div class="form-actions" style="margin-top:.5rem">
<a href="/recovery" class="btn btn-primary">Adatok visszaszerzése</a>
<a href="/recovery" class="btn btn-primary">{{T "backups_remote.adatok_visszaszerzese"}}</a>
</div>
</div>
{{end}}
@@ -31,30 +29,30 @@
{{if and (not .OffboxConfigured) .OffsiteHubEnabled}}
<!-- R-70 (v0.161.0): hub-managed offsite is provisioned for this box — "igényelhető" would be a
lie (it is already ordered) and the bare empty state hid the pre-apply window (DIAG-f10). -->
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">Felhom offsite tárhely kiépítve — a beállítás automatikus, folyamatban. Ha egy napon belül nem áll be, jelezd az üzemeltetőnek.</div>
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">{{T "backups_remote.felhom_offsite_tarhely_kiepitve_a"}}</div>
{{else if not .OffboxConfigured}}
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">Felhom offsite tárhely — igényelhető szolgáltatás. Titkosított, házon kívüli másolat a mentéseidről. Érdeklődj az üzemeltetőnél.</div>
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">{{T "backups_remote.felhom_offsite_tarhely_igenyelheto_szolg"}}</div>
{{else if eq .OffboxToggledCount 0}}
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">Aktív — nincs kijelölt alkalmazás</div>
<div class="alert alert-info" style="margin-bottom:1rem" id="felhom-offsite-card">{{T "backups_remote.aktiv_nincs_kijelolt_alkalmazas"}}</div>
{{end}}
<!-- Off-box (NAS) backup — Part B: encrypted restic repo over SFTP (the off-site 3-2-1 leg). -->
<h3 id="offbox-section" class="backup-tier-divider">Távoli mentés (3. mentés) — titkosított, offsite</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazás-mentések titkosított másolata egy távoli tárolóra — saját NAS vagy Felhom offsite tárhely — restic + SFTP kapcsolaton. A tároló csak titkosított adatot lát. Ez a 3-2-1 szabály „1 off-site" lába — független a helyi másodpéldánytól és a teljes rendszermentéstől.</p>
<h3 id="offbox-section" class="backup-tier-divider">{{T "backups_remote.tavoli_mentes_3_mentes_titkositott"}}</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">{{T "backups_remote.az_alkalmazas_mentesek_titkositott_masol"}}</p>
<div class="card" style="margin-bottom:1.5rem">
{{if .Offbox}}
<div class="stats-grid backup-page-cards">
<div class="stat-card {{if or (eq .Offbox.LastStatus "error") (eq .Offbox.LastStatus "incomplete")}}stat-warn{{end}}">
<div class="stat-value" id="offbox-status-value" style="font-size:1.15rem">{{if eq .Offbox.LastStatus "ok"}}&#10003; Rendben{{else if eq .Offbox.LastStatus "incomplete"}}&#33; Hiányos{{else if eq .Offbox.LastStatus "error"}}&#10007; Hiba{{else if eq .Offbox.LastStatus "running"}}Fut…{{else}}–{{end}}</div>
<div class="stat-label">Utolsó távoli mentés{{if .Offbox.LastRun}}<br><span class="relative-time">{{timeAgoStr .Offbox.LastRun}}</span>{{end}}</div>
<div class="stat-value" id="offbox-status-value" style="font-size:1.15rem">{{if eq .Offbox.LastStatus "ok"}}{{T "backups_remote.rendben"}}{{else if eq .Offbox.LastStatus "incomplete"}}{{T "backups_remote.hianyos"}}{{else if eq .Offbox.LastStatus "error"}}{{T "backups_remote.hiba"}}{{else if eq .Offbox.LastStatus "running"}}Fut…{{else}}–{{end}}</div>
<div class="stat-label">{{T "backups_remote.utolso_tavoli_mentes"}}{{if .Offbox.LastRun}}<br><span class="relative-time">{{timeAgoStr .Offbox.LastRun}}</span>{{end}}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.15rem">{{if and .Offbox.StatsKnown .Offbox.RepoSizeHuman}}{{.Offbox.RepoSizeHuman}}{{else}}–{{end}}</div>
<div class="stat-label">Tároló méret · {{if .Offbox.StatsKnown}}{{.Offbox.SnapshotCount}} pillanatkép{{else}}a pillanatképek száma még ismeretlen{{end}}</div>
<div class="stat-label">{{T "backups_remote.tarolo_meret"}} {{if .Offbox.StatsKnown}}{{T "backups_remote.pillanatkep"}}{{else}}{{T "backups_remote.a_pillanatkepek_szama_meg_ismeretlen"}}{{end}}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">{{if .Offbox.Enabled}}{{.Offbox.User}}@{{.Offbox.Host}}{{else}}Kikapcsolva{{end}}</div>
<div class="stat-label">{{if .Offbox.Enabled}}{{.Offbox.RepoPath}}{{else}}A távoli mentés ki van kapcsolva{{end}}</div>
<div class="stat-value" style="font-size:1.05rem">{{if .Offbox.Enabled}}{{.Offbox.User}}@{{.Offbox.Host}}{{else}}{{T "backups_remote.kikapcsolva"}}{{end}}</div>
<div class="stat-label">{{if .Offbox.Enabled}}{{.Offbox.RepoPath}}{{else}}{{T "backups_remote.a_tavoli_mentes_ki_van"}}{{end}}</div>
</div>
</div>
{{if and .Offbox.Enabled (gt .Offbox.QuotaGB 0)}}
@@ -62,7 +60,7 @@
picture of emptiness, and a picture is a claim. -->
<!-- SLICE 4: soft-quota usage bar (shared model; quota_gb from the hub descriptor). -->
<div id="offbox-quota-bar" style="max-width:560px;margin:.5rem 0">
<div class="stat-label" style="margin-bottom:.25rem">Tárhelykeret: {{if .Offbox.StatsKnown}}{{if .Offbox.RepoSizeHuman}}{{.Offbox.RepoSizeHuman}}{{else}}0{{end}} / {{.Offbox.QuotaGB}} GB ({{.OffboxQuotaPct}}%){{else}}még nem tudjuk, mennyi van a tárolóban — legfeljebb {{.Offbox.QuotaGB}} GB{{end}}</div>
<div class="stat-label" style="margin-bottom:.25rem">{{T "backups_remote.tarhelykeret"}} {{if .Offbox.StatsKnown}}{{if .Offbox.RepoSizeHuman}}{{.Offbox.RepoSizeHuman}}{{else}}0{{end}} / {{.Offbox.QuotaGB}} GB ({{.OffboxQuotaPct}}%){{else}}{{T "backups_remote.meg_nem_tudjuk_mennyi_van"}}{{end}}</div>
{{if .Offbox.StatsKnown}}<div style="background:var(--border,#334);border-radius:4px;height:8px;overflow:hidden">
<div style="height:8px;border-radius:4px;width:{{.OffboxQuotaPct}}%;background:{{if ge .OffboxQuotaPct 100}}var(--crit,#e5484d){{else if ge .OffboxQuotaPct 80}}var(--warn,#f5a524){{else}}var(--ok,#30a46c){{end}}"></div>
</div>{{end}}
@@ -79,13 +77,11 @@
promise that turns out false is worse here than saying less — the R-202 lesson. */}}
{{if .Offbox.OrphanedRenamedTo}}
<div class="alert alert-info" style="margin-top:.75rem">
<p><strong>A korábbi mentéseid félre vannak téve — nem töröltük őket.</strong></p>
<p class="form-hint">Amikor új mentési kulcsot kapott a géped, a régebbi előzményt átmozgattuk
a távoli tárhelyen, és ott is maradt. <strong>Megnyitni innen egyelőre nem lehet</strong>, és
ez nem a kódodon múlik. Ha szükséged van rá, keresd a Felhom ügyfélszolgálatát.</p>
<p><strong>{{T "backups_remote.a_korabbi_menteseid_felre_vannak"}}</strong></p>
<p class="form-hint">{{T "backups_remote.amikor_uj_mentesi_kulcsot_kapott"}}</p>
</div>
{{end}}
{{if .Offbox.LastError}}<p class="form-hint" style="color:var(--crit)">Utolsó hiba: {{.Offbox.LastError}}</p>{{end}}
{{if .Offbox.LastError}}<p class="form-hint" style="color:var(--crit)">{{T "backups_remote.utolso_hiba"}}</p>{{end}}
{{/* Part E: display pick — a stale zero-toggle warning is replaced once the selection
changed (neutral color: the replacement is reassurance, not a deviation). */}}
{{if .OffboxWarningDisplay}}<p class="form-hint"{{if eq .OffboxWarningDisplay .Offbox.LastWarning}} style="color:var(--warn)"{{end}}>{{.OffboxWarningDisplay}}</p>{{end}}
@@ -94,15 +90,15 @@
reset moves the old history aside (never deletes) and starts a fresh repo under the current key. */}}
{{if eq .Offbox.RepoState "orphaned"}}
<div class="card" style="border-left:3px solid var(--crit,#e5484d);margin:.75rem 0;padding:.75rem 1rem" id="offbox-orphan-card">
<p style="margin:0 0 .35rem;font-weight:600">A távoli tároló másik kulccsal készült mentéseket tartalmaz</p>
<p style="margin:0 0 .35rem;font-weight:600">{{T "backups_remote.a_tavoli_tarolo_masik_kulccsal"}}</p>
{{/* R-299: this paragraph carried the SAME unevaluable promise as the confirm block below, in a
different conjugation („visszaállíthatók lehetnek" vs „visszaállítható lehet") — which is
also why the first regression guard, matching the singular form, did not catch it. The
discriminator is superseded identity_blob on the HUB; the box has no wire field for it. The
two accurate halves are kept: the store IS orphaned, and new backups genuinely cannot be
written. The guard now matches the STEM, so any inflection fails the test. */}}
<p class="form-hint" style="margin:0 0 .5rem">A távoli tárhelyen lévő mentések egy korábbi, már nem elérhető kulccsal készültek (jellemzően újratelepítés után). Emiatt új mentés jelenleg nem írható a tárolóba. A meglévő mentések nem sérültek. Azt viszont ez a gép nem tudja megállapítani, hogy később megnyithatók-e — ez attól függ, megvan-e még a hozzájuk tartozó kulcs. Ha szükséged van rájuk, írj nekünk.</p>
<button type="button" class="btn btn-sm btn-outline" id="orphan-reveal" onclick="var c=document.getElementById('orphan-confirm');c.style.display='block';this.style.display='none'">Új távoli mentés indítása…</button>
<p class="form-hint" style="margin:0 0 .5rem">{{T "backups_remote.a_tavoli_tarhelyen_levo_mentesek"}}</p>
<button type="button" class="btn btn-sm btn-outline" id="orphan-reveal" onclick="var c=document.getElementById('orphan-confirm');c.style.display='block';this.style.display='none'">{{T "backups_remote.uj_tavoli_mentes_inditasa"}}</button>
<div id="orphan-confirm" style="display:none;margin-top:.6rem">
{{/* R-294 / R-202: this paragraph promised the set-aside copies "may be restorable later
with the recovery code". The discriminator is host_escrow_superseded.identity_blob,
@@ -112,11 +108,11 @@
system cannot evaluate is the same defect as an unconditional false one. It now
states what it knows, declines what it does not, and names a route.
Copy is verbatim from documentation/design/SPEC-orphan-card-copy-2026-08-10.md §4. */}}
<p class="form-hint" style="margin:0 0 .5rem">A régi előzmény <strong>félretéve marad a tárhelyen — nem töröljük</strong>. Új, üres tárolót hozunk létre, és a következő mentés oda készül.</p>
<p class="form-hint" style="margin:0 0 .5rem"><strong>A félretett mentések megnyithatóságát itt nem tudjuk megígérni.</strong> Ez attól függ, megvan-e még a hozzájuk tartozó kulcs, és ezt ez a gép nem tudja megállapítani. Ha szeretnéd, hogy utánanézzünk, <strong>írj nekünk</strong> — a félretett másolat addig is a helyén marad.</p>
<p class="form-hint" style="margin:0 0 .5rem">{{T "backups_remote.a_regi_elozmeny_felreteve_marad"}}</p>
<p class="form-hint" style="margin:0 0 .5rem">{{T "backups_remote.a_felretett_mentesek_megnyithatosagat_it"}}</p>
<form method="POST" action="/backup/offbox/reset" style="display:inline">{{.CSRFField}}
<input type="hidden" name="confirm" value="1">
<button type="submit" class="btn btn-sm btn-primary">Megerősítés — új távoli mentés indítása</button>
<button type="submit" class="btn btn-sm btn-primary">{{T "backups_remote.megerosites_uj_tavoli_mentes_inditasa"}}</button>
</form>
</div>
</div>
@@ -130,24 +126,24 @@
{{/* v0.138.0: the report-cycle gap between a completed ceremony and the hub-verified flip.
Info (blue) accent — NOT a warning: nothing is wrong, the confirmation is simply in flight. */}}
<div class="card" style="border-left:3px solid var(--blue);margin:.75rem 0;padding:.75rem 1rem">
<p style="margin:0 0 .35rem"><strong>Helyreállítási kód létrehozva</strong></p>
<p class="form-hint" style="margin:0">A helyreállítási csomag elküldve a központba, megerősítésre vár — ez általában néhány másodperc, legfeljebb 15 perc.</p>
<p style="margin:0 0 .35rem"><strong>{{T "backups_remote.helyreallitasi_kod_letrehozva"}}</strong></p>
<p class="form-hint" style="margin:0">{{T "backups_remote.a_helyreallitasi_csomag_elkuldve_a"}}</p>
</div>
{{else if .OffboxCeremonyTimedOut}}
{{/* v0.138.0: the confirmation never arrived within two report cycles + slack — degrade to a
warning with the re-ceremony CTA, never an indefinite "waiting". */}}
<div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem">
<p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">A megerősítés nem érkezett meg a várt időn belül. Ellenőrizze az internetkapcsolatot, majd készítsen új helyreállítási kódot.</p>
<p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">{{T "backups_remote.a_megerosites_nem_erkezett_meg"}}</p>
{{if .EscrowAgentOK}}
<a href="/backup/escrow" class="btn btn-sm btn-primary">Új helyreállítási kód készítése</a>
<a href="/backup/escrow" class="btn btn-sm btn-primary">{{T "backups_remote.uj_helyreallitasi_kod_keszitese"}}</a>
{{else}}
<p class="form-hint" style="margin:0">A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</p>
<p class="form-hint" style="margin:0">{{T "backups_remote.a_funkciohoz_az_ugynok_frissitese"}}</p>
{{end}}
</div>
{{else if and .OffboxConfigured (ne .Offbox.EscrowState "escrowed")}}
<div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem">
<p style="margin:0 0 .35rem"><strong>Helyreállítási kód szükséges</strong></p>
<p class="form-hint" style="margin:0 0 .5rem">A távoli mentések csak akkor állíthatók vissza egy teljes meghibásodás után, ha létrehozza a helyreállítási kódot.</p>
<p style="margin:0 0 .35rem"><strong>{{T "backups_remote.helyreallitasi_kod_szukseges"}}</strong></p>
<p class="form-hint" style="margin:0 0 .5rem">{{T "backups_remote.a_tavoli_mentesek_csak_akkor"}}</p>
{{/* §7.3 / Q7 — THE TRAP THAT MUST NOT SURVIVE THIS SESSION. While a recovery is outstanding,
creating a NEW code seals the CURRENT key and demotes the package that opens the earlier
history to retained custody, which no shipped path can read (R-199). It also re-enables
@@ -156,58 +152,54 @@
a warning beside a button is a warning people click past. */}}
{{if .RecoveryOffer}}
<p class="form-hint" style="margin:0">
Ehhez a géphez <strong>egy korábbi helyreállítási kód tartozik</strong>, és a korábbi mentéseid még megvannak.
Új kód létrehozása <strong>a régi mentéseidet elérhetetlenné tenné</strong>, ezért most nem indítható.
Előbb <a href="/recovery">add meg a meglévő kódodat</a> — vagy ott jelezheted, ha nem kéred vissza a korábbi adatokat.
{{T "backups_remote.ehhez_a_gephez_egy_korabbi"}}
</p>
{{else if .EscrowAgentOK}}
<a href="/backup/escrow" class="btn btn-sm btn-primary">Helyreállítási kód létrehozása</a>
<a href="/backup/escrow" class="btn btn-sm btn-primary">{{T "backups_remote.helyreallitasi_kod_letrehozasa"}}</a>
{{else}}
<p class="form-hint" style="margin:0">A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</p>
<p class="form-hint" style="margin:0">{{T "backups_remote.a_funkciohoz_az_ugynok_frissitese"}}</p>
{{end}}
</div>
{{else if and .OffboxConfigured .EscrowStale}}
<div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem">
<p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">A letétben lévő helyreállítási csomag nem fedi a jelenlegi távoli mentési jelszót. Hozzon létre új helyreállítási kódot.</p>
<p class="form-hint" style="color:var(--warn);margin:0 0 .5rem">{{T "backups_remote.a_letetben_levo_helyreallitasi_csomag"}}</p>
{{if .EscrowAgentOK}}
<a href="/backup/escrow" class="btn btn-sm btn-primary">Új helyreállítási kód készítése</a>
<a href="/backup/escrow" class="btn btn-sm btn-primary">{{T "backups_remote.uj_helyreallitasi_kod_keszitese"}}</a>
{{else}}
<p class="form-hint" style="margin:0">A funkcióhoz az ügynök frissítése szükséges — a frissítés automatikusan megérkezik.</p>
<p class="form-hint" style="margin:0">{{T "backups_remote.a_funkciohoz_az_ugynok_frissitese"}}</p>
{{end}}
</div>
{{else if .OffboxConfigured}}
{{/* v0.127.1: the re-ceremony CTA is a real secondary button (outline, NOT primary — escrowed
is a healthy state; available-not-urgent, unlike the stale variant above). */}}
<div style="margin:.5rem 0">
<p class="form-hint" style="margin:0 0 .4rem">A helyreállítási kód letétbe helyezve.</p>
{{if .EscrowAgentOK}}<a href="/backup/escrow" class="btn btn-sm btn-outline">Új helyreállítási kód készítése</a>{{end}}
<p class="form-hint" style="margin:0 0 .4rem">{{T "backups_remote.a_helyreallitasi_kod_letetbe_helyezve"}}</p>
{{if .EscrowAgentOK}}<a href="/backup/escrow" class="btn btn-sm btn-outline">{{T "backups_remote.uj_helyreallitasi_kod_keszitese"}}</a>{{end}}
</div>
{{end}}
{{if .AbandonActive}}
{{/* R-241: the countdown, stated for the whole window on the page the choice was made from. */}}
<div class="card" style="border-left:3px solid var(--warn);margin:.75rem 0;padding:.75rem 1rem">
<p style="margin:0 0 .35rem"><strong>A korábbi mentések törlése folyamatban</strong></p>
<p style="margin:0 0 .35rem"><strong>{{T "backups_remote.a_korabbi_mentesek_torlese_folyamatban"}}</strong></p>
<p class="form-hint" style="margin:0 0 .5rem">
{{/* R-302: the FOURTH instance of the retrieval promise, under a different verb than the
banner's — which is why no earlier guard counted it. Same condition, same single
derivation: fixing the strip above and not this would leave one page contradicting the
other. The deletion and its date are certain and always render. */}}
A kérésed szerint a korábbi távoli mentéseidet <strong>{{.AbandonDate}}</strong> napján véglegesen töröljük
(még <strong>{{.AbandonDaysLeft}} nap</strong>).
{{if .AbandonRetrievalOffered}}Addig meggondolhatod magad: ha megvan a helyreállítási kódod,
a mentéseid visszaszerezhetők, és a törlés elmarad.{{else}}Hogy ezek még visszaszerezhetők-e a helyreállítási kóddal, azt innen nem tudjuk megállapítani — ha vissza szeretnéd kapni őket, <strong>írj nekünk a törlés előtt</strong>.{{end}}
{{T "backups_remote.a_keresed_szerint_a_korabbi"}}
{{if .AbandonRetrievalOffered}}{{T "backups_remote.addig_meggondolhatod_magad_ha_megvan"}}{{else}}{{T "backups_remote.hogy_ezek_meg_visszaszerezhetok_e"}}{{end}}
</p>
<a href="/recovery" class="btn btn-sm btn-primary">Mégis visszaszerzem a kóddal</a>
<a href="/recovery" class="btn btn-sm btn-primary">{{T "backups_remote.megis_visszaszerzem_a_koddal"}}</a>
</div>
{{else if .AbandonPurging}}
<div class="card" style="border-left:3px solid var(--border,#2a3142);margin:.75rem 0;padding:.75rem 1rem">
<p class="form-hint" style="margin:0">A korábbi távoli mentéseid törlése megtörtént. A hozzájuk tartozó lezárt helyreállítási csomag eltávolítása még folyamatban van.</p>
<p class="form-hint" style="margin:0">{{T "backups_remote.a_korabbi_tavoli_menteseid_torlese"}}</p>
</div>
{{end}}
{{if .OffboxConfigured}}
<div class="schedule-actions" style="margin-top:1rem">
<form method="POST" action="/backup/offbox/run" style="display:inline">{{.CSRFField}}
<button type="submit" class="btn btn-sm btn-primary">Távoli mentés most</button>
<button type="submit" class="btn btn-sm btn-primary">{{T "backups_remote.tavoli_mentes_most"}}</button>
</form>
</div>
<!-- v0.147.0 (4c): live progress for a MANUAL run — total bytes, percent and which app is being
@@ -219,9 +211,9 @@
<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>
<h4 style="margin-top:1.25rem">{{T "backups_remote.mely_alkalmazasok_mentodnek_a_tavoli"}}</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>
<p class="form-hint">{{T "backups_remote.nincs_tavoli_mentesre_jelolt_alkalmazas"}}</p>
{{end}}
{{if .OffboxApps}}
<div class="app-row-list">
@@ -230,39 +222,39 @@
<form method="POST" action="/backup/offbox/toggle" style="display:inline">{{$.CSRFField}}
<input type="hidden" name="app" value="{{.Name}}">
<input type="hidden" name="enabled" value="{{if .Enabled}}false{{else}}true{{end}}">
<button type="submit" class="btn btn-xs {{if .Enabled}}btn-outline{{else}}btn-primary{{end}}">{{if .Enabled}}Távoli mentés kikapcsolása{{else}}Távoli mentés bekapcsolása{{end}}</button>
<button type="submit" class="btn btn-xs {{if .Enabled}}btn-outline{{else}}btn-primary{{end}}">{{if .Enabled}}{{T "backups_remote.tavoli_mentes_kikapcsolasa"}}{{else}}{{T "backups_remote.tavoli_mentes_bekapcsolasa"}}{{end}}</button>
</form>
{{if $.OffboxBlockedSet}}{{if index $.OffboxBlockedSet .Name}}
<span class="form-hint" style="display:block;margin-top:.25rem">A teljes mentés túllépné a tárhelykeretet — csak a konfiguráció és az adatbázis kerül mentésre.</span>
<span class="form-hint" style="display:block;margin-top:.25rem">{{T "backups_remote.a_teljes_mentes_tullepne_a"}}</span>
{{end}}{{end}}
{{template "app_list_row_end"}}
{{end}}
</div>
{{else}}<p class="form-hint">Nincs telepített alkalmazás.</p>{{end}}
{{else}}<p class="form-hint">{{T "backups_remote.nincs_telepitett_alkalmazas"}}</p>{{end}}
{{end}}
{{else}}
{{if .OffsiteHubEnabled}}
<p class="form-hint">Felhom offsite tárhely kiépítve — a beállítás automatikus, folyamatban. Ha egy napon belül nem áll be, jelezd az üzemeltetőnek.</p>
<p class="form-hint">{{T "backups_remote.felhom_offsite_tarhely_kiepitve_a"}}</p>
{{else}}
<p class="form-hint">Még nincs beállítva távoli mentési cél.</p>
<p class="form-hint">{{T "backups_remote.meg_nincs_beallitva_tavoli_mentesi"}}</p>
{{end}}
{{end}}
<details style="margin-top:1rem">
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">Távoli mentési cél beállítása</summary>
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">{{T "backups_remote.tavoli_mentesi_cel_beallitasa"}}</summary>
<form method="POST" action="/backup/offbox/config" style="margin-top:.75rem;padding:1rem;border:1px solid var(--border,#ddd);border-radius:6px;max-width:560px">{{.CSRFField}}
<div class="form-row"><label>Cél címe (IP vagy hosztnév — NAS vagy SFTP-kiszolgáló)</label><input type="text" name="host" class="form-input" value="{{if .Offbox}}{{.Offbox.Host}}{{end}}" placeholder="pl. 192.168.0.10"></div>
<div class="form-row"><label>{{T "backups_remote.cel_cime_ip_vagy_hosztnev"}}</label><input type="text" name="host" class="form-input" value="{{if .Offbox}}{{.Offbox.Host}}{{end}}" placeholder="{{T "backups_remote.pl_ip"}}"></div>
<div class="form-row"><label>SSH port</label><input type="number" name="port" class="form-input" value="{{if .Offbox}}{{.Offbox.Port}}{{else}}22{{end}}"></div>
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="{{if .Offbox}}{{.Offbox.User}}{{end}}" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a célgépen</label><input type="text" name="repo_path" class="form-input" value="{{if .Offbox}}{{.Offbox.RepoPath}}{{end}}" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" {{if .Offbox}}{{if .Offbox.Enabled}}checked{{end}}{{else}}checked{{end}}> Napi automatikus távoli mentés engedélyezve</label></div>
<div class="form-row"><label>SSH privát kulcs {{if .OffboxConfigured}}(üresen hagyva változatlan){{end}}</label>
<div class="form-row"><label>{{T "backups_remote.felhasznalo"}}</label><input type="text" name="user" class="form-input" value="{{if .Offbox}}{{.Offbox.User}}{{end}}" placeholder="{{T "backups_remote.pl_felhom"}}"></div>
<div class="form-row"><label>{{T "backups_remote.tarolo_utvonala_a_celgepen"}}</label><input type="text" name="repo_path" class="form-input" value="{{if .Offbox}}{{.Offbox.RepoPath}}{{end}}" placeholder="{{T "backups_remote.pl_repo_path"}}"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" {{if .Offbox}}{{if .Offbox.Enabled}}checked{{end}}{{else}}checked{{end}}> {{T "backups_remote.napi_automatikus_tavoli_mentes_engedelye"}}</label></div>
<div class="form-row"><label>{{T "backups_remote.ssh_privat_kulcs"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label>
<textarea name="ssh_key" class="form-input" rows="4" autocomplete="off" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----"></textarea>
<span class="form-hint">A kulcsot 0600-as fájlba írjuk; sosem naplózzuk és nem tároljuk a beállításokban.</span></div>
<div class="form-row"><label>A célgép ismert-host sora (known_hosts) {{if .OffboxConfigured}}(üresen hagyva változatlan){{end}}</label>
<span class="form-hint">{{T "backups_remote.a_kulcsot_0600_as_fajlba"}}</span></div>
<div class="form-row"><label>{{T "backups_remote.a_celgep_ismert_host_sora"}} {{if .OffboxConfigured}}{{T "backups_remote.uresen_hagyva_valtozatlan"}}{{end}}</label>
<textarea name="known_hosts" class="form-input" rows="2" autocomplete="off" placeholder="nas.local ssh-ed25519 AAAA…"></textarea>
<span class="form-hint">A host-kulcs rögzítése (no blind TOFU). Lekérdezhető: <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</button>
<span class="form-hint">{{T "backups_remote.a_host_kulcs_rogzitese_no"}} <span class="mono">ssh-keyscan -p &lt;port&gt; &lt;host&gt;</span></span></div>
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">{{T "backups_remote.mentes"}}</button>
</form>
</details>
</div>
@@ -308,24 +300,24 @@
with a large database it can dominate the early wall clock, and an unnamed silent
stretch at the start reads as a hang. */
if(p.phase === 'dump'){
text.textContent = 'Adatbázisok mentése a pillanatképhez…';
text.textContent = '{{T "backups_remote.adatbazisok_mentese_a_pillanatkephez"}}';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
if(p.phase === 'retention'){
text.textContent = 'Karbantartás: régi mentések rendezése a távoli tárolón…';
text.textContent = '{{T "backups_remote.karbantartas_regi_mentesek_rendezese_a"}}';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
if(p.phase === 'shares'){
text.textContent = 'Megosztott mappák mentése…';
text.textContent = '{{T "backups_remote.megosztott_mappak_mentese"}}';
bar.style.width = '100%';
box.className = 'alert alert-info'; box.style.display = '';
return;
}
var label = p.current_app ? ('Mentés: ' + p.current_app) : 'Mentés folyamatban';
var label = p.current_app ? ('{{T "backups_remote.mentes_2"}} ' + p.current_app) : '{{T "backups_remote.mentes_folyamatban"}}';
if(p.bytes_done > 0){
/* Bytes are genuinely moving — the informative case. */
label += ' — ' + Math.round(p.percent) + '% (' + p.done_human + ' / ' + p.total_human + ')';
@@ -338,16 +330,16 @@
so do not pretend: name the file being processed and the elapsed time, which is
what actually tells the customer it is alive. */
var fpct = Math.round((p.files_done / p.total_files) * 100);
label += ' — ' + p.files_done + ' / ' + p.total_files + ' fájl'
label += ' — ' + p.files_done + ' / ' + p.total_files + ' {{T "backups_remote.fajl"}}'
+ (p.total_human ? ' (' + p.total_human + ')' : '');
if(p.current_file){
var base = p.current_file.split('/').pop();
label += ' · feldolgozás alatt: ' + base;
label += ' {{T "backups_remote.feldolgozas_alatt"}} ' + base;
}
if(p.elapsed_sec > 0){ label += ' · ' + p.elapsed_sec + ' mp'; }
bar.style.width = Math.max(0, Math.min(100, fpct)) + '%';
} else {
label += ' — a mentendő adatok felmérése…';
label += ' {{T "backups_remote.a_mentendo_adatok_felmerese"}}';
bar.style.width = '0%';
}
text.textContent = label;
@@ -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';
@@ -8,8 +8,8 @@
<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>
<a href="/backups/restore" class="btn btn-sm btn-outline">{{T "backups_restore_wizard.vissza"}}</a>
<h2>{{T "backups_restore_wizard.visszaallitas"}}</h2>
</div>
<span class="domain-badge">{{.Domain}}</span>
</div>
@@ -22,19 +22,19 @@
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>
<span class="restore-wizard-phase{{if eq $phase "elokeszites"}} is-current{{end}}">{{T "backups_restore_wizard.elokeszites"}}</span>
<span class="restore-wizard-phase{{if eq $phase "megerosites"}} is-current{{end}}">{{T "backups_restore_wizard.megerosites"}}</span>
<span class="restore-wizard-phase{{if eq $phase "vegrehajtas"}} is-current{{end}}">{{T "backups_restore_wizard.vegrehajtas"}}</span>
<span class="restore-wizard-phase{{if eq $phase "eredmeny"}} is-current{{end}}">{{T "backups_restore_wizard.eredmeny"}}</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>
<h3>{{T "backups_restore_wizard.eredmeny"}}</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>
<p class="form-hint">{{if .Interrupted}}{{T "backups_restore_wizard.eszlelve"}}{{else}}{{T "backups_restore_wizard.befejezve"}}{{end}}{{T "backups_restore_wizard.ha_szeretned_alabb_ujra_indithatsz"}}</p>
</div>
{{end}}
@@ -43,15 +43,15 @@
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>
<h3>{{T "backups_restore_wizard.vegrehajtas"}}</h3>
<p>{{T "backups_restore_wizard.jelenleg_egy_mentesi_vagy_visszaallitasi"}}{{with .RunningStack}} ({{.}}){{end}}{{T "backups_restore_wizard.amig_ez_tart_uj_visszaallitas"}}</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>
<p class="form-hint">{{T "backups_restore_wizard.az_allapot_fent_automatikusan_frissul"}}</p>
<div class="form-actions">
<a href="/backups/restore/app?name={{.App}}" class="btn btn-sm btn-outline">Állapot frissítése</a>
<a href="/backups/restore/app?name={{.App}}" class="btn btn-sm btn-outline">{{T "backups_restore_wizard.allapot_frissitese"}}</a>
</div>
</div>
@@ -59,17 +59,17 @@
<!-- 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>
<h3>{{T "backups_restore_wizard.megerosites_teljes_visszaallitas_elokesz"}}</h3>
<p>{{T "backups_restore_wizard.a_teljes_visszaallitashoz_a_mentes"}}{{with .FullPrepSize}} {{T "backups_restore_wizard.a_becsult_meret"}}{{end}}{{T "backups_restore_wizard.a_letoltes_a_meghajton_egy"}}</p>
<p class="form-hint">{{T "backups_restore_wizard.a_letoltes_a_merettol_es"}}</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>
<button type="submit" class="btn btn-primary">{{T "backups_restore_wizard.elokeszites_inditasa"}}{{with .FullPrepSize}} (~{{.}}){{end}}</button>
</form>
<a href="/backups/restore/app?name={{.App}}" class="btn btn-outline">Mégsem</a>
<a href="/backups/restore/app?name={{.App}}" class="btn btn-outline">{{T "backups_restore_wizard.megsem"}}</a>
</div>
</div>
@@ -78,52 +78,52 @@
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>
<h3>{{T "backups_restore_wizard.1_ellenorzes_kulon_mappaba_beallitasok"}}</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>
<p>{{T "backups_restore_wizard.az_alkalmazas_beallitasait_es_adatbazisa"}}</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>
<button type="submit" class="btn btn-outline"{{if not .Wizard.VerifyEnabled}} disabled{{end}}>{{T "backups_restore_wizard.ellenorzes_inditasa"}}</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>
<h3>{{T "backups_restore_wizard.2_hianyzo_fajlok_visszahozasa"}}</h3>
<p>{{T "backups_restore_wizard.csak_a_hianyzo_fajlokat_masolja"}}</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>
<button type="submit" class="btn btn-outline">{{T "backups_restore_wizard.hianyzo_fajlok_visszahozasa"}}</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>
<p class="form-hint">{{T "backups_restore_wizard.ehhez_elobb_elo_kell_kesziteni"}}</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>
<h3>{{T "backups_restore_wizard.3_teljes_visszaallitas_fajlok_adatbazis"}}</h3>
<p>{{T "backups_restore_wizard.a_fajlokat_a_mentes_szerinti"}}</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}}
<strong>{{T "backups_restore_wizard.az_elokeszitett_mentes"}}</strong>
{{if not .Pair.DumpsAt.IsZero}}{{T "backups_restore_wizard.adatbazis_mentes_ideje"}}{{else}}{{T "backups_restore_wizard.az_adatbazis_mentes_ideje_nem"}}{{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>
<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "backups_restore_wizard.az_adatbazis_mentes_regebbi"}}{{if not .Pair.DumpsAt.IsZero}} ({{fmtTime .Pair.DumpsAt}}){{end}} {{T "backups_restore_wizard.a_fajlok_es_az_adatbazis"}}</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>
<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "backups_restore_wizard.a_mentett_adatbazis_uresnek_tunik"}}</div>
{{end}}
<div class="form-actions">
<form method="POST" action="/backup/offbox/reconstitute">{{.CSRFField}}
@@ -134,16 +134,16 @@
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>
onclick="confirmFullRestore(this)">{{T "backups_restore_wizard.teljes_visszaallitas_inditasa"}}</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>
<p class="form-hint">{{T "backups_restore_wizard.a_teljes_visszaallitashoz_eloszor_le"}}</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>
<button type="submit" class="btn btn-outline"{{if not .Wizard.PrepareEnabled}} disabled{{end}}>{{T "backups_restore_wizard.teljes_visszaallitas_elokeszitese"}}</button>
</form>
</div>
{{end}}
@@ -162,12 +162,12 @@ function confirmFullRestore(btn){
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.';
var q = '{{T "backups_restore_wizard.teljes_visszaallitas"}} ' + app + (when ? ' {{T "backups_restore_wizard.a_mentes_ideje"}} ' + when : '') + '.';
if (skewed) { q += ' {{T "backups_restore_wizard.figyelem_a_fajlok_es_az"}}'; }
if (empty) { q += ' {{T "backups_restore_wizard.figyelem_a_mentett_adatbazis_uresnek"}}'; }
q += ' {{T "backups_restore_wizard.a_fajlok_a_mentes_szerinti"}}';
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(){
felhomConfirm(btn, '{{T "backups_restore_wizard.utolso_megerosites_az_alkalmazas_leall"}}', function(){
var f = btn.closest('form');
if (f) { if (f.requestSubmit) f.requestSubmit(); else f.submit(); }
});
@@ -29,14 +29,14 @@
var banner = document.getElementById('restore-banner');
if (!banner) return;
var sawRunning = false;
function opLabel(op){ return op === 'tier2-restore' ? 'Fájl-visszaállítás'
: op === 'offbox-restore' ? 'Távoli visszaállítás' : 'Visszaállítás'; }
function opLabel(op){ return op === 'tier2-restore' ? '{{T "backups_shared.fajl_visszaallitas"}}'
: op === 'offbox-restore' ? '{{T "backups_shared.tavoli_visszaallitas"}}' : '{{T "backups_shared.visszaallitas"}}'; }
function render(st){
if (st.running) {
sawRunning = true;
banner.className = 'flash';
banner.style.display = 'block';
banner.textContent = opLabel(st.op) + ' folyamatban' + (st.stack ? ': ' + st.stack : '') + '…';
banner.textContent = opLabel(st.op) + ' {{T "backups_shared.folyamatban"}}' + (st.stack ? ': ' + st.stack : '') + '…';
return;
}
// R-351: `sawRunning` alone showed a terminal result ONLY to a page that watched the op
@@ -47,10 +47,10 @@
banner.style.display = 'block';
if (st.last.ok) {
banner.className = 'flash flash-success';
banner.textContent = st.last.message || (opLabel(st.last.op) + ' kész.');
banner.textContent = st.last.message || (opLabel(st.last.op) + ' {{T "backups_shared.kesz"}}');
} else {
banner.className = 'flash flash-error';
banner.textContent = st.last.message || (opLabel(st.last.op) + ' sikertelen.');
banner.textContent = st.last.message || (opLabel(st.last.op) + ' {{T "backups_shared.sikertelen"}}');
}
}
}
+37 -53
View File
@@ -1,11 +1,11 @@
{{define "recovery"}}
<!DOCTYPE html>
<html lang="hu">
<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>Adatok visszaszerzése — Felhom</title>
<title>{{T "recovery.adatok_visszaszerzese_felhom"}}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="login-body">
@@ -14,7 +14,7 @@
{{if .Unlocked}}
<!-- ── AFTER THE UNLOCK: what is in there. Read-only — nothing was restored. ────────────── -->
<h1 class="login-title">A mentéseid <span class="title-accent">elérhetők</span></h1>
<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}}
@@ -25,22 +25,18 @@
Empty=false fell through to .InvUntagged and asserted the store had opened with content. -->
{{else if .InvEmpty}}
<div class="alert alert-warning">
A tároló megnyílt, de <strong>nincs benne egyetlen mentés sem</strong>. Ez azt jelenti, hogy a
kulcs jó volt, de ehhez a géphez nem tartozik korábbi mentés. Ha korábban biztosan készültek
mentések, keresd a Felhom ügyfélszolgálatát, mielőtt bármit tennél.
{{T "recovery.a_tarolo_megnyilt_de_nincs"}}
</div>
{{else if .InvUntagged}}
<div class="alert alert-warning">
A tároló megnyílt, és van benne tartalom, de nem tudtuk alkalmazásokhoz rendelni. A Biztonsági
mentés oldalon nézheted meg részletesen.
{{T "recovery.a_tarolo_megnyilt_es_van"}}
</div>
{{else}}
<p class="login-subtitle" style="margin-bottom:1rem">
Ezek a <strong>te</strong> mentéseid, a lent jelzett időpontokból. Nézd át, hogy tényleg azt
találod-e itt, amire számítasz — <strong>semmit nem állítottunk vissza és semmi nem változott.</strong>
{{T "recovery.ezek_a_te_menteseid_a"}}
</p>
<table class="data-table" style="width:100%;margin-bottom:1rem">
<thead><tr><th>Alkalmazás</th><th>Legutóbbi mentés</th><th>Méret</th></tr></thead>
<thead><tr><th>{{T "recovery.alkalmazas"}}</th><th>{{T "recovery.legutobbi_mentes"}}</th><th>{{T "recovery.meret"}}</th></tr></thead>
<tbody>
{{range .InvApps}}
<tr>
@@ -54,54 +50,46 @@
{{end}}
<p class="form-hint">
A visszaállítás alkalmazásonként történik, a <strong>Biztonsági mentés → Visszaállítás</strong>
oldalon. Ott választhatod ki, melyik alkalmazás mit hozzon vissza.
{{T "recovery.a_visszaallitas_alkalmazasonkent_torteni"}}
</p>
<div class="form-actions">
<a href="/backups/restore" class="btn btn-primary">Tovább a visszaállításhoz</a>
<a href="/launcher" class="btn btn-outline">Vissza a kezdőlapra</a>
<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">Adatok <span class="title-accent">visszaszerzése</span></h1>
<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>
Ezt a gépet újratelepítették. A korábbi, <strong>házon kívüli mentéseid megvannak</strong> — a
Felhom központi rendszere őriz hozzájuk egy lezárt csomagot{{with .SealedAt}}, amelyet
<strong>{{.}}</strong> zártunk le{{end}}. A csomagot csak a <strong>te helyreállítási
kódoddal</strong> lehet kinyitni.
{{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">
<strong>A helyreállítási kódot senki nem tudja pótolni</strong> — sem a Felhom, sem az
ügyfélszolgálat, sem az üzemeltető. Ez szándékos: így a mentéseidet rajtad kívül senki nem
tudja megnyitni. Ha a kód elveszett, a korábbi mentések nem nyithatók meg többé.
{{T "recovery.a_helyreallitasi_kodot_senki_nem"}}
</div>
<p>
Ha megadod a kódot, <strong>feloldjuk a mentéseid zárolását és megmutatjuk, mi van bennük</strong>
— melyik alkalmazás, mikorról, mekkora. <strong>Ebben a lépésben semmit nem állítunk vissza és
semmi nem változik.</strong> A visszaállítást utána, alkalmazásonként külön választhatod.
{{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">Helyreállítási kód (tíz szó)</label>
<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íz szó, szóközökkel elválasztva" required>
placeholder="{{T "recovery.tiz_szo_szokozokkel_elvalasztva"}}" required>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Mentések feloldása</button>
<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">Most nem</button>
<button type="submit" class="btn btn-outline">{{T "recovery.most_nem"}}</button>
</form>
</div>
</form>
@@ -133,7 +121,7 @@
ev.preventDefault();
box.style.display = 'none';
var btn = form.querySelector('button[type=submit]');
if (btn) { btn.disabled = true; btn.textContent = 'Feloldás folyamatban…'; }
if (btn) { btn.disabled = true; btn.textContent = '{{T "recovery.feloldas_folyamatban"}}'; }
fetch(form.action, {
method: 'POST',
body: new FormData(form),
@@ -147,10 +135,10 @@
}).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 = 'Mentések feloldása'; }
box.textContent = 'A gép éppen újraindul, ezért most nem tudtuk befejezni a műveletet. '
+ 'Semmi nem változott. Várj néhány másodpercet, és próbáld újra — a kódodra továbbra is szükséged lesz, '
+ 'úgyhogy tartsd kéznél.';
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 = '';
});
});
@@ -158,9 +146,7 @@
</script>
<p class="form-hint">
A „Most nem” csak azt jelenti, hogy nem zavarunk vele többet a kezdőlapon. A mentéseid ettől
megmaradnak, és ez az oldal a <strong>Biztonsági mentés → Távoli mentés</strong> oldalról
bármikor újra elérhető.
{{T "recovery.a_most_nem_csak_azt"}}
</p>
<!-- ── THE EXCEPTIONAL PATH. Deliberately not an equal third button. ───────────────────── -->
@@ -172,34 +158,32 @@
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>Biztosan nem kéred vissza a korábbi mentéseket?</strong></p>
<p>Ha megerősíted:</p>
<p><strong>{{T "recovery.biztosan_nem_kered_vissza_a"}}</strong></p>
<p>{{T "recovery.ha_megerosited"}}</p>
<ul>
<li>a korábbi mentéseket <strong>most félretesszük</strong>, és <strong>{{.AbandonGraceDays}} nap múlva véglegesen töröljük</strong> — a lezárt helyreállítási csomaggal együtt;</li>
<li>a {{.AbandonGraceDays}} nap alatt <strong>meggondolhatod magad</strong>: ha előkerül a helyreállítási kódod, a mentéseid visszaszerezhetők, és a törlés elmarad;</li>
<li>a pontos dátumot a <strong>Távoli mentés</strong> oldalon végig látni fogod, és emlékeztetni is fogunk;</li>
<li>a gép <strong>új, üres mentési tárolót kezd</strong>, és mostantól oda ment;</li>
<li>a törlés után <strong>ez a kérdés nem jön vissza többé</strong> — mert nem marad mit visszaszerezni.</li>
<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>Ha csak most nincs kéznél a kódod, válaszd inkább a „Most nem” lehetőséget — az semmit nem indít el.</p>
<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">Igen, félretehetitek a korábbi mentéseket</button>
<button type="submit" class="btn btn-danger">{{T "recovery.igen_felretehetitek_a_korabbi_menteseket"}}</button>
</form>
<a href="/recovery" class="btn btn-outline">Mégsem</a>
<a href="/recovery" class="btn btn-outline">{{T "recovery.megsem"}}</a>
</div>
{{else}}
<p class="form-hint">
Ha a helyreállítási kódod véglegesen elveszett, és tudomásul veszed, hogy a korábbi mentések
így nem nyithatók meg többé:
{{T "recovery.ha_a_helyreallitasi_kodod_veglegesen"}}
{{if .CanSetAside}}
<a href="/recovery?setaside=1">nem kérem vissza a korábbi adatokat</a>.
{{T "recovery.nem_kerem_vissza_a_korabbi"}}
{{else}}
ez a lehetőség akkor válik elérhetővé, ha a gép már újra kapcsolódott a házon kívüli
tárhelyhez. Addig a mentéseid érintetlenül megmaradnak.
{{T "recovery.ez_a_lehetoseg_akkor_valik"}}
{{end}}
</p>
{{end}}
@@ -2,8 +2,8 @@
{{template "layout_start" .}}
<div class="page-header">
<h2>2. mentés beállítása — {{.DisplayName}}</h2>
<a href="/backups/apps" class="btn btn-outline btn-sm">← Vissza a mentésekhez</a>
<h2>{{T "tier2_config.2_mentes_beallitasa"}}</h2>
<a href="/backups/apps" class="btn btn-outline btn-sm">{{T "tier2_config.vissza_a_mentesekhez"}}</a>
</div>
{{if .Flash}}<div class="monitoring-banner monitoring-banner-green">{{.Flash}}</div>{{end}}
@@ -12,51 +12,44 @@
<div class="monitor-card">
{{with .Tier2}}
<p style="color:var(--text-3);font-size:.9rem;margin-top:0">
A 2. mentés egy <strong>másik fizikai meghajtóra</strong> készít másolatot az alkalmazás
helyreállítási csomagjáról és adatairól. Ez az egyetlen off-drive védelem a böngészhető
felhasználói fájlokhoz (a teljes rendszermentés/PBS nem éri el ezeket).
{{T "tier2_config.a_2_mentes_egy_masik"}}
</p>
{{if not .IsHDDApp}}
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem">
Ennek az alkalmazásnak az adatai a belső rendszerlemezen vannak, amelyek
<strong>már szerepelnek a teljes rendszermentésben (PBS)</strong>. A 2. (off-drive) másolat
kiegészítő, és elsősorban a külső adatmeghajtón tárolt alkalmazásokhoz készül — ehhez az
alkalmazáshoz nincs külön teendő.
{{T "tier2_config.ennek_az_alkalmazasnak_az_adatai"}}
</div>
{{else}}
<h3 style="margin-top:1.5rem">Jelenlegi állapot</h3>
<h3 style="margin-top:1.5rem">{{T "tier2_config.jelenlegi_allapot"}}</h3>
<div class="sysinfo-grid">
<div class="sysinfo-row">
<span class="sysinfo-label">2. mentés</span>
<span class="sysinfo-value">{{if .Disabled}}Kikapcsolva{{else}}Bekapcsolva{{end}}</span>
<span class="sysinfo-label">{{T "tier2_config.2_mentes"}}</span>
<span class="sysinfo-value">{{if .Disabled}}{{T "tier2_config.kikapcsolva"}}{{else}}{{T "tier2_config.bekapcsolva"}}{{end}}</span>
</div>
{{if .NoTarget}}
<div class="sysinfo-row">
<span class="sysinfo-label">Cél</span>
<span class="sysinfo-value text-error">Nincs elérhető off-drive cél</span>
<span class="sysinfo-label">{{T "tier2_config.cel"}}</span>
<span class="sysinfo-value text-error">{{T "tier2_config.nincs_elerheto_off_drive_cel"}}</span>
</div>
<div class="sysinfo-row">
<span class="sysinfo-label">Megjegyzés</span>
<span class="sysinfo-label">{{T "tier2_config.megjegyzes"}}</span>
<span class="sysinfo-value" style="color:var(--text-3)">{{.NoTargetReason}}</span>
</div>
{{else}}
<div class="sysinfo-row">
<span class="sysinfo-label">Cél meghajtó</span>
<span class="sysinfo-value">{{.EffectiveLabel}}{{if .EffectiveIsSSD}} — csak DB/konfiguráció{{end}}</span>
<span class="sysinfo-label">{{T "tier2_config.cel_meghajto"}}</span>
<span class="sysinfo-value">{{.EffectiveLabel}}{{if .EffectiveIsSSD}} {{T "tier2_config.csak_db_konfiguracio"}}{{end}}</span>
</div>
<div class="sysinfo-row">
<span class="sysinfo-label">Kiválasztás módja</span>
<span class="sysinfo-value" style="color:var(--text-3)">{{if .Preferred}}kézi választás{{else}}automatikus{{end}} — {{.EffectiveDesc}}</span>
<span class="sysinfo-label">{{T "tier2_config.kivalasztas_modja"}}</span>
<span class="sysinfo-value" style="color:var(--text-3)">{{if .Preferred}}{{T "tier2_config.kezi_valasztas"}}{{else}}{{T "tier2_config.automatikus_kisbetu"}}{{end}} — {{.EffectiveDesc}}</span>
</div>
{{end}}
</div>
{{if .EffectiveIsSSD}}
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem">
Jelenleg csak a belső SSD érhető el 2. célként, ezért csak az adatbázis és a konfiguráció
másolódik. A belső rendszerlemez kicsi, ezért a nagy fájlok off-drive mentéséhez egy
<strong>2. adatmeghajtó</strong> szükséges (hogy a rendszerlemez ne teljen meg).
{{T "tier2_config.jelenleg_csak_a_belso_ssd"}}
</div>
{{end}}
@@ -65,25 +58,24 @@
<div class="form-group">
<label style="display:flex;align-items:center;gap:.5rem">
<input type="checkbox" name="enabled" value="on" {{if not .Disabled}}checked{{end}}>
2. mentés bekapcsolva
{{T "tier2_config.2_mentes_bekapcsolva"}}
</label>
</div>
<div class="form-group">
<label for="target">Cél meghajtó</label>
<label for="target">{{T "tier2_config.cel_meghajto"}}</label>
<select id="target" name="target" class="form-control">
<option value="">Automatikus{{if not .NoTarget}} (jelenleg: {{.EffectiveLabel}}){{end}}</option>
<option value="">{{T "tier2_config.automatikus"}}{{if not .NoTarget}} {{T "tier2_config.jelenleg"}}{{end}}</option>
{{range .Alternatives}}
<option value="{{.Path}}" {{if eq .Path $.Tier2.Preferred}}selected{{end}}>{{.Label}}</option>
{{end}}
</select>
{{if not .Alternatives}}
<div style="font-size:.8rem;color:var(--text-3);margin-top:.4rem">
Nincs másik adatmeghajtó — automatikus cél a belső SSD (csak DB/konfiguráció). Egy 2.
adatmeghajtó hozzáadásával a teljes adat is off-drive menthető.
{{T "tier2_config.nincs_masik_adatmeghajto_automatikus_cel"}}
</div>
{{end}}
</div>
<button type="submit" class="btn btn-primary">Mentés</button>
<button type="submit" class="btn btn-primary">{{T "tier2_config.mentes"}}</button>
</form>
{{end}}
{{end}}
File diff suppressed because it is too large Load Diff