controller: drill F-11 — inline two-step confirm (felhomConfirm + data-confirm) replaces every native confirm(); native_confirm_gate.py enforces zero; stale-data delete keeps its double acknowledgement inline
Claude-Session: https://claude.ai/code/session_01GzammAMzsJTgpQHqxwM2bC
This commit is contained in:
@@ -71,7 +71,7 @@
|
||||
<option value="">Válassz céltárhelyet…</option>
|
||||
{{range .MigrateTargets}}<option value="{{.Path}}">{{.Label}} ({{.Path}})</option>{{end}}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate('{{.Stack.Name}}','{{.Meta.DisplayName}}')">Áthelyezés</button>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate(this,'{{.Stack.Name}}','{{.Meta.DisplayName}}')">Áthelyezés</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
@@ -98,14 +98,15 @@ function appMigWatch(){
|
||||
}
|
||||
tick();
|
||||
}
|
||||
function appMigrate(app,label){
|
||||
function appMigrate(btn,app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ alert('Válassz céltárhelyet.'); return; }
|
||||
if(!confirm('Áthelyezed a(z) '+label+' adatait ide: '+target+'?\n\nAz alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.')) return;
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ alert('Hiba: '+e); });
|
||||
felhomConfirm(btn,'Áthelyezed a(z) '+label+' adatait ide: '+target+'? Az alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.',function(){
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ alert('Hiba: '+e); });
|
||||
});
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
</script>
|
||||
|
||||
@@ -182,9 +182,9 @@
|
||||
<button type="submit" class="btn btn-xs {{if .Enabled}}btn-outline{{else}}btn-primary{{end}}">{{if .Enabled}}NAS-mentés kikapcsolása{{else}}NAS-mentés bekapcsolása{{end}}</button>
|
||||
</form>
|
||||
{{if .Enabled}}
|
||||
<form method="POST" action="/backup/offbox/restore" style="display:inline" onsubmit="return confirm('Visszaállítja a(z) {{.DisplayName}} adatait a NAS-ról egy ellenőrző mappába? A meglévő adatok NEM íródnak felül.')">{{$.CSRFField}}
|
||||
<form method="POST" action="/backup/offbox/restore" style="display:inline">{{$.CSRFField}}
|
||||
<input type="hidden" name="app" value="{{.Name}}">
|
||||
<button type="submit" class="btn btn-xs btn-outline">Visszaállítás (ellenőrzéshez)</button>
|
||||
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a(z) {{.DisplayName}} adatait a távoli tárolóról egy ellenőrző mappába? A meglévő adatok NEM íródnak felül.">Visszaállítás (ellenőrzéshez)</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
@@ -469,9 +469,9 @@
|
||||
<span class="tier-browsable" title="A mentés böngészhető fájlrendszerben"><svg class="ico ico-sm"><use href="#i-file-text"/></svg></span>
|
||||
<div class="layer-actions">
|
||||
{{if .Tier2LastRun}}
|
||||
<form method="POST" action="/backup/tier2/restore" style="display:inline" onsubmit="return confirm('Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: {{.Tier2LastRun}}')">{{$.CSRFField}}
|
||||
<form method="POST" action="/backup/tier2/restore" style="display:inline">{{$.CSRFField}}
|
||||
<input type="hidden" name="stack_name" value="{{.StackName}}">
|
||||
<button type="submit" class="btn btn-xs btn-outline">Fájlok visszaállítása</button>
|
||||
<button type="submit" class="btn btn-xs btn-outline" data-confirm="Visszaállítja a hiányzó fájlokat a másodlagos másolatból? A meglévő fájlok NEM módosulnak és NEM törlődnek. Az alkalmazás a művelet idejére leáll. Legutóbbi másolat: {{.Tier2LastRun}}">Fájlok visszaállítása</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<a href="/stacks/{{.StackName}}/backup" class="btn btn-xs btn-outline">Beállítás</a>
|
||||
@@ -673,19 +673,20 @@ function startBackupPolling() {
|
||||
// Whole-guest (appliance) backup — manual trigger via the quiesce loop (controller-owned quiesce:
|
||||
// stop stacks → agent vzdump → resume). Returns immediately; we poll /api/guest-backup/status.
|
||||
function triggerGuestBackup() {
|
||||
if (!confirm('Elindítja a teljes rendszermentést most? A mentés ideje alatt az alkalmazások rövid időre leállhatnak.')) return;
|
||||
var btn = document.getElementById('wg-backup-btn');
|
||||
var out = document.getElementById('wg-backup-result');
|
||||
btn.disabled = true;
|
||||
out.innerHTML = '<span class="form-hint">Mentés indítása…</span>';
|
||||
fetch('/api/guest-backup/trigger', { method: 'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()) })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) { out.innerHTML = '<div class="alert alert-error">' + (data.error || 'Hiba') + '</div>'; btn.disabled = false; return; }
|
||||
out.innerHTML = '<span class="form-hint">Mentés folyamatban…</span>';
|
||||
pollGuestBackup(out, btn);
|
||||
})
|
||||
.catch(e => { out.innerHTML = '<div class="alert alert-error">Hiba: ' + e + '</div>'; btn.disabled = false; });
|
||||
felhomConfirm(btn, 'Elindítja a teljes rendszermentést most? A mentés ideje alatt az alkalmazások rövid időre leállhatnak.', function () {
|
||||
var out = document.getElementById('wg-backup-result');
|
||||
btn.disabled = true;
|
||||
out.innerHTML = '<span class="form-hint">Mentés indítása…</span>';
|
||||
fetch('/api/guest-backup/trigger', { method: 'POST', headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()) })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (!data.ok) { out.innerHTML = '<div class="alert alert-error">' + (data.error || 'Hiba') + '</div>'; btn.disabled = false; return; }
|
||||
out.innerHTML = '<span class="form-hint">Mentés folyamatban…</span>';
|
||||
pollGuestBackup(out, btn);
|
||||
})
|
||||
.catch(e => { out.innerHTML = '<div class="alert alert-error">Hiba: ' + e + '</div>'; btn.disabled = false; });
|
||||
});
|
||||
}
|
||||
|
||||
function pollGuestBackup(out, btn) {
|
||||
|
||||
@@ -457,7 +457,7 @@ function renderWatchdogStatus(paths) {
|
||||
var simBadge = p.simulated ? ' <span class="badge-warn">SIM</span>' : '';
|
||||
var action = '';
|
||||
if (p.status === 'connected' && !p.simulated) {
|
||||
action = '<button class="btn btn-xs btn-secondary" onclick="simulateDisconnect(\'' + p.path + '\')">Leválasztás</button>';
|
||||
action = '<button class="btn btn-xs btn-secondary" onclick="simulateDisconnect(this,\'' + p.path + '\')">Leválasztás</button>';
|
||||
} else if (p.simulated) {
|
||||
action = '<button class="btn btn-xs btn-primary" onclick="simulateReconnect(\'' + p.path + '\')">Visszacsatl.</button>';
|
||||
}
|
||||
@@ -468,16 +468,17 @@ function renderWatchdogStatus(paths) {
|
||||
html += '</table>';
|
||||
document.getElementById('watchdog-status').innerHTML = html;
|
||||
}
|
||||
function simulateDisconnect(path) {
|
||||
if (!confirm('Biztosan szimulálja a leválasztást?\n\nEz leállítja az érintett alkalmazásokat.')) return;
|
||||
fetch('/api/debug/storage/simulate-disconnect', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({path: path})
|
||||
}).then(function(r){return r.json()}).then(function(data) {
|
||||
if (!data.ok) alert('Hiba: ' + (data.error || 'ismeretlen'));
|
||||
loadWatchdogStatus();
|
||||
}).catch(function(e) { alert('Hiba: ' + e.message); });
|
||||
function simulateDisconnect(btn, path) {
|
||||
felhomConfirm(btn, 'Biztosan szimulálja a leválasztást? Ez leállítja az érintett alkalmazásokat.', function () {
|
||||
fetch('/api/debug/storage/simulate-disconnect', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({path: path})
|
||||
}).then(function(r){return r.json()}).then(function(data) {
|
||||
if (!data.ok) alert('Hiba: ' + (data.error || 'ismeretlen'));
|
||||
loadWatchdogStatus();
|
||||
}).catch(function(e) { alert('Hiba: ' + e.message); });
|
||||
});
|
||||
}
|
||||
function simulateReconnect(path) {
|
||||
fetch('/api/debug/storage/simulate-reconnect', {
|
||||
@@ -560,30 +561,31 @@ function triggerDR() {
|
||||
result.textContent = 'Írja be: RESET';
|
||||
return;
|
||||
}
|
||||
if (!confirm('FIGYELEM! Ez újraindítja a controllert setup módba.\n\nBiztosan folytatja?')) return;
|
||||
var btn = document.getElementById('btn-dr-trigger');
|
||||
var result = document.getElementById('btn-dr-trigger-result');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
fetch('/api/debug/dr/trigger-setup', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({confirm: 'RESET'})
|
||||
}).then(function(r){return r.json()}).then(function(data) {
|
||||
if (data.ok) {
|
||||
result.className = 'debug-result debug-result-ok';
|
||||
result.textContent = 'Controller újraindulása...';
|
||||
} else {
|
||||
felhomConfirm(btn, 'FIGYELEM! Ez újraindítja a controllert setup módba. Biztosan folytatja?', function () {
|
||||
var result = document.getElementById('btn-dr-trigger-result');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
fetch('/api/debug/dr/trigger-setup', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type':'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({confirm: 'RESET'})
|
||||
}).then(function(r){return r.json()}).then(function(data) {
|
||||
if (data.ok) {
|
||||
result.className = 'debug-result debug-result-ok';
|
||||
result.textContent = 'Controller újraindulása...';
|
||||
} else {
|
||||
result.className = 'debug-result debug-result-error';
|
||||
result.textContent = data.error || 'Hiba';
|
||||
btn.disabled = false;
|
||||
btn.textContent = btn.dataset.label;
|
||||
}
|
||||
}).catch(function(e) {
|
||||
result.className = 'debug-result debug-result-error';
|
||||
result.textContent = data.error || 'Hiba';
|
||||
result.textContent = 'Hiba: ' + e.message;
|
||||
btn.disabled = false;
|
||||
btn.textContent = btn.dataset.label;
|
||||
}
|
||||
}).catch(function(e) {
|
||||
result.className = 'debug-result debug-result-error';
|
||||
result.textContent = 'Hiba: ' + e.message;
|
||||
btn.disabled = false;
|
||||
btn.textContent = btn.dataset.label;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -793,14 +793,15 @@ function copyPostDeployCred(btn, val) {
|
||||
}
|
||||
|
||||
function deleteStaleData(stackName, stalePath, btn) {
|
||||
if (!confirm('Biztosan törölni szeretnéd a korábbi adatokat?\n\nTárhely: ' + stalePath + '\n\nFIGYELEM: Ez a művelet visszavonhatatlan!\nElőtte győződj meg róla, hogy az alkalmazás az új tárolóról megfelelően működik.')) {
|
||||
return;
|
||||
}
|
||||
// Second confirmation
|
||||
if (!confirm('UTOLSÓ FIGYELMEZTETÉS!\n\nA törlés visszavonhatatlan. Biztosan folytatod?')) {
|
||||
return;
|
||||
}
|
||||
// Two-step inline confirm (the irreversible delete keeps its double acknowledgement).
|
||||
felhomConfirm(btn, 'Biztosan törölni szeretnéd a korábbi adatokat? Tárhely: ' + stalePath + ' — FIGYELEM: a művelet visszavonhatatlan! Előtte győződj meg róla, hogy az alkalmazás az új tárolóról megfelelően működik.', function () {
|
||||
felhomConfirm(btn, 'UTOLSÓ FIGYELMEZTETÉS! A törlés visszavonhatatlan. Biztosan folytatod?', function () {
|
||||
deleteStaleDataRun(stackName, stalePath, btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteStaleDataRun(stackName, stalePath, btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
|
||||
|
||||
@@ -10,6 +10,33 @@
|
||||
<meta name="csrf-token" content="{{.CSRFToken}}">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
// Inline two-step confirm (drill F-11): the trigger swaps IN PLACE to "<question> Igen/Mégse".
|
||||
// Never native confirm() — it is an OS-modal that blocks browser automation — and no overlay:
|
||||
// this is the LIGHT pattern for one-click consequential actions. Form buttons opt in with
|
||||
// data-confirm="…" (submitted via requestSubmit so formaction/name-value are honored);
|
||||
// JS flows call felhomConfirm(el, question, onYes) directly.
|
||||
function felhomConfirm(el,question,onYes){
|
||||
if(!el||el.dataset.fcOpen)return;
|
||||
el.dataset.fcOpen='1';
|
||||
var wrap=document.createElement('span');wrap.className='inline-confirm';
|
||||
var q=document.createElement('span');q.className='inline-confirm-q';q.textContent=question;
|
||||
var yes=document.createElement('button');yes.type='button';yes.className='btn btn-xs btn-danger';yes.textContent='Igen';
|
||||
var no=document.createElement('button');no.type='button';no.className='btn btn-xs btn-outline';no.textContent='Mégse';
|
||||
wrap.appendChild(q);wrap.appendChild(yes);wrap.appendChild(no);
|
||||
el.style.display='none';el.parentNode.insertBefore(wrap,el.nextSibling);
|
||||
function close(){wrap.remove();el.style.display='';delete el.dataset.fcOpen;}
|
||||
no.addEventListener('click',close);
|
||||
yes.addEventListener('click',function(){close();onYes();});
|
||||
}
|
||||
document.addEventListener('click',function(e){
|
||||
var btn=e.target.closest?e.target.closest('[data-confirm]'):null;
|
||||
if(!btn)return;
|
||||
e.preventDefault();
|
||||
felhomConfirm(btn,btn.getAttribute('data-confirm'),function(){
|
||||
var form=btn.closest('form');
|
||||
if(form){if(form.requestSubmit)form.requestSubmit(btn);else form.submit();}
|
||||
});
|
||||
});
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -3270,3 +3270,7 @@ span.mono, .mono { font-family: var(--font-data); }
|
||||
.drive-agent-extra .drive-actions {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Inline two-step confirm (drill F-11) — the in-place replacement for native confirm() */
|
||||
.inline-confirm { display: inline-flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
|
||||
.inline-confirm-q { font-size: .8rem; color: var(--warn); }
|
||||
|
||||
Reference in New Issue
Block a user