v0.95.0: enrollment wizards use the raw-device scan /disks/candidates (Impl-2b)

Both wizards now source candidates from the agent's Impl-2a raw-device scan
(GET /disks/candidates, proxied) instead of the Observe-based /api/disks — so a
brand-new non-PVE-storage drive is finally discoverable + enrollable end-to-end.
agentapi.ListCandidates + a passthrough proxy (no controller-side filtering; the
agent's unclaimed filter is authoritative). storage_init renders `initialize`,
storage_attach renders `attach`; the enroll flow + Impl-1 guarded mkfs unchanged.
Tests + go build/vet/test clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 18:04:49 +02:00
parent 6ce61e862a
commit feab92ccfc
7 changed files with 174 additions and 33 deletions
@@ -51,27 +51,34 @@
<script>
var selDevice = "", selFSType = "";
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
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 fmtSize(b){
if(!b) return '';
var g=b/1073741824;
return g>=1 ? g.toFixed(g>=10?0:1)+' GB' : Math.round(b/1048576)+' MB';
}
async function loadDisks(){
try{
var r = await fetch('/api/disks'); var j = await r.json();
// Impl-2b: the raw-device scan (agent GET /disks/candidates). `attach` = unclaimed disks already
// carrying a mountable ext4/xfs FS (mounted + bound, NO format — data preserved). The agent's filter
// excludes OS/enrolled/claimed disks, so we trust the list as-is.
var r = await fetch('/api/disks/candidates'); var j = await r.json();
if(!j.ok){ throw new Error(j.error||'Hiba'); }
var disks = (j.data&&j.data.disks)||[];
// Attachable: a user-data drive with a backing device, an fs-UUID identity, not mounted yet.
var attachable = disks.filter(function(d){ return d.backing_device!=="" && (d.durable_id||"").indexOf("uuid:")===0 && !d.mount_path && d.role==='user-data'; });
if(attachable.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs csatolható (fájlrendszerrel rendelkező, még nem csatolt) felhasználói adatmeghajtó.</p>'; return; }
var cands = (j.data&&j.data.attach)||[];
if(cands.length===0){ document.getElementById('disk-list').innerHTML='<p class="form-hint">Nincs csatolható meghajtó — a csatoláshoz olyan (még nem használt) meghajtó kell, amelyen már van fájlrendszer. Új, üres meghajtóhoz használja az „Inicializálás” lehetőséget.</p>'; return; }
var html='<div class="drive-list">';
attachable.forEach(function(d,i){
var sub = esc(d.type)+' · '+esc(d.backing_device);
cands.forEach(function(d,i){
var title = d.model ? esc(d.model) : esc(d.device);
var parts = [esc(d.device)];
if(d.size_bytes) parts.push(fmtSize(d.size_bytes));
if(d.fstype) parts.push(esc(d.fstype));
var sub = parts.join(' · ');
// Attach the FS-bearing node (mount_source, e.g. /dev/sdd1); the agent resolves its UUID.
var dev = d.mount_source || d.device;
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-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)+'</div></div></label>';
+'<div class="drive-card-top"><div class="drive-select"><input type="radio" name="disk" value="'+esc(dev)+'" data-fs="'+esc(d.fstype)+'" data-i="'+i+'" onchange="pickDisk(this)">'
+'<div class="drive-id"><span class="drive-name">'+title+'</span><span class="drive-sub">'+sub+'</span></div></div>'
+'<div class="drive-badges"><span class="badge badge-ok">Fájlrendszer: '+esc(d.fstype)+'</span></div></div></label>';
});
html+='</div>';
document.getElementById('disk-list').innerHTML=html;
@@ -79,7 +86,7 @@ async function loadDisks(){
}
function pickDisk(radio){
selDevice=radio.value;
selDevice=radio.value; selFSType=radio.getAttribute('data-fs')||"";
document.getElementById('sel-device').textContent=selDevice;
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');
@@ -92,7 +99,7 @@ async function submitAttach(ev){
var btn=document.getElementById('attach-btn'); var out=document.getElementById('attach-result');
btn.disabled=true; out.innerHTML='<p class="form-hint">Csatlakoztatás folyamatban…</p>';
try{
var body={device:selDevice, fstype:"", mount_name:document.getElementById('mount-name').value,
var body={device:selDevice, fstype:selFSType, 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/attach',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
var j=await r.json();