fix: terminal command output not displaying - add diagnostics
User reported: '已连接, 但终端啥也不显示'
Likely cause: command ran but stdout was empty / scrolled to wrong place
/ output widget didn't refresh before worker finished.
Changes:
- Eagerly show the typed command in the output area (\$ <cmd>) so user
sees the input was received even if the worker hangs/fails
- Show '(stdout 为空)' placeholder when worker returns no output, so it's
clear the worker actually ran
- Force output.repaint() and ensureCursorVisible() after append
- Add 2 diagnostic buttons in terminal bottom bar:
* [🔍 诊断] prints current SSH connection / worker state
* [▶ 测试命令] pre-fills a known-good test command
- Make CommandWorker explicitly check conn.connected before exec
- Surface full traceback in stderr on exception instead of bare str(e)
All 15 existing tests still pass (core 6/6 + UI 3/3 + E2E 6/6).
This commit is contained in:
+54
-6
@@ -183,9 +183,17 @@ class MainWindow(QMainWindow):
|
|||||||
self.btn_clear_out.clicked.connect(self.output.clear)
|
self.btn_clear_out.clicked.connect(self.output.clear)
|
||||||
self.btn_copy = QPushButton("复制输出")
|
self.btn_copy = QPushButton("复制输出")
|
||||||
self.btn_copy.clicked.connect(lambda: QApplication.clipboard().setText(self.output.toPlainText()))
|
self.btn_copy.clicked.connect(lambda: QApplication.clipboard().setText(self.output.toPlainText()))
|
||||||
|
self.btn_debug = QPushButton("🔍 诊断")
|
||||||
|
self.btn_debug.setToolTip("打印 SSH 连接和 worker 状态")
|
||||||
|
self.btn_debug.clicked.connect(self._debug_state)
|
||||||
|
self.btn_test = QPushButton("▶ 测试命令")
|
||||||
|
self.btn_test.setToolTip("执行 'echo hello-sshclient && pwd && uname -a' 验证连通性")
|
||||||
|
self.btn_test.clicked.connect(lambda: self.cmd_input.setText("echo hello-sshclient && pwd && uname -a"))
|
||||||
bottom.addWidget(self.btn_clear_out)
|
bottom.addWidget(self.btn_clear_out)
|
||||||
bottom.addWidget(self.btn_copy)
|
bottom.addWidget(self.btn_copy)
|
||||||
|
bottom.addWidget(self.btn_debug)
|
||||||
bottom.addStretch(1)
|
bottom.addStretch(1)
|
||||||
|
bottom.addWidget(self.btn_test)
|
||||||
v.addLayout(bottom)
|
v.addLayout(bottom)
|
||||||
|
|
||||||
return w
|
return w
|
||||||
@@ -364,31 +372,71 @@ class MainWindow(QMainWindow):
|
|||||||
if not conn or not conn.connected:
|
if not conn or not conn.connected:
|
||||||
QMessageBox.warning(self, "提示", "当前主机未连接")
|
QMessageBox.warning(self, "提示", "当前主机未连接")
|
||||||
return
|
return
|
||||||
self._append_out(f"\n$ {cmd}\n")
|
# 先在输出区显示命令(即使后续 worker 失败也能看到用户输入了啥)
|
||||||
|
self._append_out(f"\n$ {cmd}")
|
||||||
self.cmd_input.clear()
|
self.cmd_input.clear()
|
||||||
self.btn_run.setEnabled(False)
|
self.btn_run.setEnabled(False)
|
||||||
|
self.btn_run.setText("执行中...")
|
||||||
timeout = self.timeout_spin.value()
|
timeout = self.timeout_spin.value()
|
||||||
self.cmd_worker = CommandWorker(conn, cmd, timeout)
|
self.cmd_worker = CommandWorker(conn, cmd, timeout)
|
||||||
self.cmd_worker.finished_with.connect(self._on_cmd_done)
|
self.cmd_worker.finished_with.connect(self._on_cmd_done)
|
||||||
self.cmd_worker.start()
|
self.cmd_worker.start()
|
||||||
|
# 强制刷新滚动到底
|
||||||
|
self.output.repaint()
|
||||||
|
|
||||||
def _on_cmd_done(self, code: int, out: str, err: str):
|
def _on_cmd_done(self, code: int, out: str, err: str):
|
||||||
self.btn_run.setEnabled(True)
|
self.btn_run.setEnabled(True)
|
||||||
|
self.btn_run.setText("执行")
|
||||||
if out:
|
if out:
|
||||||
self._append_out(out)
|
self._append_out(out)
|
||||||
|
else:
|
||||||
|
self._append_out("(stdout 为空)")
|
||||||
if err:
|
if err:
|
||||||
self._append_out_err(err)
|
self._append_out_err(err)
|
||||||
self._append_out(f"[exit={code}]\n")
|
self._append_out(f"[exit={code}]")
|
||||||
self.statusBar().showMessage(f"命令完成 (退出码 {code})", 3000)
|
self.statusBar().showMessage(f"命令完成 (退出码 {code})", 3000)
|
||||||
|
# 强制刷新 + 滚到底
|
||||||
|
self.output.repaint()
|
||||||
|
|
||||||
def _append_out(self, text: str):
|
def _append_out(self, text: str):
|
||||||
self.output.appendPlainText(text.rstrip("\n"))
|
# 不再 rstrip("\n"),保留原样;appendPlainText 本身会处理换行
|
||||||
|
self.output.appendPlainText(text)
|
||||||
|
# 滚到底
|
||||||
|
sb = self.output.verticalScrollBar()
|
||||||
|
sb.setValue(sb.maximum())
|
||||||
|
self.output.ensureCursorVisible()
|
||||||
|
|
||||||
|
def _append_out_err(self, text: str):
|
||||||
|
self.output.appendPlainText("[ERR] " + text.replace("\n", "\n[ERR] "))
|
||||||
sb = self.output.verticalScrollBar()
|
sb = self.output.verticalScrollBar()
|
||||||
sb.setValue(sb.maximum())
|
sb.setValue(sb.maximum())
|
||||||
|
|
||||||
def _append_out_err(self, text: str):
|
def _debug_state(self):
|
||||||
# 简单的 ANSI/颜色:stderr 用红色(PlainTextEdit 不支持富文本,所以加 [ERR] 前缀)
|
"""诊断按钮:打印当前 SSH 连接和 worker 状态"""
|
||||||
self.output.appendPlainText("[ERR] " + text.rstrip("\n").replace("\n", "\n[ERR] "))
|
lines = ["=== Debug State ==="]
|
||||||
|
lines.append(f"current_host_id: {self.current_host_id}")
|
||||||
|
if self.current_host_id:
|
||||||
|
h = self.manager.get_host(self.current_host_id)
|
||||||
|
lines.append(f"host config: {h}")
|
||||||
|
conn = self.manager.get_connection(self.current_host_id)
|
||||||
|
if conn:
|
||||||
|
lines.append(f"conn.connected: {conn.connected}")
|
||||||
|
lines.append(f"conn.client: {conn.client}")
|
||||||
|
lines.append(f"conn.sftp: {conn.sftp}")
|
||||||
|
if conn.client:
|
||||||
|
try:
|
||||||
|
transport = conn.client.get_transport()
|
||||||
|
lines.append(f"transport active: {transport.is_active() if transport else 'None'}")
|
||||||
|
except Exception as e:
|
||||||
|
lines.append(f"transport error: {e}")
|
||||||
|
else:
|
||||||
|
lines.append("conn: None (not connected)")
|
||||||
|
lines.append(f"cmd_worker: {self.cmd_worker}")
|
||||||
|
if self.cmd_worker:
|
||||||
|
lines.append(f"cmd_worker.isRunning: {self.cmd_worker.isRunning()}")
|
||||||
|
lines.append("=== End Debug ===")
|
||||||
|
self._append_out("\n".join(lines))
|
||||||
|
self.statusBar().showMessage("诊断信息已输出", 3000)
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# AI / 关于
|
# AI / 关于
|
||||||
|
|||||||
+5
-1
@@ -40,9 +40,13 @@ class CommandWorker(QThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
|
if not self.conn or not self.conn.connected or not self.conn.client:
|
||||||
|
self.finished_with.emit(-1, "", "连接已断开或 client 不可用")
|
||||||
|
return
|
||||||
code, out, err = self.conn.exec_command(self.command, timeout=self.timeout)
|
code, out, err = self.conn.exec_command(self.command, timeout=self.timeout)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
code, out, err = -1, "", str(e)
|
import traceback
|
||||||
|
code, out, err = -1, "", f"执行异常: {e}\n{traceback.format_exc()}"
|
||||||
self.finished_with.emit(code, out, err)
|
self.finished_with.emit(code, out, err)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user