使用Python打造SSH连接的GUI工具(paramiko+tkinter)

前言

本项目开发一个综合性的ssh远程服务器管理工具,用户可添加多台服务器信息,可通过新建、删除、修改来管理服务器信息。具有监控服务器的cpu、内存、进程的功能。可以执行交互性指令,通过vim工具来实现对于文件的编辑。实现了对于文件的上传和下载功能,使用户轻松的管理文件,并提供了批量下载日志文件的功能,以实现对于日志的批量收集,更好的掌握服务器的日志信息。

项目概述

本项目旨在创建一个用户友好的SSH客户端,它集成了服务器连接、命令执行、文件传输等功能。通过图形界面,用户可以轻松地管理多个服务器,执行远程命令,以及上传和下载文件。(注意:本项目使用vim需要先touch创建一个文件,vim一个文件时会自动打开本地的默认文本编辑器,保存会自动 同步内容到Linux中)

项目展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用的技术

Python:版本为3.11。
paramiko:用于SSH连接和SFTP文件传输的Python库。
tkinter:Python的标准GUI库,用于构建用户界面。

功能特点

服务器列表管理:支持服务器信息的增删改查。
SSH连接与命令执行:通过图形界面连接服务器并执行命令。
文件传输:实现文件的上传和下载。
实时监控:实时显示服务器的CPU、内存、磁盘和网络状态。

代码

MySSH 类

MySSH 类负责管理SSH连接和执行相关操作。

# -*- ecoding: utf-8 -*-
# @ModuleName: project
# @Function: 
# @Author: liweijia
# @Time: 2024/5/22 15:21
# #!/usr/bin/python
import os
import re

import paramiko
import tkinter as tk
from tkinter import messagebox
from tkinter import filedialog
import ttkbootstrap as ttk
import json


