Rename the Python package to daynivo_kiosk
src/voxip_kiosk -> src/daynivo_kiosk, with every import, the launchers, install.sh, the Makefile and the manager's gunicorn app path. An upgrade replaces /usr/lib/voxip-kiosk/python as a whole, so the old package disappears; tested by upgrading an installation of the previous commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d0f3206ff4
commit
6733aacb6e
62 files changed
+43
-43
No files matched your search
@@ -0,0 +1,127 @@
|
||||
"""Network status for the web interface (read-only, no privileges needed)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .netconfig import INTERFACES, profile_name, split_terse
|
||||
|
||||
STATE_LABELS = {
|
||||
"connected": "Verbonden",
|
||||
"connecting": "Bezig met verbinden",
|
||||
"disconnected": "Niet verbonden",
|
||||
"unavailable": "Niet beschikbaar",
|
||||
"unmanaged": "Niet beheerd",
|
||||
}
|
||||
|
||||
|
||||
def _nmcli(*args: str) -> str | None:
|
||||
if not shutil.which("nmcli"):
|
||||
return None
|
||||
try:
|
||||
proc = subprocess.run(["nmcli", *args], capture_output=True, text=True, timeout=10)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
return proc.stdout if proc.returncode == 0 else None
|
||||
|
||||
|
||||
def device_details(device: str) -> dict[str, Any]:
|
||||
out = _nmcli("-t", "device", "show", device)
|
||||
info: dict[str, Any] = {"addresses": [], "dns": [], "gateway": "", "hwaddr": "", "state": "", "connection": ""}
|
||||
if not out:
|
||||
return info
|
||||
for line in out.splitlines():
|
||||
key, _, value = line.partition(":")
|
||||
value = value.replace("\\:", ":")
|
||||
base = key.split("[", 1)[0]
|
||||
if base == "IP4.ADDRESS" and value:
|
||||
info["addresses"].append(value)
|
||||
elif base == "IP4.DNS" and value:
|
||||
info["dns"].append(value)
|
||||
elif base == "IP4.GATEWAY" and value and value != "--":
|
||||
info["gateway"] = value
|
||||
elif base == "GENERAL.HWADDR":
|
||||
info["hwaddr"] = value
|
||||
elif base == "GENERAL.STATE":
|
||||
info["state"] = re.sub(r"^\d+\s*", "", value).strip("() ")
|
||||
elif base == "GENERAL.CONNECTION" and value != "--":
|
||||
info["connection"] = value
|
||||
return info
|
||||
|
||||
|
||||
def profile_settings(interface: str) -> dict[str, Any]:
|
||||
"""Current settings of our own profile, used to pre-fill the forms."""
|
||||
out = _nmcli("-t", "-f", "ipv4.method,ipv4.addresses,ipv4.gateway,ipv4.dns,802-11-wireless.ssid,802-11-wireless.hidden",
|
||||
"connection", "show", profile_name(interface))
|
||||
settings = {"exists": False, "method": "dhcp", "address": "", "gateway": "", "dns": "", "ssid": "", "hidden": False}
|
||||
if not out:
|
||||
return settings
|
||||
settings["exists"] = True
|
||||
for line in out.splitlines():
|
||||
key, _, value = line.partition(":")
|
||||
value = value.replace("\\:", ":")
|
||||
if value == "--":
|
||||
value = ""
|
||||
if key == "ipv4.method":
|
||||
settings["method"] = "static" if value == "manual" else "dhcp"
|
||||
elif key == "ipv4.addresses":
|
||||
settings["address"] = value.split(",")[0].strip()
|
||||
elif key == "ipv4.gateway":
|
||||
settings["gateway"] = value
|
||||
elif key == "ipv4.dns":
|
||||
settings["dns"] = ", ".join(x for x in value.split(",") if x)
|
||||
elif key == "802-11-wireless.ssid":
|
||||
settings["ssid"] = value
|
||||
elif key == "802-11-wireless.hidden":
|
||||
settings["hidden"] = value == "yes"
|
||||
return settings
|
||||
|
||||
|
||||
def overview() -> dict[str, Any]:
|
||||
available = shutil.which("nmcli") is not None
|
||||
devices = {}
|
||||
status = _nmcli("-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device", "status") or ""
|
||||
present = {split_terse(line)[0]: split_terse(line) for line in status.splitlines() if line}
|
||||
for iface, kind in INTERFACES.items():
|
||||
fields = present.get(iface)
|
||||
details = device_details(iface) if fields else {}
|
||||
state = fields[2] if fields and len(fields) > 2 else ""
|
||||
devices[iface] = {
|
||||
"interface": iface,
|
||||
"kind": kind,
|
||||
"present": fields is not None,
|
||||
"state": state,
|
||||
"state_label": STATE_LABELS.get(state, state or "Niet aanwezig"),
|
||||
"connected": state == "connected",
|
||||
"connection": fields[3] if fields and len(fields) > 3 and fields[3] != "--" else "",
|
||||
"addresses": details.get("addresses", []),
|
||||
"gateway": details.get("gateway", ""),
|
||||
"dns": details.get("dns", []),
|
||||
"hwaddr": details.get("hwaddr", ""),
|
||||
"profile": profile_settings(iface) if available else {},
|
||||
}
|
||||
return {"available": available, "devices": devices, "wifi_country": wifi_country()}
|
||||
|
||||
|
||||
def wifi_country() -> str:
|
||||
if not shutil.which("iw"):
|
||||
return ""
|
||||
try:
|
||||
out = subprocess.run(["iw", "reg", "get"], capture_output=True, text=True, timeout=5).stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return ""
|
||||
match = re.search(r"country ([A-Z0-9]{2}):", out)
|
||||
return match.group(1) if match and match.group(1) != "00" else ""
|
||||
|
||||
|
||||
def pending_change(pending_file: Path) -> dict[str, Any] | None:
|
||||
try:
|
||||
data = json.loads(pending_file.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
return data if isinstance(data, dict) else None
|
||||
Reference in new issue
Block a user