Files
felhom-controller/controller/internal/web/testdata/i18n_parity/backups_apps_ok.html
T
admin 0b1486df8f
gates / gates (push) Successful in 24s
fix: the recovery page's globe must write the HOUSEHOLD's setting, not a cookie nothing reads
/recovery is in the AUTHENTICATED route table — its reader is the household, not a visitor. The
first draft of v0.254.0 gave it the anonymous form, which sets the felhom_lang cookie that
langFor deliberately ignores once there is a session: the button would have appeared to work
and done nothing. Found by the live probe on demo-hp reporting no globe on /recovery (it 302s
to /login without a session) and then reading the route table.

executeTemplateLang now branches on hasSession: household form with its session CSRF, or the
visitor form without. The parity harness and TestI18nDirectRenderPagesFollowLanguage carry the
same branch, so the fixture is the form the real page serves — the trap this release already
walked into once with the shells.

A per-session CSRF token cannot be a fixture value, so it is blanked on both sides of every
parity comparison, exactly as relative ages already were. What stays pinned is that the field is
THERE and WHICH form it sits in — the half that says whether the globe writes the household's
setting or the visitor's cookie.

Evidence regenerated: 3 change shapes across 106 fixtures, 5 byte-identical (both guest share
pages and the catch-all — the three that must not change).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-09-18 14:28:24 +02:00

