mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 04:20:45 +08:00
fix(webcrt): resize hop PTY before nested CLI jump
Nested stelnet/telnet captures hop TTY width at start; Netmiko's default 511 cols made mid-line edit redraws wrap and garble in ~80-col WebCRT. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
b690914f18
commit
b540ea21c1
6 changed files with 88 additions and 3 deletions
|
|
@ -497,11 +497,32 @@ def _hop_netmiko_device_type(vendor: str, hop_protocol: str) -> str:
|
|||
return normalize_netmiko_device_type(base, hop_protocol)
|
||||
|
||||
|
||||
def _resize_pty(conn: ConnectHandler, cols: int | None = None, rows: int | None = None) -> None:
|
||||
"""Set SSH PTY size so nested telnet/stelnet inherits the interactive terminal geometry.
|
||||
|
||||
Netmiko defaults to 511x1000. If a CLI hop jump runs at that size and WebCRT is ~80
|
||||
columns, mid-line edit redraws (spaces / clear-to-EOL) wrap and garble the display.
|
||||
"""
|
||||
if cols is None and rows is None:
|
||||
return
|
||||
channel = getattr(conn, "remote_conn", None)
|
||||
if channel is None or not hasattr(channel, "resize_pty"):
|
||||
return
|
||||
c = max(20, min(500, int(cols if cols is not None else 80)))
|
||||
r = max(5, min(200, int(rows if rows is not None else 24)))
|
||||
try:
|
||||
channel.resize_pty(width=c, height=r)
|
||||
except Exception:
|
||||
_log.debug("resize_pty failed cols=%s rows=%s", c, r, exc_info=True)
|
||||
|
||||
|
||||
def _connect_via_cli_hop(
|
||||
creds: dict[str, Any],
|
||||
*,
|
||||
session_timeout: int | None = None,
|
||||
session_log: Any = None,
|
||||
cols: int | None = None,
|
||||
rows: int | None = None,
|
||||
) -> ConnectHandler:
|
||||
"""Login to ZTE/Huawei/Cisco hop NE, run CLI jump command, then target secondary auth."""
|
||||
hop_host = str(creds.get("hop_host") or "").strip()
|
||||
|
|
@ -524,6 +545,9 @@ def _connect_via_cli_hop(
|
|||
)
|
||||
conn = ConnectHandler(**hop_dev)
|
||||
try:
|
||||
# MUST resize before stelnet/telnet — nested session captures hop TTY size at start
|
||||
# and often ignores later WINCH. Wrong width → mid-line edit redraw wraps in WebCRT.
|
||||
_resize_pty(conn, cols, rows)
|
||||
pre = _read_channel(conn, wait=0.5)
|
||||
hop_prompt = extract_cli_prompt_marker(pre)
|
||||
if not hop_prompt:
|
||||
|
|
@ -713,6 +737,8 @@ def open_netmiko_connection(
|
|||
*,
|
||||
session_timeout: int | None = None,
|
||||
session_log: Any = None,
|
||||
cols: int | None = None,
|
||||
rows: int | None = None,
|
||||
) -> ConnectHandler:
|
||||
"""Open a Netmiko connection to the target NE (direct or via configured hop)."""
|
||||
if creds.get("hop_enabled"):
|
||||
|
|
@ -721,5 +747,11 @@ def open_netmiko_connection(
|
|||
return _connect_via_linux_hop(creds, session_timeout=session_timeout, session_log=session_log)
|
||||
if vendor == "bastion":
|
||||
return _connect_via_bastion(creds, session_timeout=session_timeout, session_log=session_log)
|
||||
return _connect_via_cli_hop(creds, session_timeout=session_timeout, session_log=session_log)
|
||||
return _connect_via_cli_hop(
|
||||
creds,
|
||||
session_timeout=session_timeout,
|
||||
session_log=session_log,
|
||||
cols=cols,
|
||||
rows=rows,
|
||||
)
|
||||
return _connect_direct(creds, session_timeout=session_timeout, session_log=session_log)
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ async def websocket_session(websocket: WebSocket, session_id: str) -> None:
|
|||
"rows": sess.rows,
|
||||
"device_type": sess.device_type,
|
||||
"vendor": sess.vendor,
|
||||
"cli_hop": bool(sess.cli_hop_guard),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -593,7 +593,11 @@ def create_session(
|
|||
log_buf = io.BytesIO()
|
||||
try:
|
||||
conn = open_netmiko_connection(
|
||||
creds, session_timeout=connect_timeout, session_log=log_buf
|
||||
creds,
|
||||
session_timeout=connect_timeout,
|
||||
session_log=log_buf,
|
||||
cols=c,
|
||||
rows=r,
|
||||
)
|
||||
except Exception as exc:
|
||||
partial = _session_log_text(log_buf).strip()
|
||||
|
|
@ -686,6 +690,7 @@ def create_session(
|
|||
"cols": sess.cols,
|
||||
"rows": sess.rows,
|
||||
"ws_path": f"/v1/webcrt/sessions/{session_id}/ws",
|
||||
"cli_hop": bool(hop_guard),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue