f806baf4c7
- backups_remote.html: the status stat carries data-status="{{.Offbox.LastStatus}}"; the poll reads
v.dataset.status === 'running' instead of v.textContent.indexOf('Fut'). The English page could never
see a running backup before this (slice 1 shipped the English page with that word left Hungarian).
- The running label becomes {{T "backups_remote.fut"}} — hu „Fut…" unchanged, en „Running…".
- 12 backups_remote parity fixtures re-captured. Proven (audit switch/fixture diff): each differs from
its predecessor ONLY by the data-status attribute and that one poll line; the other 94 are
byte-identical. The displayed Hungarian is unchanged.
- Tests: TestR563_PollStartsFromAttribute (hu and en), TestR563_AttributeFollowsTheStatus.
Red-proofed twice: poll back on textContent → both languages fail; word back inline → the English
page shows Hungarian.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
366 lines
26 KiB
HTML
366 lines
26 KiB
HTML
{{define "backups_remote"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>{{T "backups_remote.biztonsagi_mentes_tavoli_mentes"}}</h2>
|
||
<span class="domain-badge">{{.Domain}}</span>
|
||
</div>
|
||
|
||
{{template "backups_flash" .}}
|
||
|
||
{{if .RecoveryOffer}}
|
||
<!-- 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">
|
||
{{T "backups_remote.a_korabbi_hazon_kivuli_menteseid"}}
|
||
<div class="form-actions" style="margin-top:.5rem">
|
||
<a href="/recovery" class="btn btn-primary">{{T "backups_remote.adatok_visszaszerzese"}}</a>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
|
||
{{if not .Backup}}
|
||
{{template "backups_empty" .}}
|
||
{{else}}
|
||
|
||
<!-- Felhom-offsite status card (v0.124.0, decision 2): three honest states from LOCAL data only —
|
||
display + opt-in pointers, NEVER a state change (no form, no button in this card). State 3
|
||
(applied + apps toggled) is the existing status block below; the card renders nothing then. -->
|
||
{{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">{{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">{{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">{{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 "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" data-status="{{.Offbox.LastStatus}}" 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"}}{{T "backups_remote.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 "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}}{{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)}}
|
||
<!-- R-225: the BAR renders only when the fill is known. A 0%-wide bar over an unread store is a
|
||
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 "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}}
|
||
</div>
|
||
{{end}}
|
||
{{/* R-228 — THE SET-ASIDE HISTORY IS SAID OUT LOUD.
|
||
The customer chose "I do not want the old data", was told it would be KEPT and not deleted,
|
||
and then it vanished from every screen: the box recorded exactly where it went
|
||
(OrphanedRenamedTo) and showed that to nobody. Measured 2026-08-05 (CAMPAIGN-11 F7) —
|
||
12 535 KB at a path with zero references in any template or handler.
|
||
|
||
NOTE — IT STATES TWO FACTS AND STOPS. It does NOT promise the history can be reopened, because it
|
||
cannot be: serving a superseded package is an unbuilt link (R-199's inventory). A conditional
|
||
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>{{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)">{{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}}
|
||
{{/* v0.142.0 offsite-repo continuity: the ORPHANED card replaces the raw restic error banner. The
|
||
remote holds backups written under a previous, no-longer-available key (reinstall shape). A
|
||
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">{{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">{{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,
|
||
which lives on the HUB; the box caches only HubEscrowIdentityPresent (about the
|
||
CURRENT escrow) and no report or ACK field carries superseded-blob retention. So the
|
||
renderer cannot evaluate the condition it was stating — and a conditional promise the
|
||
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">{{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">{{T "backups_remote.megerosites_uj_tavoli_mentes_inditasa"}}</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
{{/* Escrow ceremony card (v0.127.0): the customer-driveable wizard replaced the manual-confirm
|
||
button (that deprecated endpoint stays for legacy blobs; its button is gone). States:
|
||
awaiting (v0.138.0: ceremony done, hub confirm pending) → info; awaiting timed out → warn +
|
||
re-ceremony CTA; pending → CTA; escrowed+stale → warning + re-ceremony CTA; escrowed clean →
|
||
secondary link; agent too old → the honest version note, no CTA. */}}
|
||
{{if .OffboxCeremonyAwaiting}}
|
||
{{/* 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>{{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">{{T "backups_remote.a_megerosites_nem_erkezett_meg"}}</p>
|
||
{{if .EscrowAgentOK}}
|
||
<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">{{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>{{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
|
||
the recovery screen through the orphan route while invalidating the code that screen
|
||
accepts. The button is therefore made UNAVAILABLE here rather than merely captioned:
|
||
a warning beside a button is a warning people click past. */}}
|
||
{{if .RecoveryOffer}}
|
||
<p class="form-hint" style="margin:0">
|
||
{{T "backups_remote.ehhez_a_gephez_egy_korabbi"}}
|
||
</p>
|
||
{{else if .EscrowAgentOK}}
|
||
<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">{{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">{{T "backups_remote.a_letetben_levo_helyreallitasi_csomag"}}</p>
|
||
{{if .EscrowAgentOK}}
|
||
<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">{{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">{{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>{{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. */}}
|
||
{{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">{{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">{{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 "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
|
||
pushed, parsed from restic's --json status stream. Hidden until a run reports progress; the
|
||
nightly run never populates it. -->
|
||
<div id="offbox-progress" class="alert alert-info" style="display:none;margin-top:.75rem">
|
||
<span id="offbox-progress-text"></span>
|
||
<div class="progress-bar-task" style="margin-top:.5rem">
|
||
<div id="offbox-progress-bar" class="progress-fill" style="width:0%"></div>
|
||
</div>
|
||
</div>
|
||
<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">{{T "backups_remote.nincs_tavoli_mentesre_jelolt_alkalmazas"}}</p>
|
||
{{end}}
|
||
{{if .OffboxApps}}
|
||
<div class="app-row-list">
|
||
{{range .OffboxApps}}
|
||
{{template "app_list_row" dict "Slug" .Slug "Name" .DisplayName}}
|
||
<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 "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">{{T "backups_remote.a_teljes_mentes_tullepne_a"}}</span>
|
||
{{end}}{{end}}
|
||
{{template "app_list_row_end"}}
|
||
{{end}}
|
||
</div>
|
||
{{else}}<p class="form-hint">{{T "backups_remote.nincs_telepitett_alkalmazas"}}</p>{{end}}
|
||
{{end}}
|
||
{{else}}
|
||
{{if .OffsiteHubEnabled}}
|
||
<p class="form-hint">{{T "backups_remote.felhom_offsite_tarhely_kiepitve_a"}}</p>
|
||
{{else}}
|
||
<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 "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>{{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>{{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">{{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">{{T "backups_remote.a_host_kulcs_rogzitese_no"}} <span class="mono">ssh-keyscan -p <port> <host></span></span></div>
|
||
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">{{T "backups_remote.mentes"}}</button>
|
||
</form>
|
||
</details>
|
||
</div>
|
||
{{end}}
|
||
|
||
<script>
|
||
/* Part C (v0.142.0) + 4c (v0.147.0): poll the run status, render live progress for a manual run, and
|
||
reload once it reaches a terminal state so the customer sees Rendben/Hiba + fresh numbers WITHOUT a
|
||
manual reload.
|
||
|
||
v0.147.0 changed WHEN polling starts. It used to begin only if the page already rendered "Fut…",
|
||
which loses a race the manual trigger always runs: the POST redirects and this page renders before
|
||
the detached goroutine has written LastStatus=running, so the poll never armed and the customer
|
||
watched a static page during the very run they had just started. Now it always arms and gives up
|
||
after GRACE quiet ticks if nothing is (or ever was) in flight. */
|
||
(function(){
|
||
var v = document.getElementById('offbox-status-value');
|
||
var box = document.getElementById('offbox-progress');
|
||
var text = document.getElementById('offbox-progress-text');
|
||
var bar = document.getElementById('offbox-progress-bar');
|
||
var GRACE = 5; // ~15s of quiet before concluding nothing is running
|
||
var quiet = 0;
|
||
// R-563: the page asks the ELEMENT what the status is (data-status, rendered server-side from
|
||
// Offbox.LastStatus), never the word it displays. Reading „Fut" meant an English page never noticed
|
||
// a running backup — and the word could not be translated while this line existed.
|
||
var sawRunning = (v && v.dataset.status === 'running');
|
||
|
||
var timer = setInterval(function(){
|
||
fetch('/backup/offbox/status',{headers:{'Accept':'application/json'}})
|
||
.then(function(r){return r.json();})
|
||
.then(function(d){
|
||
if(!d) return;
|
||
var p = d.progress || {};
|
||
var running = (d.status === 'running') || p.active;
|
||
|
||
if(running){
|
||
sawRunning = true; quiet = 0;
|
||
if(p.active && box){
|
||
/* Only claim a percentage once restic has told us a total — before the scan finishes,
|
||
percent is 0 of 0, and a bar pinned at 0% reads as "stuck" rather than "measuring". */
|
||
/* A run is not only the per-app loop: the shares leg and the retention/prune step follow
|
||
it and can dominate the wall clock (40 of 57 seconds, measured). Name them, or the
|
||
card freezes on the last app's finished counters for that whole tail. */
|
||
/* The dump pre-phase runs BEFORE the per-app loop (R-44): it refreshes every app's
|
||
database dump so the snapshot pairs this run's files with this run's DB. On an app
|
||
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 = '{{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 = '{{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 = '{{T "backups_remote.megosztott_mappak_mentese"}}';
|
||
bar.style.width = '100%';
|
||
box.className = 'alert alert-info'; box.style.display = '';
|
||
return;
|
||
}
|
||
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 + ')';
|
||
bar.style.width = Math.max(0, Math.min(100, p.percent)) + '%';
|
||
} else if(p.total_files > 0){
|
||
/* No bytes moving. restic 0.14 only counts a file into bytes_done/files_done when it
|
||
COMPLETES, so an app dominated by one big archive freezes both counters — measured
|
||
on the demo box: immich at 1 of 46 files, 0 bytes, for 42 seconds while restic
|
||
worked through a single ~430MB volume tar. A percentage cannot move in that window,
|
||
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 + ' {{T "backups_remote.fajl"}}'
|
||
+ (p.total_human ? ' (' + p.total_human + ')' : '');
|
||
if(p.current_file){
|
||
var base = p.current_file.split('/').pop();
|
||
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 += ' {{T "backups_remote.a_mentendo_adatok_felmerese"}}';
|
||
bar.style.width = '0%';
|
||
}
|
||
text.textContent = label;
|
||
box.style.display = '';
|
||
}
|
||
return;
|
||
}
|
||
|
||
/* Not running. Terminal only if we ever saw it running — otherwise this is the pre-start
|
||
race (or a page nobody triggered anything from). */
|
||
if(sawRunning){ clearInterval(timer); location.reload(); return; }
|
||
if(++quiet >= GRACE){ clearInterval(timer); }
|
||
})
|
||
.catch(function(){ /* transient — keep polling */ });
|
||
}, 3000);
|
||
})();
|
||
</script>
|
||
|
||
{{template "layout_end" .}}
|
||
{{end}}
|