Files
debian-manager/app/ui/tabs/systemd_tab.py
T

117 lines
5.1 KiB
Python
Raw Normal View History

"""Вкладка Сервисы: systemd-юниты, журнал."""
import flet as ft
from app.core import systemd_svc as sd
from app.core.ssh import SSHClient
from app.db.models import Server
from app.ui.components import confirm_dialog, snack
class SystemdTab(ft.Column):
def __init__(self, page: ft.Page, srv: Server):
super().__init__(expand=True, spacing=8)
self._page = page
self.srv = srv
self._units = []
self.search = ft.TextField(hint_text="Поиск юнита…", prefix_icon=ft.Icons.SEARCH,
on_change=lambda e: self._render(), expand=True, dense=True)
self.only_failed = ft.Checkbox(label="только failed", value=False,
on_change=lambda e: self._render())
self.table = ft.DataTable(columns=[
ft.DataColumn(ft.Text("Юнит")),
ft.DataColumn(ft.Text("Состояние")),
ft.DataColumn(ft.Text("Enabled")),
ft.DataColumn(ft.Text("Описание")),
ft.DataColumn(ft.Text("Действия")),
], rows=[])
self.log_text = ft.Column(scroll=ft.ScrollMode.AUTO, spacing=1)
self.controls = [
ft.Container(height=8),
ft.Row([
ft.OutlinedButton("Обновить", icon=ft.Icons.REFRESH,
on_click=lambda e: self._page.run_task(self._load)),
self.search, self.only_failed,
]),
ft.Row([
ft.Container(expand=3, content=ft.Column([self.table],
scroll=ft.ScrollMode.AUTO)),
ft.VerticalDivider(width=1),
ft.Container(expand=2, content=self.log_text),
], expand=True),
]
self._page.run_task(self._load)
async def _load(self):
try:
async with SSHClient(self.srv) as c:
self._units = await sd.list_units(c)
except Exception as e:
snack(self._page, f"systemd: {e}", error=True)
self._render()
def _render(self):
q = (self.search.value or "").lower()
units = self._units
if self.only_failed.value:
units = [u for u in units if u.active == "failed"]
if q:
units = [u for u in units if q in u.name.lower() or q in u.description.lower()]
def state_color(u):
return {"active": ft.Colors.GREEN_300, "failed": ft.Colors.RED_400,
"inactive": ft.Colors.GREY_500}.get(u.active, ft.Colors.GREY_500)
self.table.rows = [
ft.DataRow(cells=[
ft.DataCell(ft.Text(u.name, size=12, selectable=True)),
ft.DataCell(ft.Text(f"{u.active}/{u.sub}", size=12, color=state_color(u))),
ft.DataCell(ft.Text(u.enabled or "—", size=11)),
ft.DataCell(ft.Text(u.description[:50], size=11)),
ft.DataCell(ft.Row([
ft.IconButton(ft.Icons.PLAY_ARROW, icon_size=16, tooltip="start",
on_click=lambda e, n=u.name: self._act(n, "start")),
ft.IconButton(ft.Icons.STOP, icon_size=16, tooltip="stop",
on_click=lambda e, n=u.name: self._act(n, "stop")),
ft.IconButton(ft.Icons.RESTART_ALT, icon_size=16, tooltip="restart",
on_click=lambda e, n=u.name: self._act(n, "restart")),
ft.IconButton(ft.Icons.ARTICLE, icon_size=16, tooltip="логи",
on_click=lambda e, n=u.name: self._logs(n)),
], spacing=0)),
]) for u in units[:200]
]
self._page.update()
def _act(self, name, action):
def do():
async def _d():
try:
async with SSHClient(self.srv) as c:
await sd.action(c, name, action)
snack(self._page, f"{name}: {action} ✓")
await self._load()
except Exception as ex:
snack(self._page, f"{ex}", error=True)
self._page.run_task(_d)
if name in sd.CRITICAL_UNITS and action in ("stop", "restart"):
confirm_dialog(self._page, f"⚠ {action} {name}?",
"Это критичный системный сервис! Можно потерять связь с сервером.", do)
else:
do()
def _logs(self, name):
async def _l():
self.log_text.controls = [ft.Text(f"journalctl -u {name}", weight=ft.FontWeight.BOLD, size=12)]
self._page.update()
try:
async with SSHClient(self.srv) as c:
out = await sd.logs(c, name)
for line in out.splitlines()[-80:]:
self.log_text.controls.append(
ft.Text(line, size=10, font_family="Consolas", selectable=True))
except Exception as ex:
self.log_text.controls.append(ft.Text(f"ошибка: {ex}", size=11))
self._page.update()
self._page.run_task(_l)