display live RAM usage

This commit is contained in:
rambros 2026-03-12 21:38:50 +05:30
parent 975369c784
commit 6a42cdf008
5 changed files with 67 additions and 2 deletions

View file

@ -7,6 +7,7 @@ from textual.app import ComposeResult
from textual.screen import Screen
from textual.containers import Container, Vertical, VerticalScroll, Horizontal
from textual.widgets import Button, Label, Rule, Tree
from src.ui.widgets import RamDisplay
from textual import work
from rich.text import Text
from rich.style import Style
@ -211,6 +212,7 @@ class BackupStatsScreen(Screen[None]):
with Horizontal(id="bs_actions"):
yield Button("Back", id="btn_back", variant="default")
yield RamDisplay()
def on_mount(self) -> None:
self.stats_tree = self.query_one("#stats_tree", Tree)

View file

@ -9,13 +9,14 @@ from textual.app import App, ComposeResult
from textual.containers import Container, Horizontal, Vertical, VerticalScroll
from textual.widgets import (
Header, Footer, Button, Label, Input, ListItem,
ListView, Rule, RadioButton, RadioSet, Select,
ListView, Rule, RadioButton, RadioSet, Select, Static,
)
from textual.screen import Screen, ModalScreen
from src.core.configuration import (
get_available_configs, create_new_config, load_config, save_config,
)
from src.ui.widgets import RamDisplay
# ──────────────────────────────────────────────────────────────────────────────
@ -100,6 +101,7 @@ class ConfigSelectionScreen(Screen):
yield Button("New Config", id="btn_new_config", variant="success", tooltip="Create a new configuration folder")
yield Button("Exit", id="btn_exit", variant="error")
yield Footer()
yield RamDisplay()
def on_mount(self) -> None:
self.refresh_configs()
@ -298,6 +300,7 @@ class ConfigScreen(Screen):
yield Button("Save Configuration", variant="success", id="btn_save", tooltip="Save all changes to config.yaml")
yield Button("Back", id="btn_back")
yield Footer()
yield RamDisplay()
def on_mount(self) -> None:
self._toggle_target_section()
@ -488,6 +491,16 @@ class ReaperApp(App):
"config_selection": ConfigSelectionScreen,
}
DEFAULT_CSS = """
RamDisplay {
dock: bottom;
width: 30;
height: 1;
margin-left: 2;
color: green;
}
"""
def on_mount(self) -> None:
self.push_screen("config_selection")
self.theme = "dracula"

View file

