Initial commit: Debian Manager — управление Debian-серверами
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""Серверы: список, добавление, редактирование, удаление."""
|
||||
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()),
|
||||
)
|
||||
Reference in New Issue
Block a user