|
|
|
@@ -60,7 +60,7 @@
|
|
|
|
|
<div class="storage-path-actions">
|
|
|
|
|
<button class="btn btn-xs btn-primary" onclick="storageReEnroll('{{.Path}}','{{.Label}}')">Visszacsatlakoztatás</button>
|
|
|
|
|
<form method="POST" action="/settings/storage/remove" style="display:inline"
|
|
|
|
|
onsubmit="return confirm('Biztosan eltávolítja a(z) {{.Label}} ({{.Path}}) meghajtót a rendszerből?\n\nA meghajtó adatai NEM törlődnek.')">
|
|
|
|
|
onsubmit="return storageRemoveDialog(event, this, 'Biztosan eltávolítja a(z) {{.Label}} ({{.Path}}) meghajtót a rendszerből?\n\nA meghajtó adatai NEM törlődnek.')">
|
|
|
|
|
{{$.CSRFField}}
|
|
|
|
|
<input type="hidden" name="storage_path" value="{{.Path}}">
|
|
|
|
|
<button type="submit" class="btn btn-xs btn-outline">Eltávolítás a rendszerből</button>
|
|
|
|
@@ -111,6 +111,7 @@
|
|
|
|
|
<span class="form-hint">Nincs alkalmazás ezen a tárolón</span>
|
|
|
|
|
{{end}}
|
|
|
|
|
</div>
|
|
|
|
|
<div class="drive-agent-extra" id="agent-extra-{{.Path}}"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="storage-path-actions">
|
|
|
|
|
{{if not .IsDefault}}
|
|
|
|
@@ -140,7 +141,7 @@
|
|
|
|
|
{{end}}
|
|
|
|
|
{{if and (not .IsDefault) (eq .AppCount 0)}}
|
|
|
|
|
<form method="POST" action="/settings/storage/remove" style="display:inline"
|
|
|
|
|
onsubmit="return confirm('Biztosan eltávolítja a(z) {{.Path}} adattárolót?')">
|
|
|
|
|
onsubmit="return storageRemoveDialog(event, this, 'Biztosan eltávolítja a(z) {{.Path}} adattárolót?')">
|
|
|
|
|
{{$.CSRFField}}
|
|
|
|
|
<input type="hidden" name="storage_path" value="{{.Path}}">
|
|
|
|
|
<button type="submit" class="btn btn-xs btn-danger-outline">Eltávolítás</button>
|
|
|
|
@@ -259,11 +260,14 @@
|
|
|
|
|
}).catch(function(e){ msg.textContent='Hiba: '+e; });
|
|
|
|
|
}
|
|
|
|
|
function netStorageRemove(name,label){
|
|
|
|
|
if(!confirm('Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.')) return;
|
|
|
|
|
openDialog({title:'Hálózati tárhely eltávolítása', confirmLabel:'Eltávolítás',
|
|
|
|
|
message:'Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.',
|
|
|
|
|
onConfirm:function(){
|
|
|
|
|
fetch('/api/storage/netstorage/remove',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({name:name})})
|
|
|
|
|
.then(function(r){return r.json();}).then(function(d){
|
|
|
|
|
if(d.ok){ location.reload(); } else { alert('Hiba: '+(d.error||'ismeretlen')); }
|
|
|
|
|
}).catch(function(e){ alert('Hiba: '+e); });
|
|
|
|
|
}});
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
@@ -301,10 +305,13 @@
|
|
|
|
|
var sel=document.getElementById('migrate-target-'+source);
|
|
|
|
|
var target=sel?sel.value:'';
|
|
|
|
|
if(!target){ alert('Válassz céltárolót a legördülő menüből.'); return; }
|
|
|
|
|
if(!confirm('Áthelyezed a(z) '+label+' ÖSSZES adatát ide: '+target+'?\n\nAz alkalmazások az áthelyezés alatt rövid időre leállnak. A forrás adatai csak az ellenőrzés és a sikeres újraindítás után törlődnek.')) return;
|
|
|
|
|
openDialog({title:'Összes adat áthelyezése', confirmLabel:'Áthelyezés',
|
|
|
|
|
message:'Áthelyezed a(z) '+label+' ÖSSZES adatát ide: '+target+'?\n\nAz alkalmazások az áthelyezés alatt rövid időre leállnak. A forrás adatai csak az ellenőrzés és a sikeres újraindítás után törlődnek.',
|
|
|
|
|
onConfirm:function(){
|
|
|
|
|
fetch('/api/storage/migrate',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({source:source,target:target})})
|
|
|
|
|
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ migWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
|
|
|
|
.catch(function(e){ alert('Hiba: '+e); });
|
|
|
|
|
}});
|
|
|
|
|
}
|
|
|
|
|
// Resume view: if a migration is STILL IN PROGRESS when the page loads, show the panel and watch.
|
|
|
|
|
// Must NOT re-watch a terminal (done/aborted) job: the journal keeps returning the last completed
|
|
|
|
@@ -319,45 +326,53 @@
|
|
|
|
|
<a href="/storage/attach" class="btn btn-sm btn-outline"><svg class="ico ico-sm"><use href="#i-link"/></svg> Meglévő meghajtó csatolása</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:1.5rem">
|
|
|
|
|
<h4 style="margin-bottom:.25rem">Meghajtók (ügynök nézet)</h4>
|
|
|
|
|
<p class="form-hint" style="margin-bottom:.75rem">A host-ügynök által észlelt meghajtók élő nézete. A meghajtó <strong>szerepkörét</strong> az ügynök saját vizsgálattal állapítja meg: a rendszer- és biztonsági-mentés meghajtók védettek (csak operátori aláírással módosíthatók), a felhasználói adatmeghajtókat Ön kezeli.</p>
|
|
|
|
|
<div id="agent-disks">Betöltés…</div>
|
|
|
|
|
<div id="agent-warn-note"></div>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:1.5rem" id="agent-unregistered-wrap" hidden>
|
|
|
|
|
<div class="section-h"><h3>Nem regisztrált meghajtók</h3><span class="hint">az ügynök által észlelt, még nem regisztrált adatmeghajtók</span></div>
|
|
|
|
|
<div id="agent-unregistered" class="drive-list"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div style="margin-top:1.5rem" id="agent-system-wrap" hidden>
|
|
|
|
|
<div class="section-h"><h3>Rendszermeghajtók</h3><span class="hint">védett — csak operátori aláírással módosíthatók</span></div>
|
|
|
|
|
<div id="agent-system" class="drive-list"></div>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="confirm-root"></div>
|
|
|
|
|
<div id="dialog-root"></div>
|
|
|
|
|
<script>
|
|
|
|
|
window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}{{end}}];
|
|
|
|
|
(function(){
|
|
|
|
|
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];}); }
|
|
|
|
|
function hum(b){ if(!b||b<=0) return ''; var u=['B','KB','MB','GB','TB'],i=0,v=b; while(v>=1024&&i<u.length-1){v/=1024;i++;} return (v>=10||i===0?Math.round(v):v.toFixed(1))+' '+u[i]; }
|
|
|
|
|
function usageColorClass(p){ if(p>=85) return 'crit'; if(p>=70) return 'warn'; return 'nominal'; }
|
|
|
|
|
function classBadge(d){
|
|
|
|
|
if(d.class==='fast') return '<span class="badge badge-ok">gyors</span>';
|
|
|
|
|
if(d.class==='slow') return '<span class="badge badge-muted">lassú</span>';
|
|
|
|
|
var LOCK_ICO='<svg class="ico ico-sm"><use href="#i-lock"/></svg>';
|
|
|
|
|
function classTag(d){
|
|
|
|
|
if(d.class==='fast') return '<span class="tag">gyors</span>';
|
|
|
|
|
if(d.class==='slow') return '<span class="tag tag-off">lassú</span>';
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
function roleBadge(role){
|
|
|
|
|
if(role==='system') return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Rendszer</span>';
|
|
|
|
|
if(role==='backup') return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Biztonsági mentés — védett</span>';
|
|
|
|
|
if(role==='user-data') return '<span class="badge badge-ok">Felhasználói adat</span>';
|
|
|
|
|
return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Védett</span>';
|
|
|
|
|
function roleTag(role){
|
|
|
|
|
if(role==='system') return '<span class="tag">'+LOCK_ICO+'Rendszer — védett</span>';
|
|
|
|
|
if(role==='backup') return '<span class="tag">'+LOCK_ICO+'Biztonsági mentés — védett</span>';
|
|
|
|
|
if(role==='user-data') return '<span class="tag tag-run"><span class="dot"></span>Felhasználói adat</span>';
|
|
|
|
|
return '<span class="tag">'+LOCK_ICO+'Védett</span>';
|
|
|
|
|
}
|
|
|
|
|
function dataBadge(d){ return d.data_bearing ? '<span class="badge badge-error" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>' : ''; }
|
|
|
|
|
function dataTag(d){ return d.data_bearing ? '<span class="tag tag-warn" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>' : ''; }
|
|
|
|
|
// regKey is the path a drive is REGISTERED under: the STABLE intermediary path (guest_path,
|
|
|
|
|
// /mnt/felhom-drives/<name>) the agent reports, else the raw mount_path (legacy). The registry
|
|
|
|
|
// stores the STABLE path (handleStorageRegister/runStorageInit), so checking the raw mount_path
|
|
|
|
|
// alone made an enrolled drive read as "Nem regisztrált" + show a spurious Regisztrálás button.
|
|
|
|
|
function regKey(d){ return d.guest_path || d.mount_path; }
|
|
|
|
|
function regBadge(d, registered){
|
|
|
|
|
function regTag(d, registered){
|
|
|
|
|
if(!d.mount_path) return '';
|
|
|
|
|
return registered[regKey(d)] ? '<span class="badge badge-ok">Regisztrálva</span>' : '<span class="badge badge-muted">Nem regisztrált</span>';
|
|
|
|
|
return registered[regKey(d)] ? '<span class="tag tag-run"><span class="dot"></span>Regisztrálva</span>' : '<span class="tag tag-off"><span class="dot"></span>Nem regisztrált</span>';
|
|
|
|
|
}
|
|
|
|
|
// appBackingTag marks the storages that actually hold deployed apps: the internal SSD (app
|
|
|
|
|
// databases + Docker) and the external user-data drives (large app files). Keyed on the agent's
|
|
|
|
|
// authoritative role/type — pure presentation, no agent contract change.
|
|
|
|
|
function appBackingTag(d){
|
|
|
|
|
if(d.type==='lvmthin') return '<span class="badge badge-info">Alkalmazás-rendszer</span>';
|
|
|
|
|
if(d.role==='user-data') return '<span class="badge badge-info">Alkalmazás-adatok</span>';
|
|
|
|
|
if(d.type==='lvmthin') return '<span class="tag">Alkalmazás-rendszer</span>';
|
|
|
|
|
if(d.role==='user-data') return '<span class="tag">Alkalmazás-adatok</span>';
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
// purposeDesc explains, in plain Hungarian, what each storage is for — so the "which one do the
|
|
|
|
@@ -394,36 +409,58 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
|
|
|
|
|
if(d.backing_device){ btns += ' <button class="btn btn-xs btn-danger-outline" onclick="confirmWipe(\''+dev+'\',\''+reg+'\')">Törlés…</button>'; }
|
|
|
|
|
return '<div class="drive-actions">'+btns+'</div>';
|
|
|
|
|
}
|
|
|
|
|
// Unified drive view (D1): the agent disk list ENRICHES the registry cards in place
|
|
|
|
|
// (role tag + durable-id + agent-only actions), and two extra groups render below:
|
|
|
|
|
// protected system/backup drives (read-only) and unregistered user-data drives.
|
|
|
|
|
function driveRow(d, registered, withActions){
|
|
|
|
|
var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(regKey(d)):'');
|
|
|
|
|
var badges = roleTag(d.role)+appBackingTag(d)+classTag(d)+dataTag(d);
|
|
|
|
|
if(withActions) badges += regTag(d, registered);
|
|
|
|
|
var purpose = purposeDesc(d);
|
|
|
|
|
return '<div class="drive-card role-'+esc(d.role||'system')+'">'
|
|
|
|
|
+'<div class="drive-card-top"><div class="drive-id"><span class="drive-name">'+esc(d.name)+'</span><span class="drive-sub">'+sub+'</span></div>'
|
|
|
|
|
+'<div class="drive-badges">'+badges+'</div></div>'
|
|
|
|
|
+(purpose?'<div class="drive-purpose">'+esc(purpose)+'</div>':'')
|
|
|
|
|
+(d.durable_id?'<div class="drive-sub mono">'+esc(d.durable_id)+'</div>':'')
|
|
|
|
|
+capBar(d)
|
|
|
|
|
+(withActions?actions(d,registered):'')
|
|
|
|
|
+'</div>';
|
|
|
|
|
}
|
|
|
|
|
function enrichCard(d, registered){
|
|
|
|
|
var slot=document.getElementById('agent-extra-'+regKey(d));
|
|
|
|
|
if(!slot) return false;
|
|
|
|
|
var extra='<div class="metarows" style="margin:.4rem 0 0">'
|
|
|
|
|
+'<span class="metarow">'+roleTag(d.role)+'</span>'
|
|
|
|
|
+(d.class?'<span class="metarow">'+classTag(d)+'</span>':'')
|
|
|
|
|
+(d.durable_id?'<span class="metarow mono" title="Tartós azonosító">'+esc(d.durable_id)+'</span>':'')
|
|
|
|
|
+'</div>'
|
|
|
|
|
+actions(d,registered);
|
|
|
|
|
slot.innerHTML=extra;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
async function load(){
|
|
|
|
|
var box=document.getElementById('agent-disks'); if(!box) return;
|
|
|
|
|
var warn=document.getElementById('agent-warn-note');
|
|
|
|
|
try{
|
|
|
|
|
var r=await fetch('/api/disks'); var j=await r.json();
|
|
|
|
|
if(!j.ok){ box.innerHTML='<p class="form-hint">'+esc(j.error||'Nem elérhető')+'</p>'; return; }
|
|
|
|
|
if(!j.ok) throw new Error(j.error||'Nem elérhető');
|
|
|
|
|
var disks=(j.data&&j.data.disks)||[];
|
|
|
|
|
if(disks.length===0){ box.innerHTML='<p class="form-hint">Nincs észlelt meghajtó.</p>'; return; }
|
|
|
|
|
var registered={}; (window.__registeredPaths||[]).forEach(function(p){registered[p]=true;});
|
|
|
|
|
// One-line tiering explanation so "which storage do the apps use?" is answered at a glance.
|
|
|
|
|
var html='<p class="drive-tiering-note">Az alkalmazások rendszere és adatbázisai a belső SSD-n (local-lvm), a nagy méretű fájljaik a külső adattárolókon tárolódnak.</p>';
|
|
|
|
|
html+='<div class="drive-list">';
|
|
|
|
|
var sysHtml='', unregHtml='';
|
|
|
|
|
disks.forEach(function(d){
|
|
|
|
|
// Show the path the customer's system actually uses: the STABLE in-guest path
|
|
|
|
|
// (/mnt/felhom-drives/<name>, = guest_path) for an external drive, not the raw host PVE mount
|
|
|
|
|
// (/mnt/<name>) which doesn't exist inside the guest. regKey falls back to mount_path for
|
|
|
|
|
// non-intermediary drives (system/local).
|
|
|
|
|
var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(regKey(d)):'');
|
|
|
|
|
var badges = roleBadge(d.role)+appBackingTag(d)+classBadge(d)+dataBadge(d)+regBadge(d,registered);
|
|
|
|
|
var purpose = purposeDesc(d);
|
|
|
|
|
html+='<div class="drive-card role-'+esc(d.role||'system')+'">'
|
|
|
|
|
+'<div class="drive-card-top"><div class="drive-id"><span class="drive-name">'+esc(d.name)+'</span><span class="drive-sub">'+sub+'</span></div>'
|
|
|
|
|
+'<div class="drive-badges">'+badges+'</div></div>'
|
|
|
|
|
+(purpose?'<div class="drive-purpose">'+esc(purpose)+'</div>':'')
|
|
|
|
|
+capBar(d)
|
|
|
|
|
+actions(d,registered)
|
|
|
|
|
+'</div>';
|
|
|
|
|
if(d.role==='user-data'){
|
|
|
|
|
if(registered[regKey(d)] && enrichCard(d, registered)) return; // one card, enriched in place
|
|
|
|
|
unregHtml+=driveRow(d, registered, true);
|
|
|
|
|
}else{
|
|
|
|
|
sysHtml+=driveRow(d, registered, false); // protected: read-only, lock tag, NO actions
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
html+='</div>';
|
|
|
|
|
box.innerHTML=html;
|
|
|
|
|
}catch(e){ box.innerHTML='<p class="form-hint">Hiba: '+esc(e.message)+'</p>'; }
|
|
|
|
|
var sysWrap=document.getElementById('agent-system-wrap');
|
|
|
|
|
var unregWrap=document.getElementById('agent-unregistered-wrap');
|
|
|
|
|
if(sysHtml){ document.getElementById('agent-system').innerHTML=sysHtml; sysWrap.hidden=false; }
|
|
|
|
|
if(unregHtml){ document.getElementById('agent-unregistered').innerHTML=unregHtml; unregWrap.hidden=false; }
|
|
|
|
|
}catch(e){
|
|
|
|
|
if(warn) warn.innerHTML='<div class="alert alert-warning" style="margin-top:1rem">Az ügynök nem elérhető — élő meghajtóadatok nélkül.</div>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---- type-to-confirm modal (destructive user-data actions) ----
|
|
|
|
@@ -524,6 +561,40 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
// Light overlay dialog (D1) — replaces the native blocking browser dialogs: same texts,
|
|
|
|
|
// reuses the .confirm-overlay/.confirm-box pattern. typeToConfirm adds the typed-name gate
|
|
|
|
|
// ONLY where the old flow already had one.
|
|
|
|
|
function escText(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
|
|
|
|
|
function closeDialog(){ var r=document.getElementById('dialog-root'); if(r) r.innerHTML=''; }
|
|
|
|
|
function openDialog(opts){
|
|
|
|
|
var root=document.getElementById('dialog-root');
|
|
|
|
|
if(!root) return;
|
|
|
|
|
var typeGate=opts.typeToConfirm||'';
|
|
|
|
|
var html='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
|
|
|
|
|
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>'
|
|
|
|
|
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>';
|
|
|
|
|
if(typeGate){
|
|
|
|
|
html+='<div class="confirm-input"><label>Megerősítéshez írja be: <strong class="mono">'+escText(typeGate)+'</strong></label>'
|
|
|
|
|
+'<input type="text" id="dialog-type" class="form-control" autocomplete="off" placeholder="'+escText(typeGate)+'"></div>';
|
|
|
|
|
}
|
|
|
|
|
html+='<div class="form-actions"><button id="dialog-go" class="btn '+(typeGate?'btn-danger-outline':'btn-primary')+'"'+(typeGate?' disabled':'')+'>'+escText(opts.confirmLabel||'Megerősítés')+'</button>'
|
|
|
|
|
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>'
|
|
|
|
|
+'</div></div>';
|
|
|
|
|
root.innerHTML=html;
|
|
|
|
|
var go=document.getElementById('dialog-go');
|
|
|
|
|
if(typeGate){
|
|
|
|
|
document.getElementById('dialog-type').oninput=function(){ go.disabled=(this.value.trim()!==typeGate); };
|
|
|
|
|
}
|
|
|
|
|
go.onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
|
|
|
|
|
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
|
|
|
|
|
}
|
|
|
|
|
// Overlay-confirmed POST form submit (registry drive-remove forms).
|
|
|
|
|
function storageRemoveDialog(ev, form, msg){
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
openDialog({title:'Meghajtó eltávolítása', message:msg, confirmLabel:'Eltávolítás', onConfirm:function(){ form.submit(); }});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function editStorageLabel(path, currentLabel) {
|
|
|
|
|
var wrap = document.getElementById('label-wrap-' + path);
|
|
|
|
|
if (!wrap) return;
|
|
|
|
@@ -541,7 +612,9 @@ function storageDisconnect(path, label, appCount) {
|
|
|
|
|
var msg = 'Biztos leválasztja a meghajtót: ' + label + '?';
|
|
|
|
|
if (appCount > 0) msg += '\n\nA rajta futó ' + appCount + ' alkalmazás le fog állni.';
|
|
|
|
|
msg += '\n\nA meghajtó ezután biztonságosan eltávolítható.';
|
|
|
|
|
if (!confirm(msg)) return;
|
|
|
|
|
openDialog({title:'Meghajtó leválasztása', message:msg, confirmLabel:'Leválasztás', onConfirm:function(){ doStorageDisconnect(path); }});
|
|
|
|
|
}
|
|
|
|
|
function doStorageDisconnect(path) {
|
|
|
|
|
fetch('/api/storage/disconnect', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
|
|
@@ -594,18 +667,19 @@ function storageRestartApps(path) {
|
|
|
|
|
function storageDecommission(path, label, appCount) {
|
|
|
|
|
var sel = document.getElementById('migrate-target-' + path);
|
|
|
|
|
var target = sel ? sel.value : '';
|
|
|
|
|
var body;
|
|
|
|
|
if (target) {
|
|
|
|
|
if (!confirm('Leszerelés áthelyezéssel: minden adat átmásolása ide: ' + target + ', majd a(z) ' + label + ' leszerelése?\n\nAz adatok NEM törlődnek.')) return;
|
|
|
|
|
body = {where: path, mode: 'migrate', target: target};
|
|
|
|
|
openDialog({title:'Leszerelés áthelyezéssel', confirmLabel:'Leszerelés',
|
|
|
|
|
message:'Leszerelés áthelyezéssel: minden adat átmásolása ide: ' + target + ', majd a(z) ' + label + ' leszerelése?\n\nAz adatok NEM törlődnek.',
|
|
|
|
|
onConfirm:function(){ doStorageDecommission({where: path, mode: 'migrate', target: target}); }});
|
|
|
|
|
} else {
|
|
|
|
|
var name = path.split('/').pop();
|
|
|
|
|
var typed = prompt('A(z) ' + label + ' leszereléséhez (áthelyezés nélkül) írja be a meghajtó nevét megerősítésként:\n\n' + name +
|
|
|
|
|
(appCount > 0 ? '\n\nFIGYELEM: ' + appCount + ' alkalmazás leáll (az adatok megmaradnak).' : ''));
|
|
|
|
|
if (typed === null) return;
|
|
|
|
|
if (typed.trim() !== name) { alert('A név nem egyezik — megszakítva.'); return; }
|
|
|
|
|
body = {where: path, mode: 'anyway', mount_name: name};
|
|
|
|
|
openDialog({title:'Meghajtó leszerelése', confirmLabel:'Leszerelés', typeToConfirm:name,
|
|
|
|
|
message:'A(z) ' + label + ' leszereléséhez (áthelyezés nélkül) írja be a meghajtó nevét megerősítésként.' +
|
|
|
|
|
(appCount > 0 ? '\n\nFIGYELEM: ' + appCount + ' alkalmazás leáll (az adatok megmaradnak).' : ''),
|
|
|
|
|
onConfirm:function(){ doStorageDecommission({where: path, mode: 'anyway', mount_name: name}); }});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function doStorageDecommission(body) {
|
|
|
|
|
fetch('/api/storage/decommission', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
|
|
@@ -618,7 +692,11 @@ function storageDecommission(path, label, appCount) {
|
|
|
|
|
// H3: one-click re-enroll a decommissioned/ejected drive — clears the marker, re-attaches under the
|
|
|
|
|
// parent, restarts the gate-stopped apps (data is intact).
|
|
|
|
|
function storageReEnroll(path, label) {
|
|
|
|
|
if (!confirm('Visszacsatlakoztatja a(z) ' + label + ' meghajtót?\n\nAz adatok érintetlenek; a leállított alkalmazások újraindulnak.')) return;
|
|
|
|
|
openDialog({title:'Visszacsatlakoztatás', confirmLabel:'Visszacsatlakoztatás',
|
|
|
|
|
message:'Visszacsatlakoztatja a(z) ' + label + ' meghajtót?\n\nAz adatok érintetlenek; a leállított alkalmazások újraindulnak.',
|
|
|
|
|
onConfirm:function(){ doStorageReEnroll(path); }});
|
|
|
|
|
}
|
|
|
|
|
function doStorageReEnroll(path) {
|
|
|
|
|
fetch('/api/storage/reconnect', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
|
|
|