v0.141.0: F6 initialize-to-usable (detached crash-safe init job + status poll) + F7 Vissza back-routes
- F6: POST /api/storage/init runs format→mount→register as a DETACHED single-flight job (context.Background, netAddState shape) the wizard polls via GET /api/storage/init/status; 3-step progress; register-last marker-last crash-safety. Fixes the client-disconnect-aborts-mount bug. No agent change (chain reaches FileBrowser sync = controller-only). Red-proof TestStorageInit_DetachedSurvivesClientDisconnect. - F7: storage_init/attach Vissza → /storage (was /settings). Test TestStorageWizardBackAnchors_PointToStorage.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
<a href="/settings" class="btn btn-sm btn-outline">← Vissza</a>
|
||||
<a href="/storage" class="btn btn-sm btn-outline">← Vissza</a>
|
||||
<h2>Új meghajtó inicializálása</h2>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,7 +111,9 @@ function pickDisk(radio){
|
||||
document.getElementById('cfg-card').scrollIntoView({behavior:'smooth'});
|
||||
}
|
||||
|
||||
// postInit runs the init POST. confirmed/durableId carry the customer's wipe confirmation (user-data).
|
||||
// postInit STARTS the detached init job (F6). confirmed/durableId carry the customer's wipe
|
||||
// confirmation (user-data). The chain (format → mount → register) now runs server-side off the
|
||||
// request context and is followed via pollInitStatus — a closed tab no longer aborts it.
|
||||
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,
|
||||
@@ -120,24 +122,52 @@ async function postInit(confirmed, durableId){
|
||||
return {status:r.status, j:await r.json()};
|
||||
}
|
||||
|
||||
// stepper renders the 3-step progress (Formázás → Csatolás → Regisztrálás) for the running phase.
|
||||
function stepper(phase){
|
||||
var steps=['Formázás','Csatolás','Regisztrálás'];
|
||||
var order={formatting:0,mounting:1,registering:2};
|
||||
var cur=order[phase]!=null?order[phase]:0;
|
||||
var rows=steps.map(function(lbl,i){
|
||||
var state=i<cur?'kész':(i===cur?'folyamatban…':'vár');
|
||||
var style=i<cur?'color:var(--accent-ok,#3a9a4a)':(i===cur?'font-weight:600':'opacity:.5');
|
||||
return '<div style="'+style+'">'+(i+1)+'. '+esc(lbl)+' — '+state+'</div>';
|
||||
});
|
||||
return '<div class="init-steps" style="display:flex;flex-direction:column;gap:.25rem">'+rows.join('')+'</div>';
|
||||
}
|
||||
|
||||
function sleep(ms){ return new Promise(function(r){ setTimeout(r,ms); }); }
|
||||
|
||||
// pollInitStatus follows the detached job to a terminal state (F6): 3-step progress while running,
|
||||
// the confirm/refuse verdicts, or success/failure.
|
||||
async function pollInitStatus(out, btn){
|
||||
for(;;){
|
||||
await sleep(1000);
|
||||
var r; try{ r=await fetch('/api/storage/init/status'); }catch(e){ continue; }
|
||||
var j; try{ j=await r.json(); }catch(e){ continue; }
|
||||
if(!j.ok){ out.innerHTML='<div class="alert alert-error">Az állapot lekérdezése sikertelen.</div>'; btn.disabled=false; return; }
|
||||
var d=j.data||{}; var ph=d.phase;
|
||||
if(ph==='formatting'||ph==='mounting'||ph==='registering'){ out.innerHTML=stepper(ph); continue; }
|
||||
if(ph==='idle'){ continue; } // slot not yet populated
|
||||
if(ph==='needs_confirmation'){ renderConfirm(d.durable_id, out, btn); return; }
|
||||
if(ph==='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).'
|
||||
+(d.opsign?('<pre class="mono" style="white-space:pre-wrap;background:var(--bg-0);padding:.75rem;border-radius:6px;margin-top:.5rem">'+esc(d.opsign)+'</pre>'):'')+'</div>';
|
||||
btn.disabled=false; return;
|
||||
}
|
||||
if(ph==='done'){ finishInit(d.where, out); return; }
|
||||
if(ph==='failed'){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(d.error||'')+'</div>'; btn.disabled=false; return; }
|
||||
}
|
||||
}
|
||||
|
||||
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>';
|
||||
btn.disabled=true; out.innerHTML=stepper('formatting');
|
||||
try{
|
||||
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-0);padding:.75rem;border-radius:6px;margin-top:.5rem">'+esc(res.j.data.opsign)+'</pre>'):'')+'</div>';
|
||||
btn.disabled=false; return false;
|
||||
}
|
||||
finishInit(res.j, out);
|
||||
if(res.status===409){ out.innerHTML='<div class="alert alert-warning">'+esc((res.j&&res.j.error)||'Már folyamatban van egy inicializálás.')+'</div>'; btn.disabled=false; return false; }
|
||||
if(!res.j.ok){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(res.j.error||'')+'</div>'; btn.disabled=false; return false; }
|
||||
await pollInitStatus(out, btn);
|
||||
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(e.message)+'</div>'; btn.disabled=false; }
|
||||
return false;
|
||||
}
|
||||
@@ -150,18 +180,19 @@ function renderConfirm(durableId, out, btn){
|
||||
+'<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>';
|
||||
var cr=document.getElementById('init-confirm-result'); cr.innerHTML='<p class="form-hint">Törlés és inicializálás indítása…</p>';
|
||||
try{
|
||||
var res2=await postInit(true, durableId);
|
||||
if(res2.status===409){ cr.innerHTML='<div class="alert alert-warning">'+esc((res2.j&&res2.j.error)||'')+'</div>'; return; }
|
||||
if(!res2.j.ok){ cr.innerHTML='<div class="alert alert-error">Hiba: '+esc(res2.j.error||'')+'</div>'; return; }
|
||||
finishInit(res2.j, out);
|
||||
out.innerHTML=stepper('formatting');
|
||||
await pollInitStatus(out, btn);
|
||||
}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>';
|
||||
function finishInit(where, out){
|
||||
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+esc(where)+'</strong>. <a href="/storage">Vissza a tárhelyhez →</a></div>';
|
||||
}
|
||||
loadDisks();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user