v0.4+v0.5: TUI dashboard, tab completion, systemd, Docker, process isolation

- New: services/sysmon_widget.py (CPU/MEM/DISK/NET real-time)
- Enhanced: tui_service.py (4-section layout, plugin status panel)
- Enhanced: command_service.py + tui (Tab completion)
- New: deploy/sensu.service (systemd)
- New: deploy/Dockerfile (Alpine, 4200+4240)
- New: services/process_isolated.py (multiprocess plugin isolation)
- Tests: 23/23 passing
This commit is contained in:
2026-06-10 19:08:16 +08:00
parent d48ef65b35
commit 4b26b6f086
5 changed files with 219 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""SenSu TUI 系统监控组件 — CPU/内存/磁盘/插件状态"""
import psutil, time, asyncio, logging
from textual.widgets import Static
from textual.reactive import reactive
logger = logging.getLogger(__name__)
def get_system_stats():
"""获取系统实时状态"""
try:
cpu = psutil.cpu_percent(interval=0.1)
mem = psutil.virtual_memory()
disk = psutil.disk_usage("/")
net = psutil.net_io_counters()
return {
"cpu": cpu,
"mem_total": mem.total,
"mem_used": mem.used,
"mem_percent": mem.percent,
"disk_total": disk.total,
"disk_used": disk.used,
"disk_percent": disk.percent,
"net_sent": net.bytes_sent,
"net_recv": net.bytes_recv,
"time": time.time(),
}
except Exception as e:
return {"error": str(e)}
def format_bytes(b):
if b < 1024: return f"{b}B"
if b < 1024**2: return f"{b/1024:.1f}K"
if b < 1024**3: return f"{b/1024**2:.1f}M"
return f"{b/1024**3:.1f}G"
def make_bar(percent, width=20, filled="", empty=""):
n = int(percent / 100 * width)
return filled * n + empty * (width - n)
class SysMonWidget(Static):
"""系统监控显示组件"""
stats = reactive({})
plugin_info = reactive("")
def __init__(self):
super().__init__("系统监控初始化中...")
self._refresh_task = None
def on_mount(self):
self._refresh_task = asyncio.create_task(self._periodic_refresh())
async def _periodic_refresh(self):
while True:
try:
s = get_system_stats()
if "error" not in s:
cpu_bar = make_bar(s["cpu"], 20)
mem_bar = make_bar(s["mem_percent"], 20)
disk_bar = make_bar(s["disk_percent"], 20)
text = (
f"[bold cyan]━━━ 系统状态 ━━━[/]\n"
f"CPU {cpu_bar} {s['cpu']:5.1f}%\n"
f"MEM {mem_bar} {s['mem_percent']:5.1f}% "
f"({format_bytes(s['mem_used'])}/{format_bytes(s['mem_total'])})\n"
f"DISK {disk_bar} {s['disk_percent']:5.1f}% "
f"({format_bytes(s['disk_used'])}/{format_bytes(s['disk_total'])})\n"
f"NET ↑{format_bytes(s['net_sent'])}{format_bytes(s['net_recv'])}"
)
if self.plugin_info:
text += f"\n\n[bold yellow]━━━ 插件状态 ━━━[/]\n{self.plugin_info}"
self.update(text)
await asyncio.sleep(2)
except asyncio.CancelledError:
break
except Exception as e:
logger.debug(f"SysMon refresh error: {e}")
await asyncio.sleep(5)
def update_plugins(self, info: str):
self.plugin_info = info
def on_unmount(self):
if self._refresh_task:
self._refresh_task.cancel()