v0.247.0: i18n spike — the dashboard can speak English, Hungarian byte-identical
gates / gates (push) Successful in 19s

Message bundles (internal/i18n) expanded into templates before parsing, one
template set per language. Launcher, /backups, /apps/<slug> and the layout
converted; household language setting, POST /settings/language, ?lang= override,
report field. Parity test against fixtures captured from unconverted templates;
copy gates read templates expanded; new i18n_missing_gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-17 14:50:57 +02:00
parent 89dd3e94b1
commit 612c417024
49 changed files with 11734 additions and 248 deletions
+96 -97
View File
@@ -1,6 +1,6 @@
{{define "layout_start"}}
<!DOCTYPE html>
<html lang="hu" class="no-js">
<html lang="{{T "layout.html_lang"}}" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -26,8 +26,8 @@
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';
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='{{T "layout.igen"}}';
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='{{T "layout.megse"}}';
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;}
@@ -60,14 +60,14 @@
});
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);}
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>{{T "layout.uzenet"}}</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>
{{template "icon_sprite"}}
<header class="mobile-topbar">
<a href="/" class="mobile-topbar-logo"><img src="/static/felhom-logo.svg?v={{.Version}}" alt="Felhom.eu"></a>
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menü">
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="{{T "layout.menu"}}">
<svg class="ico"><use href="#i-menu"/></svg>
</button>
</header>
@@ -77,47 +77,47 @@
<img src="/static/felhom-logo.svg?v={{.Version}}" alt="Felhom.eu" class="sidebar-logo">
</div>
<ul class="nav-links">
<li><a href="/launcher" class="{{if eq .Page "launcher"}}active{{end}}"><svg class="ico"><use href="#i-rocket"/></svg>Indítópult</a></li>
<li><a href="/dashboard" class="{{if eq .Page "dashboard"}}active{{end}}"><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
<li><a href="/stacks" class="{{if eq .Page "stacks"}}active{{end}}"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
<li><a href="/launcher" class="{{if eq .Page "launcher"}}active{{end}}"><svg class="ico"><use href="#i-rocket"/></svg>{{T "layout.inditopult"}}</a></li>
<li><a href="/dashboard" class="{{if eq .Page "dashboard"}}active{{end}}"><svg class="ico"><use href="#i-layout-grid"/></svg>{{T "layout.vezerlopult"}}</a></li>
<li><a href="/stacks" class="{{if eq .Page "stacks"}}active{{end}}"><svg class="ico"><use href="#i-cloud"/></svg>{{T "layout.alkalmazasok"}}</a></li>
{{$storageOpen := or (eq .Page "storage") (eq .Page "storage-network")}}
<li class="nav-group{{if $storageOpen}} is-open{{end}}">
<button type="button" class="nav-group-toggle{{if $storageOpen}} active{{end}}" aria-expanded="{{if $storageOpen}}true{{else}}false{{end}}" 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>
<button type="button" class="nav-group-toggle{{if $storageOpen}} active{{end}}" aria-expanded="{{if $storageOpen}}true{{else}}false{{end}}" aria-controls="nav-group-storage"><svg class="ico"><use href="#i-hard-drive"/></svg>{{T "layout.tarhely"}}<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="{{if eq .Page "storage"}}active{{end}}">Meghajtók</a></li>
<li><a href="/storage/network" class="{{if eq .Page "storage-network"}}active{{end}}">Hálózati tárhely</a></li>
<li><a href="/storage" class="{{if eq .Page "storage"}}active{{end}}">{{T "layout.meghajtok"}}</a></li>
<li><a href="/storage/network" class="{{if eq .Page "storage-network"}}active{{end}}">{{T "layout.halozati_tarhely"}}</a></li>
</ul>
</li>
{{$backupsOpen := or (eq .Page "backups") (eq .Page "backups-remote") (eq .Page "backups-apps") (eq .Page "backups-restore")}}
<li class="nav-group{{if $backupsOpen}} is-open{{end}}">
<button type="button" class="nav-group-toggle{{if $backupsOpen}} active{{end}}" aria-expanded="{{if $backupsOpen}}true{{else}}false{{end}}" 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>
<button type="button" class="nav-group-toggle{{if $backupsOpen}} active{{end}}" aria-expanded="{{if $backupsOpen}}true{{else}}false{{end}}" aria-controls="nav-group-backups"><svg class="ico"><use href="#i-shield"/></svg>{{T "layout.biztonsagi_mentes"}}<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="{{if eq .Page "backups"}}active{{end}}">Áttekintés</a></li>
<li><a href="/backups/remote" class="{{if eq .Page "backups-remote"}}active{{end}}">Távoli mentés</a></li>
<li><a href="/backups/apps" class="{{if eq .Page "backups-apps"}}active{{end}}">Alkalmazások</a></li>
<li><a href="/backups/restore" class="{{if eq .Page "backups-restore"}}active{{end}}">Visszaállítás</a></li>
<li><a href="/backups" class="{{if eq .Page "backups"}}active{{end}}">{{T "layout.attekintes"}}</a></li>
<li><a href="/backups/remote" class="{{if eq .Page "backups-remote"}}active{{end}}">{{T "layout.tavoli_mentes"}}</a></li>
<li><a href="/backups/apps" class="{{if eq .Page "backups-apps"}}active{{end}}">{{T "layout.alkalmazasok"}}</a></li>
<li><a href="/backups/restore" class="{{if eq .Page "backups-restore"}}active{{end}}">{{T "layout.visszaallitas"}}</a></li>
</ul>
</li>
{{$sharingOpen := eq .Page "sharing"}}
<li class="nav-group{{if $sharingOpen}} is-open{{end}}">
<button type="button" class="nav-group-toggle{{if $sharingOpen}} active{{end}}" aria-expanded="{{if $sharingOpen}}true{{else}}false{{end}}" 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>
<button type="button" class="nav-group-toggle{{if $sharingOpen}} active{{end}}" aria-expanded="{{if $sharingOpen}}true{{else}}false{{end}}" aria-controls="nav-group-sharing"><svg class="ico"><use href="#i-share"/></svg>{{T "layout.megosztas"}}<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="{{if eq .Page "sharing"}}active{{end}}">Hálózati megosztás</a></li>
<li><a href="/sharing" class="{{if eq .Page "sharing"}}active{{end}}">{{T "layout.halozati_megosztas"}}</a></li>
</ul>
</li>
<li><a href="/monitoring" class="{{if eq .Page "monitoring"}}active{{end}}"><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
<li><a href="/monitoring" class="{{if eq .Page "monitoring"}}active{{end}}"><svg class="ico"><use href="#i-cpu"/></svg>{{T "layout.rendszermonitor"}}</a></li>
<li><a href="/debug" class="{{if eq .Page "debug"}}active{{end}}"><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>
<div class="nav-group-label">{{T "layout.beallitasok"}}</div>
<ul class="nav-links nav-links-sub">
<li><a href="/settings" class="{{if eq .Page "settings"}}active{{end}}"><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
<li><a href="/settings/notifications" class="{{if eq .Page "settings-notifications"}}active{{end}}"><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
<li><a href="/settings/security" class="{{if eq .Page "settings-security"}}active{{end}}"><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
<li><a href="/settings" class="{{if eq .Page "settings"}}active{{end}}"><svg class="ico"><use href="#i-settings"/></svg>{{T "layout.rendszer"}}</a></li>
<li><a href="/settings/notifications" class="{{if eq .Page "settings-notifications"}}active{{end}}"><svg class="ico"><use href="#i-bell"/></svg>{{T "layout.ertesitesek"}}</a></li>
<li><a href="/settings/security" class="{{if eq .Page "settings-security"}}active{{end}}"><svg class="ico"><use href="#i-lock"/></svg>{{T "layout.biztonsag_es_hozzaferes"}}</a></li>
</ul>
<div class="sidebar-footer">
<span class="version">{{.Version}}</span>
{{if .AuthEnabled}}<a href="/logout" class="logout-link">Kijelentkezés ↗</a>{{end}}
<span class="version">{{.Version}}</span>{{if .LangSwitch}}<form method="POST" action="/settings/language" class="lang-switch">{{.CSRFField}}<input type="hidden" name="back" value="{{.LangSwitchBack}}"><button type="submit" name="lang" value="hu" class="lang-switch-btn{{if eq .Lang "hu"}} active{{end}}" lang="hu">Magyar</button><button type="submit" name="lang" value="en" class="lang-switch-btn{{if eq .Lang "en"}} active{{end}}" lang="en">English</button></form>{{end}}
{{if .AuthEnabled}}<a href="/logout" class="logout-link">{{T "layout.kijelentkezes"}}</a>{{end}}
</div>
</div>
</nav>
@@ -126,7 +126,7 @@
<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 vezérlőpult még nincs jelszóval védve — a beállító kódot hamarosan e-mailben küldjük.</span>
<span class="alert-message">{{T "layout.a_vezerlopult_meg_nincs_jelszoval"}}</span>
</div>
</div>
{{end}}
@@ -146,28 +146,28 @@
reachable state: a fresh escrow ceremony during the window replaces the package, which
is the exact act that cost both demo boxes their history on 2026-08-04. A countdown
started before this shipped carries no pin and takes the cautious branch. */}}
A korábbi távoli mentéseidet <strong>{{.RecoveryAbandonDays}} nap múlva</strong> ({{.RecoveryAbandonDate}}) véglegesen töröljük, a kérésed szerint.
{{if .RecoveryAbandonRetrievalOffered}}Addig még visszaszerezheted őket a helyreállítási kóddal.{{else}}Hogy ezek még visszaszerezhetők-e a helyreállítási kóddal, azt innen nem tudjuk megállapítani — ha vissza szeretnéd kapni őket, <strong>írj nekünk a törlés előtt</strong>.{{end}}
{{T "layout.a_korabbi_tavoli_menteseidet_nap"}}
{{if .RecoveryAbandonRetrievalOffered}}{{T "layout.addig_meg_visszaszerezheted_oket_a"}}{{else}}{{T "layout.hogy_ezek_meg_visszaszerezhetok_e"}}{{end}}
{{else if ge .RecoveryReminderTier 14}}
<strong>Két hete</strong> várnak rád a korábbi távoli mentéseid, és még nem adtad meg a helyreállítási kódodat. Amíg nem teszed, ezekhez a mentésekhez nem férsz hozzá.
{{T "layout.ket_hete_varnak_rad_a"}}
{{else if ge .RecoveryReminderTier 7}}
Már <strong>egy hete</strong> megvannak a korábbi távoli mentéseid, de a helyreállítási kódod nélkül nem tudjuk megnyitni őket.
{{T "layout.mar_egy_hete_megvannak_a"}}
{{else if ge .RecoveryReminderTier 3}}
A korábbi távoli mentéseid megvannak — a megnyitásukhoz a helyreállítási kódod szükséges.
{{T "layout.a_korabbi_tavoli_menteseid_megvannak"}}
{{else}}
A korábbi távoli mentéseid megvannak, de ehhez a géphez a helyreállítási kódod szükséges.
{{T "layout.a_korabbi_tavoli_menteseid_megvannak_2"}}
{{end}}
<a href="/recovery">Megnézem</a>
<a href="/recovery">{{T "layout.megnezem"}}</a>
</span>
<span class="alert-actions">
<form method="POST" action="/recovery/banner/dismiss" style="display:inline">
{{.CSRFField}}<input type="hidden" name="back" value="{{.RecoveryBannerBack}}">
<button type="submit" class="btn btn-sm btn-outline">Most nem</button>
<button type="submit" class="btn btn-sm btn-outline">{{T "layout.most_nem"}}</button>
</form>
{{if not .RecoveryAbandonDays}}
<form method="POST" action="/recovery/remind-optout" style="display:inline">
{{.CSRFField}}<input type="hidden" name="back" value="{{.RecoveryBannerBack}}">
<button type="submit" class="btn btn-sm btn-outline">Ne emlékeztessen újra</button>
<button type="submit" class="btn btn-sm btn-outline">{{T "layout.ne_emlekeztessen_ujra"}}</button>
</form>
{{end}}
</span>
@@ -183,13 +183,12 @@
<div class="alert-banner alert-banner-warning">
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
<span class="alert-message">
A távoli mentés szünetel, amíg nem hozod létre a helyreállítási kódot.
<a href="/backup/escrow"><strong>Helyreállítási kód létrehozása &rarr;</strong></a>
{{T "layout.a_tavoli_mentes_szunetel_amig"}}
</span>
<span class="alert-actions">
<form method="POST" action="/backup/escrow/banner/dismiss" style="display:inline">
{{.CSRFField}}<input type="hidden" name="back" value="{{.EscrowBannerBack}}">
<button type="submit" class="btn btn-sm btn-outline">Most nem</button>
<button type="submit" class="btn btn-sm btn-outline">{{T "layout.most_nem"}}</button>
</form>
</span>
</div>
@@ -244,7 +243,7 @@
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.');
showAlert('{{T "layout.ez_az_alkalmazas_mar_telepitve"}}');
window.location.reload();
return false;
}
@@ -257,7 +256,7 @@
if (!btn) return;
const origText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '↻ Frissítés...';
btn.innerHTML = '{{T "layout.frissites"}}';
btn.classList.add('loading');
try {
const resp = await fetch('/api/sync', {
@@ -266,7 +265,7 @@
});
const data = await resp.json();
if (toast) {
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
toast.textContent = data.ok ? (data.message || '{{T "layout.sablonok_frissitve"}}') : ('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.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);
@@ -276,7 +275,7 @@
}
} catch (err) {
if (toast) {
toast.textContent = 'Hálózati hiba: ' + err.message;
toast.textContent = '{{T "layout.halozati_hiba"}} ' + err.message;
toast.className = 'sync-toast sync-toast-err';
toast.style.display = 'block';
setTimeout(function() { toast.style.display = 'none'; }, 5000);
@@ -290,7 +289,7 @@
const btn = event.currentTarget;
const origText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Folyamatban...';
btn.textContent = '{{T "layout.folyamatban"}}';
btn.classList.add('loading');
try {
const resp = await fetch('/api/stacks/' + name + '/' + action, {
@@ -299,7 +298,7 @@
});
const data = await resp.json();
if (!data.ok) {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
@@ -314,7 +313,7 @@
}
window.location.reload();
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
showAlert('{{T "layout.halozati_hiba"}} ' + err.message);
btn.textContent = origText;
btn.disabled = false;
btn.classList.remove('loading');
@@ -337,7 +336,7 @@
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.innerHTML = '<div class="modal-card"><h3>{{T "layout.betoltes"}}</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
document.body.appendChild(modal);
try {
@@ -346,26 +345,26 @@
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>';
hddInfo = '<div class="modal-hdd-info"><strong>{{T "layout.felhasznaloi_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 class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : '{{T "layout.nem_letezik"}}') + ')</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>';
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> {{T "layout.felhasznaloi_adatok_torlese_a_merevlemez"}}</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>' +
'<h3>{{T "layout.alkalmazas_torlese"}} ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">{{T "layout.ez_a_muvelet_eltavolitja_a"}}</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</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>' +
'<button class="btn btn-outline" onclick="closeDeleteModal()">{{T "layout.megsem"}}</button>' +
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">{{T "layout.torles"}}</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>';
'<h3>{{T "layout.hiba_2"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">{{T "layout.bezaras"}}</button></div>';
}
}
function closeDeleteModal() {
@@ -377,7 +376,7 @@
var checkbox = document.getElementById('delete-hdd-check');
var removeHDD = checkbox ? checkbox.checked : false;
btn.disabled = true;
btn.textContent = 'Törlés folyamatban...';
btn.textContent = '{{T "layout.torles_folyamatban"}}';
try {
var resp = await fetch('/api/stacks/' + name, {
method: 'DELETE',
@@ -389,36 +388,36 @@
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>';
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.torolt_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>';
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.megorzott_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>' +
'<h3>{{T "layout.sikeresen_torolve"}}</h3>' +
'<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.torolve_lett"}}</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "layout.bezaras"}}</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.disabled = false;
btn.textContent = 'Törlés';
btn.textContent = '{{T "layout.torles"}}';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
showAlert('{{T "layout.halozati_hiba"}} ' + err.message);
btn.disabled = false;
btn.textContent = 'Törlés';
btn.textContent = '{{T "layout.torles"}}';
}
}
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.innerHTML = '<div class="modal-card"><h3>{{T "layout.betoltes"}}</h3></div>';
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
document.body.appendChild(modal);
try {
@@ -429,24 +428,24 @@
var sections = '';
// Section 1: Always removed
sections += '<div class="modal-section">' +
'<strong>Mindig törlődik:</strong>' +
'<strong>{{T "layout.mindig_torlodik"}}</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>' +
'<li>{{T "layout.docker_kotetek_adatbazis_alkalmazas_konf"}}</li>' +
'<li>{{T "layout.telepitesi_konfiguracio_app_yaml"}}</li>' +
'<li>{{T "layout.masodlagos_mentes_utemezese"}}</li>' +
'</ul></div>';
// Section 2: HDD data
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>';
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : '{{T "layout.nem_letezik"}}') + ')</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>' +
'<strong>{{T "layout.felhasznaloi_adatok_a_merevlemezen"}}</strong>' + hddPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> {{T "layout.felhasznaloi_adatok_torlese"}}</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.' +
'{{T "layout.ha_ujratelepiti_az_alkalmazast_az"}}' +
'</div></div>';
hddCheckbox = '<script>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
}
@@ -459,26 +458,26 @@
});
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>' +
'<strong>{{T "layout.mentesi_adatok"}}</strong>' + bkPaths +
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> {{T "layout.mentesi_adatok_torlese"}}</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.' +
'{{T "layout.az_ejszakai_restic_pillanatkepek_nem"}}' +
'</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>' +
'<h3>{{T "layout.alkalmazas_eltavolitasa"}} ' + name + '</h3>' +
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">{{T "layout.az_alkalmazas_visszaall_nincs_telepitve"}}</p>' +
'<div class="alert alert-warning" style="margin-bottom:.75rem">{{T "layout.ez_a_muvelet_nem_visszavonhato"}}</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>' +
'<button class="btn btn-outline" onclick="closeRemoveModal()">{{T "layout.megsem"}}</button>' +
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">{{T "layout.eltavolitas"}}</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>';
'<h3>{{T "layout.hiba_2"}}</h3><p style="color:var(--text-2)">{{T "layout.nem_sikerult_lekerni_az_adatokat"}} ' + err.message + '</p>' +
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">{{T "layout.bezaras"}}</button></div>';
}
}
function closeRemoveModal() {
@@ -492,7 +491,7 @@
var removeHDD = hddCheck ? hddCheck.checked : false;
var removeBackups = backupCheck ? backupCheck.checked : false;
btn.disabled = true;
btn.textContent = 'Eltávolítás folyamatban...';
btn.textContent = '{{T "layout.eltavolitas_folyamatban"}}';
try {
var resp = await fetch('/api/stacks/' + name + '/remove', {
method: 'POST',
@@ -504,35 +503,35 @@
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>';
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.torolt_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>';
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.torolt_mentesek"}} ' + 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>';
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.megorzott_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>';
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">{{T "layout.nem_torolt_mentesek"}} ' + 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>' +
'<h3>{{T "layout.sikeresen_eltavolitva"}}</h3>' +
'<p style="color:var(--text-2)">{{T "layout.az_alkalmazas"}}' + name + '{{T "layout.eltavolitva_ujratelepitheto_a_telepites"}}</p>' +
removedInfo + preservedInfo +
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">{{T "layout.bezaras"}}</button></div>';
} else {
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
showAlert('{{T "layout.hiba"}} ' + (data.error || '{{T "layout.ismeretlen_hiba"}}'));
btn.disabled = false;
btn.textContent = 'Eltávolítás';
btn.textContent = '{{T "layout.eltavolitas"}}';
}
} catch (err) {
showAlert('Hálózati hiba: ' + err.message);
showAlert('{{T "layout.halozati_hiba"}} ' + err.message);
btn.disabled = false;
btn.textContent = 'Eltávolítás';
btn.textContent = '{{T "layout.eltavolitas"}}';
}
}
</script>