class MySSH:
    def __init__(self):

        self.servers = []  # 读取server.json。  选择/添加/删除
        self.server = {}
        # 服务器是否连接
        self.connected = False
        # 用来保存当前路径
        self.path = ""
        # 实例化并且建立一个sshclient对象
        self.ssh = paramiko.SSHClient()
        # 将信任的主机自动加入到host_allow列表,需放在connect方法前面
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
        self.cpu_percent = 0.0
        self.memory_percent = 0.0
        self.file_name = None
        self.processes = []
        self.load_server()

        # 此线程用于vim文件编辑功能
        t = threading.Thread(target=self.file_save)
        t.start()

    def load_server(self):
        # 打开文件
        with open("server.json", encoding='utf-8') as fp:
            self.servers = json.load(fp)

    def connect(self):
        # 调用connect方法连接服务器
        self.connected = None
        try:
            # 通过用户名和密码进行连接
            self.ssh.connect(hostname=self.server["ip"], port=self.server["port"], username=self.server["username"],
                             password=self.server["password"])
            print(f'连接到服务器{self.server["ip"]}成功!!!')
            self.connected = True

            # 创建一个交互式的shell窗口
            self.chan = self.ssh.invoke_shell()
            self.chan.settimeout(1000)
            # 刚进入linux服务器等待一会,否则直接通过chan.recv获取的信息不完整
            time.sleep(0.6)
            self.endSymbol = ['# ', '$ ', '> ', '* ']  # 设置我们定义的结束符

        except Exception as e:
            self.connected = False
            print(f"连接到服务器【{self.server['ip']}】失败,失败的原因:{e}")

    def transport(self, server_path, local_path, num):

        '''
        文件上传 如果传入num==1文件上传 num==0文件下载
        server_path:服务器路径
        local_path:本地路径
        num:1为上传操作 0为下载操作
        '''

        sftp = self.ssh.open_sftp()  # 初始化sftp
        file_name = ''
        pattern = r'[^\\\/]+(?=\.[^\\\/.]*$|$)'  # 正则表达式用于将文件名称从路径里抽取出来

        if num == 1:
            try:
                file_name = re.search(pattern, local_path).group(
                    0)
            except Exception as e:
                print(e)
            print(f"{server_path}/{file_name}")
            sftp.put(local_path, f"{server_path}/{file_name}")

        if num == 0:
            try:
                file_name = re.search(pattern, server_path).group(
                    0)
            except Exception as e:
                print(e)

            print(f'{server_path}')
            sftp.get(server_path, f'{local_path}/{file_name}')

    def more_transport(self, local_dir):
        sftp = self.ssh.open_sftp()  # 初始化sftp
        try:
            # 获取服务器的日志文件
            stdin, stdout, stderr = self.ssh.exec_command("ls /var/log/*.log | xargs -I {} basename '{}'")
            file_list = stdout.read().decode("utf-8").strip().split('\n')
            # 遍历文件列表并下载
            for file_name in file_list:
                remote_path = f"/var/log/{file_name}"
                local_path = f"{local_dir}/{file_name}"
                print(f"Downloading {remote_path} to {local_path}")
                sftp.get(remote_path, local_path)
        finally:
            # 关闭SFTP和SSH连接
            sftp.close()


    def runCommand(self, chanT, command, endSymbol):
        # 指令后加 '\n' 表示换行
        chanT.send(command + '\n')
        results = ''
        while True:

            result = chanT.recv(1024).decode('utf-8')
            if 'Last login:' in result: # 如果指令里包含Last...字段就再发送一次请求
                result = chanT.recv(1024).decode('utf-8')

            results += result
            # 判断最后两个字符是否是我们定义的结束符
            if results[-2:] in endSymbol:
                break

        re1 = results.split('\n')[1:]  # 第一行是我们输入的指令,丢弃
        re1 = '\n'.join(re1)

        clean_string = re.sub(r'\x1b\[[0-?]*[ -/]*[@-~]', '', re1)  # 由于我们的GUI只能展示纯文本信息所以选择 通过正则表达式对返回结果的ANSI进行匹配去除

        return clean_string+'\n'


    def vim_cat(self, command):
        '''
        :param command:
        用户编辑文件自动下载到本地,再自动打开本地编辑
        '''
        self.file_name = command.split(' ')[-1]
        self.chan.send('pwd'+'\n')
        path = ''
        while True:
            # 读取一行输出
            result = self.chan.recv(1024).decode('utf-8')
            if 'Last login:' in result:  # 如果指令里包含Last...字段就再发送一次请求
                result = self.chan.recv(1024).decode('utf-8')

            path += result
            # 判断最后两个字符是否是我们定义的结束符
            if path[-2:] in path:
                break

        re1 = path.split('\n')[1:]  # 第一行是我们输入的指令,丢弃
        re1 = re1[:-1]

        self.path = r'\n'.join(re1)
        self.path = self.path.replace('\r','')  # 将pwd返回结果中包含的\r去掉
        path_name=(self.path+'/'+self.file_name)
        ''
        self.transport(path_name,'C:/Users/Public/Documents',0) # 每个微软用户都有的路径
        time.sleep(0.3)
        os.startfile(f'C:\\Users\\Public\\Documents\\{self.file_name}') # 用系统默认软件打开这个可编辑的文件


    def send_file(self):
        '''
        用户点击编辑自动将文件上传到服务器
        :return:
        '''
        local_path = f'C:/Users/Public/Documents/{self.file_name}'
        self.transport(self.path,local_path,1)
        time.sleep(0.2)
        # print('文件已上传')

    def file_save(self):
        '''
        通过当前获取当前时间和文件最后修改时间,判断是否一致,如果一致的话自动上传文件到服务器上
        :return:
        '''
        while True:
            if self.file_name != None:

                file_time = int(os.path.getmtime(f'C:/Users/Public/Documents/{self.file_name}'))
                cur_time = int(time.time())

                if file_time == cur_time:
                    self.send_file()

            time.sleep(0.2)



    def exec2(self, command):
        if command == 'quit':
            print('Bye Bye!')
            exit(0)

            # 如果输入的是vim命令
        elif command[:3] == 'vim':
            # 调用函数查看文件的内容
            self.vim_cat(command)
            return
        else:
            return self.runCommand(self.chan, command, self.endSymbol)

    def close(self):
        # 关闭连接
        self.ssh.close()
        print(f"关闭到服务器【{self.server['ip']}】的连接")
        self.connected = False
        self.server = {}

    def get_cpu_usage(self):
        # 获取cpu使用情况
        # stdin, stdout, stderr = self.ssh.exec_command("top -bn1 | grep 'Cpu(s)'")
        stdin, stdout, stderr = self.ssh.exec_command("top -bn1 | grep 'load' | awk '{print $(NF-2)}'")
        output = stdout.read().decode("utf-8")
        # cpu_usage = re.search(r"\d+.\d+%? us", output).group()
        cpu_usage = output.strip().strip(',')
        return f'{cpu_usage}%'

    def get_memory_usage(self):
        # 获取内存情况
        # stdin, stdout, stderr = self.ssh.exec_command("free -m | grep Mem")
        stdin, stdout, stderr = self.ssh.exec_command("free | grep Mem | awk '{print $3/$2 * 100.0}'")
        output = stdout.read().decode("utf-8")
        # memory_usage = output.split()[2] + " MB"
        memory_usage = output.strip()[:4]
        return f'1.29MB%'

    def get_disk_usage(self):
        # 获取磁盘情况
        # stdin, stdout, stderr = self.ssh.exec_command("free -m | grep Mem")
        stdin, stdout, stderr = self.ssh.exec_command("df -h | grep '^/dev/.*/$' | awk '{print $5}'")
        output = stdout.read().decode("utf-8")
        # memory_usage = output.split()[2] + " MB"
        disk_usage = output.strip().strip('%')
        return f'{disk_usage}%'

    def get_processes(self):
        # 获取进程情况
        stdin, stdout, stderr = self.ssh.exec_command("ps aux")
        output = stdout.read().decode("utf-8")
        return output

    def get_network_status(self):
        # 获取网络延迟情况
        stdin, stdout, stderr = self.ssh.exec_command(f"ping -c 4 8.8.8.8")
        output = stdout.read().decode()

        # Extracting min/avg/max/mdev from ping output
        match = re.search(r"min/avg/max/mdev = (\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)", output)
        if match:
            min_latency, avg_latency, max_latency, mdev_latency = match.groups()
            return {
                "最小延迟": float(min_latency),
                "平均延迟": float(avg_latency),
                "最大延迟": float(max_latency),
                "延迟抖动": float(mdev_latency)
            }
        else:
            return None

