multiSSH3 5.85__tar.gz → 5.86__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.85
3
+ Version: 5.86
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multiSSH3
3
- Version: 5.85
3
+ Version: 5.86
4
4
  Summary: Run commands on multiple hosts via SSH
5
5
  Home-page: https://github.com/yufei-pan/multiSSH3
6
6
  Author: Yufei Pan
@@ -81,10 +81,10 @@ except :
81
81
  print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
82
82
  def cache_decorator(func):
83
83
  return func
84
- version = '5.85'
84
+ version = '5.86'
85
85
  VERSION = version
86
86
  __version__ = version
87
- COMMIT_DATE = '2025-08-13'
87
+ COMMIT_DATE = '2025-10-07'
88
88
 
89
89
  CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
90
90
  '~/multiSSH3.config.json',
@@ -1964,7 +1964,7 @@ def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None, indexO
1964
1964
  rearrangedHosts.add(host)
1965
1965
  return new_hosts_to_display , {'running':len(running_hosts), 'failed':len(failed_hosts), 'finished':len(finished_hosts), 'waiting':len(waiting_hosts)}, rearrangedHosts
1966
1966
 
1967
- 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'):
1967
+ 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,help_shown = False, config_reason = 'New Configuration'):
1968
1968
  global _encoding
1969
1969
  _ = config_reason
1970
1970
  try:
@@ -1983,9 +1983,9 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
1983
1983
  min_line_len_local = max_y-1
1984
1984
  # return True if the terminal is too small
1985
1985
  if max_x < 2 or max_y < 2:
1986
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal too small')
1986
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Terminal too small')
1987
1987
  if min_char_len_local < 1 or min_line_len_local < 1:
1988
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Minimum character or line length too small')
1988
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Minimum character or line length too small')
1989
1989
  # We need to figure out how many hosts we can fit in the terminal
1990
1990
  # We will need at least 2 lines per host, one for its name, one for its output
1991
1991
  # Each line will be at least 61 characters long (60 for the output, 1 for the borders)
@@ -1993,10 +1993,10 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
1993
1993
  max_num_hosts_y = max_y // (min_line_len_local + 1)
1994
1994
  max_num_hosts = max_num_hosts_x * max_num_hosts_y
1995
1995
  if max_num_hosts < 1:
1996
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal too small to display any hosts')
1996
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Terminal too small to display any hosts')
1997
1997
  hosts_to_display , host_stats, rearrangedHosts = _get_hosts_to_display(hosts, max_num_hosts)
1998
1998
  if len(hosts_to_display) == 0:
1999
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'No hosts to display')
1999
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'No hosts to display')
2000
2000
  # Now we calculate the actual number of hosts we will display for x and y
2001
2001
  optimal_len_x = max(min_char_len_local, 80)
2002
2002
  num_hosts_x = min(max(min(max_num_hosts_x, max_x // optimal_len_x),1),len(hosts_to_display))
@@ -2017,7 +2017,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2017
2017
  host_window_height = max_y // num_hosts_y
2018
2018
  host_window_width = max_x // num_hosts_x
2019
2019
  if host_window_height < 1 or host_window_width < 1:
2020
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Host window too small')
2020
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Host window too small')
2021
2021
 
2022
2022
  old_stat = ''
2023
2023
  old_bottom_stat = ''
@@ -2078,7 +2078,6 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2078
2078
  _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)
2079
2079
  help_panel = curses.panel.new_panel(help_window)
2080
2080
  help_panel.hide()
2081
- help_shown = False
2082
2081
  curses.panel.update_panels()
2083
2082
  indexOffset = 0
2084
2083
  while host_stats['running'] > 0 or host_stats['waiting'] > 0:
@@ -2091,7 +2090,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2091
2090
  # with open('keylog.txt','a') as f:
2092
2091
  # f.write(str(key)+'\n')
2093
2092
  if key == 410 or key == curses.KEY_RESIZE: # 410 is the key code for resize
2094
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize requested')
2093
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Terminal resize requested')
2095
2094
  # if the user pressed ctrl + d and the last line is empty, we will exit by adding 'exit\n' to the last line
2096
2095
  elif key == 4 and not __keyPressesIn[-1]:
2097
2096
  __keyPressesIn[-1].extend('exit\n')
@@ -2099,20 +2098,20 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2099
2098
  elif key == 95 and not __keyPressesIn[-1]: # 95 is the key code for _
2100
2099
  # if last line is empty, we will reconfigure the wh to be smaller
2101
2100
  if min_line_len != 1:
2102
- return (lineToDisplay,curserPosition , min_char_len , max(min_line_len -1,1), single_window, 'Decrease line length')
2101
+ return (lineToDisplay,curserPosition , min_char_len , max(min_line_len -1,1), single_window,help_shown, 'Decrease line length')
2103
2102
  elif key == 43 and not __keyPressesIn[-1]: # 43 is the key code for +
2104
2103
  # if last line is empty, we will reconfigure the wh to be larger
2105
- return (lineToDisplay,curserPosition , min_char_len , min_line_len +1, single_window, 'Increase line length')
2104
+ return (lineToDisplay,curserPosition , min_char_len , min_line_len +1, single_window,help_shown, 'Increase line length')
2106
2105
  elif key == 123 and not __keyPressesIn[-1]: # 123 is the key code for {
2107
2106
  # if last line is empty, we will reconfigure the ww to be smaller
2108
2107
  if min_char_len != 1:
2109
- return (lineToDisplay,curserPosition , max(min_char_len -1,1), min_line_len, single_window, 'Decrease character length')
2108
+ return (lineToDisplay,curserPosition , max(min_char_len -1,1), min_line_len, single_window,help_shown, 'Decrease character length')
2110
2109
  elif key == 124 and not __keyPressesIn[-1]: # 124 is the key code for |
2111
2110
  # if last line is empty, we will toggle the single window mode
2112
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, not single_window, 'Toggle single window mode')
2111
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, not single_window,help_shown, 'Toggle single window mode')
2113
2112
  elif key == 125 and not __keyPressesIn[-1]: # 125 is the key code for }
