Fix display error

This commit is contained in:
Exil Productions
2025-12-04 15:59:47 +01:00
parent b583417640
commit 2820893b4f
5 changed files with 116 additions and 108 deletions

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "statsman"
version = "0.1.2"
version = "0.1.3"
description = "A real-time terminal-based system monitoring tool with ASCII visualizations"
authors = [{name = "Exil Productions", email = "exil.productions.business@gmail.com"}]
license = {text = "MIT"}

View File

@@ -1,3 +1,3 @@
__version__ = "0.1.2"
__version__ = "0.1.3"
__author__ = "ExilProductions"
__email__ = "exil.productions.business@gmail.com"

View File

@@ -31,6 +31,8 @@ class StatsManApp:
self.live.stop()
def _handle_keyboard_input(self):
old_settings = None
termios = None
try:
import select
import termios
@@ -62,7 +64,7 @@ class StatsManApp:
finally:
try:
if 'old_settings' in locals():
if old_settings is not None and termios is not None:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
except:
pass
@@ -70,6 +72,13 @@ class StatsManApp:
def run(self):
self.running = True
def on_resize(sig, frame):
self.console.clear()
if self.live:
self.live.update(self.dashboard.render(), refresh=True)
signal.signal(signal.SIGWINCH, on_resize)
keyboard_thread = threading.Thread(target=self._handle_keyboard_input, daemon=True)
keyboard_thread.start()

View File

@@ -75,19 +75,24 @@ class ChartRenderer:
return Panel("\n".join(lines), border_style="green")
def create_mini_process_table(self, processes: List[Any], limit: int = 12) -> Panel:
def create_mini_process_table(self, processes: List[Any], limit: int = 12, console_width: int = 80) -> Panel:
if not processes:
return Panel("No processes", border_style="red")
sorted_processes = sorted(processes, key=lambda p: p.cpu_percent, reverse=True)
lines = ["PID Name CPU MEM ", "=" * 55]
max_name_width = max(10, min(20, (console_width - 35) // 2))
bar_width = max(5, min(10, (console_width - 25) // 4))
header = f"{'PID':<7} {'PROCESS':<20} {'CPU':<12} {'MEM':<12}"
separator = "=" * min(console_width - 4, 75)
lines = [header, separator]
for proc in sorted_processes[:limit]:
cpu_bar = self._create_mini_bar(proc.cpu_percent, 10)
mem_bar = self._create_mini_bar(proc.memory_percent, 10)
cpu_bar = self._create_mini_bar(proc.cpu_percent, bar_width)
mem_bar = self._create_mini_bar(proc.memory_percent, bar_width)
name = proc.name[:14] + ".." if len(proc.name) > 16 else proc.name.ljust(16)
name = (proc.name[:18] + "..") if len(proc.name) > 20 else proc.name.ljust(20)
lines.append(f"{proc.pid:<7} {name} {cpu_bar} {mem_bar}")
return Panel("\n".join(lines), title="Top Processes", border_style="magenta")
@@ -97,16 +102,17 @@ class ChartRenderer:
bar = "" * filled + "" * (width - filled)
return bar
def create_system_gauges(self, cpu_info: Any, memory_info: Any, disk_info: Any) -> Panel:
def create_system_gauges(self, cpu_info: Any, memory_info: Any, disk_info: Any, console_width: int = 80) -> Panel:
gauges = []
gauge_width = max(15, min(30, console_width // 4))
cpu_gauge = self._create_gauge(cpu_info.percent, "CPU")
cpu_gauge = self._create_gauge(cpu_info.percent, "CPU", gauge_width)
gauges.append(cpu_gauge)
mem_gauge = self._create_gauge(memory_info.percent, "MEM")
mem_gauge = self._create_gauge(memory_info.percent, "MEM", gauge_width)
gauges.append(mem_gauge)
disk_gauge = self._create_gauge(disk_info.percent, "DSK")
disk_gauge = self._create_gauge(disk_info.percent, "DSK", gauge_width)
gauges.append(disk_gauge)
return Panel(Group(*gauges), border_style="cyan")
@@ -116,7 +122,7 @@ class ChartRenderer:
bar = "" * filled + "" * (width - filled)
return Text.from_markup(f"[bold]{label}:[/bold] {bar} {percentage:5.1f}%")
def create_network_visualization(self, network_info: Any) -> Panel:
def create_network_visualization(self, network_info: Any, console_width: int = 80) -> Panel:
sent_mb = network_info.bytes_sent / (1024 * 1024)
recv_mb = network_info.bytes_recv / (1024 * 1024)
@@ -125,9 +131,10 @@ class ChartRenderer:
"DOWNLOAD": min(recv_mb * 10, 100),
}
return self.create_horizontal_bars(network_data)
bar_width = max(15, min(70, console_width // 2))
return self.create_horizontal_bars(network_data, max_width=bar_width)
def create_cpu_core_visualization(self, cpu_info: Any) -> Panel:
def create_cpu_core_visualization(self, cpu_info: Any, console_width: int = 80) -> Panel:
if not cpu_info.percent_per_core:
return Panel("No core data", border_style="red")
@@ -135,9 +142,10 @@ class ChartRenderer:
for i, core_percent in enumerate(cpu_info.percent_per_core):
core_data[f"C{i:02d}"] = core_percent
return self.create_vertical_bars(core_data, height=8, width=40)
bar_width = max(20, min(40, console_width // 8))
return self.create_vertical_bars(core_data, height=8, width=bar_width)
def create_memory_breakdown(self, memory_info: Any) -> Panel:
def create_memory_breakdown(self, memory_info: Any, console_width: int = 80) -> Panel:
used_gb = memory_info.used / (1024**3)
total_gb = memory_info.total / (1024**3)
@@ -146,7 +154,8 @@ class ChartRenderer:
"FREE": ((total_gb - used_gb) / total_gb) * 100,
}
return self.create_horizontal_bars(memory_data)
bar_width = max(15, min(70, console_width // 2))
return self.create_horizontal_bars(memory_data, max_width=bar_width)
def format_bytes(self, bytes_value: int) -> str:
bytes_float = float(bytes_value)

View File

@@ -1,11 +1,9 @@
from rich.console import Console, Group
from rich.layout import Layout
from rich.panel import Panel
from rich.live import Live
from rich.text import Text
from rich.align import Align
from typing import Optional
import time
from ..system_monitor import SystemMonitor
from .charts import ChartRenderer
@@ -14,120 +12,112 @@ from .charts import ChartRenderer
class Dashboard:
def __init__(self, console: Optional[Console] = None, no_color: bool = False):
self.console = console or Console(color_system=None if no_color else "auto")
self.monitor = SystemMonitor()
self.monitor = SystemMonitor(history_size=120)
self.charts = ChartRenderer(self.console)
self.layout = Layout()
self.sort_processes_by = "cpu"
self._setup_visual_layout()
def _setup_visual_layout(self) -> None:
self.layout.split(
def _make_layout(self) -> Layout:
"""Create responsive layout based on current terminal size"""
layout = Layout()
layout.split_column(
Layout(name="header", size=3),
Layout(name="main"),
Layout(name="body", ratio=1),
Layout(name="footer", size=3),
)
self.layout["main"].split_column(
Layout(name="top", size=16),
Layout(name="middle", size=14),
Layout(name="bottom", ratio=1),
layout["body"].split_column(
Layout(name="top", ratio=2),
Layout(name="middle", ratio=1),
Layout(name="processes", ratio=2),
)
self.layout["top"].split_row(
layout["top"].split_row(
Layout(name="gauges", ratio=1),
Layout(name="cores", ratio=1),
)
self.layout["middle"].split_row(
layout["middle"].split_row(
Layout(name="memory", ratio=1),
Layout(name="network", ratio=1),
)
self.layout["bottom"].split_row(
Layout(name="processes"),
)
return layout
def _create_header(self) -> Panel:
header_text = Text.from_markup(
"[bold blue]StatsMan[/bold blue] - System Monitor",
justify="center"
)
return Panel(
Align.center(header_text),
border_style="blue"
Align.center(Text("StatsMan - System Monitor", style="bold blue")),
border_style="bright_blue",
height=3
)
def _create_footer(self) -> Panel:
controls = Text.from_markup(
"[cyan]q:quit p:pause c:cpu m:mem r:reset[/cyan]",
justify="center"
footer_text = (
"[bold cyan]q[/] quit "
"[bold cyan]p[/] pause │ "
"[bold cyan]c[/] sort CPU │ "
"[bold cyan]m[/] sort MEM"
)
return Panel(
Align.center(controls),
border_style="cyan"
Align.center(Text.from_markup(footer_text)),
border_style="bright_black",
height=3
)
def _create_system_gauges(self) -> Panel:
cpu_info = self.monitor.get_cpu_info()
memory_info = self.monitor.get_memory_info()
disk_info = self.monitor.get_disk_info()
def render(self) -> Layout:
self.layout = self._make_layout()
return self.charts.create_system_gauges(cpu_info, memory_info, disk_info)
self.monitor.update_history()
def _create_cpu_cores(self) -> Panel:
cpu_info = self.monitor.get_cpu_info()
cpu_history = self.monitor.get_cpu_history()
sparkline = self.charts.create_sparkline(cpu_history, width=60, height=8)
sparkline_text = Text.from_markup(f"[cyan]CPU History:[/cyan] {sparkline}")
cores_panel = self.charts.create_cpu_core_visualization(cpu_info)
return Panel(
Group(sparkline_text, cores_panel),
title=f"CPU: {cpu_info.percent:.1f}%",
border_style="red"
)
def _create_memory_visual(self) -> Panel:
memory_info = self.monitor.get_memory_info()
memory_history = self.monitor.get_memory_history()
sparkline = self.charts.create_sparkline(memory_history, width=50, height=6)
sparkline_text = Text.from_markup(f"[green]Memory History:[/green] {sparkline}")
breakdown_panel = self.charts.create_memory_breakdown(memory_info)
return Panel(
Group(sparkline_text, breakdown_panel),
title=f"Memory: {memory_info.percent:.1f}%",
border_style="green"
)
def _create_network_visual(self) -> Panel:
network_info = self.monitor.get_network_info()
return self.charts.create_network_visualization(network_info)
def _create_processes_visual(self) -> Panel:
processes = self.monitor.get_process_info(limit=20)
return self.charts.create_mini_process_table(processes, limit=16)
def update_layout(self) -> None:
self.layout["header"].update(self._create_header())
self.layout["footer"].update(self._create_footer())
self.layout["top"]["gauges"].update(self._create_system_gauges())
self.layout["top"]["cores"].update(self._create_cpu_cores())
self.layout["middle"]["memory"].update(self._create_memory_visual())
self.layout["middle"]["network"].update(self._create_network_visual())
self.layout["bottom"]["processes"].update(self._create_processes_visual())
def render(self) -> Layout:
self.monitor.update_history()
self.update_layout()
self.layout["processes"].update(self._create_processes_visual())
return self.layout
def _create_system_gauges(self) -> Panel:
cpu = self.monitor.get_cpu_info()
mem = self.monitor.get_memory_info()
disk = self.monitor.get_disk_info()
return self.charts.create_system_gauges(cpu, mem, disk)
def _create_cpu_cores(self) -> Panel:
cpu = self.monitor.get_cpu_info()
history = self.monitor.get_cpu_history()
spark = self.charts.create_sparkline(history, height=6)
cores = self.charts.create_cpu_core_visualization(cpu)
return Panel(Group(Text(f"CPU Usage: {cpu.percent:.1f}%"), spark, cores),
title="CPU Cores", border_style="red")
def _create_memory_visual(self) -> Panel:
mem = self.monitor.get_memory_info()
history = self.monitor.get_memory_history()
spark = self.charts.create_sparkline(history, height=5)
breakdown = self.charts.create_memory_breakdown(mem)
return Panel(Group(Text(f"Memory: {mem.percent:.1f}%"), spark, breakdown),
title="Memory & Swap", border_style="green")
def _create_network_visual(self) -> Panel:
net = self.monitor.get_network_info()
return self.charts.create_network_visualization(net)
def _create_processes_visual(self) -> Panel:
height = self.console.size.height
limit = max(8, height // 3, 20)
procs = self.monitor.get_process_info(limit=limit + 5)
if self.sort_processes_by == "memory":
procs.sort(key=lambda p: p.memory_percent, reverse=True)
return self.charts.create_mini_process_table(procs[:limit])
def set_process_sort(self, sort_by: str) -> None:
if sort_by in ['cpu', 'memory']:
if sort_by in ("cpu", "memory"):
self.sort_processes_by = sort_by