Initial YakPanel commit
This commit is contained in:
68
YakPanel-server/backend/app/api/terminal.py
Normal file
68
YakPanel-server/backend/app/api/terminal.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""YakPanel - Web Terminal API (WebSocket)"""
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
|
||||
router = APIRouter(prefix="/terminal", tags=["terminal"])
|
||||
|
||||
|
||||
@router.websocket("/ws")
|
||||
async def terminal_websocket(websocket: WebSocket):
|
||||
"""WebSocket terminal - spawns shell and streams I/O"""
|
||||
await websocket.accept()
|
||||
|
||||
token = websocket.query_params.get("token")
|
||||
if token:
|
||||
from app.core.security import decode_token
|
||||
if not decode_token(token):
|
||||
await websocket.close(code=4001)
|
||||
return
|
||||
|
||||
if sys.platform == "win32":
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
"cmd.exe",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
)
|
||||
else:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
"/bin/bash",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
env={**os.environ, "TERM": "xterm-256color"},
|
||||
)
|
||||
|
||||
async def read_stdout():
|
||||
try:
|
||||
while proc.returncode is None and proc.stdout:
|
||||
data = await proc.stdout.read(4096)
|
||||
if data:
|
||||
await websocket.send_text(data.decode("utf-8", errors="replace"))
|
||||
except (WebSocketDisconnect, ConnectionResetError):
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
async def read_websocket():
|
||||
try:
|
||||
while True:
|
||||
msg = await websocket.receive()
|
||||
data = msg.get("text") or (msg.get("bytes") or b"").decode("utf-8", errors="replace")
|
||||
if data and proc.stdin and not proc.stdin.is_closing():
|
||||
proc.stdin.write(data.encode("utf-8"))
|
||||
await proc.stdin.drain()
|
||||
except (WebSocketDisconnect, ConnectionResetError):
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
proc.kill()
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
|
||||
await asyncio.gather(read_stdout(), read_websocket())
|
||||
Reference in New Issue
Block a user