- Python 3.12+ / Textual TUI with btop-style theme and 4 tabs (Dashboard, Logs, Services, Disks) - Paramiko SSH layer with ssh_config support, key fallback, keepalive and reconnect; async executor over a thread pool - Read-only collectors for system, storage, disks, network, logs and services; confirm dialog gates service restart/stop/start - Click CLI: dsmview ssh [-p PORT] [-i KEY] TARGET - Homebrew formula using virtualenv_install_with_resources - GitHub Actions release workflow: builds source tarball, regenerates Python resource blocks via homebrew-pypi-poet, pushes updated formula to janvete/homebrew-dsmview tap Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.5 KiB
Python
45 lines
1.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Callable, Iterable
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.binding import Binding
|
|
from textual.containers import Container
|
|
from textual.widgets import Static
|
|
|
|
from dsmview.collectors.services import ServiceInfo
|
|
from dsmview.ui.widgets import ServiceList
|
|
|
|
|
|
class ServicesTab(Container):
|
|
BINDINGS = [
|
|
Binding("r", "service_action('restart')", "Restart", show=False),
|
|
Binding("s", "service_action('stop')", "Stop", show=False),
|
|
Binding("S", "service_action('start')", "Start", show=False),
|
|
]
|
|
|
|
def __init__(self, on_action: Callable[[str, str], None]) -> None:
|
|
super().__init__()
|
|
self._on_action = on_action
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Container(classes="panel"):
|
|
yield Static(
|
|
"SERVICES — [enter] detail [r] restart [s] stop [S] start",
|
|
classes="panel-title",
|
|
)
|
|
self.table = ServiceList()
|
|
yield self.table
|
|
|
|
def update_services(self, services: Iterable[ServiceInfo]) -> None:
|
|
self.table.update_services(services)
|
|
|
|
def action_service_action(self, action: str) -> None:
|
|
row = self.table.cursor_row
|
|
if row is None or row < 0 or row >= self.table.row_count:
|
|
return
|
|
row_key = self.table.coordinate_to_cell_key((row, 0)).row_key
|
|
name = row_key.value if row_key else None
|
|
if not name:
|
|
return
|
|
self._on_action(action, name)
|