"""Плейбуки: список, редактор, запуск.""" 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()