Files
metro-warden/ui/tabs/registry.py
T

129 lines
3.7 KiB
Python

"""
Registry Tab — displays loaded plugins and their status.
Subscribes to registry.plugin.* events to show live plugin state.
Allows loading/unloading plugins interactively.
"""
from __future__ import annotations
from typing import Any, Optional
from textual.app import ComposeResult
from textual.binding import Binding
from textual.widget import Widget
from textual.widgets import DataTable, Footer, Label, Static
class RegistryTab(Widget):
"""
Plugin registry viewer.
Shows all discovered plugins with their version, status, module path,
and load timestamp. Pressing Enter on a row toggles load/unload.
"""
DEFAULT_CSS = """
RegistryTab {
layout: vertical;
height: 1fr;
padding: 1 2;
}
RegistryTab .tab-title {
color: $primary;
text-style: bold;
height: 1;
margin-bottom: 1;
}
RegistryTab DataTable {
height: 1fr;
border: round $primary;
}
RegistryTab .hint {
height: 1;
color: $text-muted;
margin-top: 1;
}
"""
BINDINGS = [
Binding("r", "refresh_registry", "Refresh"),
Binding("enter", "toggle_plugin", "Toggle"),
]
COLUMNS = [
("Name", 14),
("Version", 9),
("Status", 11),
("Tags", 22),
("Loaded At", 22),
("Module", 32),
("Error", 30),
]
def compose(self) -> ComposeResult:
yield Static("// REGISTRY — Plugin Manifest", classes="tab-title")
yield DataTable(id="registry-table", zebra_stripes=True, cursor_type="row")
yield Static(
"Press [bold]r[/bold] to refresh | [bold]Enter[/bold] to toggle load",
classes="hint",
)
def on_mount(self) -> None:
table = self.query_one("#registry-table", DataTable)
for col_label, width in self.COLUMNS:
table.add_column(col_label, width=width)
app = self.app
if hasattr(app, "bus"):
app.bus.subscribe("registry.plugin.*", self._on_registry_event)
self._refresh_table()
def _on_registry_event(self, topic: str, data: Any) -> None:
self.call_from_thread(self._refresh_table)
def _refresh_table(self) -> None:
table = self.query_one("#registry-table", DataTable)
table.clear()
app = self.app
if not hasattr(app, "registry"):
return
for record in sorted(app.registry.all_records(), key=lambda r: r.name):
loaded_at = record.loaded_at.strftime("%Y-%m-%d %H:%M:%S") if record.loaded_at else ""
table.add_row(
record.name,
record.version,
record.status.name,
", ".join(record.tags) or "",
loaded_at,
record.module_path,
record.error or "",
)
def action_refresh_registry(self) -> None:
self._refresh_table()
def action_toggle_plugin(self) -> None:
table = self.query_one("#registry-table", DataTable)
row_key = table.cursor_row
if row_key is None:
return
# Get plugin name from the focused row
try:
cell_value = table.get_cell_at((row_key, 0))
except Exception:
return
plugin_name = str(cell_value)
app = self.app
if not hasattr(app, "registry"):
return
record = app.registry.get(plugin_name)
if record is None:
return
from core.registry import PluginStatus
if record.status == PluginStatus.ACTIVE:
app.registry.unload(plugin_name)
else:
app.registry.load(plugin_name)
self._refresh_table()