"""Вкладка Обзор: метрики, update/upgrade/reboot, история.""" import flet as ft from app.core import monitor from app.core.packages import apt_update, list_upgradable from app.core.ssh import SSHClient, check_server from app.core.tasks import tasks from app.db.models import Server from app.ui.components import append_log, confirm_dialog, fmt_mb, log_view, snack class OverviewTab(ft.Column): def __init__(self, page: ft.Page, srv: Server): super().__init__(expand=True, scroll=ft.ScrollMode.AUTO, spacing=12) self._page = page self.srv = srv self.info_text = ft.Text("загрузка…", size=13) self.upg_text = ft.Text("", size=13, color=ft.Colors.ORANGE_300) self.metrics_col = ft.Column(spacing=4) self.log = log_view() self.log_container = ft.Container( height=220, border=ft.Border.all(1, ft.Colors.GREY_800), border_radius=8, padding=8, content=self.log, visible=False, ) self.controls = [ ft.Container(height=8), ft.Row([ ft.FilledButton("apt update", icon=ft.Icons.REFRESH, on_click=self._update), ft.FilledButton("Upgrade", icon=ft.Icons.UPGRADE, on_click=lambda e: self._upgrade(False)), ft.OutlinedButton("Dist-upgrade", on_click=lambda e: self._upgrade(True)), ft.FilledButton("Reboot", icon=ft.Icons.RESTART_ALT, style=ft.ButtonStyle(bgcolor=ft.Colors.RED_800), on_click=self._reboot), self.upg_text, ], spacing=10), ft.Card(content=ft.Container(padding=14, content=ft.Column([ ft.Text("Система", weight=ft.FontWeight.BOLD), self.info_text, ft.Container(height=6), self.metrics_col, ]))), self.log_container, ft.Text("История метрик", weight=ft.FontWeight.BOLD), self._chart(), ] self._page.run_task(self._load) # ---------- данные ---------- async def _load(self): info = await check_server(self.srv) if info.get("online"): self.info_text.value = ( f"🟢 {info['hostname']} • {info['os']}\n" f"{info['uptime']} • CPU: {info['cpu_cores']} ядер • RAM: {info['mem_mb']} МБ" ) try: async with SSHClient(self.srv) as c: upg = await list_upgradable(c) self.upg_text.value = ( f"доступно обновлений: {len(upg)}" if upg else "система обновлена" ) snap = await monitor.collect(c, self.srv.id) self._show_metrics(snap) except Exception as e: self.info_text.value += f"\n⚠ {e}" else: self.info_text.value = f"🔴 {info.get('error')}" self._page.update() def _show_metrics(self, snap): def bar(label, pct, color): return ft.Row([ ft.Text(label, size=12, width=45), ft.ProgressBar(value=pct / 100, height=8, expand=True, color=color, bgcolor=ft.Colors.GREY_800), ft.Text(f"{pct:.0f}%", size=12, width=42, text_align=ft.TextAlign.RIGHT), ]) self.metrics_col.controls = [ bar("CPU", snap.cpu_percent, ft.Colors.BLUE_400), bar("RAM", snap.mem_percent, ft.Colors.PURPLE_300), bar("Диск", snap.disk_percent, ft.Colors.RED_400 if snap.disk_percent > 85 else ft.Colors.GREEN_400), ft.Text(f"RAM {fmt_mb(snap.mem_used_mb)} / {fmt_mb(snap.mem_total_mb)}" f" • load {snap.load1:.2f}" f" • net ↓{snap.net_rx_kbs:.0f} ↑{snap.net_tx_kbs:.0f} КБ/с", size=11, color=ft.Colors.GREY_500), ] def _chart(self): hist = monitor.history(self.srv.id, 30) if len(hist) < 2: return ft.Text("данных пока мало — график появится после нескольких замеров", size=12, color=ft.Colors.GREY_500) # простое текстовое представление истории (LineChart недоступен в flet 0.86) cpu_vals = [h.cpu_percent for h in hist] ram_vals = [h.mem_percent for h in hist] rows = [] for i, h in enumerate(hist[-15:]): bar_cpu = "█" * int(h.cpu_percent / 5) + "░" * (20 - int(h.cpu_percent / 5)) bar_ram = "█" * int(h.mem_percent / 5) + "░" * (20 - int(h.mem_percent / 5)) rows.append(ft.Text( f"{h.ts:%H:%M} CPU {bar_cpu} {h.cpu_percent:5.1f}% RAM {bar_ram} {h.mem_percent:5.1f}%", size=10, font_family="Consolas", color=ft.Colors.GREY_400, )) return ft.Column(rows, spacing=1, scroll=ft.ScrollMode.AUTO, height=180) # ---------- действия ---------- def _show_log(self, show=True): self.log_container.visible = show self._page.update() def _update(self, e): self._show_log() async def job(log): async with SSHClient(self.srv) as c: log("$ apt-get update") out = await apt_update(c) for line in out.splitlines()[-30:]: log(line) tasks.start("update", "apt update", self.srv.id, job, on_log=lambda l: (append_log(self.log, l), self._page.update())) snack(self._page, "apt update запущен") def _upgrade(self, dist: bool): label = "dist-upgrade" if dist else "upgrade" def do(): self._show_log() async def job(log): async with SSHClient(self.srv) as c: log(f"$ apt-get {label} -y") stream = await __import__("app.core.packages", fromlist=["apt_upgrade"]).apt_upgrade(c, dist) async for line in stream: log(line) tasks.start("upgrade", label, self.srv.id, job, on_log=lambda l: (append_log(self.log, l), self._page.update())) confirm_dialog(self._page, f"Запустить {label}?", f"На сервере «{self.srv.name}» будут обновлены пакеты.", do) def _reboot(self, e): def do(): self._show_log() from app.core.group_ops import op_reboot async def job(log): async with SSHClient(self.srv) as c: await op_reboot(wait=True)(c, self.srv, log) tasks.start("reboot", "reboot", self.srv.id, job, on_log=lambda l: (append_log(self.log, l), self._page.update())) confirm_dialog(self._page, "Перезагрузить сервер?", f"Сервер «{self.srv.name}» будет перезагружен. Приложение дождётся его возврата.", do)