plugins_view.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. # -*- coding: utf-8 -*-
  2. """
  3. Plugins-View: Tabellarische Liste aller Plugins.
  4. Zeigt Plugins in einer DataTable mit Name, Version, Beschreibung, Status.
  5. Enter/Klick auf eine Zeile oeffnet die Plugin-Detailansicht.
  6. Buttons zum Aktivieren/Deaktivieren und Neuladen.
  7. """
  8. from __future__ import annotations
  9. from textual.app import ComposeResult
  10. from textual.containers import Horizontal
  11. from textual.widgets import Static, Button, DataTable
  12. from trixy_core.tui.views.base import MainView
  13. class PluginsView(MainView):
  14. """
  15. F4: Plugin-Ansicht.
  16. Zeigt alle geladenen Plugins tabellarisch.
  17. Zeilen-Auswahl oeffnet Details, Buttons fuer Enable/Disable/Reload.
  18. """
  19. TITLE = "Plugins"
  20. VIEW_ID = "plugins"
  21. def __init__(self, **kwargs) -> None:
  22. super().__init__(**kwargs)
  23. self._plugins: list[dict] = []
  24. self._refresh_counter: int = 0
  25. def compose(self) -> ComposeResult:
  26. yield Static("", id="plugin-header")
  27. yield DataTable(id="plugin-table", cursor_type="row")
  28. with Horizontal(id="plugin-actions"):
  29. yield Button("Aktivieren", id="plugin-btn-enable", variant="success")
  30. yield Button("Deaktivieren", id="plugin-btn-disable", variant="warning")
  31. yield Button("Neu laden", id="plugin-btn-reload", variant="default")
  32. yield Static("", id="plugin-status")
  33. async def on_activate(self) -> None:
  34. """Laedt die Plugin-Liste."""
  35. table = self.query_one("#plugin-table", DataTable)
  36. if not table.columns:
  37. table.add_columns("Name", "Version", "Beschreibung", "Status")
  38. await self._load_plugins()
  39. async def refresh_data(self) -> None:
  40. """Periodisches Update (alle 10 Sekunden)."""
  41. self._refresh_counter += 1
  42. if self._refresh_counter % 5 == 0:
  43. await self._load_plugins()
  44. async def _load_plugins(self) -> None:
  45. """Laedt die Plugin-Liste vom Server."""
  46. if not self.connection:
  47. return
  48. try:
  49. response = await self.connection.request_plugins()
  50. except Exception:
  51. self._set_status("Fehler beim Laden der Plugins", error=True)
  52. return
  53. if response is None:
  54. return
  55. self._plugins = response.plugins
  56. self._update_header()
  57. self._update_table()
  58. def _update_header(self) -> None:
  59. """Aktualisiert die Kopfzeile."""
  60. header = self.query_one("#plugin-header", Static)
  61. total = len(self._plugins)
  62. enabled = sum(1 for p in self._plugins if p.get("enabled", False))
  63. failed = sum(1 for p in self._plugins if p.get("failed", False))
  64. info = f"[bold]Plugins:[/bold] {enabled}/{total} aktiv"
  65. if failed:
  66. info += f" [red]{failed} fehlgeschlagen[/red]"
  67. header.update(info)
  68. def _update_table(self) -> None:
  69. """Aktualisiert die Plugin-Tabelle."""
  70. table = self.query_one("#plugin-table", DataTable)
  71. table.clear()
  72. sorted_plugins = sorted(
  73. self._plugins,
  74. key=lambda p: (
  75. p.get("failed", False),
  76. not p.get("enabled", False),
  77. p.get("name", ""),
  78. ),
  79. )
  80. for plugin_data in sorted_plugins:
  81. name = plugin_data.get("name", "Unbekannt")
  82. version = plugin_data.get("version", "")
  83. description = plugin_data.get("description", "")
  84. enabled = plugin_data.get("enabled", False)
  85. failed = plugin_data.get("failed", False)
  86. if failed:
  87. status = "[red]Fehler[/red]"
  88. elif enabled:
  89. status = "[green]Aktiv[/green]"
  90. else:
  91. status = "[dim]Inaktiv[/dim]"
  92. table.add_row(name, version, description, status, key=name)
  93. def _get_selected_plugin(self) -> dict | None:
  94. """Gibt das aktuell markierte Plugin zurueck."""
  95. try:
  96. table = self.query_one("#plugin-table", DataTable)
  97. row_key, _ = table.coordinate_to_cell_key(table.cursor_coordinate)
  98. name = str(row_key.value) if row_key else ""
  99. if name:
  100. for p in self._plugins:
  101. if p.get("name") == name:
  102. return p
  103. except Exception:
  104. pass
  105. return None
  106. async def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
  107. """Enter auf einer Zeile: Oeffnet die Detail-Ansicht."""
  108. row_key = str(event.row_key.value) if event.row_key else ""
  109. plugin_data = None
  110. for p in self._plugins:
  111. if p.get("name") == row_key:
  112. plugin_data = p
  113. break
  114. if not plugin_data:
  115. return
  116. if plugin_data.get("failed", False):
  117. self._set_status(
  118. f"Fehler: {plugin_data.get('error', 'Unbekannt')}", error=True
  119. )
  120. return
  121. try:
  122. app = self.app
  123. if hasattr(app, "open_plugin_detail"):
  124. await app.open_plugin_detail(plugin_data)
  125. except Exception:
  126. pass
  127. async def on_button_pressed(self, event: Button.Pressed) -> None:
  128. """Verarbeitet Button-Klicks."""
  129. btn_id = event.button.id
  130. if not self.connection:
  131. return
  132. plugin = self._get_selected_plugin()
  133. if not plugin:
  134. self._set_status("Kein Plugin ausgewaehlt", error=True)
  135. return
  136. name = plugin.get("name", "")
  137. if btn_id == "plugin-btn-enable":
  138. if plugin.get("failed", False):
  139. self._set_status("Fehlgeschlagene Plugins muessen neu geladen werden", error=True)
  140. return
  141. await self._plugin_action("enable", name)
  142. elif btn_id == "plugin-btn-disable":
  143. if plugin.get("failed", False):
  144. return
  145. await self._plugin_action("disable", name)
  146. elif btn_id == "plugin-btn-reload":
  147. await self._plugin_action("reload", name)
  148. async def _plugin_action(self, action: str, name: str) -> None:
  149. """Fuehrt eine Plugin-Aktion aus und aktualisiert die Liste."""
  150. result = await self.connection.plugin_action(action=action, plugin_name=name)
  151. if result and result.success:
  152. self._set_status(result.message)
  153. await self._load_plugins()
  154. else:
  155. error = result.error if result else "Unbekannter Fehler"
  156. self._set_status(f"Fehler: {error}", error=True)
  157. def _set_status(self, text: str, error: bool = False) -> None:
  158. """Setzt die Statuszeile."""
  159. status = self.query_one("#plugin-status", Static)
  160. if error:
  161. status.update(f"[red]{text}[/red]")
  162. else:
  163. status.update(f"[green]{text}[/green]")