2114
2113
  # if last line is empty, we will reconfigure the ww to be larger
2115
- return (lineToDisplay,curserPosition , min_char_len +1, min_line_len, single_window, 'Increase character length')
2114
+ return (lineToDisplay,curserPosition , min_char_len +1, min_line_len, single_window,help_shown, 'Increase character length')
2116
2115
  elif key == 60 and not __keyPressesIn[-1]: # 60 is the key code for <
2117
2116
  indexOffset = (indexOffset - 1 ) % len(hosts)
2118
2117
  elif key == 62 and not __keyPressesIn[-1]: # 62 is the key code for >
@@ -2147,11 +2146,11 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2147
2146
  curserPosition = len(__keyPressesIn[lineToDisplay])
2148
2147
  elif key == curses.KEY_REFRESH or key == curses.KEY_F5 or key == 18: # 18 is the key code for ctrl + R
2149
2148
  # if the key is refresh, we will refresh the screen
2150
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Refresh requested')
2149
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Refresh requested')
2151
2150
  elif key == curses.KEY_EXIT or key == 27: # 27 is the key code for ESC
2152
2151
  # if the key is exit, we will exit the program
2153
2152
  return
2154
- elif key == curses.KEY_HELP or key == 63 or key == curses.KEY_F1: # 63 is the key code for ?
2153
+ elif key == curses.KEY_HELP or key == 63 or key == curses.KEY_F1 or key == 8: # 63 is the key code for ?
2155
2154
  # if the key is help, we will display the help message
2156
2155
  if not help_shown:
2157
2156
  help_panel.show()
@@ -2194,7 +2193,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2194
2193
  curserPosition += 1
2195
2194
  # reconfigure when the terminal size changes
2196
2195
  if org_dim != stdscr.getmaxyx():
2197
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize detected')
2196
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Terminal resize detected')
2198
2197
  # We generate the aggregated stats if user did not input anything
2199
2198
  if not __keyPressesIn[lineToDisplay]:
2200
2199
  #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, "━")
@@ -2268,7 +2267,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2268
2267
  # print(str(e).strip())
2269
2268
  # print(traceback.format_exc().strip())
2270
2269
  if org_dim != stdscr.getmaxyx():
2271
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, 'Terminal resize detected')
2270
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, 'Terminal resize detected')
2272
2271
  if host.lastPrintedUpdateTime != host.lastUpdateTime and host.output_buffer.tell() > 0:
2273
2272
  # this means there is still output in the buffer, we will print it
2274
2273
  # we will print the output in the window
@@ -2276,11 +2275,14 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
2276
2275
  host_window.noutrefresh()
2277
2276
  host.lastPrintedUpdateTime = host.lastUpdateTime
2278
2277
  hosts_to_display, host_stats,rearrangedHosts = _get_hosts_to_display(hosts, max_num_hosts,hosts_to_display, indexOffset)
2278
+ if help_shown:
2279
+ help_window.touchwin()
2280
+ help_window.noutrefresh()
2279
2281
  curses.doupdate()
2280
2282
  last_refresh_time = time.perf_counter()
2281
2283
  except Exception as e:
2282
2284
  import traceback
2283
- return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window, f'Error: {str(e)}',traceback.format_exc())
2285
+ return (lineToDisplay,curserPosition , min_char_len, min_line_len, single_window,help_shown, f'Error: {str(e)}',traceback.format_exc())
2284
2286
  return None
2285
2287
 
2286
2288
  def curses_print(stdscr, hosts, threads, min_char_len = DEFAULT_CURSES_MINIMUM_CHAR_LEN, min_line_len = DEFAULT_CURSES_MINIMUM_LINE_LEN,single_window = DEFAULT_SINGLE_WINDOW):
