B1: migrate UI wiring — /api/storage/migrate{,-app,/status} + settings & app pages

ServeStorageAPI gains POST /api/storage/migrate (whole-namespace), POST
/api/storage/migrate-app (single app), GET /api/storage/migrate/status (poll).
settings.html: the greyed migrate-all span becomes a real target-select + button +
shared progress panel; app_info.html gains a per-app 'Áthelyezés másik tárhelyre'
control. Both poll the shared status endpoint and render Hungarian phase progress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 19:48:22 +02:00
parent b57150e3ab
commit 16a4c3e878
4 changed files with 167 additions and 1 deletions
@@ -334,13 +334,63 @@ function pollUntilBack() {
</form>
{{end}}
{{if and (gt .AppCount 0) .HasOtherPaths}}
<span class="btn btn-xs btn-outline" style="opacity:.45;cursor:not-allowed" title="Hamarosan">📦 Összes adat átköltöztetése</span>
{{$src := .Path}}
<span class="migrate-inline" style="display:inline-flex;gap:.35rem;align-items:center">
<select id="migrate-target-{{.Path}}" class="btn btn-xs btn-outline">
<option value="">📦 Áthelyezés ide…</option>
{{range $.StoragePaths}}{{if and (ne .Path $src) .Schedulable (not .Disconnected) (not .Decommissioned)}}<option value="{{.Path}}">{{.Label}} ({{.Path}})</option>{{end}}{{end}}
</select>
<button class="btn btn-xs btn-outline" onclick="storageMigrateAll('{{.Path}}','{{.Label}}')">Összes adat áthelyezése</button>
</span>
{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
<div id="migrate-progress" style="display:none;margin-top:1rem;padding:1rem;border:1px solid var(--accent);border-radius:6px;background:rgba(0,136,204,0.06)">
<strong>Adatok áthelyezése</strong>
<div id="migrate-progress-body" style="margin-top:.5rem"></div>
</div>
<script>
// Shared migration progress (used by both migrate-all here and per-app migrate on the app page).
function migFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
function migRender(job){
var names={stop:'Alkalmazások leállítása',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Forrás törlése'};
var s=names[job.phase]||job.phase;
if(job.current_app) s+=': '+job.current_app;
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+migFmtGB(job.bytes_done)+'/'+migFmtGB(job.bytes_total)+')'; }
return s+'…';
}
function migWatch(){
var panel=document.getElementById('migrate-progress');
var body=document.getElementById('migrate-progress-body');
if(panel) panel.style.display='block';
function tick(){
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
var job=d.data&&d.data.job;
if(!job){ if(body) body.textContent='Nincs folyamatban migráció.'; return; }
if(body) body.innerHTML=migRender(job);
if(job.phase==='done'){ if(body) body.innerHTML+='<br><strong>Kész ✓</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(body) body.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A forrás adatai érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
}
tick();
}
function storageMigrateAll(source,label){
var sel=document.getElementById('migrate-target-'+source);
var target=sel?sel.value:'';
if(!target){ alert('Válassz céltárolót a legördülő menüből.'); return; }
if(!confirm('Áthelyezed a(z) '+label+' ÖSSZES adatát ide: '+target+'?\n\nAz alkalmazások az áthelyezés alatt rövid időre leállnak. A forrás adatai csak az ellenőrzés és a sikeres újraindítás után törlődnek.')) return;
fetch('/api/storage/migrate',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({source:source,target:target})})
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ migWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
.catch(function(e){ alert('Hiba: '+e); });
}
// Resume view: if a migration is already running when the page loads, show the panel.
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ migWatch(); } }).catch(function(){}); })();
</script>
{{else}}
<div class="empty-state" style="padding:1.5rem">
Nincs regisztrált adattároló. Adjon hozzá egyet az alábbi űrlappal.