f624fee5e0
- SSH/SFTP управление без агентов - Пакеты (apt), Docker, файлы, systemd, ufw, cron - Плейбуки, групповые операции, планировщик - Бэкапы, отчёты, Portainer, Proxmox VE, Gitea Deploy - Терминал, Telegram-уведомления, тёмная/светлая тема
107 lines
4.8 KiB
Python
107 lines
4.8 KiB
Python
"""Portainer: статус, открытие в браузере, установка/перезапуск."""
|
|
import flet as ft
|
|
|
|
from app.core.ssh import SSHClient
|
|
from app.core import docker_svc as d
|
|
from app.db.models import Server
|
|
from app.ui.components import snack
|
|
|
|
PORTAINER_PORT = 9000
|
|
|
|
|
|
class PortainerPage(ft.Column):
|
|
def __init__(self, page: ft.Page, go):
|
|
super().__init__(expand=True, scroll=ft.ScrollMode.AUTO, spacing=12)
|
|
self._page = page
|
|
self.go = go
|
|
self.cards_col = ft.Column(spacing=10)
|
|
self.controls = [
|
|
ft.Text("Portainer", size=28, weight=ft.FontWeight.BOLD),
|
|
ft.Row([
|
|
ft.OutlinedButton("Обновить", icon=ft.Icons.REFRESH,
|
|
on_click=lambda e: self._reload()),
|
|
]),
|
|
self.cards_col,
|
|
]
|
|
self._reload()
|
|
|
|
def _reload(self):
|
|
from app.core import servers as servers_core
|
|
self.cards_col.controls = []
|
|
for srv in servers_core.list_servers():
|
|
self.cards_col.controls.append(self._card_placeholder(srv))
|
|
self._page.update()
|
|
from app.core import servers as servers_core
|
|
for srv, card in zip(servers_core.list_servers(), self.cards_col.controls):
|
|
self._page.run_task(self._fill, srv, card)
|
|
|
|
def _card_placeholder(self, srv):
|
|
return ft.Card(content=ft.Container(
|
|
padding=14, width=500,
|
|
content=ft.Column([
|
|
ft.Text(srv.name, weight=ft.FontWeight.BOLD, size=16),
|
|
ft.ProgressBar(height=3),
|
|
]),
|
|
))
|
|
|
|
async def _fill(self, srv, card):
|
|
url = f"http://{srv.host}:{PORTAINER_PORT}"
|
|
try:
|
|
async with SSHClient(srv) as c:
|
|
containers = await d.list_containers(c)
|
|
portainer = next((x for x in containers if "portainer" in x["Names"].lower()), None)
|
|
if not portainer:
|
|
card.content.content.controls[1] = ft.Column([
|
|
ft.Text("Portainer не установлен", color=ft.Colors.GREY_500),
|
|
ft.FilledButton("Установить", icon=ft.Icons.DOWNLOAD,
|
|
on_click=lambda e, s=srv: self._install(s)),
|
|
])
|
|
else:
|
|
running = "Up" in portainer["Status"]
|
|
cid = portainer["ID"]
|
|
card.content.content.controls[1] = ft.Column([
|
|
ft.Row([
|
|
ft.Icon(ft.Icons.CIRCLE, size=12,
|
|
color=ft.Colors.GREEN_400 if running else ft.Colors.RED_400),
|
|
ft.Text(portainer["Status"], size=12),
|
|
]),
|
|
ft.Row([
|
|
ft.FilledButton("Открыть в браузере", icon=ft.Icons.OPEN_IN_BROWSER,
|
|
on_click=lambda e, u=url: self._page.launch_url(u)),
|
|
ft.OutlinedButton(
|
|
"Перезапустить" if running else "Запустить",
|
|
on_click=lambda e, s=srv, c=cid, a="restart" if running else "start":
|
|
self._action(s, c, a)),
|
|
]),
|
|
ft.Text(url, size=11, color=ft.Colors.BLUE_300, selectable=True),
|
|
])
|
|
except Exception as e:
|
|
card.content.content.controls[1] = ft.Text(f"ошибка: {e}", size=12,
|
|
color=ft.Colors.RED_300)
|
|
self._page.update()
|
|
|
|
def _install(self, srv):
|
|
from app.core.tasks import tasks
|
|
from app.ui.components import append_log, log_view
|
|
async def job(log):
|
|
async with SSHClient(srv) as c:
|
|
log("$ docker volume create portainer_data")
|
|
await c.sudo("docker volume create portainer_data 2>/dev/null || true")
|
|
log("$ docker run portainer")
|
|
r = await c.sudo(
|
|
"docker rm -f portainer 2>/dev/null; "
|
|
"docker run -d -p 9000:9000 --name portainer --restart=always "
|
|
"-v /var/run/docker.sock:/var/run/docker.sock "
|
|
"-v portainer_data:/data portainer/portainer-ce:lts", timeout=600)
|
|
log(r.stdout + r.stderr)
|
|
tasks.start("portainer", "установка Portainer", srv.id, job,
|
|
on_done=lambda r: self._reload())
|
|
snack(self._page, f"Установка Portainer на {srv.name} запущена")
|
|
|
|
def _action(self, srv, cid, action):
|
|
async def _do():
|
|
async with SSHClient(srv) as c:
|
|
await d.container_action(c, cid, action)
|
|
self._reload()
|
|
self._page.run_task(_do)
|