requirements_view.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. # -*- coding: utf-8 -*-
  2. """
  3. F4: System-Pakete Uebersicht — Tabelle mit Status.
  4. """
  5. from __future__ import annotations
  6. import shutil
  7. import subprocess
  8. from textual.widgets import DataTable, Label, Static
  9. from .base import InstallerView
  10. # Kanonische Paketnamen mit Beschreibung und Typ
  11. _RUNTIME_PACKAGES = [
  12. ("python3", "Python Interpreter", "Runtime"),
  13. ("python3-venv", "Python virtuelle Umgebungen", "Runtime"),
  14. ("python3-pip", "Python Paketmanager", "Runtime"),
  15. ("git", "Versionskontrolle", "Runtime"),
  16. ("curl", "Datentransfer", "Runtime"),
  17. ("wget", "Datei-Download", "Runtime"),
  18. ("alsa-utils", "ALSA Audio-Werkzeuge", "Runtime"),
  19. ("ffmpeg", "Audio/Video Konvertierung", "Runtime"),
  20. ]
  21. _BUILD_PACKAGES = [
  22. ("python3-dev", "Python C-Extension Header", "Build"),
  23. ("portaudio-dev", "Audio-I/O (sounddevice)", "Build"),
  24. ("alsa-dev", "ALSA Header (pyalsaaudio)", "Build"),
  25. ("openblas-dev", "BLAS (numpy/scipy)", "Build"),
  26. ("lapack-dev", "LAPACK (scipy)", "Build"),
  27. ("gfortran", "Fortran Compiler (scipy)", "Build"),
  28. ("ffi-dev", "FFI (cryptography)", "Build"),
  29. ("ssl-dev", "SSL/TLS (cryptography/aiohttp)", "Build"),
  30. ]
  31. _WIFI_PACKAGES = [
  32. ("hostapd", "WiFi Access Point", "WiFi"),
  33. ("dnsmasq", "DHCP/DNS Server", "WiFi"),
  34. ("iptables", "Firewall/NAT", "WiFi"),
  35. ]
  36. def _detect_pm() -> tuple[str, list[str]]:
  37. """Erkennt den Paketmanager und gibt (name, check_cmd) zurueck."""
  38. managers = [
  39. ("apt", ["dpkg", "-s"]),
  40. ("dnf", ["rpm", "-q"]),
  41. ("pacman", ["pacman", "-Q"]),
  42. ("zypper", ["rpm", "-q"]),
  43. ]
  44. for name, check in managers:
  45. # Pruefe ob der Install-Befehl existiert
  46. install_cmd = {"apt": "apt-get", "dnf": "dnf", "pacman": "pacman", "zypper": "zypper"}
  47. if shutil.which(install_cmd.get(name, name)):
  48. return name, check
  49. return "", []
  50. def _resolve_name(canonical: str, pm: str) -> str:
  51. """Loest kanonischen Namen fuer den Paketmanager auf."""
  52. # Importiere die Map aus dem Runner
  53. from ..runner import InstallRunner
  54. mapping = InstallRunner._PKG_MAP.get(canonical, {})
  55. return mapping.get(pm, canonical)
  56. def _check_installed(pkg_name: str, check_cmd: list[str]) -> str:
  57. """Prueft ob ein Paket installiert ist."""
  58. # Zuerst: Kommandozeilen-Tool direkt pruefen
  59. simple = pkg_name.split("-")[0].replace("lib", "").replace("19", "")
  60. if shutil.which(pkg_name) or shutil.which(simple):
  61. return "Installiert"
  62. # python3 spezial
  63. if pkg_name in ("python3", "python3-venv", "python3-pip", "python"):
  64. return "Installiert" if shutil.which("python3") else "Fehlt"
  65. # Paketmanager fragen
  66. if check_cmd:
  67. try:
  68. result = subprocess.run(
  69. [*check_cmd, pkg_name],
  70. capture_output=True, timeout=3,
  71. )
  72. return "Installiert" if result.returncode == 0 else "Fehlt"
  73. except Exception:
  74. pass
  75. return "?"
  76. class RequirementsView(InstallerView):
  77. """System-Pakete Uebersicht (F4)."""
  78. TITLE = "Pakete"
  79. VIEW_ID = "requirements"
  80. MODES = ["server", "client", "standalone", "config"]
  81. def compose(self):
  82. yield Static("── System-Pakete ──", classes="section-header")
  83. yield Label("", id="pkg-pm-info")
  84. yield DataTable(id="pkg-table")
  85. async def on_activate(self) -> None:
  86. """Fuellt die Tabelle beim Aktivieren."""
  87. pm_name, check_cmd = _detect_pm()
  88. # Paketmanager-Info
  89. info = self.query_one("#pkg-pm-info", Label)
  90. if pm_name:
  91. info.update(f"Paketmanager: {pm_name}\n")
  92. else:
  93. info.update("Kein unterstuetzter Paketmanager erkannt\n")
  94. table = self.query_one("#pkg-table", DataTable)
  95. table.clear(columns=True)
  96. table.add_columns("Typ", "Paket", "Zweck", "Status")
  97. # Runtime
  98. for canonical, desc, typ in _RUNTIME_PACKAGES:
  99. resolved = _resolve_name(canonical, pm_name) if pm_name else canonical
  100. status = _check_installed(resolved, check_cmd)
  101. table.add_row(typ, resolved, desc, status)
  102. # Build (nicht fuer config)
  103. if self._config.mode != "config":
  104. for canonical, desc, typ in _BUILD_PACKAGES:
  105. resolved = _resolve_name(canonical, pm_name) if pm_name else canonical
  106. status = _check_installed(resolved, check_cmd)
  107. table.add_row(typ, resolved, desc, status)
  108. # WiFi
  109. if self._config.mode == "server" and self._config.wifi_enabled:
  110. for canonical, desc, typ in _WIFI_PACKAGES:
  111. resolved = _resolve_name(canonical, pm_name) if pm_name else canonical
  112. status = _check_installed(resolved, check_cmd)
  113. table.add_row(typ, resolved, desc, status)