0b1486df8f
gates / gates (push) Successful in 24s
/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
1307 lines
76 KiB
HTML
1307 lines
76 KiB
HTML
|
||
|
||
<!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>Debug — 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,'&').replace(/</g,'<').replace(/>/g,'>')+'</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">
|
||
<button type="button" class="nav-group-toggle" aria-expanded="false" 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="">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="active"><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>Debug</h2>
|
||
</div>
|
||
|
||
<div class="debug-banner">
|
||
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Debug mód aktív — ez az oldal csak fejlesztéshez és hibaelhárításhoz
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-diagnostic">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('diagnostic')">
|
||
<h3>Rendszer diagnosztika</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="diagnostic-content"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-secondary btn-sm" id="btn-download-dump" data-label="Nyers JSON letöltése" onclick="downloadDump()">Nyers JSON letöltése</button>
|
||
<button class="btn btn-secondary btn-sm" id="btn-refresh-diag" data-label="Frissítés" onclick="loadSectionData('diagnostic')">Frissítés</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-events">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('events')">
|
||
<h3>Értesítés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div class="debug-form-row">
|
||
<label>Esemény típus:</label>
|
||
<select id="event-type">
|
||
<option value="test">test (info)</option>
|
||
<option value="backup_failed">backup_failed (error)</option>
|
||
<option value="db_dump_failed">db_dump_failed (error)</option>
|
||
<option value="backup_integrity_failed">backup_integrity_failed (error)</option>
|
||
<option value="crossdrive_failed">crossdrive_failed (error)</option>
|
||
<option value="disk_warning">disk_warning (warning)</option>
|
||
<option value="disk_critical">disk_critical (error)</option>
|
||
<option value="storage_disconnected">storage_disconnected (error)</option>
|
||
<option value="storage_reconnected">storage_reconnected (info)</option>
|
||
<option value="health_critical">health_critical (error)</option>
|
||
<option value="health_recovered">health_recovered (info)</option>
|
||
<option value="update_available">update_available (info)</option>
|
||
<option value="controller_started">controller_started (info)</option>
|
||
</select>
|
||
<label>Súlyosság:</label>
|
||
<select id="event-severity">
|
||
<option value="error">error</option>
|
||
<option value="warning">warning</option>
|
||
<option value="info">info</option>
|
||
</select>
|
||
<button class="btn btn-primary btn-sm" id="btn-test-event" data-label="Teszt esemény küldése" onclick="sendTestEvent()">Teszt esemény küldése</button>
|
||
<span class="debug-result" id="btn-test-event-result"></span>
|
||
</div>
|
||
<h4>Utolsó események</h4>
|
||
<div id="event-history"><span class="text-muted">Betöltés...</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-backup">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('backup')">
|
||
<h3>Mentés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="backup-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-full-backup" data-label="Teljes mentés" onclick="triggerAction('btn-full-backup','/api/backup/run','POST')">Teljes mentés</button>
|
||
<span class="debug-result" id="btn-full-backup-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-dbdump" data-label="Csak DB dump" onclick="triggerAction('btn-dbdump','/api/debug/backup/dbdump','POST')">Csak DB dump</button>
|
||
<span class="debug-result" id="btn-dbdump-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-crossdrive" data-label="Csak cross-drive" onclick="triggerAction('btn-crossdrive','/api/debug/backup/crossdrive','POST')">Csak cross-drive</button>
|
||
<span class="debug-result" id="btn-crossdrive-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-integrity" data-label="Restic integritás" onclick="triggerAction('btn-integrity','/api/debug/backup/integrity','POST')">Restic integritás</button>
|
||
<span class="debug-result" id="btn-integrity-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-offsite-proof" data-label="Távoli mentés tartalma" onclick="triggerAction('btn-offsite-proof','/api/debug/backup/offsite-proof','POST')">Távoli mentés tartalma</button>
|
||
<span class="debug-result" id="btn-offsite-proof-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-hub">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('hub')">
|
||
<h3>Hub & Kapcsolatok</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="hub-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-hub-push" data-label="Hub jelentés küldése" onclick="triggerAction('btn-hub-push','/api/debug/hub/push','POST')">Hub jelentés küldése</button>
|
||
<span class="debug-result" id="btn-hub-push-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-hub-conn" data-label="Hub elérhetőség" onclick="triggerAction('btn-hub-conn','/api/debug/hub/test-connectivity','POST')">Hub elérhetőség</button>
|
||
<span class="debug-result" id="btn-hub-conn-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-pref-sync" data-label="Preferencia szinkron" onclick="triggerAction('btn-pref-sync','/api/debug/hub/preferences-sync','POST')">Preferencia szinkron</button>
|
||
<span class="debug-result" id="btn-pref-sync-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-gitea-conn" data-label="Gitea elérhetőség" onclick="triggerAction('btn-gitea-conn','/api/debug/gitea/test-connectivity','POST')">Gitea elérhetőség</button>
|
||
<span class="debug-result" id="btn-gitea-conn-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-telemetry">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('telemetry')">
|
||
<h3>Telemetria teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="telemetry-status"><span class="text-muted">Kattintson a gombra a telemetria futtatásához.</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-telemetry-run" data-label="Telemetria futtatása" onclick="runTelemetryTest()">Telemetria futtatása</button>
|
||
<span class="debug-result" id="btn-telemetry-run-result"></span>
|
||
</div>
|
||
<div id="telemetry-detail" style="display:none; margin-top:1rem;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-selfupdate">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('selfupdate')">
|
||
<h3>Önfrissítés teszt</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="selfupdate-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions">
|
||
<button class="btn btn-primary btn-sm" id="btn-check-update" data-label="Frissítés keresése" onclick="triggerAction('btn-check-update','/api/selfupdate/check','POST')">Frissítés keresése</button>
|
||
<span class="debug-result" id="btn-check-update-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-dryrun" data-label="Dry-run frissítés" onclick="triggerAction('btn-dryrun','/api/debug/selfupdate/dry-run','POST')">Dry-run frissítés</button>
|
||
<span class="debug-result" id="btn-dryrun-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-dr">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('dr')">
|
||
<h3>DR / Telepítő varázsló</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div class="debug-actions" style="margin-top:1rem">
|
||
<div class="debug-dr-danger">
|
||
<p style="color:var(--crit);font-weight:600">Vészhelyzet szimuláció</p>
|
||
<p class="text-muted" style="font-size:.85rem;margin-bottom:.5rem">Ez törli az ügyfél azonosítót és újraindítja a controllert setup módba. A konfigurációs fájlok megmaradnak a meghajtókon.</p>
|
||
<div class="debug-form-row">
|
||
<label>Írja be "RESET" a megerősítéshez:</label>
|
||
<input type="text" id="dr-confirm-input" class="form-control debug-confirm-input" placeholder="RESET" autocomplete="off">
|
||
<button class="btn btn-danger btn-sm" id="btn-dr-trigger" data-label="Újraindítás setup módban" onclick="triggerDR()">Újraindítás setup módban</button>
|
||
<span class="debug-result" id="btn-dr-trigger-result"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-appexport">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('appexport')">
|
||
<h3>Alkalmazás Export/Import</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div id="appexport-status"><span class="text-muted">Betöltés...</span></div>
|
||
<div class="debug-actions" style="margin-top:.75rem">
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-scan" data-label="Csomagok keresése" onclick="scanAppBundles()">Csomagok keresése</button>
|
||
<span class="debug-result" id="btn-appexport-scan-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-cleanup" data-label="Temp fájlok törlése" onclick="triggerAction('btn-appexport-cleanup','/api/debug/appexport/cleanup','POST')">Temp fájlok törlése</button>
|
||
<span class="debug-result" id="btn-appexport-cleanup-result"></span>
|
||
|
||
<button class="btn btn-secondary btn-sm" id="btn-appexport-refresh" data-label="Frissítés" onclick="loadSectionData('appexport')">Frissítés</button>
|
||
</div>
|
||
<div id="appexport-bundles" style="display:none;margin-top:1rem"></div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div class="card debug-section" id="section-logs">
|
||
<div class="card-header debug-section-header" onclick="toggleSection('logs')">
|
||
<h3>Naplóviewer</h3>
|
||
<span class="section-toggle">▶</span>
|
||
</div>
|
||
<div class="card-body debug-section-body" style="display:none">
|
||
<div class="debug-log-filters" style="margin-bottom:.5rem">
|
||
<button class="btn btn-xs debug-log-source active" id="log-tab-controller" onclick="setLogSource('controller',this)">Vezérlő</button>
|
||
<button class="btn btn-xs debug-log-source" id="log-tab-agent" onclick="setLogSource('agent',this)">Ügynök</button>
|
||
</div>
|
||
<div class="debug-log-controls">
|
||
<div class="debug-log-filters">
|
||
<button class="btn btn-xs debug-log-filter active" data-level="DEBUG" onclick="setLogLevel('DEBUG',this)">DEBUG</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="INFO" onclick="setLogLevel('INFO',this)">INFO</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="WARN" onclick="setLogLevel('WARN',this)">WARN</button>
|
||
<button class="btn btn-xs debug-log-filter" data-level="ERROR" onclick="setLogLevel('ERROR',this)">ERROR</button>
|
||
</div>
|
||
<div>
|
||
<label style="font-size:.85rem;cursor:pointer">
|
||
<input type="checkbox" id="log-auto-refresh" checked onchange="toggleLogAutoRefresh()"> Automatikus frissítés
|
||
</label>
|
||
<button class="btn btn-xs btn-secondary" onclick="clearLogDisplay()">Törlés</button>
|
||
<span class="text-muted" id="log-count" style="font-size:.85rem;margin-left:.5rem"></span>
|
||
</div>
|
||
</div>
|
||
<div class="debug-log-viewer" id="log-viewer"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
|
||
var pollingIntervals = {};
|
||
function toggleSection(id) {
|
||
var body = document.querySelector('#section-' + id + ' .debug-section-body');
|
||
var toggle = document.querySelector('#section-' + id + ' .section-toggle');
|
||
var visible = body.style.display !== 'none';
|
||
body.style.display = visible ? 'none' : 'block';
|
||
toggle.textContent = visible ? '▶' : '▼';
|
||
if (!visible && !body.dataset.loaded) {
|
||
body.dataset.loaded = 'true';
|
||
loadSectionData(id);
|
||
}
|
||
|
||
if (visible) {
|
||
stopPolling(id);
|
||
}
|
||
}
|
||
|
||
function startPolling(id, interval, fn) {
|
||
stopPolling(id);
|
||
fn();
|
||
pollingIntervals[id] = setInterval(fn, interval);
|
||
}
|
||
function stopPolling(id) {
|
||
if (pollingIntervals[id]) {
|
||
clearInterval(pollingIntervals[id]);
|
||
delete pollingIntervals[id];
|
||
}
|
||
}
|
||
window.addEventListener('beforeunload', function() {
|
||
for (var k in pollingIntervals) { clearInterval(pollingIntervals[k]); }
|
||
});
|
||
|
||
|
||
function triggerAction(buttonId, url, method, body) {
|
||
var btn = document.getElementById(buttonId);
|
||
var result = document.getElementById(buttonId + '-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
fetch(url, {
|
||
method: method || 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: body ? JSON.stringify(body) : undefined
|
||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message || 'OK';
|
||
if (data.data && data.data.latency_ms) result.textContent += ' (' + data.data.latency_ms + 'ms)';
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
|
||
|
||
function loadSectionData(id) {
|
||
switch (id) {
|
||
case 'diagnostic': loadDiagnostic(); break;
|
||
case 'events': loadEventHistory(); break;
|
||
case 'backup': loadBackupStatus(); break;
|
||
case 'hub': loadHubStatus(); break;
|
||
case 'telemetry': break;
|
||
case 'selfupdate': loadSelfUpdateStatus(); break;
|
||
case 'appexport': loadAppExportStatus(); break;
|
||
case 'logs': initLogViewer(); break;
|
||
}
|
||
}
|
||
|
||
|
||
function loadDiagnostic() {
|
||
document.getElementById('diagnostic-content').innerHTML = '<span class="text-muted">Betöltés...</span>';
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
renderDiagnostic(data);
|
||
}).catch(function(e) {
|
||
document.getElementById('diagnostic-content').innerHTML = '<span class="debug-result-error">Hiba: ' + e.message + '</span>';
|
||
});
|
||
}
|
||
function renderDiagnostic(d) {
|
||
var html = '';
|
||
|
||
if (d.controller) {
|
||
var c = d.controller;
|
||
var uptime = c.uptime_seconds;
|
||
var uptimeStr = uptime < 60 ? uptime + 'mp' : uptime < 3600 ? Math.floor(uptime/60) + 'p' : Math.floor(uptime/3600) + 'ó ' + Math.floor((uptime%3600)/60) + 'p';
|
||
html += '<div class="debug-kv-grid"><dt>Verzió</dt><dd class="mono">' + c.version + '</dd>';
|
||
html += '<dt>Uptime</dt><dd>' + uptimeStr + '</dd>';
|
||
html += '<dt>PID</dt><dd>' + c.pid + '</dd>';
|
||
html += '<dt>Log szint</dt><dd>' + c.logging_level + '</dd>';
|
||
if (c.config_hash) html += '<dt>Config hash</dt><dd class="mono" style="font-size:.75rem">' + c.config_hash.substring(0,12) + '…</dd>';
|
||
html += '</div>';
|
||
}
|
||
|
||
if (d.storage && d.storage.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Tárhely</h4><table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Állapot</th><th>Használat</th></tr>';
|
||
d.storage.forEach(function(s) {
|
||
var status = s.disconnected ? '<span class="status-dot red"></span> Leválasztva' : s.decommissioned ? '<span class="status-dot gray"></span> Leszerelve' : '<span class="status-dot green"></span> OK';
|
||
var usage = s.used_percent !== undefined ? s.used_gb.toFixed(1) + ' / ' + s.total_gb.toFixed(1) + ' GB (' + s.used_percent.toFixed(0) + '%)' : '-';
|
||
html += '<tr><td class="mono">' + s.path + '</td><td>' + (s.label||'-') + '</td><td>' + status + '</td><td>' + usage + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
|
||
if (d.stacks) {
|
||
html += '<h4 style="margin-top:.75rem">Stack-ek</h4>';
|
||
html += '<div class="debug-kv-grid"><dt>Telepítve</dt><dd>' + d.stacks.deployed + '</dd><dt>Futó konténerek</dt><dd>' + d.stacks.running + '</dd><dt>Leállt</dt><dd>' + d.stacks.stopped + '</dd></div>';
|
||
if (d.stacks.list && d.stacks.list.length > 0) {
|
||
html += '<table class="info-table debug-table"><tr><th>Név</th><th>Állapot</th><th>Konténerek</th></tr>';
|
||
d.stacks.list.forEach(function(st) {
|
||
var stateDot = st.state === 'running' ? '<span class="status-dot green"></span>' : st.state === 'stopped' ? '<span class="status-dot red"></span>' : '<span class="status-dot yellow"></span>';
|
||
html += '<tr><td>' + (st.display_name || st.name) + '</td><td>' + stateDot + ' ' + st.state + '</td><td class="mono" style="font-size:.75rem">' + (st.containers||[]).join(', ') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
}
|
||
|
||
if (d.scheduler && d.scheduler.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Ütemező</h4><table class="info-table debug-table"><tr><th>Név</th><th>Típus</th><th>Utolsó futás</th><th>Fut</th></tr>';
|
||
d.scheduler.forEach(function(j) {
|
||
var type = j.type === 'daily' ? j.schedule : (j.interval || '-');
|
||
html += '<tr><td>' + j.name + '</td><td>' + type + '</td><td>' + (j.last_run ? fmtTime(j.last_run) : '-') + '</td><td>' + (j.running ? 'fut' : '-') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
|
||
if (d.alerts && d.alerts.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Figyelmeztetések</h4>';
|
||
d.alerts.forEach(function(a) {
|
||
var cls = a.level === 'error' ? 'debug-result-error' : a.level === 'warning' ? 'debug-result debug-result-pending' : '';
|
||
html += '<div class="' + cls + '" style="padding:.3rem .5rem;margin-bottom:.25rem;border-radius:4px;font-size:.85rem">' + a.message + '</div>';
|
||
});
|
||
}
|
||
document.getElementById('diagnostic-content').innerHTML = html;
|
||
}
|
||
function downloadDump() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});
|
||
var a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = 'debug-dump-' + new Date().toISOString().replace(/[:.]/g,'-') + '.json';
|
||
a.click();
|
||
});
|
||
}
|
||
|
||
|
||
function sendTestEvent() {
|
||
var eventType = document.getElementById('event-type').value;
|
||
var severity = document.getElementById('event-severity').value;
|
||
triggerAction('btn-test-event', '/api/debug/event/test', 'POST', {event_type: eventType, severity: severity});
|
||
setTimeout(loadEventHistory, 1500);
|
||
}
|
||
function loadEventHistory() {
|
||
fetch('/api/debug/event/history', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok || !data.data || data.data.length === 0) {
|
||
document.getElementById('event-history').innerHTML = '<span class="text-muted">Nincs esemény</span>';
|
||
return;
|
||
}
|
||
var html = '<table class="info-table debug-table"><tr><th>Idő</th><th>Típus</th><th>Súlyosság</th><th>Hub</th></tr>';
|
||
data.data.forEach(function(e) {
|
||
var statusCls = e.hub_status >= 200 && e.hub_status < 300 ? 'debug-result-ok' : 'debug-result-error';
|
||
html += '<tr><td>' + fmtTime(e.timestamp) + '</td><td>' + e.event_type + '</td><td>' + e.severity + '</td><td class="' + statusCls + '">' + (e.hub_status || e.hub_error || '-') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
document.getElementById('event-history').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('event-history').innerHTML = '<span class="text-muted">Hiba</span>';
|
||
});
|
||
}
|
||
|
||
|
||
function loadBackupStatus() {
|
||
fetch('/api/backup/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('backup-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
var d = data.data || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>Állapot:</span><span>' + (d.running ? 'Fut...' : 'Kész') + '</span>';
|
||
if (d.last_db_dump) html += '<span>Utolsó DB dump:</span><span>' + fmtTime(d.last_db_dump) + '</span>';
|
||
if (d.last_backup) html += '<span>Utolsó restic:</span><span>' + fmtTime(d.last_backup) + '</span>';
|
||
if (d.snapshot_count !== undefined) html += '<span>Snapshotok:</span><span>' + d.snapshot_count + '</span>';
|
||
html += '</div>';
|
||
document.getElementById('backup-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('backup-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
|
||
function loadHubStatus() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var h = data.hub || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>URL:</span><span class="mono">' + (h.url||'-') + '</span>';
|
||
html += '<span>Utolsó push:</span><span>' + (h.last_success ? fmtTime(h.last_success) : '-') + '</span>';
|
||
html += '<span>Hibák egymás után:</span><span>' + (h.consecutive_failures||0) + '</span>';
|
||
if (h.last_error) html += '<span>Utolsó hiba:</span><span class="debug-result-error">' + h.last_error + '</span>';
|
||
html += '</div>';
|
||
document.getElementById('hub-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('hub-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
|
||
function loadSelfUpdateStatus() {
|
||
fetch('/api/debug/dump', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var u = data.self_update || {};
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<span>Engedélyezve:</span><span>' + (u.enabled ? 'Igen' : 'Nem') + '</span>';
|
||
html += '<span>Automatikus:</span><span>' + (u.auto ? 'Igen' : 'Nem') + '</span>';
|
||
if (u.last_check) {
|
||
html += '<span>Jelenlegi:</span><span class="mono">' + u.last_check.current_version + '</span>';
|
||
html += '<span>Legújabb:</span><span class="mono">' + u.last_check.latest_version + '</span>';
|
||
html += '<span>Frissítés:</span><span>' + (u.last_check.update_available ? '<span class="state-text-run">Elérhető</span>' : 'Naprakész') + '</span>';
|
||
}
|
||
html += '</div>';
|
||
document.getElementById('selfupdate-status').innerHTML = html;
|
||
}).catch(function() {
|
||
document.getElementById('selfupdate-status').innerHTML = '<span class="text-muted">Nem elérhető</span>';
|
||
});
|
||
}
|
||
|
||
|
||
function triggerDR() {
|
||
var input = document.getElementById('dr-confirm-input');
|
||
if (input.value !== 'RESET') {
|
||
var result = document.getElementById('btn-dr-trigger-result');
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Írja be: RESET';
|
||
return;
|
||
}
|
||
var btn = document.getElementById('btn-dr-trigger');
|
||
felhomConfirm(btn, 'FIGYELEM! Ez újraindítja a controllert setup módba. Biztosan folytatja?', function () {
|
||
var result = document.getElementById('btn-dr-trigger-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
fetch('/api/debug/dr/trigger-setup', {
|
||
method: 'POST',
|
||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||
body: JSON.stringify({confirm: 'RESET'})
|
||
}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = 'Controller újraindulása...';
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hiba: ' + e.message;
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
});
|
||
}
|
||
|
||
|
||
var currentLogLevel = 'DEBUG';
|
||
var currentLogSource = 'controller';
|
||
var lastLogTimestamp = '';
|
||
var LOG_LEVEL_PRIORITY = {DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3};
|
||
function setLogLevel(level, btn) {
|
||
currentLogLevel = level;
|
||
document.querySelectorAll('.debug-log-filter').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
refreshLogs();
|
||
}
|
||
function setLogSource(source, btn) {
|
||
currentLogSource = source;
|
||
document.querySelectorAll('.debug-log-source').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
clearLogDisplay();
|
||
refreshLogs();
|
||
}
|
||
function initLogViewer() {
|
||
refreshLogs();
|
||
if (document.getElementById('log-auto-refresh').checked) {
|
||
startPolling('logs', 2000, function() { refreshLogs(true); });
|
||
}
|
||
}
|
||
function toggleLogAutoRefresh() {
|
||
if (document.getElementById('log-auto-refresh').checked) {
|
||
startPolling('logs', 2000, function() { refreshLogs(true); });
|
||
} else {
|
||
stopPolling('logs');
|
||
}
|
||
}
|
||
function refreshLogs(append) {
|
||
if (currentLogSource === 'agent') { refreshAgentLogs(); return; }
|
||
var url = '/api/debug/logs?level=' + currentLogLevel + '&limit=1000';
|
||
if (append && lastLogTimestamp) url += '&after=' + encodeURIComponent(lastLogTimestamp);
|
||
fetch(url, {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) return;
|
||
var entries = (data.data && data.data.entries) || [];
|
||
var total = (data.data && data.data.total) || 0;
|
||
var viewer = document.getElementById('log-viewer');
|
||
if (!append) viewer.innerHTML = '';
|
||
entries.forEach(function(e) {
|
||
var cls = 'debug-log-entry debug-log-' + e.level.toLowerCase();
|
||
var ts = e.timestamp ? fmtTime(e.timestamp) : '';
|
||
var src = e.source ? ' <span class="text-muted">' + e.source + '</span>' : '';
|
||
var div = document.createElement('div');
|
||
div.className = cls;
|
||
div.innerHTML = '<span class="text-muted">' + ts + '</span> <span class="debug-log-level">[' + e.level + ']</span>' + src + ' ' + escapeHtml(e.message);
|
||
viewer.appendChild(div);
|
||
if (e.timestamp) lastLogTimestamp = e.timestamp;
|
||
});
|
||
if (!append || entries.length > 0) {
|
||
viewer.scrollTop = viewer.scrollHeight;
|
||
}
|
||
document.getElementById('log-count').textContent = viewer.children.length + ' / ' + total + ' bejegyzés';
|
||
}).catch(function(){});
|
||
}
|
||
|
||
|
||
function refreshAgentLogs() {
|
||
fetch('/api/debug/agent-logs', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
var viewer = document.getElementById('log-viewer');
|
||
viewer.innerHTML = '';
|
||
if (!data.ok) {
|
||
viewer.innerHTML = '<div class="debug-log-entry debug-log-warn">' + escapeHtml(data.error || 'Hiba') + '</div>';
|
||
document.getElementById('log-count').textContent = '';
|
||
return;
|
||
}
|
||
if (data.data && data.data.unsupported) {
|
||
viewer.innerHTML = '<div class="debug-log-entry">' + escapeHtml(data.data.notice) + '</div>';
|
||
document.getElementById('log-count').textContent = '';
|
||
return;
|
||
}
|
||
var entries = (data.data && data.data.entries) || [];
|
||
var total = (data.data && data.data.total) || 0;
|
||
var min = LOG_LEVEL_PRIORITY[currentLogLevel] || 0;
|
||
entries.forEach(function(e) {
|
||
if ((LOG_LEVEL_PRIORITY[e.level] || 0) < min) return;
|
||
var div = document.createElement('div');
|
||
div.className = 'debug-log-entry debug-log-' + e.level.toLowerCase();
|
||
var ts = e.timestamp ? fmtTime(e.timestamp) : '';
|
||
div.innerHTML = '<span class="text-muted">' + ts + '</span> <span class="debug-log-level">[' + e.level + ']</span> ' + escapeHtml(e.message);
|
||
viewer.appendChild(div);
|
||
});
|
||
viewer.scrollTop = viewer.scrollHeight;
|
||
document.getElementById('log-count').textContent = viewer.children.length + ' / ' + total + ' bejegyzés';
|
||
}).catch(function(){});
|
||
}
|
||
function clearLogDisplay() {
|
||
document.getElementById('log-viewer').innerHTML = '';
|
||
document.getElementById('log-count').textContent = '';
|
||
lastLogTimestamp = '';
|
||
}
|
||
|
||
|
||
function runTelemetryTest() {
|
||
var btn = document.getElementById('btn-telemetry-run');
|
||
var result = document.getElementById('btn-telemetry-run-result');
|
||
var detail = document.getElementById('telemetry-detail');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Folyamatban...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
detail.style.display = 'none';
|
||
|
||
fetch('/api/debug/telemetry', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message;
|
||
if (data.data && data.data.app_telemetry) {
|
||
renderTelemetryDetail(data.data);
|
||
}
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
|
||
function renderTelemetryDetail(data) {
|
||
var detail = document.getElementById('telemetry-detail');
|
||
var apps = data.app_telemetry || [];
|
||
if (apps.length === 0) {
|
||
detail.innerHTML = '<span class="text-muted">Nincs telepített alkalmazás vagy nincs mérési adat.</span>';
|
||
detail.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
var html = '<table class="debug-table" style="width:100%;font-size:.85rem">';
|
||
html += '<thead><tr><th>Alkalmazás</th><th>Konténerek</th><th>Memória (jelen.)</th><th>Memória (átlag)</th><th>Memória (csúcs)</th><th>CPU (átlag)</th><th>Katalógus limit</th><th>Hibák</th><th>Figyelmeztetések</th></tr></thead><tbody>';
|
||
|
||
for (var i = 0; i < apps.length; i++) {
|
||
var a = apps[i];
|
||
var errorClass = a.log_errors > 0 ? ' style="color:var(--crit);font-weight:600"' : '';
|
||
var warnClass = a.log_warnings > 0 ? ' style="color:var(--warn);font-weight:600"' : '';
|
||
html += '<tr>';
|
||
html += '<td><strong>' + escapeHtml(a.display_name || a.app_name) + '</strong></td>';
|
||
html += '<td class="mono" style="font-size:.8rem">' + (a.containers||[]).map(escapeHtml).join(', ') + '</td>';
|
||
html += '<td>' + (a.memory_current_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.memory_avg_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.memory_peak_mb||0).toFixed(1) + ' MB</td>';
|
||
html += '<td>' + (a.cpu_avg_percent||0).toFixed(1) + '%</td>';
|
||
html += '<td class="mono">' + escapeHtml(a.catalog_limit || '-') + '</td>';
|
||
html += '<td' + errorClass + '>' + (a.log_errors||0) + '</td>';
|
||
html += '<td' + warnClass + '>' + (a.log_warnings||0) + '</td>';
|
||
html += '</tr>';
|
||
|
||
if (a.issues && a.issues.length > 0) {
|
||
for (var j = 0; j < a.issues.length; j++) {
|
||
var issue = a.issues[j];
|
||
var sevColor = issue.severity === 'error' ? 'var(--crit)' : 'var(--warn)';
|
||
html += '<tr style="font-size:.8rem;opacity:.85"><td></td>';
|
||
html += '<td colspan="6" style="padding-left:1.5rem"><span style="color:' + sevColor + '">' + escapeHtml(issue.severity.toUpperCase()) + '</span> ' + escapeHtml(issue.message) + '</td>';
|
||
html += '<td colspan="2">×' + (issue.count||0) + '</td></tr>';
|
||
}
|
||
}
|
||
}
|
||
html += '</tbody></table>';
|
||
|
||
html += '<details style="margin-top:1rem"><summary class="text-muted" style="cursor:pointer;font-size:.85rem">Nyers JSON</summary>';
|
||
html += '<pre class="mono" style="font-size:.75rem;max-height:400px;overflow:auto;padding:.5rem;background:rgba(0,0,0,.3);border-radius:.25rem;margin-top:.5rem">' + escapeHtml(JSON.stringify(apps, null, 2)) + '</pre>';
|
||
html += '</details>';
|
||
|
||
detail.innerHTML = html;
|
||
detail.style.display = 'block';
|
||
}
|
||
|
||
|
||
function loadAppExportStatus() {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="text-muted">Betöltés...</span>';
|
||
fetch('/api/debug/appexport/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (!data.ok) { document.getElementById('appexport-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
|
||
renderAppExportStatus(data.data);
|
||
}).catch(function(e) {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="debug-result-error">Hiba: ' + e.message + '</span>';
|
||
});
|
||
}
|
||
function renderAppExportStatus(d) {
|
||
if (!d.available) {
|
||
document.getElementById('appexport-status').innerHTML = '<span class="text-muted">App export modul nem elérhető</span>';
|
||
return;
|
||
}
|
||
var html = '<div class="debug-kv-grid">';
|
||
html += '<dt>Debug mód</dt><dd>' + (d.debug_enabled ? '<span class="state-text-run">Aktív</span>' : 'Inaktív') + '</dd>';
|
||
html += '<dt>Verzió</dt><dd class="mono">' + (d.version||'-') + '</dd>';
|
||
html += '<dt>Csomagok</dt><dd>' + (d.bundle_count||0) + ' db</dd>';
|
||
html += '<dt>Temp fájlok</dt><dd>' + (d.stale_temp_count||0) + ' db';
|
||
if (d.stale_temp_count > 0) html += ' <span style="color:var(--warn)"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg></span>';
|
||
html += '</dd>';
|
||
html += '</div>';
|
||
|
||
|
||
if (d.has_active_job && d.active_job) {
|
||
var j = d.active_job;
|
||
html += '<h4 style="margin-top:.75rem">Aktív feladat</h4>';
|
||
html += '<div class="debug-kv-grid">';
|
||
html += '<dt>Típus</dt><dd>' + (j.job_type||'-') + '</dd>';
|
||
html += '<dt>Stack</dt><dd>' + (j.display_name || j.stack_name || '-') + '</dd>';
|
||
html += '<dt>Állapot</dt><dd>' + (j.running ? 'Fut...' : j.done ? 'Kész' : 'Várakozik') + '</dd>';
|
||
if (j.error) html += '<dt>Hiba</dt><dd class="debug-result-error">' + escapeHtml(j.error) + '</dd>';
|
||
if (j.output_path) html += '<dt>Kimenet</dt><dd class="mono" style="font-size:.75rem">' + escapeHtml(j.output_path) + '</dd>';
|
||
if (j.output_size) html += '<dt>Méret</dt><dd>' + j.output_size + '</dd>';
|
||
html += '</div>';
|
||
if (j.steps && j.steps.length > 0) {
|
||
html += '<div style="margin-top:.5rem">';
|
||
j.steps.forEach(function(s) {
|
||
var icon = s.status === 'done' ? '✓' : s.status === 'running' ? '↻' : s.status === 'failed' ? '×' : '○';
|
||
var cls = s.status === 'failed' ? 'color:var(--crit)' : s.status === 'done' ? 'color:var(--blue)' : s.status === 'running' ? 'color:var(--blue)' : 'color:var(--text-3)';
|
||
html += '<div style="font-size:.85rem;padding:.1rem 0;' + cls + '">' + icon + ' ' + escapeHtml(s.label);
|
||
if (s.error) html += ' <span style="font-size:.8rem">(' + escapeHtml(s.error) + ')</span>';
|
||
html += '</div>';
|
||
});
|
||
html += '</div>';
|
||
}
|
||
}
|
||
|
||
|
||
if (d.export_dirs && d.export_dirs.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem">Export könyvtárak</h4>';
|
||
html += '<table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Létezik</th></tr>';
|
||
d.export_dirs.forEach(function(dir) {
|
||
html += '<tr><td class="mono" style="font-size:.75rem">' + escapeHtml(dir.path) + '</td><td>' + (dir.label||'-') + '</td><td>' + (dir.exists ? 'igen' : '<span class="state-text-warn">nincs</span>') + '</td></tr>';
|
||
});
|
||
html += '</table>';
|
||
}
|
||
|
||
|
||
if (d.stale_temp_files && d.stale_temp_files.length > 0) {
|
||
html += '<h4 style="margin-top:.75rem;color:var(--warn)">Elavult temp fájlok</h4>';
|
||
html += '<ul style="font-size:.85rem;margin:0;padding-left:1.5rem">';
|
||
d.stale_temp_files.forEach(function(f) {
|
||
html += '<li class="mono" style="font-size:.75rem">' + escapeHtml(f) + '</li>';
|
||
});
|
||
html += '</ul>';
|
||
}
|
||
|
||
document.getElementById('appexport-status').innerHTML = html;
|
||
|
||
|
||
if (d.has_active_job && d.active_job && d.active_job.running) {
|
||
startPolling('appexport', 2000, function() {
|
||
fetch('/api/debug/appexport/status', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) renderAppExportStatus(data.data);
|
||
}).catch(function(){});
|
||
});
|
||
}
|
||
}
|
||
function scanAppBundles() {
|
||
var btn = document.getElementById('btn-appexport-scan');
|
||
var result = document.getElementById('btn-appexport-scan-result');
|
||
btn.disabled = true;
|
||
btn.textContent = 'Keresés...';
|
||
result.className = 'debug-result';
|
||
result.textContent = '';
|
||
fetch('/api/debug/appexport/bundles', {headers: csrfHeaders()}).then(function(r){return r.json()}).then(function(data) {
|
||
if (data.ok) {
|
||
result.className = 'debug-result debug-result-ok';
|
||
result.textContent = data.message;
|
||
if (data.data && data.data.bundles) {
|
||
renderAppBundles(data.data.bundles);
|
||
}
|
||
} else {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = data.error || 'Hiba';
|
||
}
|
||
}).catch(function(e) {
|
||
result.className = 'debug-result debug-result-error';
|
||
result.textContent = 'Hálózati hiba: ' + e.message;
|
||
}).finally(function() {
|
||
btn.disabled = false;
|
||
btn.textContent = btn.dataset.label;
|
||
});
|
||
}
|
||
function renderAppBundles(bundles) {
|
||
var container = document.getElementById('appexport-bundles');
|
||
if (!bundles || bundles.length === 0) {
|
||
container.innerHTML = '<span class="text-muted">Nem található .fab csomag.</span>';
|
||
container.style.display = 'block';
|
||
return;
|
||
}
|
||
var html = '<table class="info-table debug-table"><tr><th>Alkalmazás</th><th>Dátum</th><th>Méret</th><th>Tároló</th><th>Titkos</th><th>DB</th><th>HDD</th><th>Elérés</th></tr>';
|
||
bundles.forEach(function(b) {
|
||
html += '<tr>';
|
||
html += '<td><strong>' + escapeHtml(b.display_name || b.app_name) + '</strong></td>';
|
||
html += '<td>' + (b.exported_at || '-') + '</td>';
|
||
html += '<td>' + (b.size_human || '-') + '</td>';
|
||
html += '<td>' + escapeHtml(b.drive_label || b.drive_path) + '</td>';
|
||
html += '<td>' + (b.encrypted ? 'titkosított' : '-') + '</td>';
|
||
html += '<td>' + (b.has_db ? 'igen' : '-') + '</td>';
|
||
html += '<td>' + (b.needs_hdd ? 'igen' : '-') + '</td>';
|
||
html += '<td class="mono" style="font-size:.7rem;max-width:200px;overflow:hidden;text-overflow:ellipsis">' + escapeHtml(b.path) + '</td>';
|
||
html += '</tr>';
|
||
});
|
||
html += '</table>';
|
||
container.innerHTML = html;
|
||
container.style.display = 'block';
|
||
}
|
||
|
||
|
||
function fmtTime(ts) {
|
||
if (!ts) return '-';
|
||
var d = new Date(ts);
|
||
if (isNaN(d.getTime())) return ts;
|
||
var pad = function(n) { return n < 10 ? '0' + n : n; };
|
||
var now = new Date();
|
||
var time = pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
|
||
if (d.toDateString() === now.toDateString()) return time;
|
||
return pad(d.getMonth()+1) + '-' + pad(d.getDate()) + ' ' + time;
|
||
}
|
||
function escapeHtml(s) {
|
||
var d = document.createElement('div');
|
||
d.textContent = s;
|
||
return d.innerHTML;
|
||
}
|
||
</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>
|
||
|