Created
November 27, 2023 12:38
-
-
Save zhangyc310/5a21dc41b2effad59bd240a19464d633 to your computer and use it in GitHub Desktop.
flet写的ssh config search
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
``` | |
from tkinter import * | |
from tkinter.ttk import * | |
import pyperclip3 | |
import sys | |
import flet as ft | |
from flet import TextField, ElevatedButton, ListView, ListTile, Text, Row, Column, Container, colors, IconButton, icons | |
# 说明:config文件中,以空白行来分割不同的Host段落,每个段落前面可以有个 #tags niit,南京这个标签 | |
# 20231127 兼容tssh 的#!! GroupLabels 备注,多个GroupLabels会跟tags合并到一起 | |
# 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.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.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.tip_label_copy], alignment="start"), | |
list_container # 包含 ListView 的容器 | |
], | |
expand=1 | |
) | |
) | |
page.scroll = "always" | |
# 更新列表 | |
self.update_host_to_list(self.get_ssh_config()) | |
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 = f"ssh {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) | |
# | |
# if __name__ == "__main__": | |
# win = Win() | |
# 所有结果列表 = win.getSSHConfg() | |
# win.show() | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment