network_view.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # -*- coding: utf-8 -*-
  2. """
  3. F2: Netzwerk-Konfiguration — WiFi-Hotspot, WLAN-Verbindung, Server-Verbindung.
  4. """
  5. from __future__ import annotations
  6. from textual.widgets import Checkbox, Input, Label, RadioButton, RadioSet, Static
  7. from .base import InstallerView
  8. class NetworkView(InstallerView):
  9. """Netzwerk-Einstellungen (F2)."""
  10. TITLE = "Netzwerk"
  11. VIEW_ID = "network"
  12. MODES = ["server", "client", "standalone"]
  13. def compose(self):
  14. if self._config.mode == "server":
  15. yield from self._compose_server()
  16. elif self._config.mode == "client":
  17. yield from self._compose_wifi_connect()
  18. yield from self._compose_client()
  19. elif self._config.mode == "standalone":
  20. yield from self._compose_wifi_connect()
  21. def _compose_server(self):
  22. """Server: WiFi-Hotspot Konfiguration."""
  23. yield Static("── WiFi-Hotspot ──", classes="section-header")
  24. yield Checkbox(
  25. "WiFi-Hotspot aktivieren",
  26. value=self._config.wifi_enabled,
  27. id="cb-wifi-enabled",
  28. )
  29. yield Label("")
  30. yield Label("SSID:")
  31. yield Input(
  32. value=self._config.wifi_ssid,
  33. placeholder="z.B. trixyone_srv",
  34. id="input-wifi-ssid",
  35. )
  36. yield Label("Passwort (min. 8 Zeichen):")
  37. yield Input(
  38. value=self._config.wifi_password,
  39. placeholder="WiFi-Passwort",
  40. id="input-wifi-password",
  41. password=True,
  42. )
  43. yield Label("Kanal (1-13):")
  44. yield Input(
  45. value=str(self._config.wifi_channel),
  46. placeholder="6",
  47. id="input-wifi-channel",
  48. )
  49. yield Label("Access-Point IP:")
  50. yield Input(
  51. value=self._config.ap_ip,
  52. placeholder="10.10.10.1",
  53. id="input-ap-ip",
  54. )
  55. yield Label("DHCP-Bereich Start:")
  56. yield Input(
  57. value=self._config.dhcp_range_start,
  58. placeholder="10.10.10.10",
  59. id="input-dhcp-start",
  60. )
  61. yield Label("DHCP-Bereich Ende:")
  62. yield Input(
  63. value=self._config.dhcp_range_end,
  64. placeholder="10.10.10.50",
  65. id="input-dhcp-end",
  66. )
  67. def _compose_wifi_connect(self):
  68. """Client/Standalone: WLAN-Verbindung konfigurieren."""
  69. yield Static("── WLAN-Verbindung ──", classes="section-header")
  70. yield RadioSet(
  71. RadioButton(
  72. "Keine Aenderung — bestehendes WLAN beibehalten",
  73. value=not self._config.wifi_connect,
  74. id="rb-wifi-keep",
  75. ),
  76. RadioButton(
  77. "WLAN konfigurieren — mit Netzwerk verbinden",
  78. value=self._config.wifi_connect,
  79. id="rb-wifi-connect",
  80. ),
  81. id="rs-wifi-connect",
  82. )
  83. yield Label("")
  84. yield Static("[dim]Primaeres Netz (bevorzugt, z.B. Trixy-Server-Hotspot)[/dim]")
  85. yield Label("SSID:")
  86. yield Input(
  87. value=self._config.wifi_connect_ssid,
  88. placeholder="z.B. trixyone_srv",
  89. id="input-wifi-connect-ssid",
  90. disabled=not self._config.wifi_connect,
  91. )
  92. yield Label("Passwort:")
  93. yield Input(
  94. value=self._config.wifi_connect_password,
  95. placeholder="WLAN-Passwort",
  96. id="input-wifi-connect-password",
  97. password=True,
  98. disabled=not self._config.wifi_connect,
  99. )
  100. yield Label("")
  101. yield Checkbox(
  102. "Fallback-WLAN aktivieren (z.B. Heim-WLAN)",
  103. value=self._config.wifi_fallback_enabled,
  104. id="cb-wifi-fallback",
  105. disabled=not self._config.wifi_connect,
  106. )
  107. yield Static(
  108. "[dim]Wird genutzt wenn das primaere Netz nicht erreichbar ist[/dim]"
  109. )
  110. yield Label("Fallback-SSID:")
  111. yield Input(
  112. value=self._config.wifi_fallback_ssid,
  113. placeholder="z.B. FRITZ!Box 7590",
  114. id="input-wifi-fallback-ssid",
  115. disabled=not (self._config.wifi_connect and self._config.wifi_fallback_enabled),
  116. )
  117. yield Label("Fallback-Passwort:")
  118. yield Input(
  119. value=self._config.wifi_fallback_password,
  120. placeholder="Fallback-WLAN-Passwort",
  121. id="input-wifi-fallback-password",
  122. password=True,
  123. disabled=not (self._config.wifi_connect and self._config.wifi_fallback_enabled),
  124. )
  125. yield Label("")
  126. yield Checkbox(
  127. "Auto-Rueckwechsel zum primaeren Netz",
  128. value=self._config.wifi_auto_switchback,
  129. id="cb-wifi-switchback",
  130. disabled=not (self._config.wifi_connect and self._config.wifi_fallback_enabled),
  131. )
  132. yield Static(
  133. "[dim]Prueft regelmaessig ob primaeres Netz wieder erreichbar ist[/dim]"
  134. )
  135. yield Label("Pruef-Intervall (Sekunden):")
  136. yield Input(
  137. value=str(self._config.wifi_switchback_interval),
  138. placeholder="30",
  139. id="input-wifi-switchback-interval",
  140. disabled=not (
  141. self._config.wifi_connect
  142. and self._config.wifi_fallback_enabled
  143. and self._config.wifi_auto_switchback
  144. ),
  145. )
  146. yield Label("")
  147. def _compose_client(self):
  148. """Client: Server-Verbindung."""
  149. yield Static("── Server-Verbindung ──", classes="section-header")
  150. yield Label("Server-Host:")
  151. yield Input(
  152. value=self._config.server_host,
  153. placeholder="z.B. 10.10.10.1",
  154. id="input-server-host",
  155. )
  156. yield Label("Server-Port:")
  157. yield Input(
  158. value=str(self._config.server_port),
  159. placeholder="2101",
  160. id="input-server-port",
  161. )
  162. yield Label("Raum:")
  163. yield Input(
  164. value=self._config.room,
  165. placeholder="z.B. Wohnzimmer",
  166. id="input-room",
  167. )
  168. yield Label("Alias:")
  169. yield Input(
  170. value=self._config.alias,
  171. placeholder="z.B. Patrick",
  172. id="input-alias",
  173. )
  174. def _update_wifi_field_states(self) -> None:
  175. """Aktualisiert disabled-Status der WLAN-Eingabefelder."""
  176. connect = self._config.wifi_connect
  177. fallback = self._config.wifi_fallback_enabled
  178. switchback = self._config.wifi_auto_switchback
  179. field_states = {
  180. "#input-wifi-connect-ssid": not connect,
  181. "#input-wifi-connect-password": not connect,
  182. "#cb-wifi-fallback": not connect,
  183. "#input-wifi-fallback-ssid": not (connect and fallback),
  184. "#input-wifi-fallback-password": not (connect and fallback),
  185. "#cb-wifi-switchback": not (connect and fallback),
  186. "#input-wifi-switchback-interval": not (connect and fallback and switchback),
  187. }
  188. for selector, disabled in field_states.items():
  189. try:
  190. widget = self.query_one(selector)
  191. widget.disabled = disabled
  192. except Exception:
  193. pass
  194. def on_radio_set_changed(self, event: RadioSet.Changed) -> None:
  195. """WLAN-Modus umschalten."""
  196. if event.radio_set.id == "rs-wifi-connect":
  197. pressed_id = event.pressed.id if event.pressed else ""
  198. self._config.wifi_connect = pressed_id == "rb-wifi-connect"
  199. self._update_wifi_field_states()
  200. def on_input_changed(self, event: Input.Changed) -> None:
  201. """Aktualisiert Konfig bei Eingabeaenderungen."""
  202. mapping = {
  203. "input-wifi-ssid": "wifi_ssid",
  204. "input-wifi-password": "wifi_password",
  205. "input-ap-ip": "ap_ip",
  206. "input-dhcp-start": "dhcp_range_start",
  207. "input-dhcp-end": "dhcp_range_end",
  208. "input-wifi-connect-ssid": "wifi_connect_ssid",
  209. "input-wifi-connect-password": "wifi_connect_password",
  210. "input-wifi-fallback-ssid": "wifi_fallback_ssid",
  211. "input-wifi-fallback-password": "wifi_fallback_password",
  212. "input-server-host": "server_host",
  213. "input-room": "room",
  214. "input-alias": "alias",
  215. }
  216. attr = mapping.get(event.input.id or "")
  217. if attr:
  218. setattr(self._config, attr, event.value)
  219. elif event.input.id == "input-wifi-channel":
  220. try:
  221. self._config.wifi_channel = int(event.value)
  222. except ValueError:
  223. pass
  224. elif event.input.id == "input-server-port":
  225. try:
  226. self._config.server_port = int(event.value)
  227. except ValueError:
  228. pass
  229. elif event.input.id == "input-wifi-switchback-interval":
  230. try:
  231. self._config.wifi_switchback_interval = int(event.value)
  232. except ValueError:
  233. pass
  234. def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
  235. cb_id = event.checkbox.id or ""
  236. if cb_id == "cb-wifi-enabled":
  237. self._config.wifi_enabled = event.value
  238. elif cb_id == "cb-wifi-fallback":
  239. self._config.wifi_fallback_enabled = event.value
  240. self._update_wifi_field_states()
  241. elif cb_id == "cb-wifi-switchback":
  242. self._config.wifi_auto_switchback = event.value
  243. self._update_wifi_field_states()
  244. def validate(self) -> str | None:
  245. if self._config.mode == "server" and self._config.wifi_enabled:
  246. if not self._config.wifi_ssid.strip():
  247. return "WiFi SSID darf nicht leer sein"
  248. if len(self._config.wifi_password) < 8:
  249. return "WiFi Passwort muss mindestens 8 Zeichen haben"
  250. elif self._config.mode == "client":
  251. if not self._config.server_host.strip():
  252. return "Server-Host darf nicht leer sein"
  253. if not self._config.room.strip():
  254. return "Raum darf nicht leer sein"
  255. if not self._config.alias.strip():
  256. return "Alias darf nicht leer sein"
  257. # WLAN-Validierung (Client/Standalone)
  258. if self._config.wifi_connect:
  259. if not self._config.wifi_connect_ssid.strip():
  260. return "Primaere WLAN-SSID darf nicht leer sein"
  261. if len(self._config.wifi_connect_password) < 8:
  262. return "Primaeres WLAN-Passwort muss mindestens 8 Zeichen haben"
  263. if self._config.wifi_fallback_enabled:
  264. if not self._config.wifi_fallback_ssid.strip():
  265. return "Fallback-WLAN-SSID darf nicht leer sein"
  266. if len(self._config.wifi_fallback_password) < 8:
  267. return "Fallback-WLAN-Passwort muss mindestens 8 Zeichen haben"
  268. if self._config.wifi_fallback_ssid.strip() == self._config.wifi_connect_ssid.strip():
  269. return "Fallback- und primaere SSID muessen unterschiedlich sein"
  270. if self._config.wifi_switchback_interval < 10:
  271. return "Switchback-Intervall muss mindestens 10 Sekunden sein"
  272. return None