1592 lines
86 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" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alkalmazás mentések — Felhom.eu</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.247.0">
<link rel="stylesheet" href="/static/style.css?v=0.247.0">
<meta name="csrf-token" content="tok">
<script>
document.documentElement.className=document.documentElement.className.replace('no-js','js');
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
function felhomConfirm(el,question,onYes){
if(!el||el.dataset.fcOpen)return;
el.dataset.fcOpen='1';
var wrap=document.createElement('span');wrap.className='inline-confirm';
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
no.addEventListener('click',close);
yes.addEventListener('click',function(){close();onYes();});
}
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
if(!btn)return;
e.preventDefault();
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
var form=btn.closest('form');
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
});
});
document.addEventListener('click',function(e){
var btn=e.target.closest?e.target.closest('.nav-group-toggle'):null;
if(!btn)return;
var group=btn.closest('.nav-group');
if(!group)return;
var willOpen=!group.classList.contains('is-open');
document.querySelectorAll('.nav-group.is-open').forEach(function(g){
g.classList.remove('is-open');
var t=g.querySelector('.nav-group-toggle');
if(t)t.setAttribute('aria-expanded','false');
});
if(willOpen){group.classList.add('is-open');btn.setAttribute('aria-expanded','true');}
});
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-share" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3" /> <circle cx="6" cy="12" r="3" /> <circle cx="18" cy="19" r="3" /> <line x1="8.59" x2="15.42" y1="13.51" y2="17.49" /> <line x1="15.41" x2="8.59" y1="6.51" y2="10.49" /></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-rocket" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91 0z" /> <path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z" /> <path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" /> <path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" /></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>
<symbol id="i-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16" /> <path d="M4 6h16" /> <path d="M4 18h16" /></symbol>
</svg>
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v=0.247.0" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
<div class="nav-backdrop" hidden></div>
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="/static/felhom-logo.svg?v=0.247.0" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class=""><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" 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 class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-storage" 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 class="nav-group is-open">
<button type="button" class="nav-group-toggle active" aria-expanded="true" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-backups" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/backups" class="">Áttekintés</a></li>
<li><a href="/backups/remote" class="">Távoli mentés</a></li>
<li><a href="/backups/apps" class="active">Alkalmazások</a></li>
<li><a href="/backups/restore" class="">Visszaállítás</a></li>
</ul>
</li>
<li class="nav-group">
<button type="button" class="nav-group-toggle" aria-expanded="false" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>Megosztás<svg class="ico nav-chevron"><use href="#i-chevron-down"/></svg></button>
<ul id="nav-group-sharing" class="nav-links nav-links-sub nav-links-nested">
<li><a href="/sharing" class="">Hálózati megosztás</a></li>
</ul>
</li>
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</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.247.0</span><details class="lang-globe">
<summary class="lang-globe-btn" aria-label="Nyelv" title="Nyelv"><svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M2 12h20" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg></summary>
<ul class="lang-globe-menu">
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item current" aria-current="true">Magyar</button></form></li>
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="CSRF"><input type="hidden" name="back" value="/i18n-fixture"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item">English</button></form></li>
</ul>
</details>
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
</div>
</div>
</nav>
<main class="content">
<div class="page-header">
<h2>Biztonsági mentés — Alkalmazások</h2>
<span class="domain-badge">example.hu</span>
</div>
<div id="restore-banner" class="flash" style="display:none"></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="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ő: 2026-09-14 05:12</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-09-14 05:12 (# napja)</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>kimai</td>
<td><span class="db-type-badge db-type-mariadb">MariaDB</span></td>
<td class="mono">–</td>
<td class="mono">–</td>
<td>
<span class="validation-badge validation-na">–</span>
</td>
<td>
<span class="validation-badge validation-fail" title="dump failed">Hiba</span>
</td>
</tr>
<tr>
<td>bookstack</td>
<td><span class="db-type-badge db-type-mariadb">MariaDB</span></td>
<td class="mono">1.0 MB</td>
<td class="mono">09-14 05:12</td>
<td>
<span class="validation-badge validation-ok">12 tábla</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>immich</td>
<td><span class="db-type-badge db-type-postgres">PostgreSQL</span></td>
<td class="mono">2.0 MB</td>
<td class="mono">09-14 05:12</td>
<td>
<span class="validation-badge validation-fail" title="no create table">Hiba</span>
</td>
<td>
<span class="validation-badge validation-ok">OK</span>
</td>
</tr>
<tr>
<td>romm</td>
<td><span class="db-type-badge db-type-postgres">PostgreSQL</span></td>
<td class="mono">3.0 MB</td>
<td class="mono">09-14 05:12</td>
<td>
<span class="validation-badge validation-na" title="Az érvényesítés nem futott le">–</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="alert alert-error" style="margin-bottom:1.5rem">
<strong>Felhasználói adatokról nincs biztonsági mentés.</strong><br>
A szerveren tárolt fotók, dokumentumok és egyéb fájlok jelenleg csak egy példányban léteznek.
Külső meghajtó csatlakoztatásával biztonsági másolat készíthető a 3-2-1 szabály szerint.
<a href="/settings" style="color:inherit;text-decoration:underline">Meghajtó beállítása →</a>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/removed-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/removed-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App removed</span>
<div class="app-backup-row-meta">
<span class="tag tag-warn">Eltávolítva — visszaállítható</span>
<span class="meta-badge meta-badge-storage">HDD egy</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Mentés</span>
<span class="layer-badge">Megtartva</span>
<span class="tier-location">helyi</span>
<span class="tier-contents">Az alkalmazást eltávolítottad, a mentése megmaradt. Legutóbbi mentés: 2026-09-14 05:12.</span>
<div class="layer-actions">
<form method="POST" action="/backup/restore" style="display:inline">
<input type="hidden" name="stack_name" value="removed">
<input type="hidden" name="snapshot_id" value="helyi">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Újratelepíti a(z) App removed alkalmazást a megtartott mentésből (2026-09-14 05:12)? Az adatok a mentésből állnak vissza.">Visszaállítás a mentésből</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/disconnected-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/disconnected-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App disconnected</span>
<div class="app-backup-row-meta">
<span class="tag tag-warn">Meghajtó leválasztva</span>
<span class="status-dot status-ok" title="Minden rendben van"></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="layer-last">Utolsó: # napja
<span class="text-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg></span>
</span>
<span class="tier-contents">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-unconfigured">2. mentés kikapcsolva</span>
<div class="layer-actions">
<a href="/stacks/disconnected/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge text-ok">Sikeres</span>
<span class="tier-location">restic → nas.example</span>
<span class="layer-last">Utolsó: # napja</span>
<span class="tier-contents">Helyreállítási egység, titkosítva</span>
</div>
</div>
<div class="layer-warnings">
<div class="backup-layer-warning">Egy figyelmeztetés szövege</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/hdd-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/hdd-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App hdd</span>
<div class="app-backup-row-meta">
<span class="meta-badge meta-badge-storage">HDD egy</span>
<span class="mono app-backup-size" style="font-size:.8rem">12 GB</span>
<span class="status-dot status-ok" title="Minden rendben van"></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="layer-last">Utolsó: # napja
<span class="text-error"><svg class="ico ico-sm"><use href="#i-x"/></svg></span>
</span>
<span class="tier-contents">Adatbázis és beállítások</span>
<span class="state-text-neutral" style="font-size:.8rem">A fájlok nincsenek benne <a href="/backups/remote">Távoli mentés oldal &rarr;</a></span>
<span class="text-error" style="font-size:.8rem"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> DB dump hiba</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó leválasztva</span>
<span class="layer-last" style="opacity:.6">Utolsó: # napja</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/hdd/backup" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
<div class="backup-layer-row">
<span class="tier-label">3. mentés</span>
<span class="layer-badge text-error">Kulcsletétre vár</span>
<span class="tier-contents">A távoli mentés a titkosítási kulcs letétbe helyezéséig szünetel</span>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/volume-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/volume-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App volume</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + DB + Adatok</span>
<span class="status-dot status-ok" title="Minden rendben van"></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="layer-last">Utolsó: # napja
<span class="state-text-neutral" title="Erről a mentésről nincs eredményünk.">—</span>
</span>
<span class="tier-contents">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó leválasztva</span>
<span class="layer-last" style="opacity:.6">Utolsó sikeres: # napja</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/volume/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">Kikapcsolva</span>
<span class="tier-contents">Ez az alkalmazás nincs kijelölve távoli mentésre</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Bekapcsolás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/confonly-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/confonly-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App confonly</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó leválasztva</span>
<span class="layer-last" style="opacity:.6">Még nincs sikeres másolat</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/confonly/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/inact1-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/inact1-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App inact1</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó inaktív</span>
<span class="layer-last" style="opacity:.6">Utolsó: # napja</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/inact1/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/inact2-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/inact2-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App inact2</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó inaktív</span>
<span class="layer-last" style="opacity:.6">Utolsó sikeres: # napja</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/inact2/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/inact3-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/inact3-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App inact3</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig + Adatok</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</span>
</div>
<div class="backup-layer-row">
<span class="tier-label">2. mentés</span>
<span class="layer-method" style="opacity:.6">rsync</span>
<span class="layer-dest" style="opacity:.6">→ HDD kettő</span>
<span class="tag tag-warn">Cél meghajtó inaktív</span>
<span class="layer-last" style="opacity:.6">Még nincs sikeres másolat</span>
<span class="tier-contents" style="opacity:.6">Teljes helyreállítási egység</span>
<div class="layer-actions">
<a href="/stacks/inact3/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/okrun-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/okrun-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App okrun</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</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">→ HDD kettő</span>
<span class="layer-schedule">naponta 03:00</span>
<span class="layer-last">Utolsó: # napja
<span class="text-ok">
Sikeres mentés
</span>
</span>
<span class="tier-size">3 GB</span>
<span class="layer-reason" style="color:var(--warn);opacity:.9">Egy figyelmeztetés a másolatról</span>
<span class="layer-reason" style="color:var(--warn);opacity:.9">A másolat régebbi a mentésnél</span>
<span class="tier-contents">Teljes helyreállítási egység</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">
<input type="hidden" name="stack_name" value="okrun">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: 2026-09-14 05:12">Fájlok visszaállítása</button>
</form>
<form method="POST" action="/backup/tier2/unit-restore" style="display:inline">
<input type="hidden" name="stack_name" value="okrun">
<button type="submit" class="btn btn-xs btn-danger-outline" data-confirm="Biztosan visszaállítod a teljes egységet?">Teljes visszaállítás a másolatból</button>
</form>
<a href="/stacks/okrun/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/oksucc-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/oksucc-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App oksucc</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</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">→ HDD kettő</span>
<span class="layer-schedule">naponta 03:00</span>
<span class="layer-last">Utolsó sikeres: # napja
<span class="text-error">
Sikeres mentés
</span>
</span>
<span class="tier-contents">Teljes helyreállítási egység</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">
<input type="hidden" name="stack_name" value="oksucc">
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi sikeres másolat: 2026-09-14 05:12.">Fájlok visszaállítása</button>
</form>
<a href="/stacks/oksucc/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/nosucc-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/nosucc-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App nosucc</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</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">→ HDD kettő</span>
<span class="layer-schedule">naponta 03:00</span>
<span class="layer-last">Még nincs sikeres másolat
<span class="text-error">Sikeres mentés</span>
</span>
<span class="tier-contents">Teljes helyreállítási egység</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">
<span class="layer-reason" style="opacity:.85">Még nincs sikeres másolat, amiből vissza lehetne állítani.</span>
<a href="/stacks/nosucc/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/nosucc2-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/nosucc2-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App nosucc2</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</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">→ HDD kettő</span>
<span class="layer-schedule">naponta 03:00</span>
<span class="tier-contents">Teljes helyreállítási egység</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">
<a href="/stacks/nosucc2/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</div>
</div>
<div class="app-backup-row" data-status="ok">
<div class="app-backup-row-header" onclick="toggleBackupDetail(this)">
<img class="app-row-icon" src="/static/assets/t2err-logo.svg" alt="" data-fallback="/static/app-placeholder.svg"
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/t2err-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
<span class="app-backup-row-name">App t2err</span>
<div class="app-backup-row-meta">
<span class="meta-badge">Konfig</span>
<span class="status-dot status-ok" title="Minden rendben van"></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">Adatbázis és beállítások</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>
<span class="layer-reason" style="opacity:.85" title="A 2. mentés automatikus — külön beállítás nem kell">Nincs második meghajtó csatlakoztatva</span>
<div class="layer-actions">
<a href="/stacks/t2err/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)">Nincs beállítva</span>
<span class="tier-location">távoli (offsite)</span>
<span class="tier-contents">Nincs távoli mentési cél beállítva</span>
<div class="layer-actions">
<a href="/backups/remote#offbox-section" class="btn btn-xs btn-outline">Beállítás</a>
</div>
</div>
</div>
</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' ? 'Távoli 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 || st.last_recent)) {
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 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);
}
</script>
</main>
<script>
(function(){
var burger=document.querySelector('.nav-burger');
var sidebar=document.getElementById('sidebar');
var backdrop=document.querySelector('.nav-backdrop');
if(!burger||!sidebar||!backdrop)return;
function setOpen(open){
sidebar.classList.toggle('is-open',open);
document.body.classList.toggle('nav-open',open);
backdrop.hidden=!open;
burger.setAttribute('aria-expanded',open?'true':'false');
}
burger.addEventListener('click',function(){setOpen(!sidebar.classList.contains('is-open'));});
backdrop.addEventListener('click',function(){setOpen(false);});
document.addEventListener('keydown',function(e){
if(e.key==='Escape'&&sidebar.classList.contains('is-open'))setOpen(false);
});
})();
document.addEventListener('click', function(e) {
if (e.target.closest('a, button, .btn, input, select, textarea, .app-row-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;
}
if (action === 'update') {
followUpdate(name, btn);
return;
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
}
}
function followUpdate(name, btn) {
var started = Date.now();
var timer = setInterval(async function() {
if (Date.now() - started > 30 * 60 * 1000) { clearInterval(timer); window.location.reload(); return; }
try {
var r = await fetch('/api/stacks/' + name);
var d = await r.json();
if (!d.ok || !d.data) return;
if (d.data.update_phase_label) btn.textContent = d.data.update_phase_label;
if (!d.data.updating) { clearInterval(timer); window.location.reload(); }
} catch (e) {}
}, 3000);
}
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>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</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>';
}
if (data.data && data.data.hdd_note) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">' + data.data.hdd_note + '</p>';
}
if (data.data && data.data.backup_paths_refused && data.data.backup_paths_refused.length > 0) {
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Nem törölt mentések: ' + data.data.backup_paths_refused.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>