fixed server-panel
This commit is contained in:
parent
4ac94f00de
commit
bd0f2a73f0
1 changed files with 59 additions and 23 deletions
|
|
@ -166,6 +166,15 @@ class ServerManager:
|
||||||
(f"{prefix}-{service}", self.container_status(f"{prefix}-{service}"))
|
(f"{prefix}-{service}", self.container_status(f"{prefix}-{service}"))
|
||||||
for service in ("postgres", "redis")
|
for service in ("postgres", "redis")
|
||||||
]
|
]
|
||||||
|
# MinIO is optional (only relevant when TELESRV_BLOB_BACKEND=s3 points
|
||||||
|
# at the self-hosted container rather than AWS S3), so unlike
|
||||||
|
# postgres/redis above it's only added to the list when the container
|
||||||
|
# actually exists -- no "not created" row cluttering a deployment that
|
||||||
|
# never uses it.
|
||||||
|
minio_name = f"{prefix}-minio"
|
||||||
|
minio_state = self.container_status(minio_name)
|
||||||
|
if minio_state is not None:
|
||||||
|
containers.append((minio_name, minio_state))
|
||||||
return Status(
|
return Status(
|
||||||
server_pid=server_pid,
|
server_pid=server_pid,
|
||||||
server_alive=pid_alive(server_pid),
|
server_alive=pid_alive(server_pid),
|
||||||
|
|
@ -600,13 +609,36 @@ def copy_to_clipboard(text: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def system_stats() -> tuple[float, float, float]:
|
@dataclass
|
||||||
"""Returns (cpu_percent, ram_percent, disk_percent) for the drive this
|
class SystemStats:
|
||||||
project lives on."""
|
cpu_percent: float
|
||||||
|
ram_percent: float
|
||||||
|
ram_used_gb: float
|
||||||
|
ram_total_gb: float
|
||||||
|
disk_percent: float
|
||||||
|
disk_used_gb: float
|
||||||
|
disk_total_gb: float
|
||||||
|
|
||||||
|
|
||||||
|
def system_stats() -> SystemStats:
|
||||||
|
"""CPU/RAM/disk usage for the drive this project lives on. RAM/disk carry
|
||||||
|
used/total GB alongside the percentage -- the percentage alone doesn't say
|
||||||
|
whether "83%" is 8GB of 10 or 800GB of 1000, and the progress bar widget
|
||||||
|
itself already renders the percentage, so the label is where that actually
|
||||||
|
useful number belongs."""
|
||||||
|
gib = 1024**3
|
||||||
cpu = psutil.cpu_percent(interval=None)
|
cpu = psutil.cpu_percent(interval=None)
|
||||||
ram = psutil.virtual_memory().percent
|
vm = psutil.virtual_memory()
|
||||||
disk = psutil.disk_usage(str(ROOT)).percent
|
du = psutil.disk_usage(str(ROOT))
|
||||||
return cpu, ram, disk
|
return SystemStats(
|
||||||
|
cpu_percent=cpu,
|
||||||
|
ram_percent=vm.percent,
|
||||||
|
ram_used_gb=vm.used / gib,
|
||||||
|
ram_total_gb=vm.total / gib,
|
||||||
|
disk_percent=du.percent,
|
||||||
|
disk_used_gb=du.used / gib,
|
||||||
|
disk_total_gb=du.total / gib,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# --- UI -----------------------------------------------------------------
|
# --- UI -----------------------------------------------------------------
|
||||||
|
|
@ -615,16 +647,14 @@ def system_stats() -> tuple[float, float, float]:
|
||||||
class CopyButton(Static):
|
class CopyButton(Static):
|
||||||
"""A clickable label that copies `value` to the clipboard on click. The
|
"""A clickable label that copies `value` to the clipboard on click. The
|
||||||
actual value is never shown on screen -- just the label and a "click to
|
actual value is never shown on screen -- just the label and a "click to
|
||||||
copy" hint (optionally masked with dots for secret-looking fields, purely
|
copy" hint. value=None renders as "not available" and isn't clickable --
|
||||||
cosmetic since nothing is ever displayed either way). value=None renders
|
used before the first Start, when e.g. the RSA public key file doesn't
|
||||||
as "not available" and isn't clickable -- used before the first Start,
|
exist yet."""
|
||||||
when e.g. the RSA public key file doesn't exist yet."""
|
|
||||||
|
|
||||||
def __init__(self, label: str, value: str | None, on_copy_callback, *, masked: bool = False, **kwargs):
|
def __init__(self, label: str, value: str | None, on_copy_callback, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.label = label
|
self.label = label
|
||||||
self.value = value
|
self.value = value
|
||||||
self.masked = masked
|
|
||||||
self._on_copy_callback = on_copy_callback
|
self._on_copy_callback = on_copy_callback
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
|
|
@ -639,8 +669,6 @@ class CopyButton(Static):
|
||||||
# no attribute 'get_height'` crash.
|
# no attribute 'get_height'` crash.
|
||||||
if self.value is None:
|
if self.value is None:
|
||||||
self.update(f"{self.label}: [dim]not available[/]")
|
self.update(f"{self.label}: [dim]not available[/]")
|
||||||
elif self.masked:
|
|
||||||
self.update(f"{self.label}: [dim]{'•' * 10}[/] [i](click to copy)[/]")
|
|
||||||
else:
|
else:
|
||||||
self.update(f"{self.label}: [dim](click to copy)[/]")
|
self.update(f"{self.label}: [dim](click to copy)[/]")
|
||||||
|
|
||||||
|
|
@ -1162,7 +1190,7 @@ class MainScreen(Screen):
|
||||||
with Vertical(id="server-info-section"):
|
with Vertical(id="server-info-section"):
|
||||||
yield Label("Server", classes="panel-title")
|
yield Label("Server", classes="panel-title")
|
||||||
yield CopyButton("Address", server_address(), self._handle_copy_click, id="server-address")
|
yield CopyButton("Address", server_address(), self._handle_copy_click, id="server-address")
|
||||||
yield CopyButton("Public key", server_public_key_pem(), self._handle_copy_click, masked=True, id="server-public-key")
|
yield CopyButton("Public key", server_public_key_pem(), self._handle_copy_click, id="server-public-key")
|
||||||
yield Label("Admin UI", classes="panel-title")
|
yield Label("Admin UI", classes="panel-title")
|
||||||
yield Static(id="admin-link")
|
yield Static(id="admin-link")
|
||||||
yield self._admin_password_widget()
|
yield self._admin_password_widget()
|
||||||
|
|
@ -1185,7 +1213,7 @@ class MainScreen(Screen):
|
||||||
def _admin_password_widget(self) -> CopyButton:
|
def _admin_password_widget(self) -> CopyButton:
|
||||||
info = admin_ui_info()
|
info = admin_ui_info()
|
||||||
password = info[1] if info else None
|
password = info[1] if info else None
|
||||||
return CopyButton("Password", password, self._handle_copy_click, masked=True, id="admin-password")
|
return CopyButton("Password", password, self._handle_copy_click, id="admin-password")
|
||||||
|
|
||||||
def _handle_copy_click(self, widget: CopyButton) -> None:
|
def _handle_copy_click(self, widget: CopyButton) -> None:
|
||||||
if copy_to_clipboard(widget.value):
|
if copy_to_clipboard(widget.value):
|
||||||
|
|
@ -1259,13 +1287,21 @@ class MainScreen(Screen):
|
||||||
self.query_one("#server-info-section").display = status.server_alive
|
self.query_one("#server-info-section").display = status.server_alive
|
||||||
|
|
||||||
def refresh_stats(self) -> None:
|
def refresh_stats(self) -> None:
|
||||||
cpu, ram, disk = system_stats()
|
stats = system_stats()
|
||||||
self.query_one("#cpu-bar", ProgressBar).update(progress=cpu)
|
self.query_one("#cpu-bar", ProgressBar).update(progress=stats.cpu_percent)
|
||||||
self.query_one("#ram-bar", ProgressBar).update(progress=ram)
|
self.query_one("#ram-bar", ProgressBar).update(progress=stats.ram_percent)
|
||||||
self.query_one("#disk-bar", ProgressBar).update(progress=disk)
|
self.query_one("#disk-bar", ProgressBar).update(progress=stats.disk_percent)
|
||||||
self.query_one("#cpu-label", Label).update(f"CPU {cpu:5.1f}%")
|
# The bar itself already renders the percentage (ProgressBar's built-in
|
||||||
self.query_one("#ram-label", Label).update(f"RAM {ram:5.1f}%")
|
# PercentageStatus) -- repeating it here would be the exact duplication
|
||||||
self.query_one("#disk-label", Label).update(f"Disk {disk:5.1f}%")
|
# this label is for, so CPU (no byte total to show) is caption-only,
|
||||||
|
# and RAM/Disk show used/total GB instead of the percent a second time.
|
||||||
|
self.query_one("#cpu-label", Label).update("CPU")
|
||||||
|
self.query_one("#ram-label", Label).update(
|
||||||
|
f"RAM {stats.ram_used_gb:.1f} / {stats.ram_total_gb:.1f} GB"
|
||||||
|
)
|
||||||
|
self.query_one("#disk-label", Label).update(
|
||||||
|
f"Disk {stats.disk_used_gb:.1f} / {stats.disk_total_gb:.1f} GB"
|
||||||
|
)
|
||||||
|
|
||||||
def refresh_admin_link(self) -> None:
|
def refresh_admin_link(self) -> None:
|
||||||
info = admin_ui_info()
|
info = admin_ui_info()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue