159 lines
7.2 KiB
Python
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()),
|
||
|
|
)
|