MyUI 类

MyUI 类构建GUI界面,响应用户操作。

import time
import threading


# 进行图形化界面的ui
class MyUI:
    def __init__(self, ssh):
        # 服务器操作类的实例
        self.ssh = ssh
        # 应用程序的主窗口
        self.root = ttk.Window(themename="solar")
        # 实例化Frame -框架控件
        self.frame = ttk.Frame(self.root)
        # 网格布局
        self.frame.grid()
        # 输入指令的控件
        self.entry_command = ttk.Entry(self.frame, width=50)
        self.entry_command.grid(row=0, column=0,sticky='w')
        self.entry_command.bind("<Return>", self.ok2)  # 添加一个与该输入框绑定的回车事件,在调用函数中也需要有个对应的参数
        # 执行按钮
        self.btn_ok = ttk.Button(self.frame, text='执行', command=self.ok2, bootstyle="outline", width=7)
        self.btn_ok.grid(row=0, column=1,sticky='w')
        # 显示服务连接信息
        self.label_info = ttk.Label(self.frame, text='服务器无连接')
        self.label_info.grid(row=0, column=2,sticky='w')

        # 结果展示-显示指令返回的结果
        self.result_txt = ttk.Text(self.frame)
        self.result_txt.grid(row=5, columnspan=2)
        # 菜单
        self.init_menu()


        thread_info = threading.Thread(target=self.update_info)
        thread_info.start()
        # -----------------------------------------------------------------------------------------
        # 创建单独的线程更新监视服务器状态
        # thread_monitor = threading.Thread(target=self.monitor)
        # thread_monitor.start()

        self.module_is = True
        # 将程序进行挂起
        self.root.mainloop()

    def ok2(self, event=''): # event作用:用于绑定回车要求的默认形参(在函数中无作用)
        command = self.entry_command.get()
        if self.ssh.connected:
            result = ''
            if command == 'clear':
                self.result_txt.delete('1.0', tk.END)  # 输入 clear 清屏

            try:
                result = self.ssh.exec2(command)
                print(result)
            except Exception as e:
                messagebox.showinfo("", str(e))

            try:
                self.result_txt.insert(tk.END, result)
            except:
                pass
        else:
            messagebox.showinfo("", "服务器尚未连接!")


    # 更新服务器状态
    def update_info(self):
        while True:

            # print(f"更新info:{self.ssh.server}  {self.ssh.connected}")
            if "ip" not in self.ssh.server:
                info = (f"服务器无连接", "white")
            else:
                if self.ssh.connected == None:
                    info = (f"服务器{self.ssh.server['ip']}连接中...", "red")
                elif self.ssh.connected:
                    info = (f"服务器{self.ssh.server['ip']}连接成功", "green")
                    self.module()

                else:
                    info = (f"服务器{self.ssh.server['ip']}连接失败", "red")
            self.label_info.config(text=info[0], foreground=info[1])
            time.sleep(1)

    # 菜单栏
    def init_menu(self):
        menu_top = tk.Menu(self.root)
        # 创建第一个一级菜单 "连接"
        menu_link = tk.Menu(menu_top, tearoff=0)
        menu_top.add_cascade(label='连接', menu=menu_link)
        menu_link.add_command(label="新建", command=self.add_win)
        menu_link.add_command(label="打开", command=self.open_win)
        menu_link.add_command(label="断开连接", command=self.close)
        menu_link.add_separator()

        # 创建第二个一级菜单 "文件"
        file_menu = tk.Menu(menu_top, tearoff=0)
        menu_link.add_cascade(label='文件', menu=file_menu)
        file_menu.add_command(label="文件上传", command=lambda: self.interact_file(1))
        file_menu.add_command(label="文件下载", command=lambda: self.interact_file(0))
        # 创建第二个一级菜单 "文件"
        file_log_menu = tk.Menu(menu_top, tearoff=0)
        menu_link.add_cascade(label='日志文件下载', menu=file_log_menu)
        file_log_menu.add_command(label="批量文件下载", command=self.more_file_log)
        # 将菜单应用到应用程序的主窗口上
        self.root.config(menu=menu_top)


    def more_file_log(self):
        messagebox.showinfo("warning", "请选择你要下载到的文件夹")
        local_dir=filedialog.askdirectory()
        self.ssh.more_transport(local_dir)
        messagebox.showinfo("Success", "日志文件上传成功")
    def change_server_file(self,server):

        # 打开子窗口
        self.ow = tk.Toplevel(self.root)
        frame = tk.Frame(self.ow)
        frame.grid()
        ttk.Label(frame,text='请输输入要修改的内容',bootstyle="warning").pack()

        ip = ttk.Label(frame, text=f"您正在修改的ip为{server['ip']}的服务器",bootstyle="warning") # 按照正常逻辑来说修改不会改变主机ip
        ip.pack()

        ttk.Label(frame).pack()

        port = tk.Label(frame, text="请输入端口号")
        port.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_port = tk.Entry(frame)
        self.entry_port.insert(0, server['port'])
        self.entry_port.pack()

        username = tk.Label(frame, text="请输入用户名")
        username.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_username = tk.Entry(frame)
        self.entry_username.insert(0, server['username'])
        self.entry_username.pack()

        password = tk.Label(frame, text="请输入密码")
        password.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_password = tk.Entry(frame)
        self.entry_password.insert(0, server['password'])
        self.entry_password.pack()

        ds = tk.Label(frame, text="请输入描述")
        ds.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_descript = tk.Entry(frame)
        self.entry_descript.insert(0, server['description'])
        self.entry_descript.pack()

        dict_data = {"ip": server['ip'],
                    "port": int(self.entry_port.get()),
                    "username": self.entry_username.get(),
                    "password": self.entry_password.get(),
                    "description": self.entry_descript.get()
                    }

        # 创建一个 Button 控件,当点击时调用 on_submit 函数
        submit_button = tk.Button(frame, text="提交", command=lambda :self.on_submit_change(dict_data))
        submit_button.pack()

    def on_submit_change(self,dict_data):
        '''
        更改文件内容:
        self.ssh.servers.index(self.entry_ip.get())
        根据ip获取到要修改内容的下标
        :return:
        '''
        with open('server.json', 'r', encoding='utf-8') as f:
            data = json.load(f)

        server_index = self.ssh.servers.index(dict_data) # 确定文件下标
        data[server_index]['port'] = int(self.entry_port.get())
        data[server_index]['username'] = self.entry_username.get()
        data[server_index]['password'] = self.entry_password.get()
        data[server_index]['description'] = self.entry_descript.get()

        with open('server.json', 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=6)


        self.ow.destroy()
        self.ow2.destroy()
        self.ssh.load_server()
        messagebox.showinfo('', '文件修改成功!')
    def interact_file(self, n):

        '''
        n=0 下载文件  n=1 上传文件
        '''

        if self.ssh.connected:
            # 打开子窗口
            self.ow = tk.Toplevel(self.root)
            frame = tk.Frame(self.ow)
            frame.grid()

            if n == 0:
                label = tk.Label(frame, text="文件下载", fg='red')
                label.pack()

                label1 = tk.Label(frame, text="服务器的完整路径(文件)")
                label1.pack()
                # 创建一个Entry文本框供用户输入
                self.entry_server = tk.Entry(frame)
                self.entry_server.pack()

                # 创建一个Button按钮,当点击时调用函数
                button = tk.Button(frame, text="保存文件的本地的路径",
                                   command=lambda: self.operate_file(n=0))  # 这里必须使用lambda才能在函数里传参数
                button.pack()

            if n == 1:
                label = tk.Label(frame, text="文件上传", fg='red')
                label.pack()

                label1 = tk.Label(frame, text="服务器的路径(文件夹)")
                label1.pack()
                # 创建一个Entry文本框供用户输入
                self.entry_server = tk.Entry(frame)
                self.entry_server.pack()

                # 创建一个Button按钮,当点击时调用函数
                button = tk.Button(frame, text="请选择文件在本地的路径", command=lambda: self.operate_file(n=1))
                button.pack()

            if n != 0 and n != 1:
                raise Exception("n only is 1 or 0 !")

        else:
            messagebox.showinfo("", "服务器尚未连接!")

    def operate_file(self, n):

        '''
        上传下载文件
        '''

        try:
            if n == 0:
                self.chose_local_path = filedialog.askdirectory()  # askdirectory()返回文件夹路径
            if n == 1:
                self.chose_local_path = filedialog.askopenfilename()  # 返回文件路径

            self.ssh.transport(server_path=self.entry_server.get(), local_path=self.chose_local_path, num=n)
            messagebox.showinfo("Success", "文件传送成功!")
        except Exception as e:
            messagebox.showinfo('提示', str(e))

        self.ow.destroy()

    def close(self):
        if self.ssh.connected == False:
            messagebox.showinfo("", "服务器尚未连接!")
        else:
            self.ssh.close()

            messagebox.showinfo("Success", "主机已连接关闭")

    def add_win(self):
        print('调用了add_win函数')

        # 打开子窗口
        self.ow = tk.Toplevel(self.root)
        frame = tk.Frame(self.ow)
        frame.grid()

        ip = tk.Label(frame, text="请输入ip")
        ip.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_ip = tk.Entry(frame)
        self.entry_ip.pack()

        port = tk.Label(frame, text="请输入端口号")
        port.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_port = tk.Entry(frame)
        self.entry_port.pack()

        username = tk.Label(frame, text="请输入用户名")
        username.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_username = tk.Entry(frame)
        self.entry_username.pack()

        password = tk.Label(frame, text="请输入密码")
        password.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_password = tk.Entry(frame, show='*')
        self.entry_password.pack()

        ds = tk.Label(frame, text="请输入描述")
        ds.pack()
        # 创建一个Entry文本框供用户输入
        self.entry_descript = tk.Entry(frame)
        self.entry_descript.pack()
        # 创建一个 Button 控件,当点击时调用 on_submit 函数 command=lambda item=item: self.connect(item)
        submit_button = tk.Button(frame, text="提交", command=self.on_submit)
        submit_button.pack()


    def on_submit(self):
        # 获取 Entry 控件中的文本
        ip = self.entry_ip.get()
        port = self.entry_port.get()
        username = self.entry_username.get()
        password = self.entry_password.get()
        ds = self.entry_descript.get()
        if ip == '' or port == '' or username == '' or password == '':
            messagebox.showinfo("Error", "请输入ip或者用户名或者密码或者端口!")
        else:
            # 添加服务器
            self.add()

    def add(self):
        '''
        添加服务器信息
        '''
        with open('server.json', 'r', encoding='utf-8') as f:
            data = json.load(f)

        new_data = {"ip": self.entry_ip.get(),
                    "port": self.entry_port.get(),
                    "username": self.entry_username.get(),
                    "password": self.entry_password.get(),
                    "description": self.entry_descript.get()
                    }

        with open('server.json', 'w', encoding='utf-8') as f:
            data.append(new_data)
            json.dump(data, f, indent=6)

        print('添加成功')
        time.sleep(0.6)
        # 自动销毁子窗口
        self.ow.destroy()

        self.ssh.load_server()  # 更新一下server.json文件
        messagebox.showinfo("Success", "主机添加成功!")

    def open_win(self):
        print('调用了open_win函数')
        # 打开子窗口
        self.ow2 = tk.Toplevel(self.root)
        frame = tk.Frame(self.ow2)
        # 网格布局
        frame.grid()
        row = 0
        for item in self.ssh.servers:
            tk.Label(frame, text=item).grid(row=row, column=0)
            ttk.Button(frame, text='连接', command=lambda item=item: self.connect(item), bootstyle="outline").grid(
                row=row, column=1)
            ttk.Button(frame, text='删除', command=lambda item=item: self.delete(item), bootstyle="outline").grid(
                row=row, column=2)
            ttk.Button(frame, text='修改', command=lambda item=item:self.change_server_file(item), bootstyle="outline").grid(
                row=row, column=3)

            row = row + 1

    def delete(self, server):
        # 打开文件
        with open('server.json', 'w', encoding='utf-8') as f:
            # 存入文件内容,覆盖内容
            delete_host = self.ssh.servers.pop(self.ssh.servers.index(server))
            # servers:列表 server:字典
            # 使用列表的index()方法获取字典的下标再通过列表的pop方法删除
            json.dump(self.ssh.servers, f)
            messagebox.showinfo("Success", "主机ip:" + delete_host["ip"] + "已经删除!")

        self.ssh.load_server()  # 重新加载一下json文件
        self.ow2.destroy()

    def connect(self, server):
        # 连接到对应的服务器
        # 给server赋值
        self.ssh.server = server
        # self.ssh.connect()
        # 连接失败的超时会导致GUI界面无响应,通过多线程或者多进程处理
        thread_connect = threading.Thread(target=self.ssh.connect)
        thread_connect.start()

        # 启动线程实时更新服务器状态
        time.sleep(0.3)
        if self.ssh.connected:
            thread_monitor = threading.Thread(target=self.monitor)
            thread_monitor.start()
        self.ow2.destroy()

    def update_monitor(self):
        # 调用cup,内存,网络延迟,进程的函数对他们进程赋值
        cpu_usage = self.ssh.get_cpu_usage()
        memory_usage = self.ssh.get_memory_usage()
        processes = self.ssh.get_processes()
        network = self.ssh.get_network_status()
        disk = self.ssh.get_disk_usage()

        # 再键赋值的变量插入到这些标签中
        self.cpu_percent_label.config(text=cpu_usage)
        self.memory_percent_label.config(text=memory_usage)
        self.network_percent_label.config(text=network)
        self.disk_percent_label.config(text=disk)
        self.processes_text.delete(1.0, tk.END)
        self.processes_text.insert(tk.END, processes)

    def monitor(self):
        while True:
            if self.ssh.connected:
                self.update_monitor()
                time.sleep(1)
                self.monitor()
            else:
                time.sleep(1)
                continue

    def module(self):
        '''
        展开监控服务器的信息
        :return:
        '''
        if self.module_is:
            # CPU使用情况:
            self.cpu_label = ttk.Label(self.frame, text="CPU使用情况:")
            self.cpu_label.grid(row=3, column=2)

            self.cpu_percent_label = ttk.Label(self.frame, text="")
            self.cpu_percent_label.grid(row=3, column=3)

            # 内存使用情况:
            self.memory_label = ttk.Label(self.frame, text="内存使用情况:")
            self.memory_label.grid(row=4, column=2)

            self.memory_percent_label = ttk.Label(self.frame, text="")

            self.memory_percent_label.grid(row=4, column=3)

            # 运行中的进程:
            # 创建Treeview来显示进程列表
            self.processes_label = ttk.Label(self.frame, text="运行中的进程:")
            self.processes_label.grid(row=5, column=2)

            self.processes_text = tk.Text(self.frame, height=10, width=85, bg='black')
            self.processes_text.grid(row=5, column=3)

            # 网络情况:
            self.network_label = ttk.Label(self.frame, text="网络情况:")
            self.network_label.grid(row=6, column=2)

            self.network_percent_label = ttk.Label(self.frame, text="")
            self.network_percent_label.grid(row=6, column=3)

            # 磁盘情况:
            self.disk_label = ttk.Label(self.frame, text="磁盘情况:")
            self.disk_label.grid(row=7, column=2)

            self.disk_percent_label = ttk.Label(self.frame, text="")
            self.disk_percent_label.grid(row=7, column=3)
            self.module_is = False  # 此代码只用执行一次所以在执行完成渲染gui的功能后自动设置为false

