v0.310.0: „A gép kikapcsolása" — the household turns the whole machine off (R-998); honest restart text (R-999)
gates / gates (push) Successful in 48s

POST /api/server/poweroff (dashboard login, POST only, the typed word KIKAPCSOLÁS / TURN OFF)
→ agent POST /host/poweroff (MinAgent 0.155.0; hidden for an older agent). The controller refuses
during its own backup/restore/app update; the agent's reasons are shown as household sentences.
„Kiszolgáló újraindítása" becomes „A Felhom újraindítása" and says it restarts the Felhom part;
/api/server/reboot is POST only (R-983). Tests red-proofed; parity fixtures regenerated on purpose.
This commit is contained in:
2026-10-13 20:17:30 +02:00
parent 1b2f74c52f
commit 201f936e9a
20 changed files with 743 additions and 41 deletions
@@ -240,10 +240,15 @@ function openDialog(opts){
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
+'<h3>'+escText(opts.title||'{{T "settings_system.megerosites"}}')+'</h3>'
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'{{T "settings_system.megerosites"}}')+'</button>'
// R-998: typeToConfirm — the button stays off until the household types the word (the storage page's pattern).
+(opts.typeToConfirm?'<div class="confirm-input"><label>{{T "settings_system.megerositeshez_ird_be"}} <strong class="mono">'+escText(opts.typeToConfirm)+'</strong></label>'
+'<input type="text" id="dialog-type" class="form-control" autocomplete="off" placeholder="'+escText(opts.typeToConfirm)+'"></div>':'')
+'<div class="form-actions"><button id="dialog-go" class="btn '+(opts.typeToConfirm?'btn-danger-outline':'btn-primary')+'"'+(opts.typeToConfirm?' disabled':'')+'>'+escText(opts.confirmLabel||'{{T "settings_system.megerosites"}}')+'</button>'
+'<button type="button" class="btn btn-outline" id="dialog-cancel">{{T "common.megsem"}}</button></div>'
+'</div></div>';
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
var typed=document.getElementById('dialog-type');
if(typed){ typed.oninput=function(){ document.getElementById('dialog-go').disabled=(this.value.trim().toUpperCase()!==opts.typeToConfirm.toUpperCase()); }; }
document.getElementById('dialog-go').onclick=function(){ var v=typed?typed.value:''; closeDialog(); if(opts.onConfirm) opts.onConfirm(v); };
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
}
@@ -395,6 +400,20 @@ pollUntilBack();
<button type="button" class="btn btn-outline" id="btn-restart-server" onclick="restartServer()">{{T "settings_system.kiszolgalo_ujrainditasa"}}</button>
</div>
<!-- Section: R-998 — „A gép kikapcsolása": the whole machine off (agent POST /host/poweroff, doc 14). Dashboard login
only; the household types the word; the agent may still refuse (its reason is shown). Hidden for an old agent. -->
<div class="settings-card" id="poweroff-card">
<h3>{{T "settings_system.a_gep_kikapcsolasa"}}</h3>
{{if .PowerOffSupported}}
<p class="settings-card-desc">{{T "settings_system.a_gep_teljesen_kikapcsol"}}</p>
<p class="settings-card-desc"><strong>{{T "settings_system.ujra_bekapcsolni_csak_otthon"}}</strong></p>
<div id="poweroff-status"></div>
<button type="button" class="btn btn-danger-outline" id="btn-poweroff" onclick="powerOffMachine()">{{T "settings_system.a_gep_kikapcsolasa"}}</button>
{{else}}
<p class="settings-card-desc" data-poweroff-unavailable>{{T "settings_system.kikapcsolas_frissites_utan"}}</p>
{{end}}
</div>
<script>
function restartController() {
openDialog({title:'{{T "settings_system.vezerlo_ujrainditasa"}}', confirmLabel:'{{T "common.ujrainditas"}}',
@@ -424,6 +443,32 @@ function doRestartServer() {
.then(function(){ pollRestart(0); })
.catch(function(){ pollRestart(0); }); // the guest reboot drops the connection — poll regardless
}
function powerOffMachine() {
var word = '{{T "settings_system.kikapcsolas_szo"}}';
openDialog({title:'{{T "settings_system.a_gep_kikapcsolasa"}}', confirmLabel:'{{T "settings_system.kikapcsolas"}}',
message:'{{T "settings_system.biztosan_kikapcsolod_a_gepet"}}', typeToConfirm: word,
onConfirm:doPowerOffMachine});
}
function doPowerOffMachine(typed) {
var btn = document.getElementById('btn-poweroff');
var status = document.getElementById('poweroff-status');
if (btn) btn.disabled = true;
var h = csrfHeaders(); h['Content-Type'] = 'application/json';
fetch('/api/server/poweroff', { method: 'POST', headers: h, body: JSON.stringify({confirm: typed}) })
.then(function(r){ return r.json().catch(function(){ return {ok:false}; }); })
.then(function(d){
if (d && d.ok) {
status.innerHTML = '<div class="alert alert-info">' + escText('{{T "settings_system.a_gep_kikapcsol_folyamatban"}}') + '</div>';
return; // the box goes down — nothing to poll
}
status.innerHTML = '<div class="alert alert-error">' + escText((d && d.error) || '{{T "common.hiba_tortent"}}') + '</div>';
if (btn) btn.disabled = false;
})
.catch(function(){
status.innerHTML = '<div class="alert alert-error">' + escText('{{T "settings_system.kapcsolodasi_hiba"}}') + '</div>';
if (btn) btn.disabled = false;
});
}
function pollRestart(attempt) {
if (attempt > 60) { // ~2 min cap — never leave the user on a dead page silently
document.getElementById('restart-status').innerHTML =