app.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. # -*- coding: utf-8 -*-
  2. """
  3. InstallerApp — Haupteinstieg der Trixy Installer TUI.
  4. Gestartet via: python app.py --mode server --version 1.0.0 --source-archive pfad.tar.gz
  5. """
  6. from __future__ import annotations
  7. import argparse
  8. import sys
  9. from pathlib import Path
  10. from textual.app import App, ComposeResult
  11. from textual.binding import Binding
  12. from .config import InstallerConfig
  13. from .menu_context import MenuBinding, MenuContext
  14. from .widgets.header import InstallerHeader
  15. from .widgets.footer import InstallerFooter
  16. from .views.base import InstallerView
  17. from .views.system_view import SystemView
  18. from .views.network_view import NetworkView
  19. from .views.plugins_view import PluginsView
  20. from .views.requirements_view import RequirementsView
  21. from .views.agreement_view import AgreementView
  22. from .views.install_view import InstallView
  23. class InstallerApp(App):
  24. """Trixy Installer TUI-Anwendung."""
  25. CSS_PATH = "css/installer.tcss"
  26. TITLE = "Trixy Installer"
  27. BINDINGS = [
  28. Binding("f1", "fkey('f1')", "F1", show=False),
  29. Binding("f2", "fkey('f2')", "F2", show=False),
  30. Binding("f3", "fkey('f3')", "F3", show=False),
  31. Binding("f4", "fkey('f4')", "F4", show=False),
  32. Binding("f5", "fkey('f5')", "F5", show=False),
  33. Binding("f6", "fkey('f6')", "F6", show=False),
  34. Binding("q", "quit", "Beenden", show=False),
  35. ]
  36. def __init__(self, config: InstallerConfig, existing: "ExistingInstallation | None" = None) -> None:
  37. super().__init__()
  38. self._config = config
  39. self._existing = existing
  40. self._views: list[InstallerView] = []
  41. self._view_map: dict[str, InstallerView] = {}
  42. self._active_view: InstallerView | None = None
  43. self._menu = MenuContext()
  44. self._fkey_map: dict[str, str] = {}
  45. def compose(self) -> ComposeResult:
  46. yield InstallerHeader(
  47. version=self._config.version,
  48. mode=self._config.mode,
  49. id="header",
  50. )
  51. # Views erstellen — nur fuer den aktuellen Modus
  52. mode = self._config.mode
  53. all_views: list[InstallerView] = [
  54. SystemView(self._config, id="view-system"),
  55. NetworkView(self._config, id="view-network"),
  56. PluginsView(self._config, id="view-plugins"),
  57. RequirementsView(self._config, id="view-requirements"),
  58. AgreementView(self._config, id="view-agreement"),
  59. InstallView(self._config, existing=self._existing, id="view-install"),
  60. ]
  61. # Tab-Definitionen: (f-key, view)
  62. tab_defs = [
  63. ("f1", all_views[0]), # System
  64. ("f2", all_views[1]), # Netzwerk
  65. ("f3", all_views[2]), # Plugins
  66. ("f4", all_views[3]), # Pakete
  67. ("f5", all_views[4]), # Vereinbarung
  68. ("f6", all_views[5]), # Installation
  69. ]
  70. for fkey, view in tab_defs:
  71. if mode in view.MODES:
  72. self._views.append(view)
  73. self._view_map[view.VIEW_ID] = view
  74. self._fkey_map[fkey] = view.VIEW_ID
  75. self._menu.add(MenuBinding(
  76. key=fkey,
  77. label=view.TITLE,
  78. view_id=view.VIEW_ID,
  79. ))
  80. view.display = False
  81. yield view
  82. yield InstallerFooter(self._menu, id="footer")
  83. async def on_mount(self) -> None:
  84. """Aktiviert den ersten View nach dem Mount."""
  85. if self._views:
  86. await self._activate_view(self._views[0].VIEW_ID)
  87. async def action_fkey(self, key: str) -> None:
  88. """Verarbeitet F-Tasten-Druecke."""
  89. view_id = self._fkey_map.get(key)
  90. if view_id and view_id in self._view_map:
  91. await self._activate_view(view_id)
  92. async def _activate_view(self, view_id: str) -> None:
  93. """Aktiviert einen View und deaktiviert den aktuellen."""
  94. new_view = self._view_map.get(view_id)
  95. if not new_view or new_view is self._active_view:
  96. return
  97. # Alten View deaktivieren
  98. if self._active_view:
  99. await self._active_view.on_deactivate()
  100. self._active_view.display = False
  101. # Neuen View aktivieren
  102. new_view.display = True
  103. self._active_view = new_view
  104. await new_view.on_activate()
  105. # Header aktualisieren
  106. header = self.query_one("#header", InstallerHeader)
  107. header.subtitle = new_view.TITLE
  108. def main() -> None:
  109. """CLI-Einstiegspunkt."""
  110. parser = argparse.ArgumentParser(description="Trixy Installer")
  111. parser.add_argument(
  112. "--mode",
  113. choices=["server", "client", "standalone", "config"],
  114. required=True,
  115. help="Installationsmodus",
  116. )
  117. parser.add_argument("--version", default="1.0.0", help="Trixy-Version")
  118. parser.add_argument("--source-archive", default="", help="Pfad zum Quellarchiv (.tar.gz)")
  119. parser.add_argument("--install-path", default="", help="Installationspfad (ueberschreibt Default)")
  120. args = parser.parse_args()
  121. config = InstallerConfig(
  122. mode=args.mode,
  123. version=args.version,
  124. source_archive=args.source_archive,
  125. )
  126. if args.install_path:
  127. config.install_path = args.install_path
  128. # Bestehende Installation erkennen
  129. from .detect import detect_installation, apply_existing_to_config
  130. existing = detect_installation(config.install_path, config.mode, config.version)
  131. if existing.found:
  132. apply_existing_to_config(existing, config)
  133. app = InstallerApp(config, existing=existing)
  134. app.run()
  135. if __name__ == "__main__":
  136. main()