b762a37097
gates / gates (push) Successful in 17s
After a reinstall the data drive could not be re-attached through any dashboard route: both candidate lists came from the agent's unclaimed-disk scan, and the rebuilt box's drives are claimed. The restore page said it was two clicks while pointing at an empty picker. The attach list now also carries the controller's own mounted-but-unregistered filesystems. initialize is untouched, so the format wizard's system/backup protection is unchanged. The 'two clicks' sentence is conditional on the picker being non-empty, and says something true and actionable when it is not.
143 lines
8.1 KiB
HTML
143 lines
8.1 KiB
HTML
{{define "storage_attach"}}
|
|
{{template "layout_start" .}}
|
|
|
|
<div class="page-header">
|
|
<div style="display:flex;align-items:center;gap:.5rem">
|
|
<a href="/storage" class="btn btn-sm btn-outline">← Vissza</a>
|
|
<h2>Meglévő meghajtó csatolása</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-card">
|
|
<h3>1. Meghajtó kiválasztása</h3>
|
|
<p class="settings-card-desc">Válassza ki a már fájlrendszerrel rendelkező meghajtót.
|
|
<strong>A meghajtón lévő adatok nem törlődnek</strong> — a csatolás csak elérhetővé teszi azokat.</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>
|
|
|
|
<div class="settings-card" id="cfg-card" style="display:none">
|
|
<h3>2. Konfiguráció</h3>
|
|
<form id="attach-form" onsubmit="return submitAttach(event)">
|
|
<div class="form-group">
|
|
<label>Kiválasztott eszköz</label>
|
|
<span class="settings-value mono" id="sel-device">—</span>
|
|
</div>
|
|
<!-- R-280: an already-mounted store has no mount name to choose — it is already at its path,
|
|
and the action is to register that path. The group is hidden for those. -->
|
|
<div class="form-group" id="mount-name-group">
|
|
<label for="mount-name">Csatlakoztatási név <span class="required">*</span></label>
|
|
<div style="display:flex;align-items:center;gap:.25rem">
|
|
<span class="mono" style="opacity:.6">/mnt/</span>
|
|
<input type="text" id="mount-name" class="form-control" placeholder="hdd_1"
|
|
pattern="[a-zA-Z0-9_-]+" required style="max-width:180px">
|
|
</div>
|
|
<span class="form-hint">A meghajtó a /mnt/<név> útvonalra kerül.</span>
|
|
</div>
|
|
<p class="form-hint" id="mounted-note" style="display:none">
|
|
Ez a meghajtó már csatlakoztatva van, csak a gép nem tartja nyilván. A „Csatolás" a
|
|
meglévő helyén veszi nyilvántartásba — <strong>semmi nem törlődik</strong>.
|
|
</p>
|
|
<div class="form-group">
|
|
<label for="storage-label">Megnevezés</label>
|
|
<input type="text" id="storage-label" class="form-control" placeholder="Külső HDD 1TB" maxlength="50">
|
|
</div>
|
|
<label class="toggle" style="margin-bottom:1.25rem">
|
|
<input type="checkbox" id="set-default">
|
|
<span class="toggle-label">Beállítás alapértelmezett adattárolóként új telepítéseknél</span>
|
|
</label>
|
|
<div class="form-actions" style="gap:.75rem">
|
|
<button type="submit" class="btn btn-primary" id="attach-btn">Csatolás</button>
|
|
<a href="/settings" class="btn btn-outline">Mégsem</a>
|
|
</div>
|
|
<div id="attach-result" style="margin-top:1rem"></div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
var selDevice = "", selFSType = "", selMounted = false;
|
|
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];}); }
|
|
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{
|
|
// 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 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">';
|
|
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.
|
|
// R-280: for an already-mounted store mount_source is its MOUNTPOINT (/mnt/sys_drive) and the
|
|
// action is register-in-place, so the card is labelled by the path the customer will see.
|
|
var dev = d.mount_source || d.device;
|
|
var mounted = !!d.already_mounted;
|
|
if(mounted){ title = esc(d.mount_source); sub = [esc(d.device), esc(d.fstype)].filter(Boolean).join(' · '); }
|
|
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(dev)+'" data-fs="'+esc(d.fstype)+'" data-i="'+i+'" data-mounted="'+(mounted?'1':'')+'" 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">'+(mounted?'<span class="badge badge-ok">Már csatlakoztatva</span>':'<span class="badge badge-ok">Fájlrendszer: '+esc(d.fstype)+'</span>')+'</div></div></label>';
|
|
});
|
|
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; }
|
|
}
|
|
|
|
function pickDisk(radio){
|
|
selDevice=radio.value; selFSType=radio.getAttribute('data-fs')||"";
|
|
selMounted=!!radio.getAttribute('data-mounted');
|
|
document.getElementById('sel-device').textContent=selDevice;
|
|
// An already-mounted store keeps its own path; there is no name to pick. Dropping `required` too,
|
|
// or the hidden empty field blocks form submission with no visible cause.
|
|
var mng=document.getElementById('mount-name-group'), mni=document.getElementById('mount-name');
|
|
mng.style.display = selMounted ? 'none' : '';
|
|
mni.required = !selMounted;
|
|
document.getElementById('mounted-note').style.display = selMounted ? '' : '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'});
|
|
}
|
|
|
|
async function submitAttach(ev){
|
|
ev.preventDefault();
|
|
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{
|
|
// R-280: an already-mounted store is REGISTERED in place. Sending it to /api/storage/attach would
|
|
// ask the agent to mount an in-guest path as if it were a raw device.
|
|
var url, body;
|
|
if(selMounted){
|
|
url='/api/storage/register-mounted';
|
|
body={path:selDevice, label:document.getElementById('storage-label').value,
|
|
set_default:document.getElementById('set-default').checked};
|
|
}else{
|
|
url='/api/storage/attach';
|
|
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(url,{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
|
|
var j=await r.json();
|
|
if(!j.ok){ throw new Error(j.error||'Hiba'); }
|
|
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen '+(selMounted?'nyilvántartásba véve':'csatolva é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; }
|
|
return false;
|
|
}
|
|
loadDisks();
|
|
</script>
|
|
|
|
{{template "layout_end" .}}
|
|
{{end}}
|