@@ -2330,7 +2332,7 @@ def curses_print(stdscr, hosts, threads, min_char_len = DEFAULT_CURSES_MINIMUM_C
2330
2332
  stdscr.refresh()
2331
2333
  except:
2332
2334
  pass
2333
- params = (-1,0 , min_char_len, min_line_len, single_window,'new config')
2335
+ params = (-1,0 , min_char_len, min_line_len, single_window,False,'new config')
2334
2336
  while params:
2335
2337
  params = __generate_display(stdscr, hosts, *params)
2336
2338
  if not params:
@@ -2341,17 +2343,17 @@ def curses_print(stdscr, hosts, threads, min_char_len = DEFAULT_CURSES_MINIMUM_C
2341
2343
  # print the current configuration
2342
2344
  stdscr.clear()
2343
2345
  try:
2344
- stdscr.addstr(0, 0, f"{params[5]}, Reloading Configuration: min_char_len={params[2]}, min_line_len={params[3]}, single_window={params[4]} with window size {stdscr.getmaxyx()} and {len(hosts)} hosts...")
2345
- if len(params) > 6:
2346
+ stdscr.addstr(0, 0, f"{params[6]}, Reloading Configuration: min_char_len={params[2]}, min_line_len={params[3]}, single_window={params[4]} with window size {stdscr.getmaxyx()} and {len(hosts)} hosts...")
2347
+ if len(params) > 7:
2346
2348
  # traceback is available, print it
2347
2349
  i = 1
2348
- for line in params[6].split('\n'):
2350
+ for line in params[7].split('\n'):
2349
2351
  stdscr.addstr(i, 0, line)
2350
2352
  i += 1
2351
2353
  stdscr.refresh()
2352
2354
  except:
2353
2355
  pass
2354
- params = params[:5] + ('new config',)
2356
+ params = params[:6] + ('new config',)
2355
2357
  time.sleep(0.01)
2356
2358
  #time.sleep(0.25)
2357
2359
 
@@ -3095,9 +3097,9 @@ def get_parser():
3095
3097
  parser.add_argument("-j","--json", action='store_true', help=F"Output in json format. (default: {DEFAULT_JSON_MODE})", default=DEFAULT_JSON_MODE)
3096
3098
  parser.add_argument('-w',"--success_hosts", action='store_true', help=f"Output the hosts that succeeded in summary as well. (default: {DEFAULT_PRINT_SUCCESS_HOSTS})", default=DEFAULT_PRINT_SUCCESS_HOSTS)
3097
3099
  parser.add_argument('-P',"-g","--greppable",'--table', action='store_true', help=f"Output in greppable table. (default: {DEFAULT_GREPPABLE_MODE})", default=DEFAULT_GREPPABLE_MODE)
3098
- group = parser.add_mutually_exclusive_group()
3099
- group.add_argument('-x',"-su","--skip_unreachable", action='store_true', help=f"Skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {DEFAULT_SKIP_UNREACHABLE})", default=DEFAULT_SKIP_UNREACHABLE)
3100
- group.add_argument('-a',"-nsu","--no_skip_unreachable",dest = 'skip_unreachable', action='store_false', help=f"Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {not DEFAULT_SKIP_UNREACHABLE})", default=not DEFAULT_SKIP_UNREACHABLE)
3100
+ su_group = parser.add_mutually_exclusive_group()
3101
+ su_group.add_argument('-x',"-su","--skip_unreachable", action='store_true', help=f"Skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {DEFAULT_SKIP_UNREACHABLE})", default=DEFAULT_SKIP_UNREACHABLE)
3102
+ su_group.add_argument('-a',"-nsu","--no_skip_unreachable",dest = 'skip_unreachable', action='store_false', help=f"Do not skip unreachable hosts. Note: Timedout Hosts are considered unreachable. Note: multiple command sequence will still auto skip unreachable hosts. (default: {not DEFAULT_SKIP_UNREACHABLE})", default=not DEFAULT_SKIP_UNREACHABLE)
3101
3103
  parser.add_argument('-uhe','--unavailable_host_expiry', type=int, help=f"Time in seconds to expire the unavailable hosts (default: {DEFAULT_UNAVAILABLE_HOST_EXPIRY})", default=DEFAULT_UNAVAILABLE_HOST_EXPIRY)
3102
3104
  parser.add_argument('-X',"-sh","--skip_hosts", type=str, help=f"Skip the hosts in the list. (default: {DEFAULT_SKIP_HOSTS if DEFAULT_SKIP_HOSTS else 'None'})", default=DEFAULT_SKIP_HOSTS)
3103
3105
  parser.add_argument('--generate_config_file', action='store_true', help=f'Store / generate the default config file from command line argument and current config at --config_file / stdout')
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes