Fix display error
This commit is contained in:
@@ -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"}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.3"
|
||||
__author__ = "ExilProductions"
|
||||
__email__ = "exil.productions.business@gmail.com"
|
||||
@@ -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,13 +64,20 @@ 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
|
||||
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
return self.charts.create_system_gauges(cpu_info, memory_info, disk_info)
|
||||
|
||||
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:
|
||||
|
||||
def render(self) -> Layout:
|
||||
self.layout = self._make_layout()
|
||||
|
||||
self.monitor.update_history()
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user