f624fee5e0
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
159 lines
7.3 KiB
Python
159 lines
7.3 KiB
Python
"""Вкладка Обзор: метрики, 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)
|