{ "hu": [ "Docmost — Felhom.eu 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=' Üzenet '+msg.replace(/&/g,'&').replace(/ /g,'>')+' OK ';o.appendChild(c);document.body.appendChild(o);} Indítópult Vezérlőpult Alkalmazások Tárhely Meghajtók Hálózati tárhely Biztonsági mentés Áttekintés Távoli mentés Alkalmazások Visszaállítás Megosztás Hálózati megosztás Rendszermonitor Debug Beállítások Rendszer Értesítések Biztonság és hozzáférés 0.261.0 Magyar English Kijelentkezés ↗ Hub kapcsolat kikapcsolva — a központi monitoring nem aktív Rendszermonitor → ← Alkalmazások Docmost Leállítva Frissítés elérhető — ma Megnyitás ↗ Napló Exportálás Beállítások A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.", "Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.", "Mentések Modern wiki és dokumentáció platform Notion-szerű szerkesztővel ~200M RAM productivity Csak x86 Áthelyezés másik tárhelyre Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át.", "Válassz céltárhelyet… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Áthelyezés 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+=' Kész '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Megszakadt: '+(job.error||'')+' 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+'?", "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(){}); })(); Mire használható?", "Csapat tudásbázis és belső dokumentáció Notion-szerű blokk szerkesztő valós idejű együttműködéssel Terek és oldalak hierarchikus szervezése Képek, táblázatok és kód blokkok beágyazása Keresés a teljes dokumentációban Első lépések Nyisd meg a docs.DOMAIN címet a böngészőben Az első regisztrált felhasználó automatikusan admin lesz Hozd létre az első teret (Space) a dokumentumoknak Kezdj el írni a Notion-szerű szerkesztőben Előfeltételek x86 processzor szükséges Legalább 768 MB szabad RAM (Docmost + PostgreSQL + Redis) Dokumentáció Hivatalos dokumentáció ↗ (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 = ' Betöltés...", "'; 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 = ' Felhasználói adatok a merevlemezen: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?", "' + ' Ez a művelet nem visszavonható!", "' + hddInfo + checkboxHTML + ' ' + ' Mégsem ' + ' Törlés ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Hiba Nem sikerült lekérni az adatokat: ' + err.message + ' ' + ' Bezárás '; } } 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 = ' Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Sikeresen törölve!", "' + removedInfo + preservedInfo + ' Bezárás '; } 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 = ' Betöltés...", "'; 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 += ' ' + ' Mindig törlődik: ' + ' ' + ' Docker kötetek (adatbázis, alkalmazás konfiguráció) ' + ' Telepítési konfiguráció (app.yaml) ' + ' Másodlagos mentés ütemezése ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?" ], "en": [ "Docmost — Felhom.eu 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=' Message '+msg.replace(/&/g,'&').replace(/ /g,'>')+' OK ';o.appendChild(c);document.body.appendChild(o);} Launcher Dashboard Apps Storage Drives Network storage Backup Overview Remote backup Apps Restore Sharing Network sharing System monitor Debug Settings System Notifications Security and access 0.261.0 Magyar English Sign out ↗ The hub connection is off — central monitoring is not running System monitor → ← Apps Docmost Stopped Update available — today Open ↗ Log Export Settings A(z) docmost frissítése 2026-09-21 21:44-kor nem sikerült, és az alkalmazás nem indult el az új verzióval.", "Visszaállítható a Mentések oldalon ebből a biztonsági mentésből: saját meghajtó, 2026-09-21 21:44 — ez a másolat a beállításokat, az adatbázist és az adatköteteket tartalmazza.", "Backups A modern wiki and documentation platform with a Notion-like editor ~200M RAM productivity x86 only Move to another storage You can move this app’s data to another connected storage.", "Choose a target storage… SCRATCH HDD (/mnt/felhom-drives/scratch_hdd) Move 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+=' Done '; setTimeout(function(){location.reload();},1500); return; } if(job.phase==='aborted'){ if(panel) panel.innerHTML+=' Stopped: '+(job.error||'')+' 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 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(){}); })(); What is it for?", "A knowledge base and internal documentation for a team A Notion-like block editor several people can use at once Spaces and pages, arranged in a tree Embed images, tables and code blocks Search across all of it First steps Open docs.DOMAIN in your browser The first person to sign up becomes the admin Create your first Space for the documents Start writing in the Notion-like editor Before you start An x86 processor is needed At least 768 MB of free RAM (Docmost + PostgreSQL + Redis) Documentation Official documentation ↗ (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 = ' Loading...", "'; 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 = ' Your data on the hard drive: '; data.data.hdd_paths.forEach(function(p) { hddInfo += ' ' + p.path + ' (' + (p.exists ?", "' + hddInfo + checkboxHTML + ' ' + ' Cancel ' + ' Delete ' + ' '; } catch (err) { modal.querySelector('.modal-card').innerHTML = ' Error Could not load the details: ' + err.message + ' ' + ' Close '; } } 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 = ' Deleted data: ' + data.data.hdd_paths_removed.join(', ') + ' '; } var preservedInfo = ''; if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) { preservedInfo = ' Kept data: ' + data.data.hdd_paths_preserved.join(', ') + ' '; } if (data.data && data.data.hdd_note) { preservedInfo += ' ' + data.data.hdd_note + ' '; } modal.querySelector('.modal-card').innerHTML = ' Deleted!", "' + removedInfo + preservedInfo + ' Close '; } 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 = ' Loading...", "'; 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 += ' ' + ' Always deleted: ' + ' ' + ' Docker volumes (database, app configuration) ' + ' Install configuration (app.yaml) ' + ' Secondary backup schedule ' + ' '; var hddCheckbox = ''; if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) { var hddPaths = ''; hddResp.data.hdd_paths.forEach(function(p) { hddPaths += ' ' + p.path + ' (' + (p.exists ?", "Data you keep is NOT backed up automatically any more.' + ' '; hddCheckbox = '\\x3Cscript>document.getElementById(\"remove-hdd-check\").addEventListener(\"change\",function(){document.getElementById(\"remove-hdd-keep-warning\").style.display=this.checked?\"none\":\"\";}); '; } var backupCheckbox = ''; if (backupResp.ok && backupResp.data && backupResp.data.has_backups) { var bkPaths = ''; backupResp.data.backup_paths.forEach(function(p) { if (p.exists) bkPaths += ' ' + p.path + ' (' + p.size_human + ') '; }); if (bkPaths) { sections += ' ' + ' Backup data: ' + bkPaths + ' Delete backup data ' + ' ' + 'The nightly restic snapshots cannot be deleted one by one — they expire automatically under the retention rules.' + ' '; } } modal.querySelector('.modal-card').innerHTML = ' Remove app: ' + name + ' ' + ' The app goes back to \"Not installed\"." ] }