"""Вкладка Firewall: ufw.""" import flet as ft from app.core import firewall as fw from app.core.ssh import SSHClient from app.db.models import Server from app.ui.components import confirm_dialog, snack class FirewallTab(ft.Column): def __init__(self, page: ft.Page, srv: Server): super().__init__(expand=True, spacing=8, scroll=ft.ScrollMode.AUTO) self._page = page self.srv = srv self.status_text = ft.Text("…", size=13) self.table = ft.DataTable(columns=[ ft.DataColumn(ft.Text("#")), ft.DataColumn(ft.Text("Куда")), ft.DataColumn(ft.Text("Действие")), ft.DataColumn(ft.Text("Откуда")), ft.DataColumn(ft.Text("")), ], rows=[]) self.listen_text = ft.Column(spacing=1) self.controls = [ ft.Container(height=8), ft.Row([ ft.FilledButton("Включить ufw", icon=ft.Icons.SHIELD, on_click=self._enable), ft.OutlinedButton("Выключить", on_click=self._disable), ft.FilledButton("Добавить правило", icon=ft.Icons.ADD, on_click=self._add_dialog), ft.OutlinedButton("Пресет", icon=ft.Icons.LIST_ALT, on_click=self._preset_dialog), ft.IconButton(ft.Icons.REFRESH, on_click=lambda e: self._page.run_task(self._load)), ]), self.status_text, ft.Card(content=ft.Container(padding=10, content=self.table)), ft.Text("Прослушиваемые порты (ss -tlnp):", weight=ft.FontWeight.BOLD, size=13), self.listen_text, ] self._page.run_task(self._load) async def _load(self): try: async with SSHClient(self.srv) as c: if not await fw.is_installed(c): self.status_text.value = "ufw не установлен (apt install ufw)" self._page.update() return active, raw = await fw.status(c) rules = await fw.list_rules(c) listening = await fw.listening_ports(c) self.status_text.value = "🟢 ufw активен" if active else "⚪ ufw выключен" self.table.rows = [ ft.DataRow(cells=[ ft.DataCell(ft.Text(str(r.number))), ft.DataCell(ft.Text(r.to)), ft.DataCell(ft.Text(r.action, color=ft.Colors.GREEN_300 if "ALLOW" in r.action.upper() else ft.Colors.RED_300)), ft.DataCell(ft.Text(r.from_)), ft.DataCell(ft.IconButton(ft.Icons.DELETE_OUTLINE, icon_size=16, on_click=lambda e, n=r.number: self._del_rule(n))), ]) for r in rules ] self.listen_text.controls = [ ft.Text(l, size=11, font_family="Consolas", color=ft.Colors.GREY_500) for l in listening ] except Exception as e: self.status_text.value = f"ошибка: {e}" self._page.update() def _enable(self, e): def do(): async def _d(): async with SSHClient(self.srv) as c: out = await fw.enable(c, self.srv.port) snack(self._page, out.strip().splitlines()[-1] if out.strip() else "ufw включён") await self._load() self._page.run_task(_d) confirm_dialog(self._page, "Включить ufw?", f"Порт {self.srv.port} (текущий SSH) будет автоматически разрешён.", do, danger=False) def _disable(self, e): def do(): async def _d(): async with SSHClient(self.srv) as c: await fw.disable(c) await self._load() self._page.run_task(_d) confirm_dialog(self._page, "Выключить ufw?", "Файрвол будет полностью отключён.", do) def _del_rule(self, number): def do(): async def _d(): async with SSHClient(self.srv) as c: await fw.delete_rule(c, number) await self._load() self._page.run_task(_d) confirm_dialog(self._page, f"Удалить правило #{number}?", "", do) def _add_dialog(self, e): port_tf = ft.TextField(label="Порт (22, 80, 8000:8010)", width=250) action_dd = ft.Dropdown(label="Действие", value="allow", width=150, options=[ ft.dropdown.Option("allow"), ft.dropdown.Option("deny"), ft.dropdown.Option("limit"), ft.dropdown.Option("reject"), ]) proto_dd = ft.Dropdown(label="Протокол", value="tcp", width=120, options=[ ft.dropdown.Option("tcp"), ft.dropdown.Option("udp"), ft.dropdown.Option("any"), ]) from_tf = ft.TextField(label="От адреса (пусто = любой)", width=250) def do(e): async def _d(): async with SSHClient(self.srv) as c: out = await fw.add_rule(c, port_tf.value, action_dd.value, proto_dd.value, from_tf.value.strip()) self._page.pop_dialog() snack(self._page, out.strip() or "правило добавлено") await self._load() self._page.run_task(_d) dlg = ft.AlertDialog( modal=True, title=ft.Text("Новое правило"), content=ft.Column([port_tf, action_dd, proto_dd, from_tf], tight=True), actions=[ft.TextButton("Отмена", on_click=lambda e: self._close(dlg)), ft.FilledButton("Добавить", on_click=do)], ) self._page.show_dialog(dlg) def _preset_dialog(self, e): def apply(name): self._page.pop_dialog() async def _d(): async with SSHClient(self.srv) as c: out = await fw.apply_preset(c, name, self.srv.port) snack(self._page, "пресет применён") await self._load() self._page.run_task(_d) dlg = ft.AlertDialog( modal=True, title=ft.Text("Применить пресет"), content=ft.Column([ ft.FilledButton(n, on_click=lambda e, n=n: apply(n), width=250) for n in fw.PRESETS ], tight=True), actions=[ft.TextButton("Отмена", on_click=lambda e: self._close(dlg))], ) self._page.show_dialog(dlg) def _close(self, dlg): self._page.pop_dialog()