| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- # -*- coding: utf-8 -*-
- """
- Config-View: Remote-Konfiguration lesen und schreiben.
- Zeigt die Konfiguration als Baum und erlaubt das Editieren
- einzelner Werte mit typ-spezifischen Editoren und Validierung.
- """
- from __future__ import annotations
- from textual.app import ComposeResult
- from textual.containers import Vertical
- from textual.widgets import Static, Tree
- from textual.widgets.tree import TreeNode
- from trixy_core.tui.views.base import MainView
- from trixy_core.tui.config.field_registry import (
- EditorType,
- FieldMeta,
- get_field_registry,
- infer_field_meta,
- )
- from trixy_core.tui.widgets.config_editors import ConfigEditorPanel
- class ConfigView(MainView):
- """
- F2: Config-Ansicht.
- Zeigt die Remote-Konfiguration als Baum-Struktur.
- Werte koennen selektiert und mit typ-spezifischen
- Editoren bearbeitet werden.
- """
- TITLE = "Config"
- VIEW_ID = "config"
- def __init__(self, **kwargs) -> None:
- super().__init__(**kwargs)
- self._config_data: dict = {}
- self._modified: dict[str, object] = {}
- self._selected_path: str = ""
- self._field_registry: dict[str, FieldMeta] = {}
- self._dynamic_options: dict[str, list[tuple[str, str]]] = {}
- def compose(self) -> ComposeResult:
- yield Tree("Konfiguration", id="config-tree")
- yield ConfigEditorPanel(id="config-editor")
- yield Static("", id="config-status")
- async def on_activate(self) -> None:
- """Laedt die Konfiguration und baut die Registry auf."""
- if not self.connection:
- return
- # Registry basierend auf Instanz-Typ laden
- instance_type = self.connection.instance_type or "server"
- self._field_registry = get_field_registry(instance_type)
- # Dynamische Optionen einmalig abfragen
- await self._fetch_dynamic_options()
- # Konfiguration laden
- await self._load_config()
- async def _fetch_dynamic_options(self) -> None:
- """Fragt dynamische Feld-Optionen vom Server ab."""
- if not self.connection:
- return
- # Alle benoetigten dynamischen Quellen sammeln
- sources: set[str] = set()
- for meta in self._field_registry.values():
- if meta.dynamic_source:
- sources.add(meta.dynamic_source)
- if not sources:
- return
- try:
- options = await self.connection.request_field_options(list(sources))
- if options:
- self._dynamic_options = options
- except Exception:
- pass
- async def _load_config(self) -> None:
- """Laedt die Remote-Konfiguration."""
- if not self.connection:
- return
- try:
- data = await self.connection.request_config()
- except Exception:
- self._set_status("Fehler beim Laden der Konfiguration", error=True)
- return
- if data is None:
- return
- self._config_data = data
- self._build_tree()
- def _build_tree(self) -> None:
- """Baut den Konfigurationsbaum auf."""
- tree = self.query_one("#config-tree", Tree)
- tree.clear()
- self._add_tree_nodes(tree.root, self._config_data, "")
- tree.root.expand()
- def _add_tree_nodes(self, parent: TreeNode, data: dict, prefix: str) -> None:
- """Fuegt Knoten rekursiv zum Baum hinzu."""
- for key, value in sorted(data.items()):
- path = f"{prefix}.{key}" if prefix else key
- if isinstance(value, dict):
- node = parent.add(f"[bold]{key}[/bold]", data={"path": path, "is_section": True})
- self._add_tree_nodes(node, value, path)
- node.expand()
- elif isinstance(value, list):
- display = f"{key} = {value}"
- if path in self._modified:
- display = f"[yellow]{key}[/yellow] = [yellow]{value}[/yellow] *"
- parent.add_leaf(display, data={"path": path, "value": value, "is_section": False})
- else:
- # Markierung fuer modifizierte Werte
- if path in self._modified:
- display = f"[yellow]{key}[/yellow] = [yellow]{value}[/yellow] *"
- else:
- display = f"{key} = {value}"
- parent.add_leaf(display, data={"path": path, "value": value, "is_section": False})
- async def on_tree_node_selected(self, event: Tree.NodeSelected) -> None:
- """Wird aufgerufen wenn ein Baum-Knoten selektiert wird."""
- node_data = event.node.data
- if not node_data or node_data.get("is_section"):
- return
- path = node_data.get("path", "")
- value = node_data.get("value", "")
- self._selected_path = path
- # FieldMeta ermitteln (Registry oder Inferenz)
- meta = self._field_registry.get(path)
- if meta is None:
- meta = infer_field_meta(value)
- # Dynamische Optionen injizieren
- if meta.dynamic_source and meta.dynamic_source in self._dynamic_options:
- meta = FieldMeta(
- editor=meta.editor,
- choices=self._dynamic_options[meta.dynamic_source],
- min_value=meta.min_value,
- max_value=meta.max_value,
- step=meta.step,
- placeholder=meta.placeholder,
- requires_restart=meta.requires_restart,
- dynamic_source=meta.dynamic_source,
- hint=meta.hint,
- )
- # Editor anzeigen
- editor = self.query_one("#config-editor", ConfigEditorPanel)
- await editor.show_editor(path, value, meta)
- self._set_status(f"Bearbeite: {path}")
- async def on_config_editor_panel_submitted(
- self, event: ConfigEditorPanel.Submitted
- ) -> None:
- """Verarbeitet validierte Werte vom Editor-Panel."""
- if event.error:
- self._set_status(f"Fehler: {event.error}", error=True)
- return
- if not self.connection:
- return
- try:
- result = await self.connection.write_config(
- config_name="",
- key_path=event.path,
- value=event.validated_value,
- )
- if result and result.success:
- self._modified[event.path] = event.validated_value
- msg = "Gespeichert"
- if event.requires_restart or (result and result.requires_restart):
- msg += " (Neustart erforderlich)"
- self._set_status(msg)
- # Baum neu laden
- await self._load_config()
- else:
- error = result.error if result else "Unbekannter Fehler"
- self._set_status(f"Fehler: {error}", error=True)
- except Exception as e:
- self._set_status(f"Fehler: {e}", error=True)
- def _set_status(self, text: str, error: bool = False) -> None:
- """Setzt die Statuszeile."""
- status = self.query_one("#config-status", Static)
- if error:
- status.update(f"[red]{text}[/red]")
- else:
- status.update(f"[green]{text}[/green]")
|