Files
debian-manager/app/ui/pages/servers.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

159 lines
7.2 KiB
Python

"""Серверы: список, добавление, редактирование, удаление."""
import flet as ft
from app.core import servers as servers_core
from app.core.ssh import check_server
from app.ui.components import confirm_dialog, snack
class ServersPage(ft.Column):
def __init__(self, page: ft.Page, go):
super().__init__(expand=True, scroll=ft.ScrollMode.AUTO)
self._page = page
self.go = go
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("Статус")),
ft.DataColumn(ft.Text("Действия")),
],
rows=[],
)
self.controls = [
ft.Row([
ft.Text("Серверы", size=28, weight=ft.FontWeight.BOLD),
ft.FilledButton("Добавить сервер", icon=ft.Icons.ADD,
on_click=self._add_dialog),
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
ft.Card(content=ft.Container(padding=10, content=self.table)),
]
self.reload()
def reload(self):
self.table.rows = []
for s in servers_core.list_servers():
self.table.rows.append(ft.DataRow(cells=[
ft.DataCell(ft.Text(s.name, weight=ft.FontWeight.BOLD)),
ft.DataCell(ft.Text(f"{s.host}:{s.port}")),
ft.DataCell(ft.Text(s.username)),
ft.DataCell(ft.Text(s.tags or "—")),
ft.DataCell(ft.Text("…", key=f"status_{s.id}")),
ft.DataCell(ft.Row([
ft.IconButton(ft.Icons.OPEN_IN_NEW, tooltip="Открыть",
on_click=lambda e, sid=s.id: self.go("server_detail", sid)),
ft.IconButton(ft.Icons.EDIT, tooltip="Изменить",
on_click=lambda e, s=s: self._edit_dialog(s)),
ft.IconButton(ft.Icons.DELETE, tooltip="Удалить",
on_click=lambda e, s=s: self._delete(s)),
])),
]))
self._page.update()
for s in servers_core.list_servers():
self._page.run_task(self._check, s)
async def _check(self, s):
info = await check_server(s)
txt = "🟢 онлайн" if info.get("online") else "🔴 оффлайн"
self._statuses = getattr(self, "_statuses", {})
self._statuses[s.id] = txt
for i, srv in enumerate(servers_core.list_servers()):
if i < len(self.table.rows):
self.table.rows[i].cells[4].content = ft.Text(
self._statuses.get(srv.id, "…"))
self._page.update()
# ---------- диалоги ----------
def _form(self, s=None):
return {
"name": ft.TextField(label="Имя", value=s.name if s else "", width=300),
"host": ft.TextField(label="Хост", value=s.host if s else "", width=300),
"port": ft.TextField(label="Порт", value=str(s.port) if s else "22", width=100),
"username": ft.TextField(label="Пользователь", value=s.username if s else "root", width=300),
"auth_type": ft.Dropdown(
label="Авторизация", width=300,
value=s.auth_type if s else "password",
options=[
ft.dropdown.Option("password", "Пароль"),
ft.dropdown.Option("key", "SSH-ключ"),
],
),
"password": ft.TextField(label="Пароль / passphrase", password=True,
value=s.password if s else "", width=300),
"key_path": ft.TextField(label="Путь к ключу", value=s.key_path or "" if s else "",
width=300),
"sudo_password": ft.TextField(label="Sudo-пароль (если отличается)", password=True,
value=s.sudo_password or "" if s else "", width=300),
"tags": ft.TextField(label="Теги (через запятую)", value=s.tags if s else "", width=300),
}
def _add_dialog(self, e):
self._open_form("Новый сервер", self._form(), None)
def _edit_dialog(self, s):
self._open_form(f"Изменить: {s.name}", self._form(s), s.id)
def _open_form(self, title, f, server_id):
def save(e):
fields = dict(
name=f["name"].value, host=f["host"].value,
port=int(f["port"].value or 22), username=f["username"].value,
auth_type=f["auth_type"].value,
password=f["password"].value or None,
key_path=f["key_path"].value or None,
sudo_password=f["sudo_password"].value or None,
tags=f["tags"].value,
)
if server_id:
servers_core.update_server(server_id, **fields)
else:
servers_core.add_server(**fields)
self._page.pop_dialog()
self.reload()
snack(self._page, "Сохранено")
def test(e):
async def _t():
fields = dict(
id=0, name=f["name"].value or "test", host=f["host"].value,
port=int(f["port"].value or 22), username=f["username"].value,
auth_type=f["auth_type"].value,
password=f["password"].value or None,
key_path=f["key_path"].value or None,
sudo_password=f["sudo_password"].value or None,
tags="", color="blue", read_only=False,
)
from app.db.models import Server
info = await check_server(Server(**fields))
if info.get("online"):
snack(self._page, f"✅ {info['os']} | {info['uptime']}")
else:
snack(self._page, f"❌ {info.get('error')}", error=True)
self._page.run_task(_t)
dlg = ft.AlertDialog(
modal=True,
title=ft.Text(title),
content=ft.Column(list(f.values()), tight=True,
scroll=ft.ScrollMode.AUTO, height=480),
actions=[
ft.TextButton("Проверить подключение", on_click=test),
ft.TextButton("Отмена", on_click=lambda e: self._close(dlg)),
ft.FilledButton("Сохранить", on_click=save),
],
)
self._page.show_dialog(dlg)
def _close(self, dlg):
self._page.pop_dialog()
def _delete(self, s):
confirm_dialog(
self._page, "Удалить сервер?",
f"Сервер «{s.name}» ({s.host}) будет удалён из приложения. На самом сервере ничего не изменится.",
lambda: (servers_core.delete_server(s.id), self.reload()),
)