Files
debian-manager/app/ui/tabs/overview.py
T
dinlo f624fee5e0 Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов
- Пакеты (apt), Docker, файлы, systemd, ufw, cron
- Плейбуки, групповые операции, планировщик
- Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy
- Терминал, Telegram-уведомления, тёмная/светлая тема
2026-09-05 19:46:45 +08:00

159 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Вкладка Обзор: метрики, 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)