Slice 6 parts 0 and A: the guide in English, golden 0.258.0, and the CI finding
gates / gates (push) Successful in 30s
gates / gates (push) Successful in 30s
The volunteer guide has an English twin. It is a TRANSLATION, not a rewrite: 16 sections in the same order, identical step counts, table rows and warning blocks per section (measured, 0 sections differing in structure). Word counts are NOT a twin — English runs 19 % longer overall and up to 42 % on the short sections, because Hungarian is agglutinative; the +-15 % criterion the task asked for does not survive contact with this language pair, so structure is the measure reported instead. Golden 0.258.0 baked, published and vouched, with its record. One run, no aborted attempts: the 0.246.0 bake's two traps were both avoided by following its own record. Token proven not to leak with a planted control before the zero was believed. The waiver is NOT retired, and the record says why in one line: it is the mechanism of operator ruling R-468, not a note about this golden, and deleting it would turn the next release without a bake red immediately. It is also not load-bearing today. R-595: the catalog's copy gate could not run in CI at all — six pushes red, six alarm mails, while the local hook was green. Found by reading the operator's inbox, not by anything in the session that caused it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Paperless-ngx — Felhom.eu</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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='Yes';
|
||||
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
|
||||
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>Message</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.258.0" alt="Felhom.eu"></a>
|
||||
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
|
||||
<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.258.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>Launcher</a></li>
|
||||
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
|
||||
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</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>Storage<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="">Drives</a></li>
|
||||
<li><a href="/storage/network" class="">Network storage</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>Backup<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="">Overview</a></li>
|
||||
<li><a href="/backups/remote" class="">Remote backup</a></li>
|
||||
<li><a href="/backups/apps" class="">Apps</a></li>
|
||||
<li><a href="/backups/restore" class="">Restore</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>Sharing<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="">Network sharing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
|
||||
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Settings</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.258.0</span><details class="lang-globe">
|
||||
<summary class="lang-globe-btn" aria-label="Language" title="Language"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/paperless-ngx"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
|
||||
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/paperless-ngx"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
|
||||
</ul>
|
||||
</details>
|
||||
<a href="/logout" class="logout-link">Sign out ↗</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">← Apps</a>
|
||||
<h2>Paperless-ngx</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Running</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="This app is running the newest version available.">Up to date</span>
|
||||
<a href="https://papir.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
|
||||
<a href="/stacks/paperless-ngx/logs" class="btn btn-sm btn-outline">Log</a>
|
||||
|
||||
<a href="/stacks/paperless-ngx/export" class="btn btn-sm btn-outline">Export</a>
|
||||
<a href="/stacks/paperless-ngx/deploy" class="btn btn-sm btn-outline">Settings</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/paperless-ngx-logo.svg"
|
||||
alt="Paperless-ngx" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/paperless-ngx-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">A digital filing cabinet - scanning, OCR and automatic sorting</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~500M RAM</span>
|
||||
<span class="meta-badge">productivity</span>
|
||||
<span class="meta-badge meta-badge-warn">Needs a hard drive</span>
|
||||
<span class="meta-badge meta-badge-ok">Runs on Pi</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Where do I put my files?</h3>
|
||||
<p class="form-hint">You reach these folders in the File manager. You may need to sign in to the File manager first.</p>
|
||||
<div class="datapath-list">
|
||||
|
||||
<div class="datapath-row">
|
||||
<div class="datapath-head">
|
||||
<a href="https://files.enkisfelhom.hu/files/Beolvas%C3%A1s/paperless" target="_blank" rel="noopener" class="datapath-link">Documents to read in ↗</a>
|
||||
<span class="datapath-space">47.9 GB free</span>
|
||||
</div>
|
||||
<p class="datapath-note">Copy the files to be processed in here. The app reads them and then deletes them from here — this folder is temporary and is not backed up.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>What is it for?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Turn paper documents into files and keep them in order</li><li>Automatic OCR text recognition on scanned documents</li><li>Smart automatic sorting and tagging</li><li>Full text search across every document</li><li>Documents that arrive by e-mail are processed automatically</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>First steps</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Open paperless.DOMAIN in your browser</li><li>Sign in: user "admin". The password is on the Settings page, under Automatically generated values</li><li>Upload a document in the app, OR drop it into the import/paperless folder in the File manager (FileBrowser) - it is read in and processed with OCR from there. The File manager sign-in is on the File manager app page</li><li>You can upload many documents at once: they are processed one by one, and a larger batch takes a few minutes</li><li>Create tags and correspondents so new documents sort themselves</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Before you start</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>An external hard drive is recommended to keep the documents on</li><li>At least 1 GB of free RAM (for the OCR work)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Documentation</h3>
|
||||
<p><a href="https://docs.paperless-ngx.com/" target="_blank" class="app-info-link">Official documentation ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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('This app is already installed.');
|
||||
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 = '↻ Refreshing...';
|
||||
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 || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
|
||||
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 = 'Network error: ' + 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 = 'In progress...';
|
||||
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('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (action === 'update') {
|
||||
followUpdate(name, btn);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + 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>Loading...</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>Your data on the hard drive:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Delete app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</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 = 'Deleting...';
|
||||
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">Deleted data: ' + 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">Kept data: ' + 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>Deleted!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Loading...</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>Always deleted:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker volumes (database, app configuration)</li>' +
|
||||
'<li>Install configuration (app.yaml)</li>' +
|
||||
'<li>Secondary backup schedule</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 : 'does not exist') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Your data on the hard drive:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
|
||||
'</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>Backup data:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Remove app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</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 = 'Removing...';
|
||||
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">Deleted data: ' + 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">Deleted backups: ' + 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">Kept data: ' + 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">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Removed!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,617 @@
|
||||
|
||||
|
||||
<!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>Paperless-ngx — Felhom.eu</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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.258.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.258.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>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.258.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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/paperless-ngx"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/paperless-ngx"><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>Paperless-ngx</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Fut</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="Ez az alkalmazás a legfrissebb elérhető változatot futtatja.">Naprakész</span>
|
||||
<a href="https://papir.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
||||
<a href="/stacks/paperless-ngx/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
<a href="/stacks/paperless-ngx/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/paperless-ngx/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/paperless-ngx-logo.svg"
|
||||
alt="Paperless-ngx" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/paperless-ngx-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Digitális irattár - szkennelés, OCR és automatikus rendszerezés</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~500M RAM</span>
|
||||
<span class="meta-badge">productivity</span>
|
||||
<span class="meta-badge meta-badge-warn">HDD szükséges</span>
|
||||
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Hova tegyem a fájlokat?</h3>
|
||||
<p class="form-hint">Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.</p>
|
||||
<div class="datapath-list">
|
||||
|
||||
<div class="datapath-row">
|
||||
<div class="datapath-head">
|
||||
<a href="https://files.enkisfelhom.hu/files/Beolvas%C3%A1s/paperless" target="_blank" rel="noopener" class="datapath-link">Beolvasandó dokumentumok ↗</a>
|
||||
<span class="datapath-space">47.9 GB szabad</span>
|
||||
</div>
|
||||
<p class="datapath-note">Ide másold a feldolgozandó fájlokat. Az alkalmazás beolvassa, majd törli innen — ez a mappa átmeneti, és nem készül róla biztonsági mentés.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Papír dokumentumok digitalizálása és rendszerezése</li><li>Automatikus OCR szövegfelismerés szkennelt dokumentumokon</li><li>Intelligens automatikus kategorizálás és címkézés</li><li>Teljes szöveges keresés az összes dokumentumban</li><li>Email-ből érkező dokumentumok automatikus feldolgozása</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Nyisd meg a paperless.DOMAIN címet a böngészőben</li><li>Lépj be: felhasználó "admin", a jelszó a Beállítások oldalon található (Automatikusan generált értékek)</li><li>Tölts fel egy dokumentumot a webfelületen, VAGY dobd a Fájlkezelőben (FileBrowser) az import/paperless mappába — onnan automatikusan beolvassa és OCR-rel feldolgozza. A Fájlkezelő belépése a Fájlkezelő alkalmazás oldalán található</li><li>Sok dokumentumot egyszerre is feltölthetsz: egyenként dolgozza fel őket, egy nagyobb köteg néhány percig tart</li><li>Hozz létre címkéket és levelezőket az automatikus rendszerezéshez</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Előfeltételek</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Külső HDD ajánlott a dokumentumok tárolásához</li><li>Legalább 1 GB szabad RAM (OCR feldolgozáshoz)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="https://docs.paperless-ngx.com/" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" 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.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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='Yes';
|
||||
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
|
||||
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>Message</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.258.0" alt="Felhom.eu"></a>
|
||||
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
|
||||
<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.258.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>Launcher</a></li>
|
||||
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
|
||||
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</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>Storage<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="">Drives</a></li>
|
||||
<li><a href="/storage/network" class="">Network storage</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>Backup<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="">Overview</a></li>
|
||||
<li><a href="/backups/remote" class="">Remote backup</a></li>
|
||||
<li><a href="/backups/apps" class="">Apps</a></li>
|
||||
<li><a href="/backups/restore" class="">Restore</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>Sharing<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="">Network sharing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
|
||||
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Settings</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.258.0</span><details class="lang-globe">
|
||||
<summary class="lang-globe-btn" aria-label="Language" title="Language"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/privatebin"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
|
||||
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/privatebin"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
|
||||
</ul>
|
||||
</details>
|
||||
<a href="/logout" class="logout-link">Sign out ↗</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">← Apps</a>
|
||||
<h2>PrivateBin</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Running</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="This app is running the newest version available.">Up to date</span>
|
||||
<a href="https://paste.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
|
||||
<a href="/stacks/privatebin/logs" class="btn btn-sm btn-outline">Log</a>
|
||||
|
||||
<a href="/stacks/privatebin/export" class="btn btn-sm btn-outline">Export</a>
|
||||
<a href="/stacks/privatebin/deploy" class="btn btn-sm btn-outline">Settings</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Encrypted text sharing - the server never sees the content</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~30M RAM</span>
|
||||
<span class="meta-badge">security</span>
|
||||
|
||||
<span class="meta-badge meta-badge-ok">Runs on Pi</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/privatebin-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/privatebin-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/privatebin-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Move to another storage</h3>
|
||||
<p class="form-hint">You can move this app’s data to another connected storage. The app stops briefly during the move, and the data is deleted from the old place only after the check and a successful restart.</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
<select id="app-migrate-target" class="btn btn-sm btn-outline">
|
||||
<option value="">Choose a target storage…</option>
|
||||
<option value="/mnt/felhom-drives/hdd_1">NVME 1TB (/mnt/felhom-drives/hdd_1)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'privatebin','PrivateBin')">Move</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
<script>
|
||||
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
|
||||
function appMigRender(job){
|
||||
var names={stop:'Stopping',copy:'Copying data',verify:'Checking',flip:'Reinstalling',redeploy:'Reinstalling',cleanup:'Deleting old data'};
|
||||
var s=names[job.phase]||job.phase;
|
||||
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
|
||||
return s+'…';
|
||||
}
|
||||
function appMigWatch(){
|
||||
var panel=document.getElementById('app-migrate-progress');
|
||||
if(panel) panel.style.display='block';
|
||||
function tick(){
|
||||
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
|
||||
var job=d.data&&d.data.job;
|
||||
if(!job){ if(panel) panel.textContent='No move in progress.'; return; }
|
||||
if(panel) panel.innerHTML=appMigRender(job);
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Done</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Stopped: '+(job.error||'')+'</strong><br>The old data is untouched.'; return; }
|
||||
setTimeout(tick,1500);
|
||||
}).catch(function(){ setTimeout(tick,2000); });
|
||||
}
|
||||
tick();
|
||||
}
|
||||
function appMigrate(btn,app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ showAlert('Choose a target storage.'); return; }
|
||||
felhomConfirm(btn,'Move the '+label+' data to: '+target+'? The app stops briefly. The old data is deleted only after a successful move.',function(){
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Error: '+(d.error||'unknown')); } })
|
||||
.catch(function(e){ showAlert('Error: '+e); });
|
||||
});
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>What is it for?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Share sensitive text safely</li><li>End-to-end encryption - the server cannot reach the content</li><li>You choose how long it lasts (5 minutes to 1 year, or never)</li><li>Optional: delete it automatically after it is read</li><li>Password protection for extra safety</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>First steps</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Open paste.DOMAIN in your browser</li><li>Type your text and select Send</li><li>Share the link you get - the encryption key is part of the URL</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Documentation</h3>
|
||||
<p><a href="https://github.com/PrivateBin/PrivateBin/wiki" target="_blank" class="app-info-link">Official documentation ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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('This app is already installed.');
|
||||
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 = '↻ Refreshing...';
|
||||
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 || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
|
||||
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 = 'Network error: ' + 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 = 'In progress...';
|
||||
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('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (action === 'update') {
|
||||
followUpdate(name, btn);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + 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>Loading...</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>Your data on the hard drive:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Delete app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</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 = 'Deleting...';
|
||||
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">Deleted data: ' + 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">Kept data: ' + 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>Deleted!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Loading...</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>Always deleted:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker volumes (database, app configuration)</li>' +
|
||||
'<li>Install configuration (app.yaml)</li>' +
|
||||
'<li>Secondary backup schedule</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 : 'does not exist') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Your data on the hard drive:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
|
||||
'</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>Backup data:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Remove app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</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 = 'Removing...';
|
||||
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">Deleted data: ' + 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">Deleted backups: ' + 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">Kept data: ' + 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">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Removed!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
|
||||
|
||||
<!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.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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.258.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.258.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>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.258.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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/privatebin"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/privatebin"><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-run">Fut</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="Ez az alkalmazás a legfrissebb elérhető változatot futtatja.">Naprakész</span>
|
||||
<a href="https://paste.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
||||
<a href="/stacks/privatebin/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
<a href="/stacks/privatebin/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/privatebin/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Titkosított szöveg megosztás - a szerver nem látja a tartalmat</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~30M RAM</span>
|
||||
<span class="meta-badge">security</span>
|
||||
|
||||
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/privatebin-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/privatebin-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/privatebin-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Áthelyezés másik tárhelyre</h3>
|
||||
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
<select id="app-migrate-target" class="btn btn-sm btn-outline">
|
||||
<option value="">Válassz céltárhelyet…</option>
|
||||
<option value="/mnt/felhom-drives/hdd_1">NVME 1TB (/mnt/felhom-drives/hdd_1)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'privatebin','PrivateBin')">Áthelyezés</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
<script>
|
||||
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
|
||||
function appMigRender(job){
|
||||
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
|
||||
var s=names[job.phase]||job.phase;
|
||||
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
|
||||
return s+'…';
|
||||
}
|
||||
function appMigWatch(){
|
||||
var panel=document.getElementById('app-migrate-progress');
|
||||
if(panel) panel.style.display='block';
|
||||
function tick(){
|
||||
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
|
||||
var job=d.data&&d.data.job;
|
||||
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
|
||||
if(panel) panel.innerHTML=appMigRender(job);
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
|
||||
setTimeout(tick,1500);
|
||||
}).catch(function(){ setTimeout(tick,2000); });
|
||||
}
|
||||
tick();
|
||||
}
|
||||
function appMigrate(btn,app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ showAlert('Válassz céltárhelyet.'); return; }
|
||||
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { showAlert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ showAlert('Hiba: '+e); });
|
||||
});
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Érzékeny szövegek biztonságos megosztása</li><li>E2E titkosítás - a szerver nem fér hozzá a tartalomhoz</li><li>Beállítható lejárati idő (5 perc - 1 év, vagy soha)</li><li>Olvasás után automatikus törlés opció</li><li>Jelszóvédelem a még nagyobb biztonságért</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Nyisd meg a paste.DOMAIN címet a böngészőben</li><li>Írd be a szöveget és kattints a Küldés gombra</li><li>Oszd meg a generált linket - a titkosítási kulcs az URL-ben van</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="https://github.com/PrivateBin/PrivateBin/wiki" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RomM — Felhom.eu</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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='Yes';
|
||||
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Cancel';
|
||||
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>Message</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.258.0" alt="Felhom.eu"></a>
|
||||
<button type="button" class="nav-burger" aria-expanded="false" aria-controls="sidebar" aria-label="Menu">
|
||||
<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.258.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>Launcher</a></li>
|
||||
<li><a href="/dashboard" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Dashboard</a></li>
|
||||
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Apps</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>Storage<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="">Drives</a></li>
|
||||
<li><a href="/storage/network" class="">Network storage</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>Backup<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="">Overview</a></li>
|
||||
<li><a href="/backups/remote" class="">Remote backup</a></li>
|
||||
<li><a href="/backups/apps" class="">Apps</a></li>
|
||||
<li><a href="/backups/restore" class="">Restore</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>Sharing<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="">Network sharing</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>System monitor</a></li>
|
||||
<li><a href="/debug" class=""><svg class="ico"><use href="#i-wrench"/></svg>Debug</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Settings</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>System</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Notifications</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Security and access</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.258.0</span><details class="lang-globe">
|
||||
<summary class="lang-globe-btn" aria-label="Language" title="Language"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/romm"><button type="submit" name="lang" value="hu" lang="hu" class="lang-globe-item">Magyar</button></form></li>
|
||||
<li><form method="POST" action="/settings/language"><input type="hidden" name="_csrf" value="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/romm"><button type="submit" name="lang" value="en" lang="en" class="lang-globe-item current" aria-current="true">English</button></form></li>
|
||||
</ul>
|
||||
</details>
|
||||
<a href="/logout" class="logout-link">Sign out ↗</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">← Apps</a>
|
||||
<h2>RomM</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Running</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="This app is running the newest version available.">Up to date</span>
|
||||
<a href="https://jatek.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Open ↗</a>
|
||||
<a href="/stacks/romm/logs" class="btn btn-sm btn-outline">Log</a>
|
||||
|
||||
<a href="/stacks/romm/export" class="btn btn-sm btn-outline">Export</a>
|
||||
<a href="/stacks/romm/deploy" class="btn btn-sm btn-outline">Settings</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/romm-logo.svg"
|
||||
alt="RomM" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/romm-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Retro game collection manager, browser and player</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~300M RAM</span>
|
||||
<span class="meta-badge">media</span>
|
||||
<span class="meta-badge meta-badge-warn">Needs a hard drive</span>
|
||||
<span class="meta-badge meta-badge-warn">x86 only</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/romm-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/romm-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/romm-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Where do I put my files?</h3>
|
||||
<p class="form-hint">You reach these folders in the File manager. You may need to sign in to the File manager first.</p>
|
||||
<div class="datapath-list">
|
||||
|
||||
<div class="datapath-row">
|
||||
<div class="datapath-head">
|
||||
<a href="https://files.enkisfelhom.hu/files/NVME%201TB/roms" target="_blank" rel="noopener" class="datapath-link">ROM collection ↗</a>
|
||||
|
||||
</div>
|
||||
<p class="datapath-note">This is where your files are kept. It is backed up.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>What is it for?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Sort and browse a retro game collection in your browser</li><li>Game details download themselves - cover art, descriptions, ratings</li><li>Filter and search the whole collection by platform</li><li>Upload and download ROM files in your browser</li><li>Several people in the household can have their own account</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>First steps</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Open arcade.DOMAIN in your browser</li><li>Sign in with the default admin / admin account</li><li>Change the password straight away, in the Settings menu</li><li>Upload the ROM files into the roms/ folder in the File manager (FileBrowser), sorted into platform folders (for example roms/gba/, roms/snes/)</li><li>Start a Scan from the menu on the left to read the ROMs in</li><li>Optional: set up metadata providers so cover art and descriptions download themselves (see below)</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Before you start</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>An external hard drive is needed to keep the ROM files and cover art on</li><li>At least 1 GB of free RAM is recommended (MariaDB + Redis + RomM)</li><li>ROM files sorted into platform folders (for example roms/gba/, roms/snes/)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Default sign-in</h3>
|
||||
<p class="app-info-creds">admin / admin</p>
|
||||
<p class="app-info-creds-warn">Change it right after your first sign-in!</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Documentation</h3>
|
||||
<p><a href="https://github.com/rommapp/romm/wiki" target="_blank" class="app-info-link">Official documentation ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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('This app is already installed.');
|
||||
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 = '↻ Refreshing...';
|
||||
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 || 'Templates refreshed') : ('Error: ' + (data.error || 'Unknown error'));
|
||||
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 = 'Network error: ' + 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 = 'In progress...';
|
||||
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('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (action === 'update') {
|
||||
followUpdate(name, btn);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + 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>Loading...</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>Your data on the hard drive:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'does not exist') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Delete your data from the hard drive</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Delete app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">This removes the containers, the volumes and the configuration files.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Delete</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Close</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 = 'Deleting...';
|
||||
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">Deleted data: ' + 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">Kept data: ' + 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>Deleted!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was deleted.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Delete';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Loading...</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>Always deleted:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker volumes (database, app configuration)</li>' +
|
||||
'<li>Install configuration (app.yaml)</li>' +
|
||||
'<li>Secondary backup schedule</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 : 'does not exist') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Your data on the hard drive:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Delete your data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'If you reinstall the app, you need to import the data again, because the database is deleted. Data you keep is NOT backed up automatically any more.' +
|
||||
'</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>Backup data:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Delete backup data</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Remove app: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">The app goes back to "Not installed". The template stays, and you can install it again.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">You cannot undo this!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Cancel</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Remove</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Error</h3><p style="color:var(--text-2)">Could not load the details: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Close</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 = 'Removing...';
|
||||
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">Deleted data: ' + 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">Deleted backups: ' + 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">Kept data: ' + 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">Backups not deleted: ' + data.data.backup_paths_refused.join('; ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Removed!</h3>' +
|
||||
'<p style="color:var(--text-2)">The app (' + name + ') was removed. You can install it again with the Install button.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Close</button></div>';
|
||||
} else {
|
||||
showAlert('Error: ' + (data.error || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Remove';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,623 @@
|
||||
|
||||
|
||||
<!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>RomM — Felhom.eu</title>
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=0.258.0">
|
||||
<link rel="stylesheet" href="/static/style.css?v=0.258.0">
|
||||
<meta name="csrf-token" content="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13">
|
||||
<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.258.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.258.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>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.258.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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/romm"><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="9662fb88484edfadbd6b40557c95a7ea4293f5ee2a63c25a25e3a773823f5d13"><input type="hidden" name="back" value="/apps/romm"><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>RomM</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Fut</span>
|
||||
|
||||
|
||||
<span class="tag tag-ok" title="Ez az alkalmazás a legfrissebb elérhető változatot futtatja.">Naprakész</span>
|
||||
<a href="https://jatek.enkisfelhom.hu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
||||
<a href="/stacks/romm/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
<a href="/stacks/romm/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/romm/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/romm-logo.svg"
|
||||
alt="RomM" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='\/static\/assets\/romm-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-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Retró játékgyűjtemény kezelő, böngésző és lejátszó</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~300M RAM</span>
|
||||
<span class="meta-badge">media</span>
|
||||
<span class="meta-badge meta-badge-warn">HDD szükséges</span>
|
||||
<span class="meta-badge meta-badge-warn">Csak x86</span>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/romm-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/romm-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/romm-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Hova tegyem a fájlokat?</h3>
|
||||
<p class="form-hint">Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.</p>
|
||||
<div class="datapath-list">
|
||||
|
||||
<div class="datapath-row">
|
||||
<div class="datapath-head">
|
||||
<a href="https://files.enkisfelhom.hu/files/NVME%201TB/roms" target="_blank" rel="noopener" class="datapath-link">ROM gyűjtemény ↗</a>
|
||||
|
||||
</div>
|
||||
<p class="datapath-note">Itt tárolódnak a fájljaid. Biztonsági mentés készül róla.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Retró játékgyűjtemény rendszerezése és böngészése webes felületen</li><li>Játékok metaadatainak automatikus letöltése — borítók, leírások, értékelések</li><li>Platformonkénti szűrés és keresés a teljes gyűjteményben</li><li>ROM fájlok webes feltöltése és letöltése</li><li>Többfelhasználós hozzáférés a háztartás tagjai számára</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Nyisd meg az arcade.DOMAIN címet a böngészőben</li><li>Jelentkezz be az alapértelmezett admin / admin fiókkal</li><li>Változtasd meg azonnal a jelszót a Settings menüben</li><li>Töltsd fel a ROM fájlokat a Fájlkezelőben (FileBrowser) a roms/ mappába, platform mappákba rendezve (pl. roms/gba/, roms/snes/)</li><li>Indíts egy Scan-t a bal oldali menüben a ROM-ok beolvasásához</li><li>Opcionális: állíts be metaadat-szolgáltatókat a borítóképek és leírások automatikus letöltéséhez (lásd lent)</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Előfeltételek</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Külső HDD szükséges a ROM fájlok és borítóképek tárolásához</li><li>Legalább 1 GB szabad RAM ajánlott (MariaDB + Redis + RomM)</li><li>ROM fájlok platform mappákba rendezve (pl. roms/gba/, roms/snes/)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Alapértelmezett belépés</h3>
|
||||
<p class="app-info-creds">admin / admin</p>
|
||||
<p class="app-info-creds-warn">Az első bejelentkezés után azonnal változtasd meg!</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="https://github.com/rommapp/romm/wiki" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user