Files
debian-manager/app/core/terminal.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

70 lines
2.3 KiB
Python

"""Интерактивный терминал: PTY-сессия по SSH с потоковым вводом/выводом."""
from __future__ import annotations
import asyncio
from typing import Callable, Optional
import asyncssh
from app.db.models import Server
class TerminalSession:
"""PTY-терминал к серверу. output_cb(str) вызывается для каждого куска вывода."""
def __init__(self, server: Server, output_cb: Callable[[str], None],
cols: int = 120, rows: int = 30):
self.server = server
self.output_cb = output_cb
self.cols = cols
self.rows = rows
self._conn: Optional[asyncssh.SSHClientConnection] = None
self._proc: Optional[asyncssh.SSHClientProcess] = None
self._closed = False
async def open(self) -> None:
kwargs: dict = dict(
host=self.server.host, port=self.server.port,
username=self.server.username, known_hosts=None, connect_timeout=15,
)
if self.server.auth_type == "key" and self.server.key_path:
kwargs["client_keys"] = [self.server.key_path]
if self.server.password:
kwargs["passphrase"] = self.server.password
else:
kwargs["password"] = self.server.password
self._conn = await asyncssh.connect(**kwargs)
self._proc = await self._conn.create_process(
term_type="xterm-256color",
term_size=(self.cols, self.rows),
)
asyncio.ensure_future(self._reader())
async def _reader(self) -> None:
try:
while not self._closed and self._proc:
data = await self._proc.stdout.read(4096)
if not data:
break
self.output_cb(data)
except Exception:
pass
def write(self, data: str) -> None:
if self._proc:
self._proc.stdin.write(data)
def resize(self, cols: int, rows: int) -> None:
if self._proc:
self._proc.change_terminal_size(cols, rows)
def close(self) -> None:
self._closed = True
try:
if self._proc:
self._proc.terminate()
if self._conn:
self._conn.close()
except Exception:
pass