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:
@@ -10,8 +10,9 @@
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>1. Meghajtó kiválasztása</h3>
|
||||
<p class="settings-card-desc">Válassza ki a formázandó meghajtót. A formázás biztonságát a host-ügynök
|
||||
garantálja: <strong>adatot tartalmazó meghajtó nem formázható operátori aláírás nélkül</strong>.</p>
|
||||
<p class="settings-card-desc">Válassza ki a formázandó <strong>felhasználói adatmeghajtót</strong>. Rendszer- és
|
||||
biztonsági-mentés meghajtók itt nem jelennek meg — azok védettek. Ha a meghajtó adatot tartalmaz, a törlést
|
||||
Önnek meg kell erősítenie.</p>
|
||||
<div id="disk-error" class="alert alert-error" style="display:none;margin-bottom:1rem"></div>
|
||||
<div id="disk-list">Betöltés…</div>
|
||||
</div>
|
||||
@@ -48,8 +49,8 @@
|
||||
<span class="toggle-label">Beállítás alapértelmezett adattárolóként új telepítéseknél</span>
|
||||
</label>
|
||||
<div class="alert alert-warning" id="warn-databearing" style="display:none;margin-bottom:1rem">
|
||||
⚠️ A kiválasztott meghajtó <strong>adatot tartalmaz</strong>. A formázás védelmi okból csak
|
||||
operátori aláírással hajtható végre — a rendszer megmutatja a szükséges parancsot.
|
||||
⚠️ A kiválasztott meghajtó <strong>adatot tartalmaz</strong>. Az inicializálás (formázás) törli a rajta lévő
|
||||
összes adatot — a folytatáshoz meg kell erősítenie.
|
||||
</div>
|
||||
<div class="form-actions" style="gap:.75rem">
|
||||
<button type="submit" class="btn btn-danger-outline" id="init-btn">Inicializálás indítása</button>
|
||||
@@ -61,28 +62,36 @@
|
||||
|
||||
<script>
|
||||
var selDevice = "", selDataBearing = false;
|
||||
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];}); }
|
||||
|
||||
function badge(d){
|
||||
if(d.backing_device===""){ return '<span class="badge">—</span>'; }
|
||||
function dataBadge(d){
|
||||
return d.data_bearing
|
||||
? '<span class="badge badge-error" title="'+(d.data_reason||'')+'">Adatot tartalmaz</span>'
|
||||
? '<span class="badge badge-error" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>'
|
||||
: '<span class="badge badge-ok">Üres — formázható</span>';
|
||||
}
|
||||
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 '';
|
||||
}
|
||||
|
||||
async function loadDisks(){
|
||||
try{
|
||||
var r = await fetch('/api/disks'); var j = await r.json();
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
var disks = (j.data&&j.data.disks)||[];
|
||||
var formattable = disks.filter(function(d){return d.backing_device!=="";});
|
||||
if(formattable.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs formázható (blokkeszközzel rendelkező) meghajtó.</p>'; return; }
|
||||
var html='<table class="data-table"><thead><tr><th></th><th>Tároló</th><th>Típus</th><th>Eszköz</th><th>Állapot</th><th>Osztály</th><th>Adat</th></tr></thead><tbody>';
|
||||
// Only USER-DATA drives with a block device are valid init (format) targets.
|
||||
var formattable = disks.filter(function(d){ return d.backing_device!=="" && d.role==='user-data'; });
|
||||
if(formattable.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs formázható felhasználói adatmeghajtó. (A rendszer- és biztonsági-mentés meghajtók védettek.)</p>'; return; }
|
||||
var html='<div class="drive-list">';
|
||||
formattable.forEach(function(d,i){
|
||||
html+='<tr><td><input type="radio" name="disk" value="'+d.backing_device+'" data-db="'+(d.data_bearing?'1':'0')+'" onchange="pickDisk(this)"></td>'
|
||||
+'<td>'+d.name+'</td><td>'+d.type+'</td><td class="mono">'+d.backing_device+'</td>'
|
||||
+'<td>'+(d.state==='attached'?'csatlakoztatva':d.state)+'</td><td>'+(d.class||'—')+'</td><td>'+badge(d)+'</td></tr>';
|
||||
var sub = esc(d.type)+' · '+esc(d.backing_device)+(d.mount_path?' · '+esc(d.mount_path):'');
|
||||
html+='<label class="drive-card role-user-data is-selectable" id="dc-'+i+'">'
|
||||
+'<div class="drive-card-top"><div class="drive-select"><input type="radio" name="disk" value="'+esc(d.backing_device)+'" data-db="'+(d.data_bearing?'1':'0')+'" data-i="'+i+'" onchange="pickDisk(this)">'
|
||||
+'<div class="drive-id"><span class="drive-name">'+esc(d.name)+'</span><span class="drive-sub">'+sub+'</span></div></div>'
|
||||
+'<div class="drive-badges"><span class="badge badge-ok">Felhasználói adat</span>'+classBadge(d)+dataBadge(d)+'</div></div></label>';
|
||||
});
|
||||
html+='</tbody></table>';
|
||||
html+='</div>';
|
||||
document.getElementById('disk-list').innerHTML=html;
|
||||
}catch(e){ var el=document.getElementById('disk-error'); el.style.display='block'; el.textContent='Meghajtók betöltése sikertelen: '+e.message; }
|
||||
}
|
||||
@@ -91,33 +100,64 @@ function pickDisk(radio){
|
||||
selDevice=radio.value; selDataBearing=radio.getAttribute('data-db')==='1';
|
||||
document.getElementById('sel-device').textContent=selDevice;
|
||||
document.getElementById('warn-databearing').style.display=selDataBearing?'block':'none';
|
||||
document.querySelectorAll('.drive-card').forEach(function(c){c.classList.remove('is-picked');});
|
||||
var card=document.getElementById('dc-'+radio.getAttribute('data-i')); if(card) card.classList.add('is-picked');
|
||||
document.getElementById('cfg-card').style.display='block';
|
||||
document.getElementById('cfg-card').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
// postInit runs the init POST. confirmed/durableId carry the customer's wipe confirmation (user-data).
|
||||
async function postInit(confirmed, durableId){
|
||||
var body={device:selDevice, fstype:document.getElementById('fstype').value,
|
||||
mount_name:document.getElementById('mount-name').value, label:document.getElementById('storage-label').value,
|
||||
set_default:document.getElementById('set-default').checked, confirmed:!!confirmed, durable_id:durableId||""};
|
||||
var r=await fetch('/api/storage/init',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
|
||||
return {status:r.status, j:await r.json()};
|
||||
}
|
||||
|
||||
async function submitInit(ev){
|
||||
ev.preventDefault();
|
||||
var btn=document.getElementById('init-btn'); var out=document.getElementById('init-result');
|
||||
btn.disabled=true; out.innerHTML='<p class="form-hint">Formázás és csatlakoztatás folyamatban…</p>';
|
||||
try{
|
||||
var body={device:selDevice, fstype:document.getElementById('fstype').value,
|
||||
mount_name:document.getElementById('mount-name').value, label:document.getElementById('storage-label').value,
|
||||
set_default:document.getElementById('set-default').checked};
|
||||
var r=await fetch('/api/storage/init',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
|
||||
var j=await r.json();
|
||||
if(r.status===409 && j.data && j.data.refused){
|
||||
out.innerHTML='<div class="alert alert-warning"><strong>Operátori aláírás szükséges.</strong><br>'
|
||||
+'A meghajtó adatot tartalmaz, ezért a formázás védelmi okból nem hajtható végre automatikusan'
|
||||
+(j.data.reason?(' ('+j.data.reason+')'):'')+'.<br><br>Az engedélyezéshez futtassa offline az operátor gépén:'
|
||||
+'<pre class="mono" style="white-space:pre-wrap;background:var(--bg-primary);padding:.75rem;border-radius:6px;margin-top:.5rem">'+(j.data.opsign||'(nem elérhető)')+'</pre>'
|
||||
+'Az aláírás után a Hub végrehajtja a műveletet; ezután térjen vissza ide.</div>';
|
||||
var res=await postInit(false, "");
|
||||
// USER-DATA data-bearing → the customer must confirm the wipe (type-to-confirm), then re-submit.
|
||||
if(res.status===409 && res.j.data && res.j.data.needs_confirmation){
|
||||
renderConfirm(res.j.data.durable_id, out, btn);
|
||||
return false;
|
||||
}
|
||||
// SYSTEM/BACKUP (shouldn't reach here — filtered out — but surface the opsign if it does).
|
||||
if(res.status===409 && res.j.data && res.j.data.refused){
|
||||
out.innerHTML='<div class="alert alert-warning"><strong>Operátori aláírás szükséges.</strong> Ez a meghajtó védett (rendszer/biztonsági mentés).'
|
||||
+(res.j.data.opsign?('<pre class="mono" style="white-space:pre-wrap;background:var(--bg-primary);padding:.75rem;border-radius:6px;margin-top:.5rem">'+esc(res.j.data.opsign)+'</pre>'):'')+'</div>';
|
||||
btn.disabled=false; return false;
|
||||
}
|
||||
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
||||
out.innerHTML='<div class="alert alert-success">✅ A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+(j.data.where||'')+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+e.message+'</div>'; btn.disabled=false; }
|
||||
finishInit(res.j, out);
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(e.message)+'</div>'; btn.disabled=false; }
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderConfirm(durableId, out, btn){
|
||||
var name=document.getElementById('mount-name').value;
|
||||
out.innerHTML='<div class="alert alert-warning">⚠️ A meghajtó adatot tartalmaz. A formázás <strong>véglegesen törli</strong> a rajta lévő összes adatot.</div>'
|
||||
+'<div class="confirm-input"><label>Megerősítéshez írja be a csatlakoztatási nevet: <strong class="mono">'+esc(name)+'</strong></label>'
|
||||
+'<input type="text" id="init-confirm-type" class="form-control" autocomplete="off" oninput="document.getElementById(\'init-confirm-go\').disabled=(this.value!==\''+esc(name)+'\')"></div>'
|
||||
+'<div class="form-actions" style="gap:.6rem;margin-top:.75rem"><button id="init-confirm-go" class="btn btn-danger-outline" disabled>Törlés és inicializálás megerősítése</button></div>'
|
||||
+'<div id="init-confirm-result" style="margin-top:.6rem"></div>';
|
||||
document.getElementById('init-confirm-go').onclick=async function(){
|
||||
var cr=document.getElementById('init-confirm-result'); cr.innerHTML='<p class="form-hint">Törlés és inicializálás folyamatban…</p>';
|
||||
try{
|
||||
var res2=await postInit(true, durableId);
|
||||
if(!res2.j.ok){ cr.innerHTML='<div class="alert alert-error">Hiba: '+esc(res2.j.error||'')+'</div>'; return; }
|
||||
finishInit(res2.j, out);
|
||||
}catch(e){ cr.innerHTML='<div class="alert alert-error">Hiba: '+esc(e.message)+'</div>'; }
|
||||
};
|
||||
}
|
||||
|
||||
function finishInit(j, out){
|
||||
if(!j.ok){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(j.error||'')+'</div>'; document.getElementById('init-btn').disabled=false; return; }
|
||||
out.innerHTML='<div class="alert alert-success">✅ A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+esc(j.data&&j.data.where)+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
|
||||
}
|
||||
loadDisks();
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user