D1 Part 3: unified drive view + overlay migration
- storage.html: the registry cards and the separate 'Meghajtók (ügynök
nézet)' merge into ONE view. Each connected registry card gets an
#agent-extra-<path> slot; the enrichment JS joins the agent /api/disks
list on mount path and decorates the matching card in place (role tag
via i-lock, drive class, durable-id mono line, agent-only
register/eject/wipe actions). Two extra groups render below:
'Rendszermeghajtók' (system/backup — read-only, lock tag, NO actions)
and 'Nem regisztrált meghajtók' (unregistered user-data — register
action only). Agent-down: one warn note into #agent-warn-note, all
registry cards still render from server data (graceful degradation).
The agent-view helpers now emit design-system .tag markup instead of
.badge (roleTag/classTag/dataTag/regTag/appBackingTag); the 🔒 lock
emoji is gone (sprite i-lock).
- Overlay migration: every native confirm()/prompt() on the four pages
routes through a light .confirm-overlay dialog (openDialog; texts
verbatim) — storage remove forms, netStorageRemove, storageMigrateAll,
storageDisconnect, storageDecommission (migrate + the type-to-confirm
anyway branch preserved like-for-like), storageReEnroll; and on the
system page triggerUpdate + controller/server restart; on the security
page the two geo Hungary-removal confirms. Scenario F grep: zero
native confirm/prompt in the four templates.
- Deleted the now-orphaned .badge-lock/.lock-ico CSS (grep-zero first).
- Tests: no-native-confirm scan, agent-down warn-note static assertion;
integrity gate stays green.
This commit is contained in:
@@ -60,7 +60,7 @@
|
||||
<div class="storage-path-actions">
|
||||
<button class="btn btn-xs btn-primary" onclick="storageReEnroll('{{.Path}}','{{.Label}}')">Visszacsatlakoztatás</button>
|
||||
<form method="POST" action="/settings/storage/remove" style="display:inline"
|
||||
onsubmit="return confirm('Biztosan eltávolítja a(z) {{.Label}} ({{.Path}}) meghajtót a rendszerből?\n\nA meghajtó adatai NEM törlődnek.')">
|
||||
onsubmit="return storageRemoveDialog(event, this, 'Biztosan eltávolítja a(z) {{.Label}} ({{.Path}}) meghajtót a rendszerből?\n\nA meghajtó adatai NEM törlődnek.')">
|
||||
{{$.CSRFField}}
|
||||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||||
<button type="submit" class="btn btn-xs btn-outline">Eltávolítás a rendszerből</button>
|
||||
@@ -111,6 +111,7 @@
|
||||
<span class="form-hint">Nincs alkalmazás ezen a tárolón</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="drive-agent-extra" id="agent-extra-{{.Path}}"></div>
|
||||
</div>
|
||||
<div class="storage-path-actions">
|
||||
{{if not .IsDefault}}
|
||||
@@ -140,7 +141,7 @@
|
||||
{{end}}
|
||||
{{if and (not .IsDefault) (eq .AppCount 0)}}
|
||||
<form method="POST" action="/settings/storage/remove" style="display:inline"
|
||||
onsubmit="return confirm('Biztosan eltávolítja a(z) {{.Path}} adattárolót?')">
|
||||
onsubmit="return storageRemoveDialog(event, this, 'Biztosan eltávolítja a(z) {{.Path}} adattárolót?')">
|
||||
{{$.CSRFField}}
|
||||
<input type="hidden" name="storage_path" value="{{.Path}}">
|
||||
<button type="submit" class="btn btn-xs btn-danger-outline">Eltávolítás</button>
|
||||
@@ -259,11 +260,14 @@
|
||||
}).catch(function(e){ msg.textContent='Hiba: '+e; });
|
||||
}
|
||||
function netStorageRemove(name,label){
|
||||
if(!confirm('Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.')) return;
|
||||
openDialog({title:'Hálózati tárhely eltávolítása', confirmLabel:'Eltávolítás',
|
||||
message:'Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.',
|
||||
onConfirm:function(){
|
||||
fetch('/api/storage/netstorage/remove',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({name:name})})
|
||||
.then(function(r){return r.json();}).then(function(d){
|
||||
if(d.ok){ location.reload(); } else { alert('Hiba: '+(d.error||'ismeretlen')); }
|
||||
}).catch(function(e){ alert('Hiba: '+e); });
|
||||
}});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -301,10 +305,13 @@
|
||||
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;
|
||||
openDialog({title:'Összes adat áthelyezése', confirmLabel:'Áthelyezés',
|
||||
message:'Á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.',
|
||||
onConfirm:function(){
|
||||
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 STILL IN PROGRESS when the page loads, show the panel and watch.
|
||||
// Must NOT re-watch a terminal (done/aborted) job: the journal keeps returning the last completed
|
||||
@@ -319,45 +326,53 @@
|
||||
<a href="/storage/attach" class="btn btn-sm btn-outline"><svg class="ico ico-sm"><use href="#i-link"/></svg> Meglévő meghajtó csatolása</a>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem">
|
||||
<h4 style="margin-bottom:.25rem">Meghajtók (ügynök nézet)</h4>
|
||||
<p class="form-hint" style="margin-bottom:.75rem">A host-ügynök által észlelt meghajtók élő nézete. A meghajtó <strong>szerepkörét</strong> az ügynök saját vizsgálattal állapítja meg: a rendszer- és biztonsági-mentés meghajtók védettek (csak operátori aláírással módosíthatók), a felhasználói adatmeghajtókat Ön kezeli.</p>
|
||||
<div id="agent-disks">Betöltés…</div>
|
||||
<div id="agent-warn-note"></div>
|
||||
|
||||
<div style="margin-top:1.5rem" id="agent-unregistered-wrap" hidden>
|
||||
<div class="section-h"><h3>Nem regisztrált meghajtók</h3><span class="hint">az ügynök által észlelt, még nem regisztrált adatmeghajtók</span></div>
|
||||
<div id="agent-unregistered" class="drive-list"></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:1.5rem" id="agent-system-wrap" hidden>
|
||||
<div class="section-h"><h3>Rendszermeghajtók</h3><span class="hint">védett — csak operátori aláírással módosíthatók</span></div>
|
||||
<div id="agent-system" class="drive-list"></div>
|
||||
</div>
|
||||
<div id="confirm-root"></div>
|
||||
<div id="dialog-root"></div>
|
||||
<script>
|
||||
window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}{{end}}];
|
||||
(function(){
|
||||
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];}); }
|
||||
function hum(b){ if(!b||b<=0) return ''; var u=['B','KB','MB','GB','TB'],i=0,v=b; while(v>=1024&&i<u.length-1){v/=1024;i++;} return (v>=10||i===0?Math.round(v):v.toFixed(1))+' '+u[i]; }
|
||||
function usageColorClass(p){ if(p>=85) return 'crit'; if(p>=70) return 'warn'; return 'nominal'; }
|
||||
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>';
|
||||
var LOCK_ICO='<svg class="ico ico-sm"><use href="#i-lock"/></svg>';
|
||||
function classTag(d){
|
||||
if(d.class==='fast') return '<span class="tag">gyors</span>';
|
||||
if(d.class==='slow') return '<span class="tag tag-off">lassú</span>';
|
||||
return '';
|
||||
}
|
||||
function roleBadge(role){
|
||||
if(role==='system') return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Rendszer</span>';
|
||||
if(role==='backup') return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Biztonsági mentés — védett</span>';
|
||||
if(role==='user-data') return '<span class="badge badge-ok">Felhasználói adat</span>';
|
||||
return '<span class="badge badge-lock"><span class="lock-ico">🔒</span>Védett</span>';
|
||||
function roleTag(role){
|
||||
if(role==='system') return '<span class="tag">'+LOCK_ICO+'Rendszer — védett</span>';
|
||||
if(role==='backup') return '<span class="tag">'+LOCK_ICO+'Biztonsági mentés — védett</span>';
|
||||
if(role==='user-data') return '<span class="tag tag-run"><span class="dot"></span>Felhasználói adat</span>';
|
||||
return '<span class="tag">'+LOCK_ICO+'Védett</span>';
|
||||
}
|
||||
function dataBadge(d){ return d.data_bearing ? '<span class="badge badge-error" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>' : ''; }
|
||||
function dataTag(d){ return d.data_bearing ? '<span class="tag tag-warn" title="'+esc(d.data_reason)+'">Adatot tartalmaz</span>' : ''; }
|
||||
// regKey is the path a drive is REGISTERED under: the STABLE intermediary path (guest_path,
|
||||
// /mnt/felhom-drives/<name>) the agent reports, else the raw mount_path (legacy). The registry
|
||||
// stores the STABLE path (handleStorageRegister/runStorageInit), so checking the raw mount_path
|
||||
// alone made an enrolled drive read as "Nem regisztrált" + show a spurious Regisztrálás button.
|
||||
function regKey(d){ return d.guest_path || d.mount_path; }
|
||||
function regBadge(d, registered){
|
||||
function regTag(d, registered){
|
||||
if(!d.mount_path) return '';
|
||||
return registered[regKey(d)] ? '<span class="badge badge-ok">Regisztrálva</span>' : '<span class="badge badge-muted">Nem regisztrált</span>';
|
||||
return registered[regKey(d)] ? '<span class="tag tag-run"><span class="dot"></span>Regisztrálva</span>' : '<span class="tag tag-off"><span class="dot"></span>Nem regisztrált</span>';
|
||||
}
|
||||
// appBackingTag marks the storages that actually hold deployed apps: the internal SSD (app
|
||||
// databases + Docker) and the external user-data drives (large app files). Keyed on the agent's
|
||||
// authoritative role/type — pure presentation, no agent contract change.
|
||||
function appBackingTag(d){
|
||||
if(d.type==='lvmthin') return '<span class="badge badge-info">Alkalmazás-rendszer</span>';
|
||||
if(d.role==='user-data') return '<span class="badge badge-info">Alkalmazás-adatok</span>';
|
||||
if(d.type==='lvmthin') return '<span class="tag">Alkalmazás-rendszer</span>';
|
||||
if(d.role==='user-data') return '<span class="tag">Alkalmazás-adatok</span>';
|
||||
return '';
|
||||
}
|
||||
// purposeDesc explains, in plain Hungarian, what each storage is for — so the "which one do the
|
||||
@@ -394,36 +409,58 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
|
||||
if(d.backing_device){ btns += ' <button class="btn btn-xs btn-danger-outline" onclick="confirmWipe(\''+dev+'\',\''+reg+'\')">Törlés…</button>'; }
|
||||
return '<div class="drive-actions">'+btns+'</div>';
|
||||
}
|
||||
// Unified drive view (D1): the agent disk list ENRICHES the registry cards in place
|
||||
// (role tag + durable-id + agent-only actions), and two extra groups render below:
|
||||
// protected system/backup drives (read-only) and unregistered user-data drives.
|
||||
function driveRow(d, registered, withActions){
|
||||
var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(regKey(d)):'');
|
||||
var badges = roleTag(d.role)+appBackingTag(d)+classTag(d)+dataTag(d);
|
||||
if(withActions) badges += regTag(d, registered);
|
||||
var purpose = purposeDesc(d);
|
||||
return '<div class="drive-card role-'+esc(d.role||'system')+'">'
|
||||
+'<div class="drive-card-top"><div class="drive-id"><span class="drive-name">'+esc(d.name)+'</span><span class="drive-sub">'+sub+'</span></div>'
|
||||
+'<div class="drive-badges">'+badges+'</div></div>'
|
||||
+(purpose?'<div class="drive-purpose">'+esc(purpose)+'</div>':'')
|
||||
+(d.durable_id?'<div class="drive-sub mono">'+esc(d.durable_id)+'</div>':'')
|
||||
+capBar(d)
|
||||
+(withActions?actions(d,registered):'')
|
||||
+'</div>';
|
||||
}
|
||||
function enrichCard(d, registered){
|
||||
var slot=document.getElementById('agent-extra-'+regKey(d));
|
||||
if(!slot) return false;
|
||||
var extra='<div class="metarows" style="margin:.4rem 0 0">'
|
||||
+'<span class="metarow">'+roleTag(d.role)+'</span>'
|
||||
+(d.class?'<span class="metarow">'+classTag(d)+'</span>':'')
|
||||
+(d.durable_id?'<span class="metarow mono" title="Tartós azonosító">'+esc(d.durable_id)+'</span>':'')
|
||||
+'</div>'
|
||||
+actions(d,registered);
|
||||
slot.innerHTML=extra;
|
||||
return true;
|
||||
}
|
||||
async function load(){
|
||||
var box=document.getElementById('agent-disks'); if(!box) return;
|
||||
var warn=document.getElementById('agent-warn-note');
|
||||
try{
|
||||
var r=await fetch('/api/disks'); var j=await r.json();
|
||||
if(!j.ok){ box.innerHTML='<p class="form-hint">'+esc(j.error||'Nem elérhető')+'</p>'; return; }
|
||||
if(!j.ok) throw new Error(j.error||'Nem elérhető');
|
||||
var disks=(j.data&&j.data.disks)||[];
|
||||
if(disks.length===0){ box.innerHTML='<p class="form-hint">Nincs észlelt meghajtó.</p>'; return; }
|
||||
var registered={}; (window.__registeredPaths||[]).forEach(function(p){registered[p]=true;});
|
||||
// One-line tiering explanation so "which storage do the apps use?" is answered at a glance.
|
||||
var html='<p class="drive-tiering-note">Az alkalmazások rendszere és adatbázisai a belső SSD-n (local-lvm), a nagy méretű fájljaik a külső adattárolókon tárolódnak.</p>';
|
||||
html+='<div class="drive-list">';
|
||||
var sysHtml='', unregHtml='';
|
||||
disks.forEach(function(d){
|
||||
// Show the path the customer's system actually uses: the STABLE in-guest path
|
||||
// (/mnt/felhom-drives/<name>, = guest_path) for an external drive, not the raw host PVE mount
|
||||
// (/mnt/<name>) which doesn't exist inside the guest. regKey falls back to mount_path for
|
||||
// non-intermediary drives (system/local).
|
||||
var sub = esc(d.type)+' · '+esc(d.backing_device||'—')+(d.mount_path?' · '+esc(regKey(d)):'');
|
||||
var badges = roleBadge(d.role)+appBackingTag(d)+classBadge(d)+dataBadge(d)+regBadge(d,registered);
|
||||
var purpose = purposeDesc(d);
|
||||
html+='<div class="drive-card role-'+esc(d.role||'system')+'">'
|
||||
+'<div class="drive-card-top"><div class="drive-id"><span class="drive-name">'+esc(d.name)+'</span><span class="drive-sub">'+sub+'</span></div>'
|
||||
+'<div class="drive-badges">'+badges+'</div></div>'
|
||||
+(purpose?'<div class="drive-purpose">'+esc(purpose)+'</div>':'')
|
||||
+capBar(d)
|
||||
+actions(d,registered)
|
||||
+'</div>';
|
||||
if(d.role==='user-data'){
|
||||
if(registered[regKey(d)] && enrichCard(d, registered)) return; // one card, enriched in place
|
||||
unregHtml+=driveRow(d, registered, true);
|
||||
}else{
|
||||
sysHtml+=driveRow(d, registered, false); // protected: read-only, lock tag, NO actions
|
||||
}
|
||||
});
|
||||
html+='</div>';
|
||||
box.innerHTML=html;
|
||||
}catch(e){ box.innerHTML='<p class="form-hint">Hiba: '+esc(e.message)+'</p>'; }
|
||||
var sysWrap=document.getElementById('agent-system-wrap');
|
||||
var unregWrap=document.getElementById('agent-unregistered-wrap');
|
||||
if(sysHtml){ document.getElementById('agent-system').innerHTML=sysHtml; sysWrap.hidden=false; }
|
||||
if(unregHtml){ document.getElementById('agent-unregistered').innerHTML=unregHtml; unregWrap.hidden=false; }
|
||||
}catch(e){
|
||||
if(warn) warn.innerHTML='<div class="alert alert-warning" style="margin-top:1rem">Az ügynök nem elérhető — élő meghajtóadatok nélkül.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ---- type-to-confirm modal (destructive user-data actions) ----
|
||||
@@ -524,6 +561,40 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Light overlay dialog (D1) — replaces the native blocking browser dialogs: same texts,
|
||||
// reuses the .confirm-overlay/.confirm-box pattern. typeToConfirm adds the typed-name gate
|
||||
// ONLY where the old flow already had one.
|
||||
function escText(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
|
||||
function closeDialog(){ var r=document.getElementById('dialog-root'); if(r) r.innerHTML=''; }
|
||||
function openDialog(opts){
|
||||
var root=document.getElementById('dialog-root');
|
||||
if(!root) return;
|
||||
var typeGate=opts.typeToConfirm||'';
|
||||
var html='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
|
||||
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>'
|
||||
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>';
|
||||
if(typeGate){
|
||||
html+='<div class="confirm-input"><label>Megerősítéshez írja be: <strong class="mono">'+escText(typeGate)+'</strong></label>'
|
||||
+'<input type="text" id="dialog-type" class="form-control" autocomplete="off" placeholder="'+escText(typeGate)+'"></div>';
|
||||
}
|
||||
html+='<div class="form-actions"><button id="dialog-go" class="btn '+(typeGate?'btn-danger-outline':'btn-primary')+'"'+(typeGate?' disabled':'')+'>'+escText(opts.confirmLabel||'Megerősítés')+'</button>'
|
||||
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>'
|
||||
+'</div></div>';
|
||||
root.innerHTML=html;
|
||||
var go=document.getElementById('dialog-go');
|
||||
if(typeGate){
|
||||
document.getElementById('dialog-type').oninput=function(){ go.disabled=(this.value.trim()!==typeGate); };
|
||||
}
|
||||
go.onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
|
||||
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
|
||||
}
|
||||
// Overlay-confirmed POST form submit (registry drive-remove forms).
|
||||
function storageRemoveDialog(ev, form, msg){
|
||||
ev.preventDefault();
|
||||
openDialog({title:'Meghajtó eltávolítása', message:msg, confirmLabel:'Eltávolítás', onConfirm:function(){ form.submit(); }});
|
||||
return false;
|
||||
}
|
||||
|
||||
function editStorageLabel(path, currentLabel) {
|
||||
var wrap = document.getElementById('label-wrap-' + path);
|
||||
if (!wrap) return;
|
||||
@@ -541,7 +612,9 @@ function storageDisconnect(path, label, appCount) {
|
||||
var msg = 'Biztos leválasztja a meghajtót: ' + label + '?';
|
||||
if (appCount > 0) msg += '\n\nA rajta futó ' + appCount + ' alkalmazás le fog állni.';
|
||||
msg += '\n\nA meghajtó ezután biztonságosan eltávolítható.';
|
||||
if (!confirm(msg)) return;
|
||||
openDialog({title:'Meghajtó leválasztása', message:msg, confirmLabel:'Leválasztás', onConfirm:function(){ doStorageDisconnect(path); }});
|
||||
}
|
||||
function doStorageDisconnect(path) {
|
||||
fetch('/api/storage/disconnect', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
@@ -594,18 +667,19 @@ function storageRestartApps(path) {
|
||||
function storageDecommission(path, label, appCount) {
|
||||
var sel = document.getElementById('migrate-target-' + path);
|
||||
var target = sel ? sel.value : '';
|
||||
var body;
|
||||
if (target) {
|
||||
if (!confirm('Leszerelés áthelyezéssel: minden adat átmásolása ide: ' + target + ', majd a(z) ' + label + ' leszerelése?\n\nAz adatok NEM törlődnek.')) return;
|
||||
body = {where: path, mode: 'migrate', target: target};
|
||||
openDialog({title:'Leszerelés áthelyezéssel', confirmLabel:'Leszerelés',
|
||||
message:'Leszerelés áthelyezéssel: minden adat átmásolása ide: ' + target + ', majd a(z) ' + label + ' leszerelése?\n\nAz adatok NEM törlődnek.',
|
||||
onConfirm:function(){ doStorageDecommission({where: path, mode: 'migrate', target: target}); }});
|
||||
} else {
|
||||
var name = path.split('/').pop();
|
||||
var typed = prompt('A(z) ' + label + ' leszereléséhez (áthelyezés nélkül) írja be a meghajtó nevét megerősítésként:\n\n' + name +
|
||||
(appCount > 0 ? '\n\nFIGYELEM: ' + appCount + ' alkalmazás leáll (az adatok megmaradnak).' : ''));
|
||||
if (typed === null) return;
|
||||
if (typed.trim() !== name) { alert('A név nem egyezik — megszakítva.'); return; }
|
||||
body = {where: path, mode: 'anyway', mount_name: name};
|
||||
openDialog({title:'Meghajtó leszerelése', confirmLabel:'Leszerelés', typeToConfirm:name,
|
||||
message:'A(z) ' + label + ' leszereléséhez (áthelyezés nélkül) írja be a meghajtó nevét megerősítésként.' +
|
||||
(appCount > 0 ? '\n\nFIGYELEM: ' + appCount + ' alkalmazás leáll (az adatok megmaradnak).' : ''),
|
||||
onConfirm:function(){ doStorageDecommission({where: path, mode: 'anyway', mount_name: name}); }});
|
||||
}
|
||||
}
|
||||
function doStorageDecommission(body) {
|
||||
fetch('/api/storage/decommission', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
@@ -618,7 +692,11 @@ function storageDecommission(path, label, appCount) {
|
||||
// H3: one-click re-enroll a decommissioned/ejected drive — clears the marker, re-attaches under the
|
||||
// parent, restarts the gate-stopped apps (data is intact).
|
||||
function storageReEnroll(path, label) {
|
||||
if (!confirm('Visszacsatlakoztatja a(z) ' + label + ' meghajtót?\n\nAz adatok érintetlenek; a leállított alkalmazások újraindulnak.')) return;
|
||||
openDialog({title:'Visszacsatlakoztatás', confirmLabel:'Visszacsatlakoztatás',
|
||||
message:'Visszacsatlakoztatja a(z) ' + label + ' meghajtót?\n\nAz adatok érintetlenek; a leállított alkalmazások újraindulnak.',
|
||||
onConfirm:function(){ doStorageReEnroll(path); }});
|
||||
}
|
||||
function doStorageReEnroll(path) {
|
||||
fetch('/api/storage/reconnect', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
|
||||
Reference in New Issue
Block a user