Add web UI reboot/shutdown controls and system action API
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import json
|
||||
import queue
|
||||
import subprocess
|
||||
import threading
|
||||
from typing import Any, Dict
|
||||
|
||||
from flask import Flask, Response, jsonify, render_template, request, stream_with_context
|
||||
@@ -70,6 +72,27 @@ class WebPortal:
|
||||
self.network_manager.refresh_state()
|
||||
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")
|
||||
def serial_events() -> Response:
|
||||
@stream_with_context
|
||||
|
||||
@@ -59,6 +59,20 @@ button:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #3b4958;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #b42318;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
font-size: 14px;
|
||||
|
||||
@@ -36,6 +36,15 @@
|
||||
<h2>Serial Monitor</h2>
|
||||
<a href="/serial">Zur Live-Serial-Seite</a>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
@@ -86,7 +95,7 @@
|
||||
const msg = document.getElementById('connectMsg');
|
||||
|
||||
if (!ssid) {
|
||||
msg.textContent = 'Bitte SSID wählen.';
|
||||
msg.textContent = 'Bitte SSID waehlen.';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,8 +118,32 @@
|
||||
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('connectBtn').addEventListener('click', connectWifi);
|
||||
document.getElementById('rebootBtn').addEventListener('click', () => triggerSystemAction('reboot'));
|
||||
document.getElementById('shutdownBtn').addEventListener('click', () => triggerSystemAction('shutdown'));
|
||||
|
||||
refreshStatus();
|
||||
setInterval(refreshStatus, 5000);
|
||||
|
||||
Reference in New Issue
Block a user