Add DHCP leases management to dashboard and UI

Implement a new API endpoint to retrieve current DHCP leases from dnsmasq, enhancing the dashboard's functionality for monitoring network devices. Update the home.html template to display DHCP lease information in a structured table format, including IP, MAC, hostname, and expiry details. Introduce buttons for enabling and disabling DHCP network boot, improving user interaction. Enhance JavaScript to fetch and display lease data dynamically, ensuring users have real-time visibility of network activity.
This commit is contained in:
nearxos
2026-02-20 17:30:23 +02:00
parent 7e1bf8a4c2
commit 90296498f5
6 changed files with 158 additions and 7 deletions

View File

@@ -42,6 +42,7 @@ CLOUDINIT_TEMPLATES_FILE = Path(os.environ.get("CM4_CLOUDINIT_TEMPLATES_FILE", s
PORTAL_DESCRIPTIONS_FILE = Path(os.environ.get("CM4_PORTAL_DESCRIPTIONS_FILE", str(BASE_DIR / "portal_descriptions.json")))
DB_PATH = Path(os.environ.get("CM4_DASHBOARD_DB", str(BASE_DIR / "dashboard.db")))
TOGGLE_NETWORK_BOOT_SCRIPT = os.environ.get("CM4_TOGGLE_NETWORK_BOOT_SCRIPT", "/opt/cm4-provisioning/toggle-network-boot-dhcp.sh")
DHCP_LEASES_FILE = os.environ.get("CM4_DHCP_LEASES_FILE", "/var/lib/misc/dnsmasq.leases")
# --- Database (admin users + activity logs) ---
@@ -593,13 +594,46 @@ def api_dhcp_network_boot_post():
@app.route("/api/action-done", methods=["POST"])
def api_action_done():
"""Called by a device when deploy or backup has completed. Disables DHCP network-boot so the device boots from eMMC next time."""
mac = (request.args.get("mac") or request.get_json(silent=True) or {}).get("mac", "")
mac = request.args.get("mac") or ((request.get_json(silent=True) or {}).get("mac") or "")
ok, _ = _dhcp_network_boot_run("disable")
if not ok:
return jsonify({"ok": False, "error": "Could not disable DHCP network boot"}), 500
return jsonify({"ok": True, "message": "Network boot disabled; device will boot from eMMC on next boot"})
def _read_dhcp_leases():
"""Read dnsmasq lease file. Returns (leases_list, error_string). leases_list items: {expiry, mac, ip, hostname}."""
if not DHCP_LEASES_FILE or not os.path.isfile(DHCP_LEASES_FILE):
return [], None
try:
leases = []
with open(DHCP_LEASES_FILE, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if len(parts) >= 4:
leases.append({
"expiry": int(parts[0]) if parts[0].isdigit() else 0,
"mac": parts[1],
"ip": parts[2],
"hostname": parts[3] if len(parts) > 3 else "",
})
return leases, None
except (OSError, PermissionError) as e:
return [], str(e)
@app.route("/api/dhcp-leases")
def api_dhcp_leases():
"""Return current DHCP leases from dnsmasq (when dashboard runs on LXC with dnsmasq)."""
leases, err = _read_dhcp_leases()
if err:
return jsonify({"leases": [], "error": err})
return jsonify({"leases": leases, "error": None})
@app.route("/api/device-action-poll")
def api_device_action_poll():
"""Network device polls this to get its assigned action (deploy/backup) and URL."""