complete navigation

This commit is contained in:
Digital Studium 2024-04-11 09:25:12 +03:00
parent f5c483f82f
commit f9dcbf2a13
9 changed files with 258 additions and 226 deletions

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

8
.idea/kls.iml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/kls.iml" filepath="$PROJECT_DIR$/.idea/kls.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

330
kls
View File

@ -1,12 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import curses import curses
import subprocess import subprocess
from enum import Enum
stdscr = None stdscr = None
running = True
# states of menu
class MenuState(Enum):
SELECTED_WITHOUT_SEARCH = 1 SELECTED_WITHOUT_SEARCH = 1
SELECTED_WITH_SEARCH = 2 SELECTED_WITH_SEARCH = 2
NOT_SELECTED_WITHOUT_SEARCH = 3 NOT_SELECTED_WITHOUT_SEARCH = 3
@ -23,8 +22,10 @@ def init_screen():
stdscr.keypad(True) stdscr.keypad(True)
curses.curs_set(0) curses.curs_set(0)
init_screen() init_screen()
class Menu: class Menu:
def __init__(self, name, rows, begin_x, state): def __init__(self, name, rows, begin_x, state):
self.state = state self.state = state
@ -32,34 +33,39 @@ class Menu:
self.rows = rows # строки окна self.rows = rows # строки окна
self.filtered_rows = rows # фильтрованные строки окна self.filtered_rows = rows # фильтрованные строки окна
self.begin_x = begin_x # где начинается окно по х? self.begin_x = begin_x # где начинается окно по х?
self.win = curses.newwin(curses.LINES, curses.COLS // 3, 0, begin_x) # окно с высотой во весь экран, шириной экран / 3, и началом по х в точке begin_x self.win = curses.newwin(curses.LINES, curses.COLS // 3, 0,
begin_x) # окно с высотой во весь экран, шириной экран / 3, и началом по х в точке begin_x
self.win.box() # ? self.win.box() # ?
self.win.addstr(1, 2, self.name) # рисуем заголовок self.win.addstr(1, 2, self.name) # рисуем заголовок
for index, row in enumerate(self.rows): # рисуем строки for index, row in enumerate(self.rows): # рисуем строки
self.win.addstr(index + 3, 2, row) # + 3 потому что я хочу чтобы строки оборажались ниже заголовка на три строки self.win.addstr(index + 3, 2,
row) # + 3 потому что я хочу чтобы строки оборажались ниже заголовка на три строки
self.row = 0 # выбранная строка self.row = 0 # выбранная строка
self.search_string = "" self.search_string = ""
# рисуем первое меню # рисуем первое меню
## готовим контент ## готовим контент
bytes_list = subprocess.check_output("kubectl get ns --no-headers -o template='{{range .items}}{{.metadata.name}} {{end}}'", shell=True).split() bytes_list = subprocess.check_output(
"kubectl get ns --no-headers -o template='{{range .items}}{{.metadata.name}} {{end}}'", shell=True).split()
namespaces = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))] namespaces = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))]
## отрисовываем меню ## отрисовываем меню
menu1 = Menu("Namespaces", namespaces, 0, MenuState.SELECTED_WITHOUT_SEARCH) menu1 = Menu("Namespaces", namespaces, 0, SELECTED_WITHOUT_SEARCH)
# рисуем второе меню # рисуем второе меню
## готовим контент ## готовим контент
api_resources = ["pods", "services", "deployments", "ingresses"] api_resources = ["pods", "services", "deployments", "ingresses"]
## отрисовываем меню ## отрисовываем меню
menu2 = Menu("API resources", api_resources, 0 + curses.COLS // 3, MenuState.NOT_SELECTED_WITHOUT_SEARCH) menu2 = Menu("API resources", api_resources, 0 + curses.COLS // 3, NOT_SELECTED_WITHOUT_SEARCH)
# рисуем третье меню # рисуем третье меню
## готовим контент ## готовим контент
bytes_list = subprocess.check_output("kubectl get pods -n kube-system --no-headers -o template='{{range .items}}{{.metadata.name}} {{end}}'", shell=True).split() bytes_list = subprocess.check_output(
"kubectl get pods -n kube-system --no-headers -o template='{{range .items}}{{.metadata.name}} {{end}}'",
shell=True).split()
pods = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))] pods = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))]
## отрисовываем меню ## отрисовываем меню
menu3 = Menu("Resources", pods, 0 + curses.COLS // 3 * 2, MenuState.NOT_SELECTED_WITHOUT_SEARCH) menu3 = Menu("Resources", pods, 0 + curses.COLS // 3 * 2, NOT_SELECTED_WITHOUT_SEARCH)
menus = [menu1, menu2, menu3] menus = [menu1, menu2, menu3]
@ -92,32 +98,21 @@ menus = [menu1, menu2, menu3]
# return current_menu # return current_menu
def update_menu3(menu): # def update_menu3(menu):
namespace = menu1.filtered_rows[menu1.row] # namespace = menu1.filtered_rows[menu1.row]
api_resource = menu2.filtered_rows[menu2.row] # api_resource = menu2.filtered_rows[menu2.row]
command = "f'kubectl get {api_resource} -n {namespace} --no-headers -o template=\"{{{{range .items}}}}{{{{.metadata.name}}}} {{{{end}}}}\"'" # command = "f'kubectl get {api_resource} -n {namespace} --no-headers -o template=\"{{{{range .items}}}}{{{{.metadata.name}}}} {{{{end}}}}\"'"
bytes_list = subprocess.check_output(eval(command), shell=True).split() # bytes_list = subprocess.check_output(eval(command), shell=True).split()
resources = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))] # resources = [bytes_list[i].decode('utf-8') for i in range(len(bytes_list))]
if not resources: # if not resources:
resources = [f"No resources found in {namespace} namespace.",] # resources = [f"No resources found in {namespace} namespace.",]
menu3.rows = resources # menu3.rows = resources
menu3.filtered_rows = resources # menu3.filtered_rows = resources
menu3.win.clear() # menu3.win.clear()
draw_rows(menu3) # draw_rows(menu3)
# run_command(command, menu, rows=resources) # run_command(command, menu, rows=resources)
def navigate_vertically(direction, menu):
increment = {"down": 1, "up": -1}
if not menu.filtered_rows:
return
if menu.filtered_rows[menu.row].startswith("No resources"): # это касается только третьего меню
return
menu.win.addstr(menu.row + 3, 2, menu.filtered_rows[menu.row]) # удаляем выделение с текущей строки
menu.row = (menu.row + increment[direction]) % len(menu.filtered_rows) # переходим к предыдущей/следующей строке
menu.win.addstr(menu.row + 3, 2, menu.filtered_rows[menu.row], curses.A_REVERSE | curses.A_ITALIC) # и выделяем её
def draw_search_box(menu, content): def draw_search_box(menu, content):
menu.win.addstr(curses.LINES - 2, 2, content) # рисуем контент menu.win.addstr(curses.LINES - 2, 2, content) # рисуем контент
menu.win.clrtoeol() # очищаем остальную часть строки menu.win.clrtoeol() # очищаем остальную часть строки
@ -135,64 +130,39 @@ def draw_header(menu, selected=False):
def draw_rows(menu): def draw_rows(menu):
menu.win.addstr(1, 2, menu.name) # рисуем заголовок menu.win.addstr(1, 2, menu.name) # рисуем заголовок
menu.filtered_rows = list(filter(lambda x: (x.startswith(menu.search_string)), menu.rows)) # фильтруем строки filtered_rows = list(filter(lambda x: (x.startswith(menu.search_string)), menu.rows)) # фильтруем строки
for index, row in enumerate(menu.filtered_rows): # рисуем то, что отфильтровали for index, row in enumerate(filtered_rows): # рисуем то, что отфильтровали
menu.win.addstr(index + 3, 2, row) menu.win.addstr(index + 3, 2, row)
if menu.filtered_rows: if filtered_rows:
menu.win.addstr(3, 2, menu.filtered_rows[0], curses.A_REVERSE | curses.A_ITALIC) # выделяем первую строку menu.win.addstr(3, 2, filtered_rows[0], curses.A_REVERSE | curses.A_ITALIC) # выделяем первую строку
menu.win.box() menu.win.box()
menu.win.refresh() menu.win.refresh()
def main(stdscr): def navigation_without_search(menu):
stdscr.refresh() global running
running = True
# начальный экран
for menu in menus:
draw_rows(menu) # рисуем строки меню
draw_search_box(menu, "Press / for search") # рисуем строку поиска
while running:
match menu1.state:
# если первое меню выделено и в нём выключен поиск
case MenuState.SELECTED_WITHOUT_SEARCH:
menu = menu1
draw_header(menu, selected=True) draw_header(menu, selected=True)
draw_search_box(menu, "Press / for search") # рисуем строку поиска draw_search_box(menu, "Press / for search") # рисуем строку поиска
key_pressed = stdscr.getkey() key_pressed = stdscr.getkey()
match key_pressed: match key_pressed:
case "/": case "/":
menu.state = MenuState.SELECTED_WITH_SEARCH menu.state = SELECTED_WITH_SEARCH
case "q": case "q":
running = False running = False
continue
case '\t' | "KEY_RIGHT": case '\t' | "KEY_RIGHT":
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH navigate_horizontally("right", menu)
menu2.state = MenuState.SELECTED_WITHOUT_SEARCH
case "KEY_BTAB" | "KEY_LEFT": case "KEY_BTAB" | "KEY_LEFT":
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH navigate_horizontally("left", menu)
menu3.state = MenuState.SELECTED_WITHOUT_SEARCH
case "KEY_DOWN": case "KEY_DOWN":
navigate_vertically("down", menu) navigate_vertically("down", menu)
# update_menu3(menu) # update_menu3(menu)
case "KEY_UP": case "KEY_UP":
navigate_vertically("up", menu) navigate_vertically("up", menu)
# update_menu3(menu) # update_menu3(menu)
match menu2.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
menu = menu2 def navigation_with_search(menu):
draw_search_box(menu, "Press / for search") # рисуем строку поиска global running
match menu3.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
menu = menu3
draw_header(menu)
draw_search_box(menu, "Press / for search")
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
# если первое меню выделено и в нём включен поиск
case MenuState.SELECTED_WITH_SEARCH:
menu = menu1
draw_header(menu, selected=True) draw_header(menu, selected=True)
draw_search_box(menu, f"/{menu.search_string}") # рисуем строку поиска draw_search_box(menu, f"/{menu.search_string}") # рисуем строку поиска
key_pressed = stdscr.getkey() key_pressed = stdscr.getkey()
@ -201,11 +171,14 @@ def main(stdscr):
if menu.search_string: if menu.search_string:
menu.search_string = menu.search_string[:-1] # удаляем символ из строки поиска menu.search_string = menu.search_string[:-1] # удаляем символ из строки поиска
menu.win.clear() menu.win.clear()
draw_search_box(menu, f"/{menu1.search_string}") draw_search_box(menu, f"/{menu.search_string}")
draw_rows(menu) draw_rows(menu)
else: else:
menu.state = MenuState.SELECTED_WITHOUT_SEARCH menu.state = SELECTED_WITHOUT_SEARCH
continue case '\t' | "KEY_RIGHT":
navigate_horizontally("right", menu)
case "KEY_BTAB" | "KEY_LEFT":
navigate_horizontally("left", menu)
case "KEY_DOWN": case "KEY_DOWN":
navigate_vertically("down", menu) navigate_vertically("down", menu)
# update_menu3(menu) # update_menu3(menu)
@ -216,110 +189,100 @@ def main(stdscr):
if key_pressed.isalpha() or key_pressed == "-": # namespace не может иметь иных символов кроме a-z и - if key_pressed.isalpha() or key_pressed == "-": # namespace не может иметь иных символов кроме a-z и -
menu.search_string += key_pressed menu.search_string += key_pressed
menu.win.clear() menu.win.clear()
draw_search_box(menu, f"/{menu1.search_string}") menu.row = 0
draw_search_box(menu, f"/{menu.search_string}")
draw_rows(menu) draw_rows(menu)
match menu2.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
match menu3.state: def navigate_horizontally(direction, menu):
case MenuState.NOT_SELECTED_WITHOUT_SEARCH: increment = {"right": 1, "left": -1}
pass menu_index = {menu1: 0, menu2: 1, menu3: 2}
case MenuState.NOT_SELECTED_WITH_SEARCH: next_menu = menus[(menu_index[menu] + increment[direction]) % 3]
pass draw_header(menu) # убираем выделение с текущего меню
case MenuState.NOT_SELECTED_WITH_SEARCH: if menu.search_string:
pass menu.state = NOT_SELECTED_WITH_SEARCH
# если первое меню не выделено и в нём выключен поиск else:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH: menu.state = NOT_SELECTED_WITHOUT_SEARCH
menu = menu1 if next_menu.search_string:
draw_header(menu) next_menu.state = SELECTED_WITH_SEARCH
else:
next_menu.state = SELECTED_WITHOUT_SEARCH
def navigate_vertically(direction, menu):
increment = {"down": 1, "up": -1}
filtered_rows = list(filter(lambda x: (x.startswith(menu.search_string)), menu.rows)) # фильтруем строки
if not filtered_rows:
return
if filtered_rows[menu.row].startswith("No resources"): # это касается только третьего меню
return
menu.win.addstr(menu.row + 3, 2, filtered_rows[menu.row]) # удаляем выделение с текущей строки
menu.row = (menu.row + increment[direction]) % len(filtered_rows) # переходим к предыдущей/следующей строке
menu.win.addstr(menu.row + 3, 2, filtered_rows[menu.row], curses.A_REVERSE | curses.A_ITALIC) # и выделяем её
def main(stdscr):
global running
stdscr.refresh()
# начальный экран
for menu in menus:
draw_rows(menu) # рисуем строки меню
draw_search_box(menu, "Press / for search") # рисуем строку поиска draw_search_box(menu, "Press / for search") # рисуем строку поиска
match menu2.state: while running:
# если второе меню не выделено и в нём выключен поиск ### выбрано первое меню ###
case MenuState.NOT_SELECTED_WITHOUT_SEARCH: if menu1.state == 1 and menu2.state == 3 and menu3.state == 3:
menu = menu2 navigation_without_search(menu1)
draw_header(menu) if menu1.state == 1 and menu2.state == 4 and menu3.state == 3:
draw_search_box(menu, "Press / for search") # рисуем строку поиска navigation_without_search(menu1)
match menu3.state: if menu1.state == 1 and menu2.state == 3 and menu3.state == 4:
# если третье меню выделено и в нём выключен поиск navigation_without_search(menu1)
case MenuState.SELECTED_WITHOUT_SEARCH: if menu1.state == 1 and menu2.state == 4 and menu3.state == 4:
menu = menu3 navigation_without_search(menu1)
draw_header(menu, selected=True) elif menu1.state == 2 and menu2.state == 3 and menu3.state == 3:
draw_search_box(menu, "Press / for search") # рисуем строку поиска navigation_with_search(menu1)
key_pressed = stdscr.getkey() elif menu1.state == 2 and menu2.state == 4 and menu3.state == 3:
match key_pressed: navigation_with_search(menu1)
case "/": elif menu1.state == 2 and menu2.state == 3 and menu3.state == 4:
menu.state = MenuState.SELECTED_WITH_SEARCH navigation_with_search(menu1)
continue elif menu1.state == 2 and menu2.state == 4 and menu3.state == 4:
case "q": navigation_with_search(menu1)
running = False
continue ### выбрано второе меню ###
case '\t' | "KEY_RIGHT": if menu1.state == 3 and menu2.state == 1 and menu3.state == 3:
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH navigation_without_search(menu2)
draw_header(menu) if menu1.state == 3 and menu2.state == 1 and menu3.state == 4:
menu1.state = MenuState.SELECTED_WITHOUT_SEARCH navigation_without_search(menu2)
continue if menu1.state == 4 and menu2.state == 1 and menu3.state == 3:
case "KEY_BTAB" | "KEY_LEFT": navigation_without_search(menu2)
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH if menu1.state == 4 and menu2.state == 1 and menu3.state == 4:
draw_header(menu) navigation_without_search(menu2)
menu2.state = MenuState.SELECTED_WITHOUT_SEARCH elif menu1.state == 3 and menu2.state == 2 and menu3.state == 3:
case "KEY_DOWN": navigation_with_search(menu2)
navigate_vertically("down", menu) elif menu1.state == 3 and menu2.state == 2 and menu3.state == 4:
case "KEY_UP": navigation_with_search(menu2)
navigate_vertically("up", menu) elif menu1.state == 4 and menu2.state == 2 and menu3.state == 3:
# если второе меню не выделено и в нём включен поиск navigation_with_search(menu2)
case MenuState.NOT_SELECTED_WITH_SEARCH: elif menu1.state == 4 and menu2.state == 2 and menu3.state == 4:
pass navigation_with_search(menu2)
# если второе меню выделено и в нём выключен поиск
case MenuState.SELECTED_WITHOUT_SEARCH: ### выбрано третье меню ###
menu = menu2 if menu1.state == 3 and menu2.state == 3 and menu3.state == 1:
draw_header(menu, selected=True) navigation_without_search(menu3)
draw_search_box(menu, "Press / for search") if menu1.state == 3 and menu2.state == 4 and menu3.state == 1:
key_pressed = stdscr.getkey() navigation_without_search(menu3)
match key_pressed: if menu1.state == 4 and menu2.state == 3 and menu3.state == 1:
case "/": navigation_without_search(menu3)
menu.state = MenuState.SELECTED_WITH_SEARCH if menu1.state == 4 and menu2.state == 4 and menu3.state == 1:
continue navigation_without_search(menu3)
case "q": elif menu1.state == 3 and menu2.state == 3 and menu3.state == 2:
running = False navigation_with_search(menu3)
continue elif menu1.state == 3 and menu2.state == 4 and menu3.state == 2:
case '\t' | "KEY_RIGHT": navigation_with_search(menu3)
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH elif menu1.state == 4 and menu2.state == 3 and menu3.state == 2:
menu3.state = MenuState.SELECTED_WITHOUT_SEARCH navigation_with_search(menu3)
continue elif menu1.state == 4 and menu2.state == 4 and menu3.state == 2:
case "KEY_BTAB" | "KEY_LEFT": navigation_with_search(menu3)
menu.state = MenuState.NOT_SELECTED_WITHOUT_SEARCH
draw_header(menu)
menu1.state = MenuState.SELECTED_WITHOUT_SEARCH
case "KEY_DOWN":
navigate_vertically("down", menu)
case "KEY_UP":
navigate_vertically("up", menu)
match menu3.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
pass
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
# если второе меню выделено и в нём включен поиск
case MenuState.SELECTED_WITH_SEARCH:
pass
# если первое меню не выделено и в нём включен поиск
case MenuState.NOT_SELECTED_WITH_SEARCH:
match menu2.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
match menu3.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
pass
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
case MenuState.SELECTED_WITHOUT_SEARCH:
match menu3.state:
case MenuState.NOT_SELECTED_WITHOUT_SEARCH:
pass
case MenuState.NOT_SELECTED_WITH_SEARCH:
pass
case MenuState.SELECTED_WITH_SEARCH:
pass
# if current_menu == 0: # if current_menu == 0:
# if key_pressed == "/": # if key_pressed == "/":
@ -402,11 +365,12 @@ def main(stdscr):
# navigate_vertically("down", current_menu) # navigate_vertically("down", current_menu)
# case "KEY_UP": # case "KEY_UP":
# navigate_vertically("up", current_menu) # navigate_vertically("up", current_menu)
main(stdscr) # main(stdscr)
curses.wrapper(main)
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()
subprocess.call(["clear"])
# curses.nocbreak()
# stdscr.keypad(False)
# curses.echo()
# curses.endwin()
# subprocess.call(["clear"])
#

16
test.py Normal file
View File

@ -0,0 +1,16 @@
import time
import curses
def draw(canvas):
while True:
key = canvas.getkey()
if key == ("\x1b"):
print(f"Вы ввели Escape!")
print(f"Вы ввели {key}")
if __name__ == '__main__':
curses.update_lines_cols()
curses.wrapper(draw)

14
~ Normal file
View File

@ -0,0 +1,14 @@
import time
import curses
def draw(canvas):
while True:
key = canvas.getkey()
print(f"Вы ввели {key}")
if __name__ == '__main__':
curses.update_lines_cols()
curses.wrapper(draw)