@ -7,6 +7,7 @@ from textual.containers import Horizontal, Vertical, VerticalScroll, Container,
from textual.widgets import Button, Label, Input, ProgressBar, RichLog, Rule, RadioButton, LoadingIndicator, Header, Footer, RadioSet, OptionList
from textual.widgets.option_list import Option
from textual.screen import ModalScreen, Screen
from src.ui.widgets import RamDisplay
import time
@ -113,6 +114,7 @@ class ProgressScreen(Screen[None]):
with Horizontal(classes="action_row", id="prog_actions_cancel"):
yield Button("Back", id="btn_cancel", variant="default", tooltip="Stop current operation")
yield Footer()
yield RamDisplay()
def __init__(self, log_level: str = "INFO", *args, **kwargs):
super().__init__(*args, **kwargs)
@ -419,6 +421,7 @@ class SubMenuModal(ModalScreen[str]):
yield Button(label, id=btn_id, variant=variant)
yield Rule(id="footer_rule")
yield Button("Cancel", id="btn_cancel_sub")
yield RamDisplay()
def on_button_pressed(self, event: Button.Pressed):
if event.button.id == "btn_cancel_sub":
@ -475,6 +478,7 @@ class OptionSelectModal(ModalScreen[list[str]]):
with Horizontal(id="opt_buttons"):
yield Button("Proceed", variant="success", id="btn_opt_ok", tooltip="Proceed with the selected options")
yield Button("Back", id="btn_opt_back", tooltip="Return to the previous menu")
yield RamDisplay()
def on_button_pressed(self, event: Button.Pressed):
if event.button.id == "btn_opt_back":
@ -618,6 +622,7 @@ class ChannelPickerScreen(Screen[tuple]):
yield Button("Select", variant="success", id="btn_pick_ok", tooltip="Confirm selection and start migration")
yield Button("Back", id="btn_pick_back", tooltip="Cancel selection")
yield Footer()
yield RamDisplay()
def on_mount(self) -> None:
"""Set initial highlights after mounting."""
@ -805,7 +810,8 @@ class ChannelSelectScreen(Screen[dict]):
yield Button("Backup", variant="success", id="btn_backup", tooltip="Start backing up selected channels")
yield Button("Back", id="btn_cancel_chan", tooltip="Cancel and go back")
yield Footer()
yield RamDisplay()
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "btn_all":
for cb in self.query(RadioButton):
@ -874,6 +880,7 @@ class MessageIDInputModal(ModalScreen[int | None]):
with Horizontal(id="msg_id_buttons"):
yield Button("Verify", variant="primary", id="btn_verify_start", disabled=True, tooltip="Check if this message ID exists in the channel")
yield Button("Back", variant="warning", id="btn_cancel_msg_id", tooltip="Cancel and go back")
yield RamDisplay()
def on_input_changed(self, event: Input.Changed) -> None:
if event.input.id == "input_msg_id":
@ -970,6 +977,7 @@ class ChannelNameInputModal(ModalScreen[str | None]):
with Horizontal(id="chan_name_buttons"):
yield Button("Create", variant="success", id="btn_create_chan")
yield Button("Back", id="btn_cancel_chan_name")
yield RamDisplay()
def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "btn_cancel_chan_name":
@ -1028,6 +1036,7 @@ class ChannelIDInputModal(ModalScreen[dict | None]):
with Horizontal(id="chan_id_buttons"):
yield Button("Verify", variant="primary", id="btn_verify_chan", disabled=True)
yield Button("Back", variant="warning", id="btn_cancel_chan_id")
yield RamDisplay()
def on_input_changed(self, event: Input.Changed) -> None:
if event.input.id == "input_chan_id":
@ -1076,3 +1085,4 @@ class ChannelIDInputModal(ModalScreen[dict | None]):
btn.variant = "success"
else:
preview.update(f"[bold red]No channel found with ID: {chan_id_str}[/bold red]\n[dim]Make sure the ID belongs to a channel in the target community.[/dim]")
yield RamDisplay()

View file

@ -15,6 +15,7 @@ from textual.screen import Screen
from src.core.configuration import load_config
from src.ui.shuttle_ops import OperationPane
from src.ui.widgets import RamDisplay, Footnote
class ModeScreen(Screen):
@ -138,6 +139,8 @@ class ModeScreen(Screen):
yield Button("Exit", id="btn_exit", variant="error")
yield Footer()
yield Footnote()
yield RamDisplay()
def on_button_pressed(self, event: Button.Pressed) -> None:
bid = event.button.id

37
src/ui/widgets.py Normal file
View file

@ -0,0 +1,37 @@
from textual.widgets import Static
import logging
logger = logging.getLogger(__name__)
class RamDisplay(Static):
"""Widget to display current RAM usage."""
def on_mount(self) -> None:
self.update_ram()
self.set_interval(1.0, self.update_ram)
def update_ram(self) -> None:
"""Fetch and display RAM usage (RSS)."""
try:
# RSS is in KB in /proc/self/status on Linux
with open("/proc/self/status", "r") as f:
for line in f:
if line.startswith("VmRSS:"):
rss_kb = int(line.split()[1])
break
else:
rss_kb = 0
if rss_kb > 1024 * 1024:
usage = f"{rss_kb / (1024 * 1024):.2f} GB"
else:
usage = f"{rss_kb / 1024:.2f} MB"
self.update(f" RAM: [yellow]{usage}[/yellow]")
except Exception:
self.update(" RAM: [bold red]N/A[/bold red]")
class Footnote(Static):
"""Widget to display branding text at the bottom right."""
def on_mount(self) -> None:
self.update("made by [bold]RamBros[/bold]")