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,168 @@
|
||||
"""Gitea: деплой compose-проектов."""
|
||||
import flet as ft
|
||||
|
||||
from app.core import gitea
|
||||
from app.core import servers as servers_core
|
||||
from app.core.ssh import SSHClient
|
||||
from app.core.tasks import tasks
|
||||
from app.ui.components import append_log, log_view, snack
|
||||
|
||||
|
||||
class GiteaPage(ft.Column):
|
||||
def __init__(self, page: ft.Page, go):
|
||||
super().__init__(expand=True, scroll=ft.ScrollMode.AUTO, spacing=12)
|
||||
self._page = page
|
||||
|
||||
cfg = gitea.get_config()
|
||||
self.url_tf = ft.TextField(label="URL Gitea", value=cfg["url"], width=350)
|
||||
self.token_tf = ft.TextField(label="API-токен", value=cfg["token"],
|
||||
password=True, width=350)
|
||||
self.status = ft.Text("", size=12)
|
||||
|
||||
self.repos_dd = ft.Dropdown(label="Репозиторий", width=350, options=[])
|
||||
self.srv_dd = ft.Dropdown(label="Сервер", width=250, options=[
|
||||
ft.dropdown.Option(str(s.id), s.name) for s in servers_core.list_servers()
|
||||
])
|
||||
self.branch_tf = ft.TextField(label="Ветка", value="main", width=150)
|
||||
self.compose_tf = ft.TextField(label="Путь к compose", value="docker-compose.yml",
|
||||
width=250)
|
||||
self.env_tf = ft.TextField(label=".env (опционально)", multiline=True,
|
||||
min_lines=3, max_lines=5, width=450,
|
||||
hint_text="KEY=value")
|
||||
self.log = log_view()
|
||||
self.log_container = ft.Container(
|
||||
height=200, border=ft.Border.all(1, ft.Colors.GREY_800),
|
||||
border_radius=8, padding=8, content=self.log, visible=False)
|
||||
self.bindings_col = ft.Column(spacing=4)
|
||||
|
||||
self.controls = [
|
||||
ft.Text("Gitea Deploy", size=28, weight=ft.FontWeight.BOLD),
|
||||
ft.Row([self.url_tf, self.token_tf,
|
||||
ft.FilledButton("Сохранить и проверить", on_click=self._save_cfg)]),
|
||||
self.status,
|
||||
ft.Divider(),
|
||||
ft.Row([self.repos_dd,
|
||||
ft.IconButton(ft.Icons.REFRESH, tooltip="Загрузить репозитории",
|
||||
on_click=self._load_repos)]),
|
||||
ft.Row([self.srv_dd, self.branch_tf, self.compose_tf]),
|
||||
self.env_tf,
|
||||
ft.FilledButton("Деплой", icon=ft.Icons.ROCKET_LAUNCH,
|
||||
on_click=self._deploy),
|
||||
self.log_container,
|
||||
ft.Divider(),
|
||||
ft.Text("Webhook-автодеплой (push → redeploy)", weight=ft.FontWeight.BOLD, size=16),
|
||||
ft.Text("Приложение слушает порт 9100. В Gitea: репозиторий → Настройки → Webhooks → "
|
||||
"создать: http://<этот-ПК>:9100/webhooks/gitea, событие push.",
|
||||
size=12, color=ft.Colors.GREY_500),
|
||||
ft.Row([
|
||||
ft.FilledButton("Добавить привязку", icon=ft.Icons.ADD,
|
||||
on_click=self._binding_dialog),
|
||||
ft.IconButton(ft.Icons.REFRESH, on_click=lambda e: self._load_bindings()),
|
||||
]),
|
||||
self.bindings_col,
|
||||
]
|
||||
self._load_bindings()
|
||||
|
||||
def _load_bindings(self):
|
||||
from app.core import webhook as wh
|
||||
from app.core import servers as servers_core
|
||||
servers = {s.id: s.name for s in servers_core.list_servers()}
|
||||
self.bindings_col.controls = []
|
||||
for b in wh.list_bindings():
|
||||
self.bindings_col.controls.append(ft.Card(content=ft.Container(
|
||||
padding=10,
|
||||
content=ft.Row([
|
||||
ft.Icon(ft.Icons.LINK, color=ft.Colors.BLUE_300),
|
||||
ft.Text(f"{b.repo}@{b.branch} → {servers.get(b.server_id, '?')}",
|
||||
size=13, expand=True),
|
||||
ft.Text(b.compose_path, size=11, color=ft.Colors.GREY_500),
|
||||
ft.IconButton(ft.Icons.DELETE_OUTLINE, icon_size=16,
|
||||
on_click=lambda e, bid=b.id: self._del_binding(bid)),
|
||||
]),
|
||||
)))
|
||||
if not self.bindings_col.controls:
|
||||
self.bindings_col.controls.append(
|
||||
ft.Text("привязок нет — push в репозиторий не будет триггерить деплой",
|
||||
size=12, color=ft.Colors.GREY_500))
|
||||
self._page.update()
|
||||
|
||||
def _del_binding(self, bid):
|
||||
from app.core import webhook as wh
|
||||
wh.delete_binding(bid)
|
||||
self._load_bindings()
|
||||
|
||||
def _binding_dialog(self, e):
|
||||
from app.core import webhook as wh
|
||||
from app.core import servers as servers_core
|
||||
repo_tf = ft.TextField(label="Репозиторий (owner/name)", width=300,
|
||||
hint_text="dimon/myapp")
|
||||
srv_dd = ft.Dropdown(label="Сервер", width=250, options=[
|
||||
ft.dropdown.Option(str(s.id), s.name) for s in servers_core.list_servers()
|
||||
])
|
||||
branch_tf = ft.TextField(label="Ветка", value="main", width=150)
|
||||
compose_tf = ft.TextField(label="compose-файл", value="docker-compose.yml", width=250)
|
||||
env_tf = ft.TextField(label=".env (опционально)", multiline=True,
|
||||
min_lines=2, max_lines=4, width=350)
|
||||
|
||||
def save(e):
|
||||
if not repo_tf.value or not srv_dd.value:
|
||||
return
|
||||
wh.add_binding(repo=repo_tf.value.strip(),
|
||||
server_id=int(srv_dd.value),
|
||||
branch=branch_tf.value, compose_path=compose_tf.value,
|
||||
env=env_tf.value or "")
|
||||
self._page.pop_dialog()
|
||||
self._load_bindings()
|
||||
snack(self._page, "Привязка добавлена")
|
||||
|
||||
dlg = ft.AlertDialog(
|
||||
modal=True, title=ft.Text("Привязка webhook"),
|
||||
content=ft.Column([repo_tf, srv_dd, branch_tf, compose_tf, env_tf], tight=True),
|
||||
actions=[
|
||||
ft.TextButton("Отмена", on_click=lambda e: self._page.pop_dialog()),
|
||||
ft.FilledButton("Сохранить", on_click=save),
|
||||
],
|
||||
)
|
||||
self._page.show_dialog(dlg)
|
||||
|
||||
def _save_cfg(self, e):
|
||||
gitea.save_config(self.url_tf.value, self.token_tf.value)
|
||||
async def _t():
|
||||
ok, msg = await gitea.test_connection()
|
||||
self.status.value = ("✅ " if ok else "❌ ") + msg
|
||||
self.status.color = ft.Colors.GREEN_300 if ok else ft.Colors.RED_300
|
||||
self._page.update()
|
||||
self._page.run_task(_t)
|
||||
|
||||
def _load_repos(self, e):
|
||||
async def _l():
|
||||
try:
|
||||
repos = await gitea.list_repos()
|
||||
self.repos_dd.options = [
|
||||
ft.dropdown.Option(r["full_name"],
|
||||
f"{r['full_name']} ({r['default_branch']})")
|
||||
for r in repos
|
||||
]
|
||||
self._repos = {r["full_name"]: r for r in repos}
|
||||
self.status.value = f"загружено {len(repos)} репозиториев"
|
||||
except Exception as ex:
|
||||
self.status.value = f"ошибка: {ex}"
|
||||
self._page.update()
|
||||
self._page.run_task(_l)
|
||||
|
||||
def _deploy(self, e):
|
||||
if not self.repos_dd.value or not self.srv_dd.value:
|
||||
snack(self._page, "Выберите репозиторий и сервер", error=True)
|
||||
return
|
||||
repo = self._repos[self.repos_dd.value]
|
||||
srv = servers_core.get_server(int(self.srv_dd.value))
|
||||
self.log_container.visible = True
|
||||
|
||||
async def job(log):
|
||||
async with SSHClient(srv) as c:
|
||||
await gitea.deploy(c, repo["clone_url"], repo["name"],
|
||||
self.branch_tf.value, self.compose_tf.value,
|
||||
self.env_tf.value or None, log)
|
||||
tasks.start("gitea", f"deploy {repo['name']}", srv.id, job,
|
||||
on_log=lambda l: (append_log(self.log, l), self._page.update()))
|
||||
snack(self._page, f"Деплой {repo['name']} запущен")
|
||||
Reference in New Issue
Block a user