feat: SSHClient v1.0.0 - PyQt5 + paramiko 跨平台 SSH 客户端

功能:
- 多主机管理 (增删改查, 密码/私钥双认证, 导入导出)
- 远程终端 (命令执行 + 常用命令快捷栏 + 超时控制)
- SFTP 文件浏览 (上传/下载带进度, 新建/删除/重命名)
- 实时监控 (CPU/内存/磁盘/网络, 1-10秒可调刷新)
- AI Agent (OpenAI 兼容 API, 5 工具自动调用: 命令/指标/列文件/读文件/上传)

技术栈: PyQt5 + paramiko + psutil + requests + PyInstaller
打包: build_windows.bat / build.sh 一键产出 ~57MB 单文件 exe
测试: core 6/6 + UI 3/3 + E2E 6/6 全部通过
This commit is contained in:
Hermes
2026-07-28 21:01:31 +08:00
commit a57dbc0252
22 changed files with 3294 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
"""
UI 渲染截图:用 QWidget.grab() 把主窗口、各 Tab 渲染成 PNG。
不开真实显示器,offscreen 平台即可。
"""
import os
import sys
os.environ["QT_QPA_PLATFORM"] = "offscreen"
sys.path.insert(0, os.path.dirname(__file__))
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QTimer, Qt
from PyQt5.QtGui import QPixmap
from ui.main_window import MainWindow
def main():
out_dir = os.path.join(os.path.dirname(__file__), "screenshots")
os.makedirs(out_dir, exist_ok=True)
app = QApplication(sys.argv)
w = MainWindow()
w.resize(1280, 800)
w.show()
# 等一帧
QTimer.singleShot(500, lambda: grab_and_quit(w, app, out_dir))
app.exec_()
def grab_and_quit(w, app, out_dir):
# 主窗口
pix = w.grab()
p = os.path.join(out_dir, "main_window.png")
pix.save(p, "PNG")
print(f"[1/4] 主窗口: {p} ({pix.width()}x{pix.height()})")
# 各 Tab
for idx, name in enumerate(["terminal", "files", "monitor", "ai"]):
w.tabs.setCurrentIndex(idx)
app.processEvents()
pix = w.grab()
p = os.path.join(out_dir, f"tab_{idx}_{name}.png")
pix.save(p, "PNG")
print(f"[{idx+2}/5] Tab '{name}': {p}")
# 主机对话框
from ui.host_dialog import HostDialog
dlg = HostDialog(w, current={"name": "示例", "host": "192.168.1.1", "port": 22,
"username": "root", "password": "", "key_path": ""})
dlg.show()
app.processEvents()
QTimer.singleShot(200, lambda: finalize(dlg, app, out_dir))
def finalize(dlg, app, out_dir):
pix = dlg.grab()
p = os.path.join(out_dir, "host_dialog.png")
pix.save(p, "PNG")
print(f"[5/5] HostDialog: {p}")
dlg.close()
app.quit()
if __name__ == "__main__":
main()