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:
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
主机新增/编辑对话框
|
||||
"""
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit, QSpinBox,
|
||||
QPushButton, QDialogButtonBox, QFileDialog, QMessageBox, QCheckBox,
|
||||
QGroupBox, QTabWidget, QWidget, QTextEdit,
|
||||
)
|
||||
|
||||
|
||||
class HostDialog(QDialog):
|
||||
def __init__(self, parent=None, current: dict = None):
|
||||
super().__init__(parent)
|
||||
self.current = current or {}
|
||||
title = "编辑主机" if current else "新增主机"
|
||||
self.setWindowTitle(title)
|
||||
self.resize(480, 380)
|
||||
self._build()
|
||||
self._load()
|
||||
|
||||
def _build(self):
|
||||
v = QVBoxLayout(self)
|
||||
|
||||
form = QFormLayout()
|
||||
|
||||
self.name_edit = QLineEdit()
|
||||
self.name_edit.setPlaceholderText("给这台主机起个名字(用于显示)")
|
||||
form.addRow("名称:", self.name_edit)
|
||||
|
||||
self.host_edit = QLineEdit()
|
||||
self.host_edit.setPlaceholderText("192.168.1.1 或 example.com")
|
||||
form.addRow("主机/IP:", self.host_edit)
|
||||
|
||||
self.port_spin = QSpinBox()
|
||||
self.port_spin.setRange(1, 65535)
|
||||
self.port_spin.setValue(22)
|
||||
form.addRow("端口:", self.port_spin)
|
||||
|
||||
self.user_edit = QLineEdit()
|
||||
self.user_edit.setPlaceholderText("root")
|
||||
form.addRow("用户名:", self.user_edit)
|
||||
|
||||
self.pwd_edit = QLineEdit()
|
||||
self.pwd_edit.setEchoMode(QLineEdit.Password)
|
||||
self.pwd_edit.setPlaceholderText("密码(密钥无密码可留空)")
|
||||
form.addRow("密码:", self.pwd_edit)
|
||||
|
||||
# 私钥
|
||||
key_row = QHBoxLayout()
|
||||
self.key_edit = QLineEdit()
|
||||
self.key_edit.setPlaceholderText("可选,例如 C:/Users/xxx/.ssh/id_rsa")
|
||||
self.btn_browse = QPushButton("浏览...")
|
||||
self.btn_browse.clicked.connect(self._browse_key)
|
||||
key_row.addWidget(self.key_edit, 1)
|
||||
key_row.addWidget(self.btn_browse)
|
||||
form.addRow("私钥文件:", key_row)
|
||||
|
||||
self.show_pwd = QCheckBox("显示密码")
|
||||
self.show_pwd.toggled.connect(
|
||||
lambda c: self.pwd_edit.setEchoMode(QLineEdit.Normal if c else QLineEdit.Password))
|
||||
form.addRow("", self.show_pwd)
|
||||
|
||||
self.notes_edit = QTextEdit()
|
||||
self.notes_edit.setMaximumHeight(60)
|
||||
self.notes_edit.setPlaceholderText("备注(仅本地保存)")
|
||||
form.addRow("备注:", self.notes_edit)
|
||||
|
||||
v.addLayout(form)
|
||||
|
||||
bb = QDialogButtonBox()
|
||||
bb.addButton(QDialogButtonBox.Save).clicked.connect(self._save)
|
||||
bb.addButton(QDialogButtonBox.Cancel).clicked.connect(self.reject)
|
||||
v.addWidget(bb)
|
||||
|
||||
def _browse_key(self):
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择私钥文件", "", "所有文件 (*)")
|
||||
if path:
|
||||
self.key_edit.setText(path)
|
||||
|
||||
def _load(self):
|
||||
c = self.current
|
||||
self.name_edit.setText(c.get("name", ""))
|
||||
self.host_edit.setText(c.get("host", ""))
|
||||
self.port_spin.setValue(int(c.get("port", 22)))
|
||||
self.user_edit.setText(c.get("username", ""))
|
||||
self.pwd_edit.setText(c.get("password", ""))
|
||||
self.key_edit.setText(c.get("key_path", ""))
|
||||
self.notes_edit.setPlainText(c.get("notes", ""))
|
||||
|
||||
def _save(self):
|
||||
host = self.host_edit.text().strip()
|
||||
user = self.user_edit.text().strip()
|
||||
if not host or not user:
|
||||
QMessageBox.warning(self, "校验失败", "主机和用户名不能为空")
|
||||
return
|
||||
self.accept()
|
||||
|
||||
def get_value(self) -> dict:
|
||||
return {
|
||||
"name": self.name_edit.text().strip() or self.host_edit.text().strip(),
|
||||
"host": self.host_edit.text().strip(),
|
||||
"port": self.port_spin.value(),
|
||||
"username": self.user_edit.text().strip(),
|
||||
"password": self.pwd_edit.text(),
|
||||
"key_path": self.key_edit.text().strip(),
|
||||
"notes": self.notes_edit.toPlainText().strip(),
|
||||
}
|
||||
Reference in New Issue
Block a user