f624fee5e0
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
132 lines
5.5 KiB
Python
132 lines
5.5 KiB
Python
"""Плейбуки: список, редактор, запуск."""
|
|
import flet as ft
|
|
|
|
from app.core import playbooks as pb
|
|
from app.core import servers as servers_core
|
|
from app.core.ssh import SSHClient
|
|
from app.core.tasks import tasks
|
|
from app.ui.components import append_log, log_view, snack
|
|
|
|
|
|
class PlaybooksPage(ft.Column):
|
|
def __init__(self, page: ft.Page, go):
|
|
super().__init__(expand=True, spacing=12)
|
|
self._page = page
|
|
self.go = go
|
|
|
|
self.list_col = ft.Column(spacing=6, scroll=ft.ScrollMode.AUTO)
|
|
self.editor = ft.TextField(multiline=True, min_lines=18, max_lines=18,
|
|
text_size=12,
|
|
text_style=ft.TextStyle(font_family="Consolas"),
|
|
expand=True)
|
|
self.editor_name = ft.Text("", weight=ft.FontWeight.BOLD)
|
|
self.log = log_view()
|
|
self.log_container = ft.Container(
|
|
height=180, border=ft.Border.all(1, ft.Colors.GREY_800),
|
|
border_radius=8, padding=8, content=self.log, visible=False)
|
|
|
|
self.controls = [
|
|
ft.Text("Плейбуки", size=28, weight=ft.FontWeight.BOLD),
|
|
ft.Row([
|
|
ft.Container(width=300, content=ft.Column([
|
|
ft.FilledButton("Новый плейбук", icon=ft.Icons.ADD,
|
|
on_click=self._new),
|
|
ft.Divider(),
|
|
self.list_col,
|
|
])),
|
|
ft.VerticalDivider(width=1),
|
|
ft.Container(expand=True, content=ft.Column([
|
|
ft.Row([
|
|
self.editor_name,
|
|
ft.Container(expand=True),
|
|
ft.OutlinedButton("Сохранить", icon=ft.Icons.SAVE,
|
|
on_click=self._save),
|
|
ft.FilledButton("Запустить", icon=ft.Icons.PLAY_ARROW,
|
|
on_click=self._run_dialog),
|
|
]),
|
|
self.editor,
|
|
self.log_container,
|
|
], expand=True)),
|
|
], expand=True),
|
|
]
|
|
self._current = None
|
|
self._reload_list()
|
|
|
|
def _reload_list(self):
|
|
self.list_col.controls = [
|
|
ft.ListTile(
|
|
title=ft.Text(name, size=13),
|
|
subtitle=ft.Text("встроенный" if name in pb.BUILTIN else "пользовательский",
|
|
size=10),
|
|
dense=True,
|
|
on_click=lambda e, n=name: self._open(n),
|
|
) for name in pb.list_playbooks()
|
|
]
|
|
self._page.update()
|
|
|
|
def _open(self, name):
|
|
import yaml
|
|
self._current = name
|
|
data = pb.load(name)
|
|
self.editor.value = pb.BUILTIN.get(name) or (
|
|
pb.PLAYBOOKS_DIR / f"{name}.yml").read_text(encoding="utf-8")
|
|
self.editor_name.value = name
|
|
self._page.update()
|
|
|
|
def _new(self, e):
|
|
name_tf = ft.TextField(label="Имя", autofocus=True)
|
|
def create(e):
|
|
self._current = name_tf.value
|
|
self.editor_name.value = name_tf.value
|
|
self.editor.value = 'name: "Новый плейбук"\nsteps:\n - name: Проверка\n shell: "uptime"\n'
|
|
self._page.pop_dialog()
|
|
dlg = ft.AlertDialog(modal=True, title=ft.Text("Имя плейбука"), content=name_tf,
|
|
actions=[ft.TextButton("Отмена", on_click=lambda e: self._close(dlg)),
|
|
ft.FilledButton("Создать", on_click=create)])
|
|
self._page.show_dialog(dlg)
|
|
|
|
def _save(self, e):
|
|
if not self._current or self._current in pb.BUILTIN:
|
|
snack(self._page, "Встроенный плейбук нельзя перезаписать — создайте копию",
|
|
error=True)
|
|
return
|
|
try:
|
|
pb.save(self._current, self.editor.value)
|
|
snack(self._page, "Сохранено")
|
|
self._reload_list()
|
|
except Exception as ex:
|
|
snack(self._page, f"Ошибка YAML: {ex}", error=True)
|
|
|
|
def _run_dialog(self, e):
|
|
if not self._current:
|
|
return
|
|
servers = servers_core.list_servers()
|
|
checks = {s.id: ft.Checkbox(label=s.name, value=False) for s in servers}
|
|
def run(e):
|
|
selected = [s for s in servers if checks[s.id].value]
|
|
if not selected:
|
|
snack(self._page, "Выберите сервер", error=True)
|
|
return
|
|
self._page.pop_dialog()
|
|
self._run_on(selected)
|
|
dlg = ft.AlertDialog(
|
|
modal=True, title=ft.Text(f"Запустить «{self._current}»"),
|
|
content=ft.Column(list(checks.values()), tight=True),
|
|
actions=[ft.TextButton("Отмена", on_click=lambda e: self._close(dlg)),
|
|
ft.FilledButton("Запустить", on_click=run)],
|
|
)
|
|
self._page.show_dialog(dlg)
|
|
|
|
def _run_on(self, servers):
|
|
self.log_container.visible = True
|
|
data = pb.load(self._current)
|
|
for srv in servers:
|
|
async def job(log, srv=srv):
|
|
async with SSHClient(srv) as c:
|
|
await pb.run_playbook(c, srv, data, log)
|
|
tasks.start("playbook", f"плейбук {self._current}", srv.id, job,
|
|
on_log=lambda l: (append_log(self.log, l), self._page.update()))
|
|
|
|
def _close(self, dlg):
|
|
self._page.pop_dialog()
|