Files
felhom.eu/documentation/tests/campaign2-evidence-2026-07-07/evidence/T-PAGE-ALL/body__backups.html
T
admin 7714f1f6e4 docs(tests): Campaign 2 report + evidence — DR primitives, self-update, storage, faults, endurance
Full-system validation on demo 9201 (controller v0.102.0 / agent v0.73.0). Headline:
ONE genuine pilot-critical finding — the non-root agent cannot pct-restore the
standard Felhom guest because it has bind-mount mountpoints (mp8 /mnt/felhom-drives,
mp9 bootstrap) and pct restore refuses bind mounts unless root. This breaks BOTH
--selftest=restore-test AND the offsite DR bring-up (R1), linked to a missing
VM.Audit/VM.Allocate on the agent token (R2). The large raw FAIL count is dominated
by one harness bug (auth-session expiry mis-scoring P3-P9) plus D1-injection/soak-probe/
dryrun-journal/escrow-blob calibration issues; the product where actually exercised was
healthy with ZERO resource leaks over ~7h. Demo node healed to pre-campaign state
(auth off, notifications restored, no scratch leaks). No production code changed.

Evidence snapshot + README under campaign2-evidence-2026-07-07/ (cookie jars scrubbed).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-07 14:26:52 +02:00

1903 lines
98 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Biztonsági mentés — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/style.css">
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<script>
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
</svg>
<nav class="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
<span class="customer-name">Demo Ügyfél</span>
</div>
<ul class="nav-links">
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
<ul class="nav-links nav-links-sub nav-links-nested">
<li><a href="/storage" class="">Meghajtók</a></li>
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
</ul>
</li>
<li><a href="/backups" class="active"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
</ul>
<div class="sidebar-bottom">
<div class="nav-group-label">Beállítások</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">0.102.0</span>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="alerts-container">
<div class="alert-banner alert-banner-error">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
<a href="/settings" class="alert-link">Beállítások →</a>
</div>
</div>
<div class="page-header">
<h2>Biztonsági mentés</h2>
<span class="domain-badge">demo-felhom.eu</span>
</div>
<div id="restore-banner" class="flash" style="display:none"></div>
<div class="backup-section-card">
<h3>Tárhely áttekintés</h3>
<div class="storage-overview-grid">
<div class="storage-bars">
<div class="storage-item meter nominal">
<div class="storage-header">
<span class="storage-label">Rendszer (/)</span>
<span class="storage-value">20.9 GB / 196 GB (11%)</span>
</div>
<div class="meter-track">
<div class="meter-fill" style="width:10.7%"></div>
</div>
</div>
<div class="storage-item meter nominal">
<div class="storage-header">
<span class="storage-label">Tárhely (felhom-flash)</span>
<span class="storage-value">4.02 GB / 117 GB (3%)</span>
</div>
<div class="meter-track">
<div class="meter-fill" style="width:3.4%"></div>
</div>
</div>
<div class="storage-item meter nominal">
<div class="storage-header">
<span class="storage-label">Tárhely (felhom-usb)</span>
<span class="storage-value">1.21 GB / 916 GB (0%)</span>
</div>
<div class="meter-track">
<div class="meter-fill" style="width:0.1%"></div>
</div>
</div>
<div class="storage-item meter nominal">
<div class="storage-header">
<span class="storage-label">teszt_enroll</span>
<span class="storage-value">0.00 GB / 58.2 GB (0%)</span>
</div>
<div class="meter-track">
<div class="meter-fill" style="width:0.0%"></div>
</div>
</div>
</div>
<div class="storage-stats">
<div class="storage-stat-row">
<span class="storage-stat-label">DB mentések</span>
<span class="storage-stat-value mono">5 fájl</span>
</div>
</div>
</div>
</div>
<div class="backup-section-card">
<h3>Rendszermentés (teljes mentés)</h3>
<p class="form-hint" style="margin-bottom:1rem">A teljes szerver — alkalmazások, beállítások és adatbázisok együtt — időszakos mentése, amelyből az egész készülék visszaállítható. Ezt a host-ügynök készíti és kezeli.</p>
<div class="stats-grid backup-page-cards">
<div class="stat-card stat-stopped">
<div class="stat-value">&#10007;</div>
<div class="stat-label">Utolsó teljes mentés
<br><span class="relative-time">2026-07-07 00:48 (4 perce)</span>
</div>
</div>
<div class="stat-card stat-total">
<div class="stat-value">0 B</div>
<div class="stat-label">Helyi tároló (local)</div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.15rem">Esedékes</div>
<div class="stat-label">Következő mentés
</div>
</div>
<div class="stat-card">
<div class="stat-value"></div>
<div class="stat-label">Visszaállítás ellenőrizve
<br><span class="relative-time">Még nem futott</span>
</div>
</div>
</div>
<div class="schedule-actions" style="margin-top:1rem">
<button class="btn btn-sm btn-primary" id="wg-backup-btn" onclick="triggerGuestBackup()" >Mentés most</button>
<span class="form-hint" style="margin-left:.5rem">Pillanatkép-mód: az alkalmazások csak néhány másodpercre állnak le.</span>
<div id="wg-backup-result" style="margin-top:.6rem"></div>
</div>
</div>
<h3 class="backup-tier-divider">Külső (NAS) mentés — titkosított, off-site</h3>
<p class="form-hint" style="margin:-0.25rem 0 1rem">Az alkalmazások adatainak + adatbázis-kiírásainak titkosított mentése a saját NAS-odra (restic, SFTP-n). A NAS csak titkosított adatot lát. Ez a 3-2-1 „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">
<div class="stats-grid backup-page-cards">
<div class="stat-card ">
<div class="stat-value" style="font-size:1.15rem">&#10003; Rendben</div>
<div class="stat-label">Utolsó NAS-mentés<br><span class="relative-time">5 órája</span></div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.15rem">292.4 KB</div>
<div class="stat-label">Tároló méret · 1 pillanatkép</div>
</div>
<div class="stat-card">
<div class="stat-value" style="font-size:1.05rem">kisfenyo@192.168.0.180</div>
<div class="stat-label">/home/kisfenyo/campaign-nomercy-2026-07-05/offbox-target</div>
</div>
</div>
<div class="schedule-actions" style="margin-top:1rem">
<form method="POST" action="/backup/offbox/run" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<button type="submit" class="btn btn-sm btn-primary">NAS-mentés most</button>
</form>
</div>
<h4 style="margin-top:1.25rem">Mely alkalmazások mentődnek a NAS-ra?</h4>
<div class="storage-paths-list">
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">ActualBudget</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="actualbudget">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">BookStack</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="bookstack">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Cal.com</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="calcom">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Gitea</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="gitea">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Mealie</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="mealie">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Navidrome</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="navidrome">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Nextcloud</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="nextcloud">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Paperless-ngx</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="paperless-ngx">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Rallly</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="rallly">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">SparkyFitness</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="sparkyfitness">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Uptime Kuma</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="uptime-kuma">
<input type="hidden" name="enabled" value="true">
<button type="submit" class="btn btn-xs btn-primary">NAS-mentés bekapcsolása</button>
</form>
</div>
</div>
</div>
<div class="storage-path-item">
<div class="storage-path-header">
<div class="storage-path-info"><span class="storage-path-label">Vaultwarden</span></div>
<div class="storage-path-actions">
<form method="POST" action="/backup/offbox/toggle" style="display:inline"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="vaultwarden">
<input type="hidden" name="enabled" value="false">
<button type="submit" class="btn btn-xs btn-outline">NAS-mentés kikapcsolása</button>
</form>
<form method="POST" action="/backup/offbox/restore" style="display:inline" onsubmit="return confirm('Visszaállítja a(z) Vaultwarden adatait a NAS-ról egy ellenőrző mappába? A meglévő adatok NEM íródnak felül.')"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="app" value="vaultwarden">
<button type="submit" class="btn btn-xs btn-outline">Visszaállítás (ellenőrzéshez)</button>
</form>
</div>
</div>
</div>
</div>
<details style="margin-top:1rem">
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">NAS 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"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<div class="form-row"><label>NAS címe (IP vagy hosztnév)</label><input type="text" name="host" class="form-input" value="192.168.0.180" placeholder="pl. 192.168.0.10"></div>
<div class="form-row"><label>SSH port</label><input type="number" name="port" class="form-input" value="22"></div>
<div class="form-row"><label>Felhasználó</label><input type="text" name="user" class="form-input" value="kisfenyo" placeholder="pl. felhom"></div>
<div class="form-row"><label>Tároló útvonala a NAS-on</label><input type="text" name="repo_path" class="form-input" value="/home/kisfenyo/campaign-nomercy-2026-07-05/offbox-target" placeholder="pl. /volume1/felhom-backup/repo"></div>
<div class="form-row"><label><input type="checkbox" name="enabled" checked> Napi automatikus NAS-mentés engedélyezve</label></div>
<div class="form-row"><label>SSH privát kulcs (üresen hagyva változatlan)</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>NAS ismert-host sora (known_hosts) (üresen hagyva változatlan)</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>
</form>
</details>
</div>
<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>
<div class="stats-grid backup-page-cards">
<div class="stat-card stat-running">
<div class="stat-value">&#10003;</div>
<div class="stat-label">Adatmentés aktív</div>
</div>
<div class="stat-card" style="border-left-color: var(--text-3);">
<div class="stat-value" style="background:var(--text-3);-webkit-background-clip:text;background-clip:text;"></div>
<div class="stat-label">Távoli mentés<br><span class="relative-time">nincs beállítva</span></div>
</div>
<div class="stat-card stat-total">
<div class="stat-value">
5
</div>
<div class="stat-label">Adatbázis mentve</div>
</div>
</div>
<div class="schedule-card">
<h3>Ütemezés</h3>
<div class="schedule-rows">
<div class="schedule-row">
<span class="schedule-task">Adatbázis mentés</span>
<span class="schedule-time">02:30</span>
<span class="schedule-next">Következő: ma 02:30</span>
</div>
</div>
<div class="schedule-summary">
<div class="schedule-summary-row">
<span>Utolsó adatbázis mentés:</span>
<span class="schedule-summary-value">2026-07-06 20:32 (4 órája)</span>
</div>
</div>
<div class="schedule-actions">
<button class="btn btn-sm btn-primary" onclick="triggerBackupFromPage()" id="backup-page-btn"
>
Mentés most
</button>
</div>
</div>
<div class="backup-section-card">
<h3>Adatbázisok</h3>
<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>
</tr>
</thead>
<tbody>
<tr>
<td>bookstack</td>
<td><span class="db-type-badge db-type-mariadb">MariaDB</span></td>
<td class="mono">52.8 KB</td>
<td class="mono">07-06 20:32</td>
<td>
<span class="validation-badge validation-ok">40 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>calcom</td>
<td><span class="db-type-badge db-type-postgres">PostgreSQL</span></td>
<td class="mono">281.2 KB</td>
<td class="mono">07-06 20:32</td>
<td>
<span class="validation-badge validation-ok">72 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>rallly</td>
<td><span class="db-type-badge db-type-postgres">PostgreSQL</span></td>
<td class="mono">31.0 KB</td>
<td class="mono">07-06 20:32</td>
<td>
<span class="validation-badge validation-ok">13 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>sparkyfitness</td>
<td><span class="db-type-badge db-type-postgres">PostgreSQL</span></td>
<td class="mono">277.8 KB</td>
<td class="mono">07-06 20:32</td>
<td>
<span class="validation-badge validation-ok">68 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>nextcloud</td>
<td><span class="db-type-badge db-type-mariadb">MariaDB</span></td>
<td class="mono">220.1 KB</td>
<td class="mono">07-06 20:32</td>
<td>
<span class="validation-badge validation-ok">127 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="backup-section-card">
<h3>Alkalmazások mentési állapota</h3>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">ActualBudget</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/actualbudget/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">BookStack</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + DB + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/bookstack/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Cal.com</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + DB + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/calcom/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Gitea</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/gitea/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Mealie</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/mealie/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Navidrome</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method">rsync</span>
<span class="layer-dest">→ felhom-flash</span>
<span class="layer-schedule">Naponta</span>
<span class="layer-last">Utolsó: 2026-07-06T18:36:59Z
<span class="text-ok">
Sikeres
</span>
</span>
<span class="tier-size">535.2 KB</span>
<span class="tier-contents">Konfig &#43; Adatok</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>
<div class="layer-actions">
<form method="POST" action="/backup/tier2/restore" style="display:inline" onsubmit="return 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: 2026-07-06T18:36:59Z')"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="stack_name" value="navidrome">
<button type="submit" class="btn btn-xs btn-outline">Fájlok visszaállítása</button>
</form>
<a href="/stacks/navidrome/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Nextcloud</span>
<div class="app-backup-row-meta">
<span class="meta-badge meta-badge-storage">Tárhely (felhom-usb)</span>
<span class="mono app-backup-size" style="font-size:.8rem">36.8 MB</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method">rsync</span>
<span class="layer-dest">→ felhom-flash</span>
<span class="layer-schedule">Naponta</span>
<span class="layer-last">Utolsó: 2026-07-06T18:37:36Z
<span class="text-ok">
Sikeres
</span>
</span>
<span class="tier-size">969.2 MB</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</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>
<div class="layer-actions">
<form method="POST" action="/backup/tier2/restore" style="display:inline" onsubmit="return 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: 2026-07-06T18:37:36Z')"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="stack_name" value="nextcloud">
<button type="submit" class="btn btn-xs btn-outline">Fájlok visszaállítása</button>
</form>
<a href="/stacks/nextcloud/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Paperless-ngx</span>
<div class="app-backup-row-meta">
<span class="meta-badge meta-badge-storage">Tárhely (felhom-usb)</span>
<span class="mono app-backup-size" style="font-size:.8rem">40.4 KB</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method">rsync</span>
<span class="layer-dest">→ felhom-flash</span>
<span class="layer-schedule">Naponta</span>
<span class="layer-last">Utolsó: 2026-07-06T18:37:37Z
<span class="text-ok">
Sikeres
</span>
</span>
<span class="tier-size">67.8 MB</span>
<span class="tier-contents">Konfig &#43; Adatok</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>
<div class="layer-actions">
<form method="POST" action="/backup/tier2/restore" style="display:inline" onsubmit="return 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: 2026-07-06T18:37:37Z')"><input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
<input type="hidden" name="stack_name" value="paperless-ngx">
<button type="submit" class="btn btn-xs btn-outline">Fájlok visszaállítása</button>
</form>
<a href="/stacks/paperless-ngx/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Rallly</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + DB + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/rallly/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">SparkyFitness</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + DB + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">DB &#43; Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/sparkyfitness/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Uptime Kuma</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/uptime-kuma/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="green">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<span class="status-dot status-green" title="Alkalmazás-adat mentés rendben"></span>
<span class="app-backup-row-name">Vaultwarden</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
</div>
<span class="expand-icon"></span>
</div>
<div class="app-backup-row-detail" style="display:none">
<div class="backup-layers">
<div class="backup-layer-row">
<span class="tier-label">1. mentés</span>
<span class="layer-badge">Auto</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Konfig &#43; Adatok</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<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>
<div class="layer-actions">
<a href="/stacks/vaultwarden/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<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)">Hamarosan</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents" style="font-style:normal;color:var(--text-3)">B2 / S3 / SFTP — hamarosan elérhető</span>
</div>
</div>
</div>
</div>
</div>
<div class="repo-card">
<h3>Részletek</h3>
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title">1. szint — Helyi mentés (adatbázis + konfiguráció)</h4>
</div>
<div class="details-tier-body">
<div class="repo-info-rows" style="margin-top:0.5rem">
<div class="repo-info-row">
<span class="repo-label">Adatbázis mentések:</span>
<span class="repo-value">5 dump fájl — 863.0 KB</span>
</div>
</div>
</div>
</div>
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title">2. szint — Másodlagos másolat</h4>
</div>
<div class="details-tier-body" style="display:none">
<div class="tier-empty-state">Nincs 2. szintű mentés konfigurálva.</div>
</div>
</div>
<div class="details-tier">
<div class="details-tier-header" onclick="toggleTier(this)">
<span class="expand-icon"></span>
<h4 class="repo-tier-title" style="opacity:.6">3. szint — Távoli mentés (offsite)</h4>
</div>
<div class="details-tier-body" style="display:none">
<div class="tier-empty-state">B2 / S3 / SFTP — hamarosan elérhető</div>
</div>
</div>
</div>
<div class="backup-section-card">
<h3>Visszaállítás</h3>
<div class="restore-section">
<div class="restore-form-row">
<label class="restore-label">Alkalmazás:</label>
<select id="restore-app" class="restore-select" onchange="onRestoreAppChange()">
<option value="">— Válasszon —</option>
<option value="actualbudget" data-has-hdd="false" data-has-db="false" data-has-volumes="true">ActualBudget</option>
<option value="bookstack" data-has-hdd="false" data-has-db="true" data-has-volumes="true">BookStack</option>
<option value="calcom" data-has-hdd="false" data-has-db="true" data-has-volumes="true">Cal.com</option>
<option value="gitea" data-has-hdd="false" data-has-db="false" data-has-volumes="true">Gitea</option>
<option value="mealie" data-has-hdd="false" data-has-db="false" data-has-volumes="true">Mealie</option>
<option value="navidrome" data-has-hdd="false" data-has-db="false" data-has-volumes="true">Navidrome</option>
<option value="nextcloud" data-has-hdd="true" data-has-db="true" data-has-volumes="true">Nextcloud</option>
<option value="paperless-ngx" data-has-hdd="true" data-has-db="false" data-has-volumes="true">Paperless-ngx</option>
<option value="rallly" data-has-hdd="false" data-has-db="true" data-has-volumes="true">Rallly</option>
<option value="sparkyfitness" data-has-hdd="false" data-has-db="true" data-has-volumes="true">SparkyFitness</option>
<option value="uptime-kuma" data-has-hdd="false" data-has-db="false" data-has-volumes="true">Uptime Kuma</option>
<option value="vaultwarden" data-has-hdd="false" data-has-db="false" data-has-volumes="true">Vaultwarden</option>
</select>
</div>
<div class="restore-form-row">
<label class="restore-label">Pillanatkép:</label>
<select id="restore-snapshot" class="restore-select" onchange="onRestoreConfirmChange()">
<option value="">— Válasszon alkalmazást —</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.
</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.
</div>
<div class="restore-confirm">
<label>
<input type="checkbox" id="restore-confirm-cb" onchange="onRestoreConfirmChange()">
Megértettem, visszaállítás indítása.
</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>
</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>
</div>
</div>
</div>
<script>
(function(){
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' ? 'NAS-visszaállítás' : 'Visszaállítás'; }
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 : '') + '…';
return;
}
if (st.last && sawRunning) {
banner.style.display = 'block';
if (st.last.ok) {
banner.className = 'flash flash-success';
banner.textContent = st.last.message || (opLabel(st.last.op) + ' kész.');
} else {
banner.className = 'flash flash-error';
banner.textContent = st.last.message || (opLabel(st.last.op) + ' sikertelen.');
}
}
}
function poll(){
fetch('/api/backup/restore-status', {headers: {'Accept':'application/json'}})
.then(function(r){ return r.json(); })
.then(function(j){ if (j && j.data) render(j.data); })
.catch(function(){});
}
poll();
setInterval(poll, 3000);
})();
function toggleBackupDetail(header) {
var detail = header.nextElementSibling;
var icon = header.querySelector('.expand-icon');
if (detail.style.display === 'none') {
detail.style.display = 'block';
icon.textContent = '▼';
} else {
detail.style.display = 'none';
icon.textContent = '▶';
}
}
function toggleTier(header) {
var body = header.nextElementSibling;
var icon = header.querySelector('.expand-icon');
if (body.style.display === 'none') {
body.style.display = 'block';
icon.textContent = '▼';
} else {
body.style.display = 'none';
icon.textContent = '▶';
}
}
function triggerBackupFromPage() {
const btn = document.getElementById('backup-page-btn');
btn.disabled = true;
btn.textContent = 'Mentés indítása...';
fetch('/api/backup/run', { method: 'POST', headers: csrfHeaders() })
.then(r => r.json())
.then(data => {
if (data.ok) {
btn.textContent = 'Mentés folyamatban...';
btn.classList.add('loading');
startBackupPolling();
} else {
btn.textContent = data.error || 'Hiba';
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
}
})
.catch(() => {
btn.textContent = 'Hiba';
setTimeout(() => { btn.textContent = 'Mentés most'; btn.disabled = false; }, 3000);
});
}
function startBackupPolling() {
const poll = setInterval(() => {
fetch('/api/backup/status')
.then(r => r.json())
.then(data => {
if (data.ok && data.data && !data.data.running) {
clearInterval(poll);
window.location.reload();
}
})
.catch(() => {});
}, 3000);
}
function triggerGuestBackup() {
if (!confirm('Elindítja a teljes rendszermentést most? A mentés ideje alatt az alkalmazások rövid időre leállhatnak.')) return;
var btn = document.getElementById('wg-backup-btn');
var out = document.getElementById('wg-backup-result');
btn.disabled = true;
out.innerHTML = '<span class="form-hint">Mentés indítása…</span>';
fetch('/api/guest-backup/trigger', { method: 'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()) })
.then(r => r.json())
.then(data => {
if (!data.ok) { out.innerHTML = '<div class="alert alert-error">' + (data.error || 'Hiba') + '</div>'; btn.disabled = false; return; }
out.innerHTML = '<span class="form-hint">Mentés folyamatban…</span>';
pollGuestBackup(out, btn);
})
.catch(e => { out.innerHTML = '<div class="alert alert-error">Hiba: ' + e + '</div>'; btn.disabled = false; });
}
function pollGuestBackup(out, btn) {
var tries = 0;
var iv = setInterval(function () {
tries++;
fetch('/api/guest-backup/status')
.then(r => r.json())
.then(data => {
if (data.ok && data.data) {
var ph = data.data.phase;
out.innerHTML = '<span class="form-hint">Állapot: ' + ph + '</span>';
if (ph === 'done') { clearInterval(iv); out.innerHTML = '<div class="flash flash-success">A rendszermentés elkészült.</div>'; setTimeout(() => window.location.reload(), 1500); }
else if (ph === 'failed') { clearInterval(iv); out.innerHTML = '<div class="alert alert-error">A mentés sikertelen.</div>'; btn.disabled = false; }
}
})
.catch(() => {});
if (tries > 180) { clearInterval(iv); btn.disabled = false; }
}, 5000);
}
function toggleResticPw() {
var el = document.getElementById('restic-pw');
el.type = el.type === 'password' ? 'text' : 'password';
}
function copyResticPw() {
var el = document.getElementById('restic-pw');
navigator.clipboard.writeText(el.value).then(function() {
var btn = event.target;
btn.textContent = 'Másolva!';
setTimeout(function() { btn.textContent = 'Másolás'; }, 2000);
});
}
var huDays = ['vasárnap', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek', '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';
if (s.drive_label) {
label += ' — ' + tierLabel + ', ' + s.drive_label;
} else {
label += ' — ' + tierLabel;
}
return label;
}
function onRestoreAppChange() {
var sel = document.getElementById('restore-app');
var appName = sel.value;
var snapSel = document.getElementById('restore-snapshot');
var noSnaps = document.getElementById('restore-no-snapshots');
var typeInfo = document.getElementById('restore-type-info');
document.getElementById('restore-confirm-cb').checked = false;
document.getElementById('restore-btn').disabled = true;
noSnaps.style.display = 'none';
typeInfo.style.display = 'none';
if (!appName) {
snapSel.innerHTML = '<option value="">— Válasszon alkalmazást —</option>';
return;
}
var opt = sel.options[sel.selectedIndex];
var hasHDD = opt.getAttribute('data-has-hdd') === 'true';
var hasDB = opt.getAttribute('data-has-db') === 'true';
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.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.className = 'restore-info restore-info-partial';
} else {
typeInfo.innerHTML = 'Csak konfiguráció visszaállítása (compose fájlok, beállítások).';
typeInfo.className = 'restore-info restore-info-partial';
}
typeInfo.style.display = 'block';
snapSel.innerHTML = '<option value="">— Betöltés... —</option>';
fetch('/api/backup/snapshots?stack=' + encodeURIComponent(appName))
.then(function(r) { return r.json(); })
.then(function(data) {
snapSel.innerHTML = '<option value="">— Válasszon —</option>';
if (data.ok && data.data && data.data.length > 0) {
var tier1 = data.data.filter(function(s) { return s.tier !== 2; });
var tier2 = data.data.filter(function(s) { return s.tier === 2; });
if (tier1.length > 0) {
var grp1 = document.createElement('optgroup');
grp1.label = '1. szint — Helyi mentés (ajánlott)';
tier1.forEach(function(s) {
var o = document.createElement('option');
o.value = s.short_id;
o.textContent = formatSnapshot(s);
grp1.appendChild(o);
});
snapSel.appendChild(grp1);
}
if (tier2.length > 0) {
var grp2 = document.createElement('optgroup');
grp2.label = '2. szint — Másodlagos másolat';
tier2.forEach(function(s) {
var o = document.createElement('option');
o.value = s.short_id;
o.textContent = formatSnapshot(s);
grp2.appendChild(o);
});
snapSel.appendChild(grp2);
}
} else {
snapSel.innerHTML = '<option value="">— Nincs elérhető mentés —</option>';
noSnaps.style.display = 'block';
}
});
}
function onRestoreConfirmChange() {
var cb = document.getElementById('restore-confirm-cb');
var app = document.getElementById('restore-app').value;
var snap = document.getElementById('restore-snapshot').value;
document.getElementById('restore-btn').disabled = !(cb.checked && app && snap);
}
function submitRestore() {
var app = document.getElementById('restore-app').value;
var snap = document.getElementById('restore-snapshot').value;
if (!app || !snap) return;
var btn = document.getElementById('restore-btn');
btn.disabled = true;
btn.textContent = 'Visszaállítás folyamatban...';
var form = document.createElement('form');
form.method = 'POST';
form.action = '/backup/restore';
var fc = document.createElement('input');
fc.type = 'hidden'; fc.name = '_csrf';
fc.value = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
form.appendChild(fc);
var f1 = document.createElement('input');
f1.type = 'hidden'; f1.name = 'stack_name'; f1.value = app;
form.appendChild(f1);
var f2 = document.createElement('input');
f2.type = 'hidden'; f2.name = 'snapshot_id'; f2.value = snap;
form.appendChild(f2);
document.body.appendChild(form);
form.submit();
}
</script>
</main>
<script>
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
var card = e.target.closest('[data-href]');
if (card) window.location.href = card.dataset.href;
});
async function checkBeforeDeploy(e, name) {
try {
var resp = await fetch('/api/stacks/' + name);
var data = await resp.json();
if (data.ok && data.data && data.data.deployed) {
e.preventDefault();
showAlert('Ez az alkalmazás már telepítve van.');
window.location.reload();
return false;
}
} catch(err) {}
return true;
}
async function syncTemplates() {
const btn = document.getElementById('sync-btn');
const toast = document.getElementById('sync-toast');
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
setTimeout(function() { window.location.reload(); }, 1500);
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
}
}
btn.innerHTML = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
async function stackAction(event, name, action) {
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
async function deleteOrphanStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'delete-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
var data = await resp.json();
var hddInfo = '';
var checkboxHTML = '';
if (data.ok && data.data && data.data.has_hdd_data) {
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
data.data.hdd_paths.forEach(function(p) {
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
hddInfo += '</div>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
hddInfo + checkboxHTML +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
'</div>';
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
}
}
function closeDeleteModal() {
var modal = document.getElementById('delete-modal');
if (modal) modal.remove();
}
async function confirmDelete(name) {
var btn = document.getElementById('confirm-delete-btn');
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('delete-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen törölve!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Törlés';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
}
}
async function removeStack(name) {
var modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.id = 'remove-modal';
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
var [hddResp, backupResp] = await Promise.all([
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
]);
var sections = '';
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
'<li>Telepítési konfiguráció (app.yaml)</li>' +
'<li>Másodlagos mentés ütemezése</li>' +
'</ul></div>';
var hddCheckbox = '';
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
var hddPaths = '';
hddResp.data.hdd_paths.forEach(function(p) {
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
});
sections += '<div class="modal-section">' +
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
'</div></div>';
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
var backupCheckbox = '';
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
var bkPaths = '';
backupResp.data.backup_paths.forEach(function(p) {
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
});
if (bkPaths) {
sections += '<div class="modal-section">' +
'<strong>Mentési adatok:</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
'</div></div>';
}
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
sections +
'<div class="modal-actions">' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
'</div>' + hddCheckbox;
} catch (err) {
modal.querySelector('.modal-card').innerHTML =
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
}
}
function closeRemoveModal() {
var modal = document.getElementById('remove-modal');
if (modal) modal.remove();
}
async function confirmRemoveStack(name) {
var btn = document.getElementById('confirm-remove-btn');
var hddCheck = document.getElementById('remove-hdd-check');
var backupCheck = document.getElementById('remove-backup-check');
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
});
var data = await resp.json();
if (data.ok) {
var modal = document.getElementById('remove-modal');
var removedInfo = '';
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
}
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
}
var preservedInfo = '';
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
}
modal.querySelector('.modal-card').innerHTML =
'<h3>Sikeresen eltávolítva!</h3>' +
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
}
}
</script>
</body>
</html>