noVNC bridge: per-device live screens in dashboard (websockify + TokenFile + VNC button)
This commit is contained in:
41
app.py
41
app.py
@@ -691,6 +691,45 @@ def api_shot_latest(mac):
|
||||
return jsonify({"error": "no screenshot yet"}), 404
|
||||
return send_file(path, mimetype="image/png")
|
||||
|
||||
VNC_TOKEN_FILE = "/opt/android-fleet/vnc_tokens.json"
|
||||
|
||||
def regen_vnc_tokens():
|
||||
"""Map each online device MAC -> its QEMU VNC port for websockify TokenFile."""
|
||||
try:
|
||||
con = db()
|
||||
rows = con.execute("SELECT mac, vmid FROM devices WHERE vmid IS NOT NULL").fetchall()
|
||||
con.close()
|
||||
lines = []
|
||||
for r in rows:
|
||||
got = pve("GET", f"/nodes/pve/qemu/{r['vmid']}/config") or {}
|
||||
cfg = (got.get("data") or {}) or {}
|
||||
args = cfg.get("args", "") or ""
|
||||
m = re.search(r"-vnc 0\.0\.0\.0:(\d{4})", args)
|
||||
if m:
|
||||
port = int(m.group(1)) + 5900 # QEMU: -vnc :N listens on N+5900
|
||||
lines.append(f"{r['mac']}: 10.30.20.85:{port}")
|
||||
with open(VNC_TOKEN_FILE, "w") as f:
|
||||
f.write("\n".join(lines) + ("\n" if lines else ""))
|
||||
except Exception as e:
|
||||
log("WARN", f"vnc token regen: {e}")
|
||||
|
||||
def vnc_loop():
|
||||
while True:
|
||||
regen_vnc_tokens()
|
||||
time.sleep(60)
|
||||
|
||||
@app.route("/api/device/<mac>/vnc")
|
||||
def api_vnc(mac):
|
||||
regen_vnc_tokens()
|
||||
try:
|
||||
tokens = open(VNC_TOKEN_FILE).read()
|
||||
except FileNotFoundError:
|
||||
tokens = ""
|
||||
if mac not in tokens:
|
||||
return jsonify({"error": "device has no VNC mapping (needs vmid + -vnc args)"}), 404
|
||||
return jsonify({"url": (f"http://10.30.20.31:8081/vnc.html?autoconnect=true"
|
||||
f"&reconnect=true&resize=scale&path=websockify?token={mac}")})
|
||||
|
||||
@app.route("/api/device/<mac>", methods=["DELETE"])
|
||||
def api_delete_device(mac):
|
||||
"""Remove a device row (and release its proxy)."""
|
||||
@@ -841,7 +880,7 @@ if __name__ == "__main__":
|
||||
subprocess.run(["adb", "start-server"], capture_output=True, timeout=10)
|
||||
except Exception:
|
||||
pass
|
||||
for fn in (discovery_loop, adb_loop, proxy_loop, ws_broadcast_loop, screenshot_loop):
|
||||
for fn in (discovery_loop, adb_loop, proxy_loop, ws_broadcast_loop, screenshot_loop, vnc_loop):
|
||||
threading.Thread(target=fn, daemon=True).start()
|
||||
log("STARTUP", f"godhead up — template {TEMPLATE}, LAN {LAN}")
|
||||
app.run(host="0.0.0.0", port=8080, threaded=True)
|
||||
|
||||
Reference in New Issue
Block a user