multiSSH3 5.72__py3-none-any.whl → 5.74__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- multiSSH3.py +73 -13
- {multissh3-5.72.dist-info → multissh3-5.74.dist-info}/METADATA +1 -1
- multissh3-5.74.dist-info/RECORD +6 -0
- {multissh3-5.72.dist-info → multissh3-5.74.dist-info}/WHEEL +1 -1
- multissh3-5.72.dist-info/RECORD +0 -6
- {multissh3-5.72.dist-info → multissh3-5.74.dist-info}/entry_points.txt +0 -0
- {multissh3-5.72.dist-info → multissh3-5.74.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -10,6 +10,7 @@ __curses_available = False
|
|
|
10
10
|
__resource_lib_available = False
|
|
11
11
|
try:
|
|
12
12
|
import curses
|
|
13
|
+
import curses.panel
|
|
13
14
|
__curses_available = True
|
|
14
15
|
except ImportError:
|
|
15
16
|
pass
|
|
@@ -54,10 +55,10 @@ except AttributeError:
|
|
|
54
55
|
# If neither is available, use a dummy decorator
|
|
55
56
|
def cache_decorator(func):
|
|
56
57
|
return func
|
|
57
|
-
version = '5.
|
|
58
|
+
version = '5.74'
|
|
58
59
|
VERSION = version
|
|
59
60
|
__version__ = version
|
|
60
|
-
COMMIT_DATE = '2025-
|
|
61
|
+
COMMIT_DATE = '2025-06-03'
|
|
61
62
|
|
|
62
63
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
63
64
|
'~/multiSSH3.config.json',
|
|
@@ -1824,14 +1825,14 @@ def _curses_add_string_to_window(window, line = '', y = 0, x = 0, number_of_char
|
|
|
1824
1825
|
charsWritten += min(len(segment), numChar - charsWritten)
|
|
1825
1826
|
# if we have finished printing segments but we still have space, we will fill it with fill_char
|
|
1826
1827
|
trail_fill_length = numChar - charsWritten - len(trail_str)
|
|
1827
|
-
if trail_fill_length > 0:
|
|
1828
|
-
window.addnstr(y, x + charsWritten,fill_char * (trail_fill_length // len(fill_char) + 1), trail_fill_length, boxAttr)
|
|
1828
|
+
if trail_fill_length > 0 and fill_char:
|
|
1829
|
+
window.addnstr(y, x + charsWritten,fill_char * (trail_fill_length // len(fill_char) + 1), trail_fill_length , boxAttr)
|
|
1829
1830
|
charsWritten += trail_fill_length
|
|
1830
1831
|
if len(trail_str) > 0 and charsWritten < numChar:
|
|
1831
1832
|
window.addnstr(y, x + charsWritten, trail_str, numChar - charsWritten, boxAttr)
|
|
1832
1833
|
charsWritten += min(len(trail_str), numChar - charsWritten)
|
|
1833
1834
|
|
|
1834
|
-
def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None):
|
|
1835
|
+
def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None, indexOffset = 0):
|
|
1835
1836
|
'''
|
|
1836
1837
|
Generate a list for the hosts to be displayed on the screen. This is used to display as much relevant information as possible.
|
|
1837
1838
|
|
|
@@ -1852,7 +1853,9 @@ def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None):
|
|
|
1852
1853
|
failed_hosts = [host for host in hosts if host.returncode is not None and host.returncode != 0]
|
|
1853
1854
|
finished_hosts = [host for host in hosts if host.returncode is not None and host.returncode == 0]
|
|
1854
1855
|
waiting_hosts = [host for host in hosts if host.returncode is None and not host.output]
|
|
1855
|
-
new_hosts_to_display = (running_hosts + failed_hosts + finished_hosts + waiting_hosts)
|
|
1856
|
+
new_hosts_to_display = (running_hosts + failed_hosts + finished_hosts + waiting_hosts)
|
|
1857
|
+
new_hosts_to_display = new_hosts_to_display[indexOffset:] + new_hosts_to_display[:indexOffset]
|
|
1858
|
+
new_hosts_to_display = new_hosts_to_display[:max_num_hosts]
|
|
1856
1859
|
if not hosts_to_display:
|
|
1857
1860
|
return new_hosts_to_display , {'running':len(running_hosts), 'failed':len(failed_hosts), 'finished':len(finished_hosts), 'waiting':len(waiting_hosts)}, set(new_hosts_to_display)
|
|
1858
1861
|
# we will compare the new_hosts_to_display with the old one, if some hosts are not in their original position, we will reprint all lines
|
|
@@ -1866,6 +1869,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1866
1869
|
_ = config_reason
|
|
1867
1870
|
try:
|
|
1868
1871
|
box_ansi_color = None
|
|
1872
|
+
refresh_all = True
|
|
1869
1873
|
org_dim = stdscr.getmaxyx()
|
|
1870
1874
|
# To do this, first we need to know the size of the terminal
|
|
1871
1875
|
max_y, max_x = org_dim
|
|
@@ -1949,6 +1953,34 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1949
1953
|
#bottom_border.addnstr(0, 0, '-' * (max_x - 1), max_x - 1)
|
|
1950
1954
|
_curses_add_string_to_window(window=bottom_border, y=0, line='-' * (max_x - 1),fill_char='-',box_ansi_color=box_ansi_color)
|
|
1951
1955
|
bottom_border.refresh()
|
|
1956
|
+
help_window_hight = min(14, max_y)
|
|
1957
|
+
help_window_width = min(31, max_x)
|
|
1958
|
+
# Create a centered help window
|
|
1959
|
+
help_window_y = (max_y - help_window_hight) // 2
|
|
1960
|
+
help_window_x = (max_x - help_window_width) // 2
|
|
1961
|
+
help_window = curses.newwin(help_window_hight, help_window_width, help_window_y, help_window_x)
|
|
1962
|
+
help_window.leaveok(True)
|
|
1963
|
+
help_window.scrollok(True)
|
|
1964
|
+
help_window.idlok(True)
|
|
1965
|
+
help_window.box()
|
|
1966
|
+
_curses_add_string_to_window(window=help_window,y=0,line='Help', color_pair_list=[-1,-1,1], centered=True, fill_char='─', lead_str='┌', box_ansi_color=box_ansi_color)
|
|
1967
|
+
_curses_add_string_to_window(window=help_window,y=1,line='? : Toggle Help Menu', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1968
|
+
_curses_add_string_to_window(window=help_window,y=2,line='_ or + : Change window hight', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1969
|
+
_curses_add_string_to_window(window=help_window,y=3,line='{ or } : Change window width', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1970
|
+
_curses_add_string_to_window(window=help_window,y=4,line='< or > : Change host index', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1971
|
+
_curses_add_string_to_window(window=help_window,y=5,line='|(pipe) : Toggle single host', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1972
|
+
_curses_add_string_to_window(window=help_window,y=6,line='Ctrl+D : Exit', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1973
|
+
_curses_add_string_to_window(window=help_window,y=7,line='Ctrl+R : Force refresh', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1974
|
+
_curses_add_string_to_window(window=help_window,y=8,line='↑ or ↓ : Navigate history', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1975
|
+
_curses_add_string_to_window(window=help_window,y=9,line='← or → : Move cursor', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1976
|
+
_curses_add_string_to_window(window=help_window,y=10,line='PgUp/Dn : Scroll history by 5', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1977
|
+
_curses_add_string_to_window(window=help_window,y=11,line='Home/End: Jump cursor', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1978
|
+
_curses_add_string_to_window(window=help_window,y=12,line='Esc : Clear line', color_pair_list=[-1,-1,1], lead_str='│', box_ansi_color=box_ansi_color)
|
|
1979
|
+
help_panel = curses.panel.new_panel(help_window)
|
|
1980
|
+
help_panel.hide()
|
|
1981
|
+
help_shown = False
|
|
1982
|
+
curses.panel.update_panels()
|
|
1983
|
+
indexOffset = 0
|
|
1952
1984
|
while host_stats['running'] > 0 or host_stats['waiting'] > 0:
|
|
1953
1985
|
# Check for keypress
|
|
1954
1986
|
key = stdscr.getch()
|
|
@@ -1958,7 +1990,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1958
1990
|
# When we encounter a newline, we add a new list to the list of lists. ( a new line of input )
|
|
1959
1991
|
# with open('keylog.txt','a') as f:
|
|
1960
1992
|
# f.write(str(key)+'\n')
|
|
1961
|
-
if key == 410: # 410 is the key code for resize
|
|
1993
|
+
if key == 410 or key == curses.KEY_RESIZE: # 410 is the key code for resize
|
|
1962
1994
|
return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize requested')
|
|
1963
1995
|
# if the user pressed ctrl + d and the last line is empty, we will exit by adding 'exit\n' to the last line
|
|
1964
1996
|
elif key == 4 and not __keyPressesIn[-1]:
|
|
@@ -1981,10 +2013,17 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1981
2013
|
elif key == 125 and not __keyPressesIn[-1]: # 125 is the key code for }
|
|
1982
2014
|
# if last line is empty, we will reconfigure the ww to be larger
|
|
1983
2015
|
return (lineToDisplay,curserPosition , min_char_len +1, min_line_len, single_window, 'Increase character length')
|
|
2016
|
+
elif key == 60 and not __keyPressesIn[-1]: # 60 is the key code for <
|
|
2017
|
+
indexOffset = (indexOffset - 1 ) % len(hosts)
|
|
2018
|
+
elif key == 62 and not __keyPressesIn[-1]: # 62 is the key code for >
|
|
2019
|
+
indexOffset = (indexOffset +1 ) % len(hosts)
|
|
1984
2020
|
# We handle positional keys
|
|
1985
2021
|
# if the key is up arrow, we will move the line to display up
|
|
1986
2022
|
elif key == 259: # 259 is the key code for up arrow
|
|
2023
|
+
# also scroll curserPosition to last if it is currently at the last line and curserPosition is at 0
|
|
1987
2024
|
lineToDisplay = max(lineToDisplay - 1, -len(__keyPressesIn))
|
|
2025
|
+
if lineToDisplay == -2 and not __keyPressesIn[-1]:
|
|
2026
|
+
curserPosition = len(__keyPressesIn[lineToDisplay])
|
|
1988
2027
|
# if the key is down arrow, we will move the line to display down
|
|
1989
2028
|
elif key == 258: # 258 is the key code for down arrow
|
|
1990
2029
|
lineToDisplay = min(lineToDisplay + 1, -1)
|
|
@@ -2006,6 +2045,23 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2006
2045
|
# if the key is end, we will move the cursor to the end of the line
|
|
2007
2046
|
elif key == 360: # 360 is the key code for end
|
|
2008
2047
|
curserPosition = len(__keyPressesIn[lineToDisplay])
|
|
2048
|
+
elif key == curses.KEY_REFRESH or key == curses.KEY_F5 or key == 18: # 18 is the key code for ctrl + R
|
|
2049
|
+
# if the key is refresh, we will refresh the screen
|
|
2050
|
+
return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Refresh requested')
|
|
2051
|
+
elif key == curses.KEY_EXIT or key == 27: # 27 is the key code for ESC
|
|
2052
|
+
# if the key is exit, we will exit the program
|
|
2053
|
+
return
|
|
2054
|
+
elif key == curses.KEY_HELP or key == 63 or key == curses.KEY_F1: # 63 is the key code for ?
|
|
2055
|
+
# if the key is help, we will display the help message
|
|
2056
|
+
if not help_shown:
|
|
2057
|
+
help_panel.show()
|
|
2058
|
+
help_shown = True
|
|
2059
|
+
else:
|
|
2060
|
+
help_panel.hide()
|
|
2061
|
+
help_shown = False
|
|
2062
|
+
refresh_all = True
|
|
2063
|
+
#return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Help closed')
|
|
2064
|
+
curses.panel.update_panels()
|
|
2009
2065
|
# We are left with these are keys that mofidy the current line.
|
|
2010
2066
|
else:
|
|
2011
2067
|
# This means the user have done scrolling and is committing to modify the current line.
|
|
@@ -2042,7 +2098,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2042
2098
|
# We generate the aggregated stats if user did not input anything
|
|
2043
2099
|
if not __keyPressesIn[lineToDisplay]:
|
|
2044
2100
|
#stats = '┍'+ f" Total: {len(hosts)} Running: {host_stats['running']} Failed: {host_stats['failed']} Finished: {host_stats['finished']} Waiting: {host_stats['waiting']} ww: {min_char_len} wh:{min_line_len} "[:max_x - 2].center(max_x - 2, "━")
|
|
2045
|
-
stats = f"Total: {len(hosts)} Running: {host_stats['running']} Failed: {host_stats['failed']} Finished: {host_stats['finished']} Waiting: {host_stats['waiting']} ww: {min_char_len} wh:{min_line_len} "
|
|
2101
|
+
stats = f"Total: {len(hosts)} Running: {host_stats['running']} Failed: {host_stats['failed']} Finished: {host_stats['finished']} Waiting: {host_stats['waiting']} ww: {min_char_len} wh:{min_line_len} i:{indexOffset} "
|
|
2046
2102
|
else:
|
|
2047
2103
|
# we use the stat bar to display the key presses
|
|
2048
2104
|
encodedLine = ''.join(__keyPressesIn[lineToDisplay]).encode().decode().strip('\n') + ' '
|
|
@@ -2051,7 +2107,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2051
2107
|
# displayCurserPosition is needed as the curserPosition can be larger than the length of the encodedLine. This is wanted to keep scrolling through the history less painful
|
|
2052
2108
|
displayCurserPosition = min(curserPosition,len(encodedLine) -1)
|
|
2053
2109
|
stats = f'Send CMD: {encodedLine[:displayCurserPosition]}\x1b[7m{encodedLine[displayCurserPosition]}\x1b[0m{encodedLine[displayCurserPosition + 1:]}'
|
|
2054
|
-
if stats != old_stat :
|
|
2110
|
+
if stats != old_stat or refresh_all:
|
|
2055
2111
|
old_stat = stats
|
|
2056
2112
|
# calculate the real curser position in stats as we centered the stats
|
|
2057
2113
|
# if 'Send CMD: ' in stats:
|
|
@@ -2071,7 +2127,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2071
2127
|
#target_length = max_x - 2 + len('\x1b[33m\x1b[0m\x1b[31m\x1b[0m\x1b[32m\x1b[0m')
|
|
2072
2128
|
#bottom_stats = '└'+ f" Total: {len(hosts)} Running: \x1b[33m{host_stats['running']}\x1b[0m Failed: \x1b[31m{host_stats['failed']}\x1b[0m Finished: \x1b[32m{host_stats['finished']}\x1b[0m Waiting: {host_stats['waiting']} "[:target_length].center(target_length, "─")
|
|
2073
2129
|
bottom_stats = f" Total: {len(hosts)} Running: \x1b[33m{host_stats['running']}\x1b[0m Failed: \x1b[31m{host_stats['failed']}\x1b[0m Finished: \x1b[32m{host_stats['finished']}\x1b[0m Waiting: {host_stats['waiting']} "
|
|
2074
|
-
if bottom_stats != old_bottom_stat:
|
|
2130
|
+
if bottom_stats != old_bottom_stat or refresh_all:
|
|
2075
2131
|
old_bottom_stat = bottom_stats
|
|
2076
2132
|
#bottom_border.clear()
|
|
2077
2133
|
#bottom_border.addnstr(0, 0, bottom_stats, max_x - 1)
|
|
@@ -2080,6 +2136,9 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2080
2136
|
# set the maximum refresh rate to 100 Hz
|
|
2081
2137
|
if time.perf_counter() - last_refresh_time < 0.01:
|
|
2082
2138
|
time.sleep(max(0,0.01 - time.perf_counter() + last_refresh_time))
|
|
2139
|
+
if refresh_all:
|
|
2140
|
+
rearrangedHosts = set(hosts_to_display)
|
|
2141
|
+
refresh_all = False
|
|
2083
2142
|
#stdscr.clear()
|
|
2084
2143
|
for host_window, host in zip(host_windows, hosts_to_display):
|
|
2085
2144
|
# we will only update the window if there is new output or the window is not fully printed
|
|
@@ -2102,15 +2161,16 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2102
2161
|
for lineNumToReprint in sorted(lineNumToPrintSet):
|
|
2103
2162
|
# if the line is visible, we will reprint it
|
|
2104
2163
|
if visibleLowerBound <= lineNumToReprint <= len(host.output):
|
|
2105
|
-
_curses_add_string_to_window(window=host_window, y=lineNumToReprint + 1, line=host.output[lineNumToReprint], color_pair_list=host.current_color_pair,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2164
|
+
_curses_add_string_to_window(window=host_window, y=lineNumToReprint + 1, line=host.output[lineNumToReprint], color_pair_list=host.current_color_pair,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color,fill_char='')
|
|
2106
2165
|
except Exception as e:
|
|
2107
2166
|
# import traceback
|
|
2108
2167
|
# print(str(e).strip())
|
|
2109
2168
|
# print(traceback.format_exc().strip())
|
|
2110
2169
|
if org_dim != stdscr.getmaxyx():
|
|
2111
2170
|
return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize detected')
|
|
2112
|
-
host_window.
|
|
2113
|
-
hosts_to_display, host_stats,rearrangedHosts = _get_hosts_to_display(hosts, max_num_hosts,hosts_to_display)
|
|
2171
|
+
host_window.noutrefresh()
|
|
2172
|
+
hosts_to_display, host_stats,rearrangedHosts = _get_hosts_to_display(hosts, max_num_hosts,hosts_to_display, indexOffset)
|
|
2173
|
+
curses.doupdate()
|
|
2114
2174
|
last_refresh_time = time.perf_counter()
|
|
2115
2175
|
except Exception as e:
|
|
2116
2176
|
import traceback
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=h8FnoCjcZGOjyPypzJ7H4XLOhMIKU2I-kN-m6PGdjJY,149182
|
|
2
|
+
multissh3-5.74.dist-info/METADATA,sha256=ErFNVhzY6qUCJAiI2-paOpyfNiLJKJayjXCYiQRHJPg,18093
|
|
3
|
+
multissh3-5.74.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
+
multissh3-5.74.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multissh3-5.74.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multissh3-5.74.dist-info/RECORD,,
|
multissh3-5.72.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=d4tiPSSa51_OrTZujc0XrzR9elZq10lyyRBLtRffWyk,144750
|
|
2
|
-
multissh3-5.72.dist-info/METADATA,sha256=d4CQxszsQx7a7GWkQSYC2VGWdLQP13C3kp1RTAIHK5Y,18093
|
|
3
|
-
multissh3-5.72.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
4
|
-
multissh3-5.72.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
-
multissh3-5.72.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
-
multissh3-5.72.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|