mian函数

if __name__ == '__main__':
    ssh = MySSH()
    ui = MyUI(ssh)

结论

本项目通过Python和相关库,实现了一个功能完备的SSH客户端GUI工具。它不仅简化了服务器的管理操作,还提供了实时监控功能,极大地提升了用户体验和工作效率。

  • 25
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要使用PythonSSH连接程序并带有图形用户界面(GUI),需要使用PythonSSH库和GUI框架。以下是使用Paramiko库和Tkinter框架编的示例代码,可以连接到远程主机并在GUI中显示命令输出。 ```python import paramiko import tkinter as tk from tkinter import scrolledtext class SSH_Client: def __init__(self, hostname, username, password): self.hostname = hostname self.username = username self.password = password self.client = paramiko.SSHClient() self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.connect() def connect(self): try: self.client.connect(hostname=self.hostname, username=self.username, password=self.password) print(f"Connected to {self.hostname} successfully") except paramiko.AuthenticationException: print(f"Authentication failed when connecting to {self.hostname}") raise SystemExit except Exception as e: print(f"Could not connect to {self.hostname}") print(e) raise SystemExit def run_command(self, command): stdin, stdout, stderr = self.client.exec_command(command) output = stdout.read().decode('utf-8') return output class App: def __init__(self, master): self.master = master master.title("SSH Client") self.hostname_label = tk.Label(master, text="Hostname:") self.hostname_label.grid(row=0, column=0) self.hostname_entry = tk.Entry(master) self.hostname_entry.grid(row=0, column=1) self.username_label = tk.Label(master, text="Username:") self.username_label.grid(row=1, column=0) self.username_entry = tk.Entry(master) self.username_entry.grid(row=1, column=1) self.password_label = tk.Label(master, text="Password:") self.password_label.grid(row=2, column=0) self.password_entry = tk.Entry(master, show="*") self.password_entry.grid(row=2, column=1) self.connect_button = tk.Button(master, text="Connect", command=self.connect) self.connect_button.grid(row=3, column=0) self.command_label = tk.Label(master, text="Command:") self.command_label.grid(row=4, column=0) self.command_entry = tk.Entry(master) self.command_entry.grid(row=4, column=1) self.execute_button = tk.Button(master, text="Execute", command=self.execute_command) self.execute_button.grid(row=5, column=0) self.output_label = tk.Label(master, text="Output:") self.output_label.grid(row=6, column=0) self.output_text = scrolledtext.ScrolledText(master, width=50, height=10) self.output_text.grid(row=7, column=0, columnspan=2) self.ssh_client = None def connect(self): self.ssh_client = SSH_Client(self.hostname_entry.get(), self.username_entry.get(), self.password_entry.get()) def execute_command(self): output = self.ssh_client.run_command(self.command_entry.get()) self.output_text.insert(tk.END, output) if __name__ == '__main__': root = tk.Tk() app = App(root) root.mainloop() ``` 在这个示例中,`SSH_Client`类提供了与SSH服务器的连接和命令执行。`App`类则是一个Tkinter GUI,允许用户输入SSH连接参数,连接到远 ### 回答2: 要使用PythonSSH带界面的连接程序,可以使用paramiko库来实现SSH连接和操作。 首先,需要安装paramiko库,可以使用pip命令进行安装。在命令行中运行以下命令: ``` pip install paramiko ``` 接下来,可以使用以下代码编SSH连接的程序: ```python import paramiko def ssh_connect(hostname, username, password): # 创建SSH客户端 client = paramiko.SSHClient() # 设置自动接受ssh密钥 client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接SSH服务器 client.connect(hostname=hostname, username=username, password=password) # 返回SSH客户端对象 return client def main(): # 输入SSH服务器的地址、用户名和密码 hostname = input("请输入SSH服务器地址:") username = input("请输入用户名:") password = input("请输入密码:") # 连接SSH服务器 client = ssh_connect(hostname, username, password) # 在SSH连接上执行命令 command = input("请输入要执行的命令:") stdin, stdout, stderr = client.exec_command(command) # 打印命令执行结果 print("命令执行结果:") print(stdout.read().decode()) # 关闭SSH连接 client.close() if __name__ == "__main__": main() ``` 以上代码定义了一个`ssh_connect`函数用于连接SSH服务器,然后在主函数`main`中接收用户输入的SSH服务器地址、用户名和密码,并通过`ssh_connect`函数连接SSH服务器。接着,用户可以输入要执行的命令,程序将在SSH连接上执行该命令,并打印命令的执行结果。最后,关闭SSH连接。 通过以上代码,可以编一个简单的SSH带界面的连接程序。当然,还可以根据需求对程序进行更多的扩展和优化。 ### 回答3: 用PythonSSH带界面的连接程序可以使用Paramiko库来实现。Paramiko一个Python实现的SSHv2协议库,它可以让我们在Python中进行SSH连接和操作。 首先,我们需要使用pip命令安装Paramiko库,命令如下: ``` pip install paramiko ``` 然后,我们可以编一个包含界面的连接程序。可以使用PythonGUI库,如Tkinter来创建一个简单的界面。以下是一个示例代码: ```python import tkinter as tk import paramiko def ssh_connect(): host = host_entry.get() port = int(port_entry.get()) username = username_entry.get() password = password_entry.get() try: # 创建SSH客户端 client = paramiko.SSHClient() # 自动添加远程服务器的主机密钥 client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 连接远程服务器 client.connect(host, port, username, password) # 在这里可以进行远程命令执行等操作 # 关闭SSH连接 client.close() result_label.config(text="连接成功") except Exception as e: result_label.config(text=f"连接失败: {str(e)}") # 创建界面 root = tk.Tk() root.title("SSH连接") root.geometry("300x200") # 添加控件 host_label = tk.Label(root, text="主机地址") host_label.pack() host_entry = tk.Entry(root) host_entry.pack() port_label = tk.Label(root, text="端口号") port_label.pack() port_entry = tk.Entry(root) port_entry.pack() username_label = tk.Label(root, text="用户名") username_label.pack() username_entry = tk.Entry(root) username_entry.pack() password_label = tk.Label(root, text="密码") password_label.pack() password_entry = tk.Entry(root, show="*") password_entry.pack() connect_button = tk.Button(root, text="连接", command=ssh_connect) connect_button.pack() result_label = tk.Label(root, text="") result_label.pack() # 运行界面 root.mainloop() ``` 在这个示例代码中,我们创建了一个简单的界面,包括主机地址、端口号、用户名、密码的输入框,以及一个连接按钮。点击连接按钮后,程序将使用Paramiko库进行SSH连接,并根据连接结果在界面上显示连接成功或连接失败的信息。 需要注意的是,SSH连接一个耗时的操作,为了避免在连接过程中界面卡住,可以在连接过程中使用多线程或异步操作来进行。以上代码只是一个简单的示例,实际应用中可能需要更复杂的处理和错误处理机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值