scm-harmonic-cmts-admin/routers/terminal.py

100 lines
3.7 KiB
Python
Raw Normal View History

import asyncio
import asyncssh
from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from logger import get_logger
logger = get_logger("app.ssh")
router = APIRouter()
@router.websocket("/ws/terminal")
async def websocket_terminal(websocket: WebSocket, host: str, username: str, password: str):
await websocket.accept()
conn = None
process = None
ws_task = None
ssh_task = None
try:
# 建立連線
conn = await asyncssh.connect(host, username=username, password=password, known_hosts=None)
# 💡 關鍵修復:使用 term_size 參數來指定寬高 (width, height)
process = await conn.create_process(
term_type='xterm-256color',
term_size=(80, 24), # 正確的 asyncssh 參數格式
encoding=None
)
async def forward_to_ws():
try:
while True:
data_bytes = await process.stdout.read(8192)
if not data_bytes:
logger.debug("設備端主動關閉了 stdout 通道")
break
safe_text = data_bytes.decode('utf-8', errors='replace')
await websocket.send_text(safe_text)
except asyncio.CancelledError:
pass
except Exception as e:
logger.error("❌ [WS Forward Error] 讀取設備畫面時發生錯誤", exc_info=True)
async def forward_to_ssh():
try:
while True:
data_text = await websocket.receive_text()
data_bytes = data_text.encode('utf-8')
# 攔截 xterm.js 預設的 DEL (\x7f),轉換為 BS (\x08)
if b'\x7f' in data_bytes:
data_bytes = data_bytes.replace(b'\x7f', b'\x08')
process.stdin.write(data_bytes)
await process.stdin.drain()
except WebSocketDisconnect:
# 主動拋出,讓外層捕捉以進行資源回收
feat(core): 系統底層架構重構、資安強化與極致效能優化 本次提交涵蓋了 Phase 3.5 至 Phase 4 的核心架構翻修,徹底解決了 SSH 互動陷阱、資料庫冗餘、機密外洩以及巨量 DOM 渲染的效能瓶頸。 🏗️ 1. 資料庫與快取架構整併 (DB Consolidation) - 重構 `cmts_options` 資料表,主鍵簡化為 `(host, path)`,徹底解耦 `config_type`。 - 實現 `running-tree` 與 `full-tree` 共用全域選項字典,達成極致的「增量掃描 (Incremental Scanning)」。 - 強化 `init_db.py` 防呆機制,保留 `config_backups` 避免誤刪珍貴快照。 🔒 2. 企業級資安與權限管理 (Security Hardening) - 導入 `python-dotenv`,全面落實 12-Factor App 規範。 - 徹底淨化 `index.html`, `shared.py`, `database.py` 中的硬編碼 (Hardcoded) 密碼。 - 建立 `.env.example` 範例檔,確保正式機密碼「零外洩 (Zero Secrets Leakage)」。 - 強化 God Mode 驗證邏輯,未設定環境變數時預設拒絕所有進階授權 (Fail-Fast)。 📡 3. SSH 引擎與 SSE 廣播機制 (SSH & SSE Engine) - 修復 SSH Echo 與 MOTD 陷阱,導入精準的 `prompt_pattern` 絕對靜音偵測。 - 廢除危險的 `Ctrl+C`,改用底層 `Ctrl+U (\x15)` 瞬間清空輸入緩衝區,確保 100% 停留在 config 模式。 - 完善 SSE (Server-Sent Events) 廣播機制,前端精準呈現「百分比進度條」與「動態狀態文字」。 ⚡ 4. 前端效能與使用者體驗 (Performance & UX) - 升級 Regex 解析器,完美支援水平列表與無標題說明,成功找回消失的 `❓` 提示。 - 導入 HTML5 `<datalist>`,將 Enum 欄位無痛升級為「半自由下拉選單 (Combobox)」。 - 解決「延遲載入 (Lazy Load)」與「編輯模式」的衝突:實作「預先渲染但不展開」機制,結合 `content-visibility: auto` 達成零效能損耗。 - 實作 Xterm.js DOM 事件防火牆 (Capture Phase Interception),完美阻擋第三方外掛產生的殘缺鍵盤事件,消除 Console 紅字報錯。 ⚠️ Breaking Changes (破壞性變更) - 系統啟動前必須建立 `.env` 檔案並填入必要的資料庫與設備連線資訊。 - 資料庫結構已變更,舊有環境需執行 `python init_db.py --reset` 重新建表。
2026-06-10 06:37:16 +00:00
# raise
# 🌟 修正:不要再 raise 拋出去了,直接 return 結束任務,讓外層自然回收
logger.debug("前端 WebSocket 正常斷開 (使用者重整或關閉網頁)")
return
except asyncio.CancelledError:
pass
except Exception as e:
logger.error("❌ [SSH Forward Error] 寫入指令到設備時發生錯誤", exc_info=True)
ws_task = asyncio.create_task(forward_to_ws())
ssh_task = asyncio.create_task(forward_to_ssh())
done, pending = await asyncio.wait(
[ws_task, ssh_task],
return_when=asyncio.FIRST_COMPLETED
)
for task in pending:
task.cancel()
except WebSocketDisconnect:
logger.debug("WebSocket 正常斷線,正在終止背景任務...")
if ws_task and not ws_task.done():
ws_task.cancel()
if ssh_task and not ssh_task.done():
ssh_task.cancel()
except Exception as e:
error_msg = f"\r\n\x1b[31mSSH Connection Error: {str(e)}\x1b[0m\r\n"
try:
await websocket.send_text(error_msg)
except:
pass
logger.error("❌ [Connection Error] 建立連線或執行過程中發生錯誤", exc_info=True)
finally:
# 🌟 確保 process 與 conn 絕對被關閉,防止 Memory Leak
if process:
process.close()
if conn:
conn.close()
try:
await websocket.close()
except:
pass