v0.44.0: role-aware drive management — protected lockout + customer type-to-confirm wipe + drive-list restyle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -354,46 +354,122 @@ function pollUntilBack() {
|
||||
|
||||
<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 tárolás végrehajtása az ügynöké).</p>
|
||||
<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>
|
||||
<div id="confirm-root"></div>
|
||||
<script>
|
||||
window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}{{end}}];
|
||||
(function(){
|
||||
function badge(d){
|
||||
if(d.backing_device===""){ return ''; }
|
||||
return d.data_bearing
|
||||
? '<span class="badge badge-error" title="'+(d.data_reason||'')+'">Adatot tartalmaz</span>'
|
||||
: '<span class="badge badge-ok">Üres</span>';
|
||||
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 'system-bar-red'; if(p>=70) return 'system-bar-yellow'; return 'system-bar-green'; }
|
||||
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>';
|
||||
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 dataBadge(d){ return d.data_bearing ? '<span class="badge badge-error" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>' : ''; }
|
||||
function regBadge(d, registered){
|
||||
if(!d.mount_path) return '';
|
||||
return registered[d.mount_path] ? '<span class="badge badge-ok">Regisztrálva</span>' : '<span class="badge badge-muted">Nem regisztrált</span>';
|
||||
}
|
||||
function capBar(d){
|
||||
if(!d.total_bytes || d.total_bytes<=0) return '';
|
||||
var pct = d.used_fraction ? d.used_fraction*100 : (d.used_bytes/d.total_bytes*100);
|
||||
pct = Math.max(0, Math.min(100, pct));
|
||||
return '<div class="drive-cap"><div class="system-bar"><div class="system-bar-fill '+usageColorClass(pct)+'" style="width:'+pct.toFixed(1)+'%"></div></div>'
|
||||
+'<div class="drive-cap-label">'+hum(d.used_bytes)+' / '+hum(d.total_bytes)+' ('+pct.toFixed(0)+'%)</div></div>';
|
||||
}
|
||||
function actions(d){
|
||||
// Destructive controls ONLY for user-data drives that are mounted under /mnt. System/backup get none.
|
||||
if(d.role!=='user-data' || !d.mount_path || d.mount_path.indexOf('/mnt/')!==0) return '';
|
||||
var dev = esc(d.backing_device||''), mp = esc(d.mount_path);
|
||||
var btns = '<button class="btn btn-xs btn-danger-outline" onclick="confirmEject(\''+mp+'\')">Leválasztás</button>';
|
||||
if(d.backing_device){ btns += ' <button class="btn btn-xs btn-danger-outline" onclick="confirmWipe(\''+dev+'\',\''+mp+'\')">Törlés…</button>'; }
|
||||
return '<div class="drive-actions">'+btns+'</div>';
|
||||
}
|
||||
function reg(d, registered){ return registered[d.mount_path] ? '<span class="badge badge-ok">Regisztrálva</span>' : (d.mount_path?'<span class="badge">Nem regisztrált</span>':''); }
|
||||
async function load(){
|
||||
var box=document.getElementById('agent-disks'); if(!box) return;
|
||||
try{
|
||||
var r=await fetch('/api/disks'); var j=await r.json();
|
||||
if(!j.ok){ box.innerHTML='<p class="form-hint">'+(j.error||'Nem elérhető')+'</p>'; return; }
|
||||
if(!j.ok){ box.innerHTML='<p class="form-hint">'+esc(j.error||'Nem elérhető')+'</p>'; return; }
|
||||
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;});
|
||||
var html='<table class="data-table"><thead><tr><th>Tároló</th><th>Típus</th><th>Eszköz</th><th>Csatolás</th><th>Osztály</th><th>Adat</th><th>Reg.</th><th></th></tr></thead><tbody>';
|
||||
var html='<div class="drive-list">';
|
||||
disks.forEach(function(d){
|
||||
var ej = (d.mount_path && d.mount_path.indexOf('/mnt/')===0) ? '<button class="btn btn-xs btn-danger-outline" onclick="ejectDisk(\''+d.mount_path+'\')">Leválasztás</button>' : '';
|
||||
html+='<tr><td>'+d.name+'</td><td>'+d.type+'</td><td class="mono">'+(d.backing_device||'—')+'</td><td class="mono">'+(d.mount_path||'—')+'</td><td>'+(d.class||'—')+'</td><td>'+badge(d)+'</td><td>'+reg(d,registered)+'</td><td>'+ej+'</td></tr>';
|
||||
var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(d.mount_path):'');
|
||||
var badges = roleBadge(d.role)+classBadge(d)+dataBadge(d)+regBadge(d,registered);
|
||||
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>'
|
||||
+capBar(d)
|
||||
+actions(d)
|
||||
+'</div>';
|
||||
});
|
||||
html+='</tbody></table>';
|
||||
html+='</div>';
|
||||
box.innerHTML=html;
|
||||
}catch(e){ box.innerHTML='<p class="form-hint">Hiba: '+e.message+'</p>'; }
|
||||
}catch(e){ box.innerHTML='<p class="form-hint">Hiba: '+esc(e.message)+'</p>'; }
|
||||
}
|
||||
window.ejectDisk=async function(where){
|
||||
if(!confirm('Leválasztja a(z) '+where+' meghajtót? Az adatok megmaradnak, de az ott lévő alkalmazások elveszítik a tárhelyet.')) return;
|
||||
|
||||
// ---- type-to-confirm modal (destructive user-data actions) ----
|
||||
function closeModal(){ document.getElementById('confirm-root').innerHTML=''; }
|
||||
window.__closeConfirm=closeModal;
|
||||
async function openConfirm(opts){
|
||||
// opts: {title, mount, mountName, danger, onConfirm}
|
||||
var apps=[];
|
||||
try{
|
||||
var r=await fetch('/api/storage/eject',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({where:where})});
|
||||
var j=await r.json();
|
||||
if(!j.ok){ alert('Hiba: '+(j.error||'')); return; }
|
||||
var dep=(j.data&&j.data.dependent_guests)||[];
|
||||
if(dep.length>0){ alert('Leválasztva. Figyelem: '+dep.length+' vendég (VMID: '+dep.join(', ')+') függött ettől a tárhelytől.'); }
|
||||
location.reload();
|
||||
}catch(e){ alert('Hiba: '+e.message); }
|
||||
var r=await fetch('/api/storage/impact?where='+encodeURIComponent(opts.mount));
|
||||
var j=await r.json(); if(j.ok && j.data && j.data.apps) apps=j.data.apps;
|
||||
}catch(e){}
|
||||
var appsHtml = apps.length
|
||||
? '<p>A művelet után a következő alkalmazások <strong>nem fognak működni</strong>:</p><ul class="confirm-apps">'+apps.map(function(a){return '<li>'+esc(a)+'</li>';}).join('')+'</ul>'
|
||||
: '<p class="form-hint">Ehhez a meghajtóhoz jelenleg nincs telepített alkalmazás rendelve.</p>';
|
||||
var root=document.getElementById('confirm-root');
|
||||
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)__closeConfirm()"><div class="confirm-box">'
|
||||
+'<h3>'+esc(opts.title)+'</h3>'
|
||||
+'<div class="alert alert-warning">'+esc(opts.danger)+'</div>'
|
||||
+appsHtml
|
||||
+'<div class="confirm-input"><label>Megerősítéshez írja be a csatlakoztatási nevet: <strong class="mono">'+esc(opts.mountName)+'</strong></label>'
|
||||
+'<input type="text" id="confirm-type" class="form-control" autocomplete="off" placeholder="'+esc(opts.mountName)+'" oninput="document.getElementById(\'confirm-go\').disabled=(this.value!==\''+esc(opts.mountName)+'\')"></div>'
|
||||
+'<div class="form-actions"><button id="confirm-go" class="btn btn-danger-outline" disabled>Megerősítés</button>'
|
||||
+'<button class="btn btn-outline" onclick="__closeConfirm()">Mégsem</button></div>'
|
||||
+'<div id="confirm-result" style="margin-top:.6rem"></div></div></div>';
|
||||
document.getElementById('confirm-go').onclick=opts.onConfirm;
|
||||
}
|
||||
|
||||
window.confirmEject=function(where){
|
||||
var name=where.replace(/^\/mnt\//,'');
|
||||
openConfirm({title:'Meghajtó leválasztása', mount:where, mountName:name,
|
||||
danger:'A meghajtó leválasztásra kerül. Az adatok megmaradnak, de az ott tárolt alkalmazások elvesztik a tárhelyüket, amíg újra nem csatolja.',
|
||||
onConfirm:async function(){
|
||||
var out=document.getElementById('confirm-result'); out.innerHTML='<p class="form-hint">Leválasztás folyamatban…</p>';
|
||||
try{
|
||||
var r=await fetch('/api/storage/eject',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({where:where})});
|
||||
var j=await r.json(); if(!j.ok){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(j.error||'')+'</div>'; return; }
|
||||
location.reload();
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(e.message)+'</div>'; }
|
||||
}});
|
||||
};
|
||||
window.confirmWipe=function(device, where){
|
||||
var name=where.replace(/^\/mnt\//,'');
|
||||
openConfirm({title:'Meghajtó törlése (formázás)', mount:where, mountName:name,
|
||||
danger:'FIGYELEM: a meghajtón lévő ÖSSZES ADAT véglegesen törlődik (formázás). Ez nem vonható vissza.',
|
||||
onConfirm:async function(){
|
||||
var out=document.getElementById('confirm-result'); out.innerHTML='<p class="form-hint">Törlés folyamatban…</p>';
|
||||
try{
|
||||
var r=await fetch('/api/storage/wipe',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({device:device, where:where, mount_name:name})});
|
||||
var j=await r.json(); if(!j.ok){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(j.error||'')+'</div>'; return; }
|
||||
location.reload();
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(e.message)+'</div>'; }
|
||||
}});
|
||||
};
|
||||
load();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user