V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
yqf0215
V2EX  ›  分享创造

更新了我的.ssh/config host 检索,现在支持 tssh

  •  
  •   yqf0215 · 2023-12-08 14:58:34 +08:00 · 1327 次点击
    这是一个创建于 379 天前的主题,其中的信息可能已经有所发展或是发生改变。

    更新了我的.ssh/config host 检索,现在支持 tssh 原贴地址: https://v2ex.com/t/993262

    完整代码如下:

    from tkinter import *
    from tkinter.ttk import *
    import pyperclip3
    import sys
    
    import flet as ft
    from flet import TextField, ElevatedButton, ListView, ListTile,Checkbox, Text, Row, Column, Container, colors, IconButton, icons
    
    # 说明:config 文件中,以空白行来分割不同的 Host 段落,每个段落前面可以有个 #tags niit,南京这个标签
    # 20231119 用 flet 这个 python 的 flutter 包来改写了一下,之前 tk 包在升级了 mac 系统后,有点说不出来的小 bug 。
    # 20220630v2 左键单击改双击了
    # 20220630v1 单击复制后,window 的 title 会提示复制的命令
    # 程序的作用是,自动解析 .ssh/config 文件,
    # tree 可以排序
    # 单击会复制 ssh your_host_config 到剪贴板;
    # 右键单击,会 Term 中运行 ssh your_host_config
    
    class Win:
        def __init__(self):
            self.root = self.__win()
            self.tk_button_search_btn = self.__tk_button_search_btn()
            self.tk_input_search_content = self.__tk_input_search_content()
            self.tk_label_tip = self.__tk_label_tip()
            # self.tk_list_box_listbox = self.__tk_list_box_listbox()
            self.ttk_tree_content = self.__ttk_tree()
            results = self.getSSHConfg()
            self.updateHost2Tree(results)
            self.ttk_tree_content.bind('<Double-Button-1>', self.treeviewClick)
            self.ttk_tree_content.bind('<ButtonRelease-2>', self.treeviewDoubleClick)
            self.tk_button_search_btn.bind('<Button-1>', self.search_Host)
            self.tk_input_search_content.bind('<Key-Return>', self.search_Host)
    
        def __win(self):
            root = Tk()
            root.title("我是标题 ~ TkinterHelper")
            # 设置大小 居中展示
            width = 600
            height = 500
            screenwidth = root.winfo_screenwidth()
            screenheight = root.winfo_screenheight()
            geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
            root.geometry(geometry)
            root.resizable(width=False, height=False)
            return root
    
        def show(self):
            self.root.mainloop()
    
        def __tk_button_search_btn(self):
            btn = Button(self.root, text="检索")
            btn.place(x=290, y=20, width=50, height=24)
    
            return btn
    
        def __tk_input_search_content(self):
            ipt = Entry(self.root)
            ipt.place(x=120, y=20, width=150, height=24)
            return ipt
    
        def __tk_label_tip(self):
            label = Label(self.root,text="选择")
            label.place(x=40, y=20, width=50, height=24)
            return label
    
        def __tk_list_box_listbox(self):
            lb = Listbox(self.root)
            lb.insert(END, "列表框")
            lb.insert(END, "Python")
            lb.insert(END, "Tkinter Helper")
            lb.place(x=40, y=60, width=528, height=428)
            return lb
    
        def treeview_sort_column(self,tv, col, reverse):
            l = [(tv.set(k, col), k) for k in tv.get_children('')]
            l.sort(reverse=reverse)
    
            # rearrange items in sorted positions
            for index, (val, k) in enumerate(l):
                tv.move(k, '', index)
    
            # reverse sort next time
            tv.heading(col, command=lambda: \
                self.treeview_sort_column(tv, col, not reverse))
        def __ttk_tree(self):
            columns = ('Group', 'Host','tags', 'Hostname')
            tree = Treeview(self.root,columns = columns,  show='headings')
            for col in columns:
                tree.heading(col, text=col, command=lambda _col=col:self.treeview_sort_column(tree, _col, False))
            tree.grid()
            tree.place(x=40, y=60, width=528, height=428)
            return tree
    
        def updateHost2Tree(self,hostDatas):
            for rs in hostDatas:
                print(rs)
                third = ""
                if "Hostname" in rs:
                    third = rs['Hostname']
                if "Port" in rs:
                    third = third + ":" + rs['Port']
                tags=""
                if "tags" in rs:
                    tags=rs['tags']
                group = 'Default'
                if "Group" in rs:
                    group = rs['Group']
                li = [group, rs["Host"], tags, third+"\nabc"]
                if "color" in rs:
                    self.ttk_tree_content.insert('', 'end', values=li, tags = (rs['color'],))
                    colors = rs['color'].split(",",1)
                    self.ttk_tree_content.tag_configure(rs['color'], background=colors[0])
                else:
                    self.ttk_tree_content.insert('', 'end', values=li)
    
        def clearTree(self):
            x = self.ttk_tree_content.get_children()
            for item in x:
                self.ttk_tree_content.delete(item)
    
        def print_contents(self, event):
            print("okkkkk")
    
        def getSSHConfg(self):
            return getSSHConfg()
    
        def treeviewClick(self,event):  # 单击
            print('单击')
            for item in self.ttk_tree_content.selection():
                item_text =  self.ttk_tree_content.item(item, "values")
                print(item_text[1])  # 输出所选行的第一列的值
                pyperclip3.copy("ssh "+item_text[1])
                self.root.title("复制—— ssh " + item_text[1])
    
        def treeviewDoubleClick(self,event):  # 单击
            print('右键单击')
            for item in self.ttk_tree_content.selection():
                item_text =  self.ttk_tree_content.item(item, "values")
                print(item_text[1])  # 输出所选行的第一列的值
                pyperclip3.copy("ssh "+item_text[1])
    
        def search_Host(self ,event):
            所有结果列表 2 = self.getSSHConfg()
            要查找的字符=  self.tk_input_search_content.get()
            print("要查找的字符: ",要查找的字符)
            rs=[]
            if 要查找的字符 == "0":
                rs = 所有结果列表
            else:
                for 单个结果 in 所有结果列表 2:
                        # field 是 jieguo 这个 dict 的 key ,用 jieguo[field]获取对应的 value
                        find = False
                        for field in 单个结果:
                            if 单个结果[field].find(要查找的字符) > -1:
                                find=True
                                break
                        if find:
                            rs.append(单个结果)
            for search_rs in rs:
                print("ssh", search_rs["Host"], " \t,", search_rs)
            self.clearTree()
            self.updateHost2Tree(rs)
    
    def getSSHConfg():
            f = open("/Users/zhangyingchun/.ssh/config")
            neirong = f.read()
            f.close();
            所有结果列表 = []
            所有段落 = neirong.split('\n\n', -1)
            i = 0;
            for 每个段落 in 所有段落:
                # 去除 .ssh/config 文件的第一段
                if i == 0:
                    i = i + 1
                    continue
                i = i + 1
                每个段落的所有行 = 每个段落.split("\n", -1)
                每个段落的解析结果列表 = {}
                isUsefule = False
                for 每一行 in 每个段落的所有行:
                    分割后元素列表 = 每一行.split(" ", 1)
                    # 处理 Host 、Hostname 、Port 、#color 、#tags 这个 5 个元素
                    # print("中间调试信息 Host hang:", 每一行)
                    if 每一行.startswith("Host "):
                        # 必须有 Host 的段落才会保存到 所有结果列表
                        isUsefule = True
                        每个段落的解析结果列表["Host"] = 分割后元素列表[1]
                    elif 每一行.startswith("Hostname"):
                        每个段落的解析结果列表["Hostname"] = 分割后元素列表[1]
                    elif 每一行.startswith("Port"):
                        每个段落的解析结果列表["Port"] = 分割后元素列表[1]
                    elif (每一行.startswith("#tags") or 每一行.startswith("#!! GroupLabels")):
                        # 解析 GroupList 标记并添加到 tags
                        tags = 每个段落的解析结果列表.get("tags", "")
                        if tags:
                            tags += ", "
                        tags += 分割后元素列表[1].strip()
                        每个段落的解析结果列表["tags"] = tags
                        # 每个段落的解析结果列表["tags"] = 分割后元素列表[1]
                    elif 每一行.startswith("#color"):
                        每个段落的解析结果列表["color"] = 分割后元素列表[1]
                    elif 每一行.startswith("ProxyJump"):
                        每个段落的解析结果列表["ProxyJump"] = 分割后元素列表[1]
                    elif 每一行.startswith("#group"):
                        每个段落的解析结果列表["Group"] = 分割后元素列表[1]
                        print("Group::: ",分割后元素列表[1])
                if isUsefule:
                    所有结果列表.append(每个段落的解析结果列表)
            print("所有结果列表: ",所有结果列表)
            return 所有结果列表
    
    class FletApp:
        def build(self, page):
            self.ssh_command="ssh"
            self.page = page
            self.page.title = "ssh Host 快速检索程序"
            self.search_content = TextField(on_submit=self.search_host)
            self.search_button = ElevatedButton(text="检索", on_click=self.search_host)
            self.bt_is_tssh = Checkbox(value=False, on_change=self.on_tssh_change, label="使用 tssh",)
            self.tip_label = Text("选择")
            self.tip_label_copy=Text("复制")
            self.list_view = ListView(expand=True)
            self.copied_host = None  # 保存被复制行的标识
            # ListView 放在 Column 中,并让它扩展以填充空间
            list_container = Column(
                controls=[self.list_view],
                expand=1  # 让 Column 扩展以填充父容器
            )
            page.add(
                Column(
                    controls=[
                        Row(controls=[self.tip_label, self.search_content, self.search_button,self.bt_is_tssh, self.tip_label_copy], alignment="start"),
                        list_container  # 包含 ListView 的容器
                    ],
                    expand=1
                )
            )
            # scroll_view = ScrollView(content=list_container)
            # page.add(scroll_view)
            page.scroll = "always"
    
            # 更新列表
            self.update_host_to_list(self.get_ssh_config())
        def on_tssh_change(self, event):
            if self.bt_is_tssh.value:
                self.tip_label_copy.text = "使用 tssh"
                self.ssh_command = "tssh"
            else:
                self.ssh_command = "ssh"
                self.tip_label_copy.text = "使用 ssh"
        def update_host_to_list(self, host_data):
            self.list_view.controls.clear()
            for i, data in enumerate(host_data):
                bg_color = ft.colors.BACKGROUND
                if i % 2 == 0 :
                    bg_color= colors.GREY_100  # 交替颜色
                if data["Host"] == self.copied_host:
                    bg_color = colors.BLUE_100  # 被复制行的背景色
                host=data.get("Host")
                tags=data.get("tags")
    
                jump = f"jump: {data.get('ProxyJump', '')}"
    
                # subtitle_text = f"[{data.get('Hostname', '')}:{data.get('Port', '22')}]\t {jump}"
                # title_text=f"{host}\t, {subtitle_text}\t{tags}"
                display_port = data.get('Port', '22')
                display_hostname = data.get('Hostname', '')
    
                if display_port!='22' :
                    display_hostname =f"{display_hostname}:{display_port}"
                if jump=="jump: ":
                    jump=""
                    display_hostname=f"[{display_hostname}]"
                else:
                    display_hostname=f"[{display_hostname}]\n {jump}"
                host_text =Container(content= Text(data["Host"] , color=colors.BLUE), width=90)
                subtitle_text =Container(content=  Text(f"{display_hostname}", color=colors.RED),width=170)
                tags_text = Text(tags, color=colors.BLUE)
    
                iconCPHost=IconButton(icon=icons.CONTENT_COPY, on_click=lambda e, data=data: self.copy_command(data["Host"]))
                iconCPsshHost=IconButton(icon=icons.SAVE, on_click=lambda e, data=data: self.copy_ssh_command(data["Host"]))
                title_row = Row(controls=[iconCPsshHost,iconCPHost,host_text, subtitle_text, tags_text])
                list_item = Container(
                    content=ListTile(
                        title=title_row
                        # title=Text(title_text),
                        # subtitle=Text(subtitle_text),
                        # trailing=icon_row
                        # trailing=IconButton(icon=icons.CONTENT_COPY, on_click=lambda e: self.copy_ssh_command(data["Host"]))
                    ),
    
                    bgcolor=bg_color,
                    padding=5
                )
                self.list_view.controls.append(list_item)
                # 添加分割线
                if i < len(host_data) - 1:
                    divider = Container(height=1, width=self.page.width, bgcolor=colors.GREY_300)
                    self.list_view.controls.append(divider)
            self.page.update()
    
        def get_ssh_config(self):
            # 这里应该是读取 SSH 配置的代码,我简化了一下
            return getSSHConfg()
            # return [{"Host": "example.com", "Hostname": "192.168.1.1", "Port": "22"}, {"Host": "test.com", "Hostname": "192.168.1.2", "Port": "22"}]
    
        def search_host(self, e):
            search_term = self.search_content.value
            all_config = self.get_ssh_config()
            filtered_config = [
                config for config in all_config if
                search_term in config["Host"].lower() or
                search_term in config.get("Hostname", "").lower() or
                search_term in config.get("tags", "").lower()
            ]
            self.update_host_to_list(filtered_config)
            self.search_content.focus()
            # self.page.update()
    
        def copy_ssh_command(self, host):
            ssh_command =self.ssh_command+ f" {host}"
            self.copied_host = host
            self.tip_label_copy.value = ssh_command  # 更新文字
            self.tip_label_copy.update()  # 应用更改
            self.page.title = ssh_command  # 更改标题
            self.page.update()  # 应用更改
            copy_to_clipboard(ssh_command)
    
        def copy_command(self, host):
            ssh_command = f"{host}"
            self.copied_host = host
            self.tip_label_copy.value = ssh_command  # 更新文字
            self.tip_label_copy.update()  # 应用更改
            self.page.title = ssh_command  # 更改标题
            self.page.update()  # 应用更改
            copy_to_clipboard(ssh_command)
    
    def copy_to_clipboard(text):
        # 这是一个示例函数,您需要根据您的操作系统实现实际的复制功能
        # 在 Windows 上,您可以使用 pyperclip 或 tkinter 的剪贴板功能
        # 在 web 应用中,您可以使用 Flet 的 clipboard 对象
        pyperclip3.copy(text)
        print("复制到剪贴板:", text)
    
    if __name__ == "__main__":
        # 根据传入的 -tk 参数来决定启动 Tkinter 版本还是 Flet 版本的应用
        if "-tk" in sys.argv:
            # 启动 Tkinter 版本
            win = Win()
            所有结果列表 = win.getSSHConfg()
            win.show()
        else:
            # 启动 Flet 版本
            app = FletApp()
            ft.app(target=app.build)
    
    
    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2510 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 03:01 · PVG 11:01 · LAX 19:01 · JFK 22:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.