e66d4669d6
gates / gates (push) Failing after 47s
An app was six pages — details, install/settings, logs, export, the per-app backup panel and a
details card for moving data — and every "Vissza" on them went to the app LIST, so there was no way
back to the app you were looking at. It is now ONE page with four tab URLs under /apps/{slug}/,
one shared header and one set of banners.
settings Mukodes (Start/Restart/Stop, Stop confirms) + the cards that want something from the
household + the install-time and optional settings. The default for an installed app,
RENDERED at the bare URL and never redirected to.
data the data folders, move to another drive, the nightly copy, the 2nd-copy setting, export
guide tagline, screenshots, use cases, first steps, the catalog's guide, the FAQ, the docs
link — and the WHOLE page for an app that is not installed
logs the log, and the only tab whose poller runs
The tabs are links, not panels: a link in an alert has to land ON a tab, and the log poller must not
run on the other three. Start/Restart/Stop left the page header, where they read as menu buttons.
The old /stacks/{name}/{deploy,logs,export,backup} GETs answer 302 — never 301, which a browser
caches beyond anyone's reach — to the tab that now holds them, query string carried through, with
name->slug resolved through the stack list rather than assumed equal. Every POST on those paths is
untouched: the backup form, the app-email toggle and the reveal endpoints keep their URLs.
A value asked AFTER the app starts is now generic (D2). The catalog marks a field `after_start: true`
and supplies its label; the ACTION is Go, in a registry keyed by slug + env var. The catalog can
never declare what runs — a declared field with no registered action renders nothing, POSTs 404 and
logs one WARN with the env var KEY, never the value. A red-proof found the second half of that guard
unpinned: deleting the "the catalog must have DECLARED this field" check left the suite green, and a
POST for an undeclared field then reached Plex and opened the setup gate.
TestAfterStart_RegistryAloneIsNotAnInvitation pins it.
New catalog block app_info.guide (sections + faq + checked_version), parsed, localised field by
field and rendered with the household's own address filled in. It renders only when checked_version
names the image tag its author clicked through. No template carries one yet.
The Pi and "csak x86" badges are gone; PiCompatible and the catalog key stay this release, or a box
on 0.314.0 would read "csak x86" on every app (R-1067).
template_id_gate.py now follows {{template}} includes, with a scope decoy proving the walk did not
become vacuous.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
777 lines
46 KiB
HTML
777 lines
46 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>PrivateBin — 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}:{};}
|
|
var fhEN=document.documentElement.lang==='en';
|
|
function fhNum(v,prec){var s=Number(v).toFixed(prec);return fhEN?s:s.replace('.',',');}
|
|
function fhIsoDate(iso){return fhEN?iso:iso.replace(/^(\d{4})-(\d{2})-(\d{2})/,'$1. $2. $3.');}
|
|
function fhDateTime(d){return fhIsoDate(d.toLocaleString('sv-SE',{timeZone:'Europe/Budapest',year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}));}
|
|
function fhMonthDay(d){var s=d.toLocaleDateString('sv-SE',{timeZone:'Europe/Budapest',month:'2-digit',day:'2-digit'});return fhEN?s:s.replace(/^(\d{2})-(\d{2})$/,'$1. $2.');}
|
|
|
|
|
|
|
|
|
|
|
|
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="active"><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=""><svg class="ico"><use href="#i-wrench"/></svg>Hibakeresés</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">
|
|
<div style="display:flex;align-items:center;gap:1rem">
|
|
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
|
<h2>PrivateBin</h2>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:.5rem">
|
|
|
|
<span class="stack-state-badge state-progress">Telepítés...</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="app-hero">
|
|
<img class="app-hero-logo" src="/static/assets/privatebin-logo.svg"
|
|
alt="PrivateBin" data-fallback="/static/app-placeholder.svg"
|
|
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/privatebin-logo.png';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
|
|
<div class="app-hero-text">
|
|
<p class="app-info-tagline"></p>
|
|
<div class="stack-meta-badges">
|
|
<span class="meta-badge">~ RAM</span>
|
|
<span class="meta-badge"></span>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="alert alert-info" style="margin-top:1rem" data-update-phase="pull">Letöltés folyamatban…</div>
|
|
|
|
|
|
|
|
|
|
<nav class="app-tabs" aria-label="Alkalmazás lapjai">
|
|
<a class="app-tab app-tab-active" href="/apps/privatebin/settings" data-tab="settings"
|
|
aria-current="page" aria-selected="true">Beállítások</a>
|
|
<a class="app-tab" href="/apps/privatebin/data" data-tab="data"
|
|
aria-selected="false">Adatok és mentés</a>
|
|
<a class="app-tab" href="/apps/privatebin/guide" data-tab="guide"
|
|
aria-selected="false">Útmutató</a>
|
|
<a class="app-tab" href="/apps/privatebin/logs" data-tab="logs"
|
|
aria-selected="false">Napló</a>
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="deploy-container">
|
|
|
|
|
|
|
|
|
|
<div class="app-info-card" id="app-run-card" data-run-card="1">
|
|
<h3>Működés</h3>
|
|
<p class="form-hint" data-run-state="deploying">Telepítés...</p>
|
|
<div style="display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.5rem">
|
|
|
|
<button class="btn btn-sm btn-success" data-run-action="start" onclick="stackAction(event, 'privatebin', 'start')">Indítás</button>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="app-info-grid">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="alert alert-info">
|
|
Ez az alkalmazás már telepítve van. Az alábbi beállítások csak olvashatók.
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<form id="deploy-form" class="deploy-form">
|
|
|
|
|
|
|
|
|
|
|
|
</form>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
function appPress(btn, url) {
|
|
var err = btn.parentNode.querySelector('.app-press-err');
|
|
if (err) { err.style.display = 'none'; }
|
|
btn.disabled = true;
|
|
fetch(url, {method: 'POST', headers: csrfHeaders(), credentials: 'same-origin'})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (j) {
|
|
if (!j.ok) { throw new Error(j.error || ''); }
|
|
window.location.reload();
|
|
})
|
|
.catch(function (e) { btn.disabled = false; if (err) { err.textContent = e.message; err.style.display = 'inline'; } });
|
|
}
|
|
|
|
|
|
function appAfterStart(btn, slug, envVar) {
|
|
var box = btn.closest('.after-start-card'), err = box.querySelector('.app-press-err');
|
|
var input = box.querySelector('input');
|
|
err.style.display = 'none'; btn.disabled = true;
|
|
fetch('/apps/' + slug + '/after-start/' + envVar, {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify({value: (input ? input.value : '').trim()})
|
|
}).then(function (r) { return r.json(); })
|
|
.then(function (j) { if (!j.ok) { throw new Error(j.error || ''); } window.location.reload(); })
|
|
.catch(function (e) { btn.disabled = false; err.textContent = e.message; err.style.display = 'inline'; });
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
function checkStorageSpace(sel) {
|
|
var opt = sel.options[sel.selectedIndex];
|
|
var warn = document.getElementById('storage-space-warn');
|
|
if (!warn) return;
|
|
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
|
warn.style.display = freePct < 20 ? 'block' : 'none';
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
|
if (sel) checkStorageSpace(sel);
|
|
});
|
|
|
|
|
|
|
|
|
|
function revealPasswordField(stackName, envVar, btn) {
|
|
var el = document.getElementById('field-' + envVar);
|
|
if (!el) return;
|
|
if (btn.dataset.shown === '1') {
|
|
el.value = '';
|
|
el.type = 'password';
|
|
btn.dataset.shown = '';
|
|
return;
|
|
}
|
|
btn.disabled = true;
|
|
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
|
credentials: 'same-origin',
|
|
body: 'env_var=' + encodeURIComponent(envVar)
|
|
}).then(function (r) { return r.json(); }).then(function (j) {
|
|
btn.disabled = false;
|
|
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
|
el.value = j.data.value;
|
|
el.type = 'text';
|
|
el.dataset.revealed = '1';
|
|
btn.dataset.shown = '1';
|
|
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
|
}
|
|
function revealAutoField(stackName, envVar, btn, elId) {
|
|
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
|
if (!el) return;
|
|
if (btn.dataset.shown === '1') {
|
|
el.value = '';
|
|
el.type = 'password';
|
|
el.dataset.revealed = '';
|
|
btn.dataset.shown = '';
|
|
btn.textContent = 'Megjelenítés';
|
|
return;
|
|
}
|
|
btn.disabled = true;
|
|
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
|
credentials: 'same-origin',
|
|
body: 'env_var=' + encodeURIComponent(envVar)
|
|
}).then(function (r) { return r.json(); }).then(function (j) {
|
|
btn.disabled = false;
|
|
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
|
el.value = j.data.value;
|
|
el.type = 'text';
|
|
btn.dataset.shown = '1';
|
|
btn.textContent = 'Elrejtés';
|
|
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
|
}
|
|
function deleteStaleData(stackName, stalePath, btn) {
|
|
|
|
felhomConfirm(btn, 'Biztosan törölni szeretnéd a korábbi adatokat? Tárhely: ' + stalePath + ' — FIGYELEM: a művelet visszavonhatatlan! Előtte győződj meg róla, hogy az alkalmazás az új tárolóról megfelelően működik.', function () {
|
|
felhomConfirm(btn, 'UTOLSÓ FIGYELMEZTETÉS! A törlés visszavonhatatlan. Biztosan folytatod?', function () {
|
|
deleteStaleDataRun(stackName, stalePath, btn);
|
|
});
|
|
});
|
|
}
|
|
|
|
function deleteStaleDataRun(stackName, stalePath, btn) {
|
|
btn.disabled = true;
|
|
btn.textContent = 'Törlés folyamatban...';
|
|
|
|
fetch('/api/storage/stale-cleanup', {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
body: JSON.stringify({stack_name: stackName, stale_path: stalePath})
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.ok) {
|
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
|
btn.disabled = false;
|
|
btn.textContent = 'Korábbi adatok törlése';
|
|
return;
|
|
}
|
|
var msg = 'Korábbi adatok törölve!\n\nFelszabadított hely: ' + (data.freed_human || '?');
|
|
if (data.errors && data.errors.length > 0) {
|
|
msg += '\n\nNéhány hiba történt:\n' + data.errors.join('\n');
|
|
}
|
|
showAlert(msg);
|
|
|
|
var item = btn.closest('.stale-data-item');
|
|
if (item) item.remove();
|
|
|
|
var container = document.querySelector('.deploy-stale-data');
|
|
if (container && container.querySelectorAll('.stale-data-item').length === 0) {
|
|
container.remove();
|
|
}
|
|
})
|
|
.catch(function(e) {
|
|
showAlert('Hálózati hiba: ' + e.message);
|
|
btn.disabled = false;
|
|
btn.textContent = 'Korábbi adatok törlése';
|
|
});
|
|
}
|
|
|
|
</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>';
|
|
}
|
|
|
|
if (data.ok && data.data && data.data.userdata_kept && data.data.userdata_kept.length > 0) {
|
|
var udPaths = '';
|
|
data.data.userdata_kept.forEach(function(p) { udPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>'; });
|
|
hddInfo += '<div class="modal-section" data-userdata-kept="true"><strong>A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket:</strong>' + udPaths + '</div>';
|
|
}
|
|
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>';
|
|
}
|
|
if (data.data && data.data.userdata_kept && data.data.userdata_kept.length > 0) {
|
|
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem" data-userdata-kept="true">A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket: ' + data.data.userdata_kept.join(', ') + '</p>';
|
|
}
|
|
modal.querySelector('.modal-card').innerHTML =
|
|
'<h3>Sikeresen törölve!</h3>' +
|
|
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
|
removedInfo + preservedInfo +
|
|
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
|
} else {
|
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
|
btn.disabled = false;
|
|
btn.textContent = 'Törlés';
|
|
}
|
|
} catch (err) {
|
|
showAlert('Hálózati hiba: ' + err.message);
|
|
btn.disabled = false;
|
|
btn.textContent = 'Törlés';
|
|
}
|
|
}
|
|
async function removeStack(name) {
|
|
var modal = document.createElement('div');
|
|
modal.className = 'modal-overlay';
|
|
modal.id = 'remove-modal';
|
|
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
|
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
|
document.body.appendChild(modal);
|
|
try {
|
|
var [hddResp, backupResp] = await Promise.all([
|
|
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
|
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
|
]);
|
|
var sections = '';
|
|
|
|
var keepOnly = hddResp.ok && hddResp.data && hddResp.data.keep_data_only;
|
|
if (keepOnly) {
|
|
sections += '<div class="alert alert-info" style="margin-bottom:.75rem" data-remove-keep-only="true">Az ügyfélszolgálat foglalkozik ezzel az alkalmazással, ezért az adatai megmaradnak: most csak magát az alkalmazást távolíthatod el.</div>';
|
|
}
|
|
|
|
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 (!keepOnly && 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íted az alkalmazást, az adatokat újra importálnod 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 (!keepOnly && 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>';
|
|
}
|
|
}
|
|
|
|
if (hddResp.ok && hddResp.data && hddResp.data.userdata_kept && hddResp.data.userdata_kept.length > 0) {
|
|
var udPaths = '';
|
|
hddResp.data.userdata_kept.forEach(function(p) { udPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>'; });
|
|
sections += '<div class="modal-section" data-userdata-kept="true"><strong>A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket:</strong>' + udPaths + '</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.userdata_kept && data.data.userdata_kept.length > 0) {
|
|
preservedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem" data-userdata-kept="true">A fájljaid ezekben a mappákban megmaradnak — a fájlböngészőben látod őket: ' + data.data.userdata_kept.join(', ') + '</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>
|
|
|