Files
debian-manager/app/ui/tabs/firewall_tab.py
T
dinlo f624fee5e0 Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов
- Пакеты (apt), Docker, файлы, systemd, ufw, cron
- Плейбуки, групповые операции, планировщик
- Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy
- Терминал, Telegram-уведомления, тёмная/светлая тема
2026-09-05 19:46:45 +08:00

156 lines
6.8 KiB
Python

"""Вкладка 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()