63a22e5911
Third real run, third thing only a live run could show. The per-app legs finished in ~15 seconds; the remaining 40 of the 57-second run was the shares leg and forget --prune, during which the card sat frozen on "calibre-web — 8 / 8 fájl". The same frozen-looking silence 4c exists to remove, relocated to the end. Progress now carries a phase. The post-app stages announce themselves and the app-scoped counters are cleared when a phase starts, so the last app's finished numbers are never shown against work that is no longer about that app. Starting the next app clears the phase again. Pinned by a test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
273 lines
19 KiB
HTML
273 lines
19 KiB
HTML
{{define "backups_remote"}}
|
||
{{template "layout_start" .}}
|
||
|
||
<div class="page-header">
|
||
<h2>Biztonsági mentés — Távoli mentés</h2>
|
||
<span class="domain-badge">{{.Domain}}</span>
|
||
</div>
|
||
|
||
{{template "backups_flash" .}}
|
||
|
||
{{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 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>
|
||
{{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>
|
||
{{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>
|
||
<div class="card" style="margin-bottom:1.5rem">
|
||
{{if .Offbox}}
|
||
<div class="stats-grid backup-page-cards">
|
||
<div class="stat-card {{if eq .Offbox.LastStatus "error"}}stat-warn{{end}}">
|
||
<div class="stat-value" id="offbox-status-value" style="font-size:1.15rem">{{if eq .Offbox.LastStatus "ok"}}✓ Rendben{{else if eq .Offbox.LastStatus "error"}}✗ 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>
|
||
<div class="stat-card">
|
||
<div class="stat-value" style="font-size:1.15rem">{{if .Offbox.RepoSizeHuman}}{{.Offbox.RepoSizeHuman}}{{else}}–{{end}}</div>
|
||
<div class="stat-label">Tároló méret · {{.Offbox.SnapshotCount}} pillanatkép</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>
|
||
</div>
|
||
{{if and .Offbox.Enabled (gt .Offbox.QuotaGB 0)}}
|
||
<!-- 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.RepoSizeHuman}}{{.Offbox.RepoSizeHuman}}{{else}}0{{end}} / {{.Offbox.QuotaGB}} GB ({{.OffboxQuotaPct}}%)</div>
|
||
<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>
|
||
</div>
|
||
{{end}}
|
||
{{if .Offbox.LastError}}<p class="form-hint" style="color:var(--crit)">Utolsó hiba: {{.Offbox.LastError}}</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">A távoli tároló másik kulccsal készült mentéseket tartalmaz</p>
|
||
<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 — a hozzájuk tartozó helyreállítási kóddal később visszaállíthatók lehetnek.</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>
|
||
<div id="orphan-confirm" style="display:none;margin-top:.6rem">
|
||
<p class="form-hint" style="margin:0 0 .5rem">A régi előzmény <strong>félretéve marad</strong> (nem törlődik), és a hozzá tartozó helyreállítási kóddal később visszaállítható lehet. Egy üres, új tároló jön létre a mostani kulccsal, és a következő mentés ide készül.</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>
|
||
</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>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>
|
||
</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>
|
||
{{if .EscrowAgentOK}}
|
||
<a href="/backup/escrow" class="btn btn-sm btn-primary">Új helyreállítási kód készítése</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>
|
||
{{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>
|
||
{{if .EscrowAgentOK}}
|
||
<a href="/backup/escrow" class="btn btn-sm btn-primary">Helyreállítási kód létrehozása</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>
|
||
{{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>
|
||
{{if .EscrowAgentOK}}
|
||
<a href="/backup/escrow" class="btn btn-sm btn-primary">Új helyreállítási kód készítése</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>
|
||
{{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}}
|
||
</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>
|
||
</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">Mely alkalmazások mentődnek a távoli tárolóra?</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>
|
||
{{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ávoli mentés kikapcsolása{{else}}Távoli mentés bekapcsolása{{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>
|
||
{{end}}{{end}}
|
||
{{template "app_list_row_end"}}
|
||
{{end}}
|
||
</div>
|
||
{{else}}<p class="form-hint">Nincs telepített alkalmazás.</p>{{end}}
|
||
{{end}}
|
||
{{else}}
|
||
<p class="form-hint">Még nincs beállítva távoli mentési cél.</p>
|
||
{{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>
|
||
<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>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>
|
||
<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>
|
||
<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 <port> <host></span></span></div>
|
||
<button type="submit" class="btn btn-sm btn-primary" style="margin-top:.5rem">Mentés</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;
|
||
var sawRunning = (v && v.textContent.indexOf('Fut') >= 0);
|
||
|
||
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. */
|
||
if(p.phase === 'retention'){
|
||
text.textContent = 'Karbantartás: régi mentések rendezése a távoli tárolón…';
|
||
bar.style.width = '100%';
|
||
box.className = 'alert alert-info'; box.style.display = '';
|
||
return;
|
||
}
|
||
if(p.phase === 'shares'){
|
||
text.textContent = 'Megosztott mappák mentése…';
|
||
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';
|
||
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 + ' fájl'
|
||
+ (p.total_human ? ' (' + p.total_human + ')' : '');
|
||
if(p.current_file){
|
||
var base = p.current_file.split('/').pop();
|
||
label += ' · feldolgozás 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…';
|
||
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}}
|