multiSSH3 5.62__py3-none-any.whl → 5.64__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.
- multiSSH3.py +44 -60
- {multissh3-5.62.dist-info → multissh3-5.64.dist-info}/METADATA +1 -1
- multissh3-5.64.dist-info/RECORD +6 -0
- {multissh3-5.62.dist-info → multissh3-5.64.dist-info}/WHEEL +1 -1
- multissh3-5.62.dist-info/RECORD +0 -6
- {multissh3-5.62.dist-info → multissh3-5.64.dist-info}/entry_points.txt +0 -0
- {multissh3-5.62.dist-info → multissh3-5.64.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -54,10 +54,10 @@ except AttributeError:
|
|
|
54
54
|
# If neither is available, use a dummy decorator
|
|
55
55
|
def cache_decorator(func):
|
|
56
56
|
return func
|
|
57
|
-
version = '5.
|
|
57
|
+
version = '5.64'
|
|
58
58
|
VERSION = version
|
|
59
59
|
__version__ = version
|
|
60
|
-
COMMIT_DATE = '2025-
|
|
60
|
+
COMMIT_DATE = '2025-05-02'
|
|
61
61
|
|
|
62
62
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
63
63
|
'~/multiSSH3.config.json',
|
|
@@ -225,8 +225,7 @@ class Host:
|
|
|
225
225
|
self.output = [] # the output of the command for curses
|
|
226
226
|
self.stdout = [] # the stdout of the command
|
|
227
227
|
self.stderr = [] # the stderr of the command
|
|
228
|
-
self.
|
|
229
|
-
self.lineNumToReprintSet = set() # line numbers to reprint
|
|
228
|
+
self.lineNumToPrintSet = set() # line numbers to reprint
|
|
230
229
|
self.lastUpdateTime = time.monotonic() # the last time the output was updated
|
|
231
230
|
self.files = files # the files to be copied to the host
|
|
232
231
|
self.ipmi = ipmi # whether to use ipmi to connect to the host
|
|
@@ -248,7 +247,7 @@ class Host:
|
|
|
248
247
|
def __repr__(self):
|
|
249
248
|
# return the complete data structure
|
|
250
249
|
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr}, \
|
|
251
|
-
output={self.output},
|
|
250
|
+
output={self.output}, lineNumToPrintSet={self.lineNumToPrintSet}, files={self.files}, ipmi={self.ipmi}, \
|
|
252
251
|
interface_ip_prefix={self.interface_ip_prefix}, scp={self.scp}, gatherMode={self.gatherMode}, \
|
|
253
252
|
extraargs={self.extraargs}, resolvedName={self.resolvedName}, i={self.i}, uuid={self.uuid}), \
|
|
254
253
|
identity_file={self.identity_file}, ip={self.ip}, current_color_pair={self.current_color_pair}"
|
|
@@ -1129,7 +1128,7 @@ def __handle_reading_stream(stream,target, host):
|
|
|
1129
1128
|
current_line_str = current_line.decode('utf-8',errors='backslashreplace')
|
|
1130
1129
|
target.append(current_line_str)
|
|
1131
1130
|
host.output.append(current_line_str)
|
|
1132
|
-
host.
|
|
1131
|
+
host.lineNumToPrintSet.add(len(host.output)-1)
|
|
1133
1132
|
host.lastUpdateTime = time.monotonic()
|
|
1134
1133
|
current_line = bytearray()
|
|
1135
1134
|
lastLineCommited = True
|
|
@@ -1419,12 +1418,12 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 5):
|
|
|
1419
1418
|
if host.output and timeoutLineAppended and host.output[-1].strip().endswith('] seconds!') and host.output[-1].strip().startswith('Timeout in ['):
|
|
1420
1419
|
host.output.pop()
|
|
1421
1420
|
host.output.append(timeoutLine)
|
|
1422
|
-
host.
|
|
1421
|
+
host.lineNumToPrintSet.add(len(host.output)-1)
|
|
1423
1422
|
timeoutLineAppended = True
|
|
1424
1423
|
elif host.output and timeoutLineAppended and host.output[-1].strip().endswith('] seconds!') and host.output[-1].strip().startswith('Timeout in ['):
|
|
1425
1424
|
host.output.pop()
|
|
1426
1425
|
host.output.append('')
|
|
1427
|
-
host.
|
|
1426
|
+
host.lineNumToPrintSet.add(len(host.output)-1)
|
|
1428
1427
|
timeoutLineAppended = False
|
|
1429
1428
|
if _emo:
|
|
1430
1429
|
host.stderr.append('Ctrl C detected, Emergency Stop!')
|
|
@@ -1855,10 +1854,10 @@ def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None):
|
|
|
1855
1854
|
new_hosts_to_display = (running_hosts + failed_hosts + finished_hosts + waiting_hosts)[:max_num_hosts]
|
|
1856
1855
|
if not hosts_to_display:
|
|
1857
1856
|
return new_hosts_to_display , {'running':len(running_hosts), 'failed':len(failed_hosts), 'finished':len(finished_hosts), 'waiting':len(waiting_hosts)}
|
|
1858
|
-
# we will compare the new_hosts_to_display with the old one, if some hosts are not in their original position, we will
|
|
1857
|
+
# 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
|
|
1859
1858
|
for i, host in enumerate(new_hosts_to_display):
|
|
1860
1859
|
if host not in hosts_to_display or i != hosts_to_display.index(host):
|
|
1861
|
-
host.
|
|
1860
|
+
host.lineNumToPrintSet.update(range(len(host.output)))
|
|
1862
1861
|
return new_hosts_to_display , {'running':len(running_hosts), 'failed':len(failed_hosts), 'finished':len(finished_hosts), 'waiting':len(waiting_hosts)}
|
|
1863
1862
|
|
|
1864
1863
|
def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window=DEFAULT_SINGLE_WINDOW, config_reason = 'New Configuration'):
|
|
@@ -1924,7 +1923,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1924
1923
|
global __keyPressesIn
|
|
1925
1924
|
stdscr.nodelay(True)
|
|
1926
1925
|
# we generate a stats window at the top of the screen
|
|
1927
|
-
stat_window = curses.newwin(1, max_x, 0, 0)
|
|
1926
|
+
stat_window = curses.newwin(1, max_x+1, 0, 0)
|
|
1928
1927
|
stat_window.leaveok(True)
|
|
1929
1928
|
# We create a window for each host
|
|
1930
1929
|
host_windows = []
|
|
@@ -1935,7 +1934,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1935
1934
|
x = (i % num_hosts_x) * host_window_width
|
|
1936
1935
|
#print(f"Creating a window at {y},{x}")
|
|
1937
1936
|
# We create the window
|
|
1938
|
-
host_window = curses.newwin(host_window_height, host_window_width, y, x)
|
|
1937
|
+
host_window = curses.newwin(host_window_height, host_window_width + 1, y, x)
|
|
1939
1938
|
host_window.idlok(True)
|
|
1940
1939
|
host_window.scrollok(True)
|
|
1941
1940
|
host_window.leaveok(True)
|
|
@@ -2084,60 +2083,33 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
2084
2083
|
hosts_to_display, host_stats = _get_hosts_to_display(hosts, max_num_hosts,hosts_to_display)
|
|
2085
2084
|
for host_window, host in zip(host_windows, hosts_to_display):
|
|
2086
2085
|
# we will only update the window if there is new output or the window is not fully printed
|
|
2087
|
-
if new_configured
|
|
2086
|
+
if new_configured:
|
|
2087
|
+
host.lineNumToPrintSet.update(range(len(host.output)))
|
|
2088
|
+
linePrintOut = f'{host.name}:[{host.command}]'.replace('\n', ' ').replace('\r', ' ').strip()
|
|
2089
|
+
_curses_add_string_to_window(window=host_window, y=0, line=linePrintOut, color_pair_list=[-1, -1, 1],centered=True,fill_char='─',lead_str='┼',box_ansi_color=box_ansi_color)
|
|
2090
|
+
# clear the window
|
|
2091
|
+
for i in range(host_window_height - 1):
|
|
2092
|
+
_curses_add_string_to_window(window=host_window, color_pair_list=[-1, -1, 1], y=i + 1,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2093
|
+
# for i in range(host.printedLines, len(host.output)):
|
|
2094
|
+
# _curses_add_string_to_window(window=host_window, y=i + 1, line=host.output[i], color_pair_list=host.current_color_pair,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2095
|
+
# host.printedLines = len(host.output)
|
|
2096
|
+
if host.lineNumToPrintSet:
|
|
2088
2097
|
try:
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
_curses_add_string_to_window(window=host_window, y=0, line=linePrintOut, color_pair_list=[-1, -1, 1],centered=True,fill_char='─',lead_str='┼',box_ansi_color=box_ansi_color)
|
|
2098
|
-
#_add_line_with_ansi_colors(window=host_window, y=0, x=0, line=linePrintOut, n=host_window_width - 1, color_pair_list = host.current_color_pair)
|
|
2099
|
-
# we will display the latest outputs of the host as much as we can
|
|
2100
|
-
#for i, line in enumerate(host.output[-(host_window_height - 1):]):
|
|
2101
|
-
# print(f"Printng a line at {i + 1} with length of {len('│'+line[:host_window_width - 1])}")
|
|
2102
|
-
# time.sleep(10)
|
|
2103
|
-
#linePrintOut = ('│'+line[:host_window_width - 2].replace('\n', ' ').replace('\r', ' ')).strip()
|
|
2104
|
-
#host_window.addnstr(i + 1, 0, linePrintOut, host_window_width - 1)
|
|
2105
|
-
#_curses_add_string_to_window(window=host_window, y=i + 1, line=line, color_pair_list=host.current_color_pair,lead_str='│')
|
|
2106
|
-
# we draw the rest of the available lines
|
|
2107
|
-
# for i in range(len(host.output), host_window_height - 1):
|
|
2108
|
-
# # print(f"Printng a line at {i + 1} with length of {len('│')}")
|
|
2109
|
-
# host_window.addnstr(i + 1, 0, '│'.ljust(host_window_width - 1, ' '), host_window_width - 1)
|
|
2110
|
-
for i in range(host.printedLines, len(host.output)):
|
|
2111
|
-
_curses_add_string_to_window(window=host_window, y=i + 1, line=host.output[i], color_pair_list=host.current_color_pair,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2112
|
-
for i in range(len(host.output), host_window_height - 1):
|
|
2113
|
-
_curses_add_string_to_window(window=host_window, y=i + 1,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2114
|
-
host.printedLines = len(host.output)
|
|
2115
|
-
host_window.refresh()
|
|
2098
|
+
# visible range is from len(host.output) - host_window_height + 1 to len(host.output)
|
|
2099
|
+
visibleLowerBound = max(0, len(host.output) - host_window_height + 1)
|
|
2100
|
+
lineNumToPrintSet = host.lineNumToPrintSet.copy()
|
|
2101
|
+
host.lineNumToPrintSet = set()
|
|
2102
|
+
for lineNumToReprint in sorted(lineNumToPrintSet):
|
|
2103
|
+
# if the line is visible, we will reprint it
|
|
2104
|
+
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)
|
|
2116
2106
|
except Exception as e:
|
|
2117
2107
|
# import traceback
|
|
2118
2108
|
# print(str(e).strip())
|
|
2119
2109
|
# print(traceback.format_exc().strip())
|
|
2120
2110
|
if org_dim != stdscr.getmaxyx():
|
|
2121
2111
|
return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize detected')
|
|
2122
|
-
|
|
2123
|
-
# visible range is from host.printedLines - host_window_height + 1 to host.printedLines
|
|
2124
|
-
visibleLowerBound = host.printedLines - host_window_height + 1
|
|
2125
|
-
lineNumToReprintSet = host.lineNumToReprintSet
|
|
2126
|
-
host.lineNumToReprintSet = set()
|
|
2127
|
-
for lineNumToReprint in lineNumToReprintSet:
|
|
2128
|
-
# if the line is visible, we will reprint it
|
|
2129
|
-
if visibleLowerBound <= lineNumToReprint <= host.printedLines:
|
|
2130
|
-
if visibleLowerBound <= 0:
|
|
2131
|
-
# this means all lines are visible
|
|
2132
|
-
linePos = lineNumToReprint
|
|
2133
|
-
else:
|
|
2134
|
-
# calculate the position of the line to reprint
|
|
2135
|
-
linePos = lineNumToReprint - visibleLowerBound
|
|
2136
|
-
# Note: color can be incorrect if repainting an old line with new colors already initialized,
|
|
2137
|
-
# Thus we will not use any presistent color pair for old lines
|
|
2138
|
-
cpl = host.current_color_pair if lineNumToReprint == host.printedLines else [-1,-1,1]
|
|
2139
|
-
_curses_add_string_to_window(window=host_window, y=linePos + 1, line=host.output[lineNumToReprint], color_pair_list=cpl,lead_str='│',keep_top_n_lines=1,box_ansi_color=box_ansi_color)
|
|
2140
|
-
host_window.refresh()
|
|
2112
|
+
host_window.refresh()
|
|
2141
2113
|
new_configured = False
|
|
2142
2114
|
last_refresh_time = time.perf_counter()
|
|
2143
2115
|
except Exception as e:
|
|
@@ -2225,7 +2197,10 @@ def generate_output(hosts, usejson = False, greppable = False):
|
|
|
2225
2197
|
# remove hosts with returncode 0
|
|
2226
2198
|
hosts = [dict(host) for host in hosts if host.returncode != 0]
|
|
2227
2199
|
if not hosts:
|
|
2228
|
-
|
|
2200
|
+
if usejson:
|
|
2201
|
+
return '{"Success": true}'
|
|
2202
|
+
else:
|
|
2203
|
+
return 'Success'
|
|
2229
2204
|
else:
|
|
2230
2205
|
hosts = [dict(host) for host in hosts]
|
|
2231
2206
|
if usejson:
|
|
@@ -2976,6 +2951,7 @@ def main():
|
|
|
2976
2951
|
parser.add_argument('-ci','--copy_id', action='store_true', help='Copy the ssh id to the hosts')
|
|
2977
2952
|
parser.add_argument('-I','-nh','--no_history', action='store_true', help=f'Do not record the command to history. Default: {DEFAULT_NO_HISTORY}', default=DEFAULT_NO_HISTORY)
|
|
2978
2953
|
parser.add_argument('-hf','--history_file', type=str, help=f'The file to store the history. (default: {DEFAULT_HISTORY_FILE})', default=DEFAULT_HISTORY_FILE)
|
|
2954
|
+
parser.add_argument('--script', action='store_true', help='Run the command in script mode, short for -SCRIPT or --no_watch --skip_unreachable --no_env --no_history --greppable --error_only')
|
|
2979
2955
|
parser.add_argument("-V","--version", action='version', version=f'%(prog)s {version} @ {COMMIT_DATE} with [ {", ".join(_binPaths.keys())} ] by {AUTHOR} ({AUTHOR_EMAIL})')
|
|
2980
2956
|
|
|
2981
2957
|
# parser.add_argument('-u', '--user', metavar='user', type=str, nargs=1,
|
|
@@ -2993,6 +2969,14 @@ def main():
|
|
|
2993
2969
|
if unknown:
|
|
2994
2970
|
eprint(f"Warning: Unknown arguments, treating all as commands: {unknown!r}")
|
|
2995
2971
|
args.commands += unknown
|
|
2972
|
+
|
|
2973
|
+
if args.script:
|
|
2974
|
+
args.no_watch = True
|
|
2975
|
+
args.skip_unreachable = True
|
|
2976
|
+
args.no_env = True
|
|
2977
|
+
args.no_history = True
|
|
2978
|
+
args.greppable = True
|
|
2979
|
+
args.error_only = True
|
|
2996
2980
|
|
|
2997
2981
|
if args.generate_config_file or args.store_config_file:
|
|
2998
2982
|
if args.store_config_file:
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=ibtxrdUOFvF-Wh6tbOwAZnVzbB4l8pbum9__mHXN06A,144227
|
|
2
|
+
multissh3-5.64.dist-info/METADATA,sha256=GVpomUNwfiFv4kzVF4Hbn2oMfhPxi_Ifb9HQ4IDQeeQ,18093
|
|
3
|
+
multissh3-5.64.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
|
4
|
+
multissh3-5.64.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multissh3-5.64.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multissh3-5.64.dist-info/RECORD,,
|
multissh3-5.62.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=aIPftXdNHKopUv-DIOPcBxQd9IQhCAg4mNLgxefh3zM,145959
|
|
2
|
-
multissh3-5.62.dist-info/METADATA,sha256=TEWayZf__9rv11wQTJp8kSY7IULgqQHOPf9CJefLooI,18093
|
|
3
|
-
multissh3-5.62.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
4
|
-
multissh3-5.62.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
-
multissh3-5.62.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
-
multissh3-5.62.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|