display live RAM usage
This commit is contained in:
parent
975369c784
commit
6a42cdf008
5 changed files with 67 additions and 2 deletions
|
|
@ -7,6 +7,7 @@ from textual.app import ComposeResult
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.containers import Container, Vertical, VerticalScroll, Horizontal
|
from textual.containers import Container, Vertical, VerticalScroll, Horizontal
|
||||||
from textual.widgets import Button, Label, Rule, Tree
|
from textual.widgets import Button, Label, Rule, Tree
|
||||||
|
from src.ui.widgets import RamDisplay
|
||||||
from textual import work
|
from textual import work
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
|
|
@ -211,6 +212,7 @@ class BackupStatsScreen(Screen[None]):
|
||||||
|
|
||||||
with Horizontal(id="bs_actions"):
|
with Horizontal(id="bs_actions"):
|
||||||
yield Button("Back", id="btn_back", variant="default")
|
yield Button("Back", id="btn_back", variant="default")
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.stats_tree = self.query_one("#stats_tree", Tree)
|
self.stats_tree = self.query_one("#stats_tree", Tree)
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ from textual.app import App, ComposeResult
|
||||||
from textual.containers import Container, Horizontal, Vertical, VerticalScroll
|
from textual.containers import Container, Horizontal, Vertical, VerticalScroll
|
||||||
from textual.widgets import (
|
from textual.widgets import (
|
||||||
Header, Footer, Button, Label, Input, ListItem,
|
Header, Footer, Button, Label, Input, ListItem,
|
||||||
ListView, Rule, RadioButton, RadioSet, Select,
|
ListView, Rule, RadioButton, RadioSet, Select, Static,
|
||||||
)
|
)
|
||||||
from textual.screen import Screen, ModalScreen
|
from textual.screen import Screen, ModalScreen
|
||||||
|
|
||||||
from src.core.configuration import (
|
from src.core.configuration import (
|
||||||
get_available_configs, create_new_config, load_config, save_config,
|
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("New Config", id="btn_new_config", variant="success", tooltip="Create a new configuration folder")
|
||||||
yield Button("Exit", id="btn_exit", variant="error")
|
yield Button("Exit", id="btn_exit", variant="error")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.refresh_configs()
|
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("Save Configuration", variant="success", id="btn_save", tooltip="Save all changes to config.yaml")
|
||||||
yield Button("Back", id="btn_back")
|
yield Button("Back", id="btn_back")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self._toggle_target_section()
|
self._toggle_target_section()
|
||||||
|
|
@ -488,6 +491,16 @@ class ReaperApp(App):
|
||||||
"config_selection": ConfigSelectionScreen,
|
"config_selection": ConfigSelectionScreen,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
RamDisplay {
|
||||||
|
dock: bottom;
|
||||||
|
width: 30;
|
||||||
|
height: 1;
|
||||||
|
margin-left: 2;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.push_screen("config_selection")
|
self.push_screen("config_selection")
|
||||||
self.theme = "dracula"
|
self.theme = "dracula"
|
||||||
|
|
|
||||||
|
|
@ -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 import Button, Label, Input, ProgressBar, RichLog, Rule, RadioButton, LoadingIndicator, Header, Footer, RadioSet, OptionList
|
||||||
from textual.widgets.option_list import Option
|
from textual.widgets.option_list import Option
|
||||||
from textual.screen import ModalScreen, Screen
|
from textual.screen import ModalScreen, Screen
|
||||||
|
from src.ui.widgets import RamDisplay
|
||||||
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
@ -113,6 +114,7 @@ class ProgressScreen(Screen[None]):
|
||||||
with Horizontal(classes="action_row", id="prog_actions_cancel"):
|
with Horizontal(classes="action_row", id="prog_actions_cancel"):
|
||||||
yield Button("Back", id="btn_cancel", variant="default", tooltip="Stop current operation")
|
yield Button("Back", id="btn_cancel", variant="default", tooltip="Stop current operation")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def __init__(self, log_level: str = "INFO", *args, **kwargs):
|
def __init__(self, log_level: str = "INFO", *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -419,6 +421,7 @@ class SubMenuModal(ModalScreen[str]):
|
||||||
yield Button(label, id=btn_id, variant=variant)
|
yield Button(label, id=btn_id, variant=variant)
|
||||||
yield Rule(id="footer_rule")
|
yield Rule(id="footer_rule")
|
||||||
yield Button("Cancel", id="btn_cancel_sub")
|
yield Button("Cancel", id="btn_cancel_sub")
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed):
|
def on_button_pressed(self, event: Button.Pressed):
|
||||||
if event.button.id == "btn_cancel_sub":
|
if event.button.id == "btn_cancel_sub":
|
||||||
|
|
@ -475,6 +478,7 @@ class OptionSelectModal(ModalScreen[list[str]]):
|
||||||
with Horizontal(id="opt_buttons"):
|
with Horizontal(id="opt_buttons"):
|
||||||
yield Button("Proceed", variant="success", id="btn_opt_ok", tooltip="Proceed with the selected options")
|
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 Button("Back", id="btn_opt_back", tooltip="Return to the previous menu")
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed):
|
def on_button_pressed(self, event: Button.Pressed):
|
||||||
if event.button.id == "btn_opt_back":
|
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("Select", variant="success", id="btn_pick_ok", tooltip="Confirm selection and start migration")
|
||||||
yield Button("Back", id="btn_pick_back", tooltip="Cancel selection")
|
yield Button("Back", id="btn_pick_back", tooltip="Cancel selection")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Set initial highlights after mounting."""
|
"""Set initial highlights after mounting."""
|
||||||
|
|
@ -805,6 +810,7 @@ class ChannelSelectScreen(Screen[dict]):
|
||||||
yield Button("Backup", variant="success", id="btn_backup", tooltip="Start backing up selected channels")
|
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 Button("Back", id="btn_cancel_chan", tooltip="Cancel and go back")
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
if event.button.id == "btn_all":
|
if event.button.id == "btn_all":
|
||||||
|
|
@ -874,6 +880,7 @@ class MessageIDInputModal(ModalScreen[int | None]):
|
||||||
with Horizontal(id="msg_id_buttons"):
|
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("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 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:
|
def on_input_changed(self, event: Input.Changed) -> None:
|
||||||
if event.input.id == "input_msg_id":
|
if event.input.id == "input_msg_id":
|
||||||
|
|
@ -970,6 +977,7 @@ class ChannelNameInputModal(ModalScreen[str | None]):
|
||||||
with Horizontal(id="chan_name_buttons"):
|
with Horizontal(id="chan_name_buttons"):
|
||||||
yield Button("Create", variant="success", id="btn_create_chan")
|
yield Button("Create", variant="success", id="btn_create_chan")
|
||||||
yield Button("Back", id="btn_cancel_chan_name")
|
yield Button("Back", id="btn_cancel_chan_name")
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
if event.button.id == "btn_cancel_chan_name":
|
if event.button.id == "btn_cancel_chan_name":
|
||||||
|
|
@ -1028,6 +1036,7 @@ class ChannelIDInputModal(ModalScreen[dict | None]):
|
||||||
with Horizontal(id="chan_id_buttons"):
|
with Horizontal(id="chan_id_buttons"):
|
||||||
yield Button("Verify", variant="primary", id="btn_verify_chan", disabled=True)
|
yield Button("Verify", variant="primary", id="btn_verify_chan", disabled=True)
|
||||||
yield Button("Back", variant="warning", id="btn_cancel_chan_id")
|
yield Button("Back", variant="warning", id="btn_cancel_chan_id")
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_input_changed(self, event: Input.Changed) -> None:
|
def on_input_changed(self, event: Input.Changed) -> None:
|
||||||
if event.input.id == "input_chan_id":
|
if event.input.id == "input_chan_id":
|
||||||
|
|
@ -1076,3 +1085,4 @@ class ChannelIDInputModal(ModalScreen[dict | None]):
|
||||||
btn.variant = "success"
|
btn.variant = "success"
|
||||||
else:
|
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]")
|
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()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from textual.screen import Screen
|
||||||
|
|
||||||
from src.core.configuration import load_config
|
from src.core.configuration import load_config
|
||||||
from src.ui.shuttle_ops import OperationPane
|
from src.ui.shuttle_ops import OperationPane
|
||||||
|
from src.ui.widgets import RamDisplay, Footnote
|
||||||
|
|
||||||
|
|
||||||
class ModeScreen(Screen):
|
class ModeScreen(Screen):
|
||||||
|
|
@ -138,6 +139,8 @@ class ModeScreen(Screen):
|
||||||
yield Button("Exit", id="btn_exit", variant="error")
|
yield Button("Exit", id="btn_exit", variant="error")
|
||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
yield Footnote()
|
||||||
|
yield RamDisplay()
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
bid = event.button.id
|
bid = event.button.id
|
||||||
|
|
|
||||||
37
src/ui/widgets.py
Normal file
37
src/ui/widgets.py
Normal 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]")
|
||||||
Loading…
Add table
Reference in a new issue