Add web UI reboot/shutdown controls and system action API
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import queue
|
import queue
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from flask import Flask, Response, jsonify, render_template, request, stream_with_context
|
from flask import Flask, Response, jsonify, render_template, request, stream_with_context
|
||||||
@@ -70,6 +72,27 @@ class WebPortal:
|
|||||||
self.network_manager.refresh_state()
|
self.network_manager.refresh_state()
|
||||||
return jsonify({"ok": True, "message": message})
|
return jsonify({"ok": True, "message": message})
|
||||||
|
|
||||||
|
@self.app.route("/api/system/<action>", methods=["POST"])
|
||||||
|
def system_action(action: str) -> Response:
|
||||||
|
commands = {
|
||||||
|
"reboot": ["systemctl", "reboot"],
|
||||||
|
"shutdown": ["systemctl", "poweroff"],
|
||||||
|
}
|
||||||
|
cmd = commands.get(action)
|
||||||
|
if cmd is None:
|
||||||
|
return jsonify({"ok": False, "message": "Unknown action"}), 400
|
||||||
|
|
||||||
|
self.state.update_status(f"System action requested: {action}", "")
|
||||||
|
|
||||||
|
def _exec() -> None:
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, capture_output=True, text=True, timeout=20, check=False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
threading.Thread(target=_exec, daemon=True, name=f"system-{action}").start()
|
||||||
|
return jsonify({"ok": True, "message": f"{action} triggered"})
|
||||||
|
|
||||||
@self.app.route("/events/serial")
|
@self.app.route("/events/serial")
|
||||||
def serial_events() -> Response:
|
def serial_events() -> Response:
|
||||||
@stream_with_context
|
@stream_with_context
|
||||||
|
|||||||
@@ -59,6 +59,20 @@ button:hover {
|
|||||||
filter: brightness(0.95);
|
filter: brightness(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #3b4958;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #b42318;
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -36,6 +36,15 @@
|
|||||||
<h2>Serial Monitor</h2>
|
<h2>Serial Monitor</h2>
|
||||||
<a href="/serial">Zur Live-Serial-Seite</a>
|
<a href="/serial">Zur Live-Serial-Seite</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="card">
|
||||||
|
<h2>System</h2>
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="rebootBtn" class="btn-secondary">Reboot</button>
|
||||||
|
<button id="shutdownBtn" class="btn-danger">Shutdown</button>
|
||||||
|
</div>
|
||||||
|
<div id="systemMsg"></div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -86,7 +95,7 @@
|
|||||||
const msg = document.getElementById('connectMsg');
|
const msg = document.getElementById('connectMsg');
|
||||||
|
|
||||||
if (!ssid) {
|
if (!ssid) {
|
||||||
msg.textContent = 'Bitte SSID wählen.';
|
msg.textContent = 'Bitte SSID waehlen.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +118,32 @@
|
|||||||
setTimeout(refreshStatus, 1000);
|
setTimeout(refreshStatus, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function triggerSystemAction(action) {
|
||||||
|
const msg = document.getElementById('systemMsg');
|
||||||
|
const label = action === 'reboot' ? 'Reboot' : 'Shutdown';
|
||||||
|
const ok = window.confirm(`System wirklich ${label.toLowerCase()} ausfuehren?`);
|
||||||
|
if (!ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.textContent = `${label} wird gestartet...`;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`/api/system/${action}`, {method: 'POST'});
|
||||||
|
const data = await resp.json();
|
||||||
|
if (resp.ok && data.ok) {
|
||||||
|
msg.textContent = `${label} ausgeloest.`;
|
||||||
|
} else {
|
||||||
|
msg.textContent = `Fehler: ${data.message || 'Aktion fehlgeschlagen'}`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
msg.textContent = `${label} fehlgeschlagen.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('scanBtn').addEventListener('click', scan);
|
document.getElementById('scanBtn').addEventListener('click', scan);
|
||||||
document.getElementById('connectBtn').addEventListener('click', connectWifi);
|
document.getElementById('connectBtn').addEventListener('click', connectWifi);
|
||||||
|
document.getElementById('rebootBtn').addEventListener('click', () => triggerSystemAction('reboot'));
|
||||||
|
document.getElementById('shutdownBtn').addEventListener('click', () => triggerSystemAction('shutdown'));
|
||||||
|
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
setInterval(refreshStatus, 5000);
|
setInterval(refreshStatus, 5000);
|
||||||
|
|||||||
Reference in New Issue
Block a user