multiSSH3 5.97__py3-none-any.whl → 5.99__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 +203 -97
- {multissh3-5.97.dist-info → multissh3-5.99.dist-info}/METADATA +1 -1
- multissh3-5.99.dist-info/RECORD +6 -0
- multissh3-5.97.dist-info/RECORD +0 -6
- {multissh3-5.97.dist-info → multissh3-5.99.dist-info}/WHEEL +0 -0
- {multissh3-5.97.dist-info → multissh3-5.99.dist-info}/entry_points.txt +0 -0
- {multissh3-5.97.dist-info → multissh3-5.99.dist-info}/top_level.txt +0 -0
multiSSH3.py
CHANGED
|
@@ -84,10 +84,10 @@ except Exception:
|
|
|
84
84
|
print('Warning: functools.lru_cache is not available, multiSSH3 will run slower without cache.',file=sys.stderr)
|
|
85
85
|
def cache_decorator(func):
|
|
86
86
|
return func
|
|
87
|
-
version = '5.
|
|
87
|
+
version = '5.99'
|
|
88
88
|
VERSION = version
|
|
89
89
|
__version__ = version
|
|
90
|
-
COMMIT_DATE = '2025-10-
|
|
90
|
+
COMMIT_DATE = '2025-10-24'
|
|
91
91
|
|
|
92
92
|
CONFIG_FILE_CHAIN = ['./multiSSH3.config.json',
|
|
93
93
|
'~/multiSSH3.config.json',
|
|
@@ -377,6 +377,24 @@ ERROR_MESSAGES_TO_IGNORE = [
|
|
|
377
377
|
'Killed by signal',
|
|
378
378
|
'Connection reset by peer',
|
|
379
379
|
]
|
|
380
|
+
__DEFAULT_COLOR_PALETTE = {
|
|
381
|
+
'cyan': (86, 173, 188),
|
|
382
|
+
'green': (114, 180, 43),
|
|
383
|
+
'magenta': (140, 107, 200),
|
|
384
|
+
'red': (196, 38, 94),
|
|
385
|
+
'white': (227, 227, 221),
|
|
386
|
+
'yellow': (179, 180, 43),
|
|
387
|
+
'blue': (106, 126, 200),
|
|
388
|
+
'bright_black': (102, 102, 102),
|
|
389
|
+
'bright_blue': (129, 154, 255),
|
|
390
|
+
'bright_cyan': (102, 217, 239),
|
|
391
|
+
'bright_green': (126, 226, 46),
|
|
392
|
+
'bright_magenta': (174, 129, 255),
|
|
393
|
+
'bright_red': (249, 38, 114),
|
|
394
|
+
'bright_white': (248, 248, 242),
|
|
395
|
+
'bright_yellow': (226, 226, 46),
|
|
396
|
+
}
|
|
397
|
+
COLOR_PALETTE = __DEFAULT_COLOR_PALETTE.copy()
|
|
380
398
|
_DEFAULT_CALLED = True
|
|
381
399
|
_DEFAULT_RETURN_UNFINISHED = False
|
|
382
400
|
_DEFAULT_UPDATE_UNREACHABLE_HOSTS = True
|
|
@@ -936,6 +954,8 @@ def get_terminal_color_capability():
|
|
|
936
954
|
return '24bit'
|
|
937
955
|
elif "256" in term:
|
|
938
956
|
return '256'
|
|
957
|
+
elif "16" in term:
|
|
958
|
+
return '16'
|
|
939
959
|
try:
|
|
940
960
|
curses.setupterm()
|
|
941
961
|
colors = curses.tigetnum("colors")
|
|
@@ -954,96 +974,120 @@ def get_terminal_color_capability():
|
|
|
954
974
|
return 'None'
|
|
955
975
|
|
|
956
976
|
@cache_decorator
|
|
957
|
-
def
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
977
|
+
def rgb_to_ansi_color_string(r, g, b):
|
|
978
|
+
"""
|
|
979
|
+
Return an ANSI escape sequence setting the foreground to (r,g,b)
|
|
980
|
+
approximated to the terminal's capability, or '' if none.
|
|
981
|
+
"""
|
|
982
|
+
cap = get_terminal_color_capability()
|
|
983
|
+
if cap == 'None':
|
|
984
|
+
return ''
|
|
985
|
+
if cap == '24bit':
|
|
986
|
+
return f'\x1b[38;2;{r};{g};{b}m'
|
|
987
|
+
if cap == '256':
|
|
988
|
+
idx = _rgb_to_256_color(r, g, b)
|
|
989
|
+
return f'\x1b[38;5;{idx}m'
|
|
990
|
+
if cap == '16':
|
|
991
|
+
idx = _rgb_to_16_color(r, g, b)
|
|
992
|
+
# 0–7 = 30–37, 8–15 = 90–97
|
|
993
|
+
if idx < 8:
|
|
994
|
+
return f'\x1b[{30 + idx}m'
|
|
995
|
+
else:
|
|
996
|
+
return f'\x1b[{90 + (idx - 8)}m'
|
|
997
|
+
if cap == '8':
|
|
998
|
+
idx = _rgb_to_8_color(r, g, b)
|
|
999
|
+
return f'\x1b[{30 + idx}m'
|
|
1000
|
+
return ''
|
|
979
1001
|
|
|
980
|
-
|
|
981
|
-
|
|
1002
|
+
def _rgb_to_256_color(r, g, b):
|
|
1003
|
+
"""
|
|
1004
|
+
Map (r,g,b) to the 256-color cube or grayscale ramp.
|
|
982
1005
|
"""
|
|
983
|
-
|
|
984
|
-
r
|
|
985
|
-
|
|
1006
|
+
# if it’s already gray, use the 232–255 grayscale ramp
|
|
1007
|
+
if r == g == b:
|
|
1008
|
+
# 24 shades from 232 to 255
|
|
1009
|
+
return 232 + int(round(r / 255 * 23))
|
|
1010
|
+
# else map each channel to 0–5
|
|
1011
|
+
def to6(v):
|
|
1012
|
+
return int(round(v / 255 * 5))
|
|
1013
|
+
r6, g6, b6 = to6(r), to6(g), to6(b)
|
|
1014
|
+
return 16 + 36 * r6 + 6 * g6 + b6
|
|
1015
|
+
|
|
1016
|
+
def _rgb_to_16_color(r, g, b):
|
|
986
1017
|
"""
|
|
987
|
-
|
|
1018
|
+
Pick the nearest of the 16 ANSI standard colors.
|
|
1019
|
+
Returns an index 0-15.
|
|
1020
|
+
"""
|
|
1021
|
+
palette = [
|
|
1022
|
+
(0, 0, 0), # 0 black
|
|
1023
|
+
(128, 0, 0), # 1 red
|
|
1024
|
+
(0, 128, 0), # 2 green
|
|
1025
|
+
(128, 128, 0), # 3 yellow
|
|
1026
|
+
(0, 0, 128), # 4 blue
|
|
1027
|
+
(128, 0, 128), # 5 magenta
|
|
1028
|
+
(0, 128, 128), # 6 cyan
|
|
1029
|
+
(192, 192, 192), # 7 white (light gray)
|
|
1030
|
+
(128, 128, 128), # 8 bright black (dark gray)
|
|
1031
|
+
(255, 0, 0), # 9 bright red
|
|
1032
|
+
(0, 255, 0), # 10 bright green
|
|
1033
|
+
(255, 255, 0), # 11 bright yellow
|
|
1034
|
+
(0, 0, 255), # 12 bright blue
|
|
1035
|
+
(255, 0, 255), # 13 bright magenta
|
|
1036
|
+
(0, 255, 255), # 14 bright cyan
|
|
1037
|
+
(255, 255, 255), # 15 bright white
|
|
1038
|
+
]
|
|
1039
|
+
best_idx = 0
|
|
988
1040
|
best_dist = float('inf')
|
|
989
|
-
for i, (pr, pg, pb) in enumerate(
|
|
990
|
-
|
|
991
|
-
dg = pg - g
|
|
992
|
-
db = pb - b
|
|
993
|
-
dist = dr*dr + dg*dg + db*db
|
|
1041
|
+
for i, (pr, pg, pb) in enumerate(palette):
|
|
1042
|
+
dist = (r - pr)**2 + (g - pg)**2 + (b - pb)**2
|
|
994
1043
|
if dist < best_dist:
|
|
995
1044
|
best_dist = dist
|
|
996
|
-
|
|
997
|
-
return
|
|
1045
|
+
best_idx = i
|
|
1046
|
+
return best_idx
|
|
998
1047
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1048
|
+
def _rgb_to_8_color(r, g, b):
|
|
1049
|
+
"""
|
|
1050
|
+
Reduce to 8 colors by mapping to the 16-color index then clamping 0-7.
|
|
1051
|
+
"""
|
|
1052
|
+
return _rgb_to_16_color(r//2, g//2, b//2)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def int_to_color(hash_value, min_brightness=100,max_brightness=220):
|
|
1002
1056
|
r = (hash_value >> 16) & 0xFF
|
|
1003
1057
|
g = (hash_value >> 8) & 0xFF
|
|
1004
1058
|
b = hash_value & 0xFF
|
|
1005
|
-
|
|
1006
|
-
|
|
1059
|
+
brightness = math.sqrt(0.299 * r**2 + 0.587 * g**2 + 0.114 * b**2)
|
|
1060
|
+
if brightness < min_brightness:
|
|
1061
|
+
return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
|
|
1062
|
+
if brightness > max_brightness:
|
|
1063
|
+
return int_to_color(hash(str(hash_value)), min_brightness, max_brightness)
|
|
1007
1064
|
return (r, g, b)
|
|
1008
1065
|
|
|
1009
|
-
|
|
1066
|
+
__previous_color_rgb = ()
|
|
1010
1067
|
@cache_decorator
|
|
1011
|
-
def
|
|
1068
|
+
def int_to_unique_ansi_color(number):
|
|
1012
1069
|
'''
|
|
1013
|
-
Convert a
|
|
1070
|
+
Convert a number to a unique ANSI color code
|
|
1014
1071
|
|
|
1015
1072
|
Args:
|
|
1016
|
-
|
|
1017
|
-
|
|
1073
|
+
number (int): The number to convert
|
|
1018
1074
|
Returns:
|
|
1019
1075
|
int: The ANSI color code
|
|
1020
1076
|
'''
|
|
1021
|
-
global
|
|
1077
|
+
global __previous_color_rgb
|
|
1022
1078
|
# Use a hash function to generate a consistent integer from the string
|
|
1023
1079
|
color_capability = get_terminal_color_capability()
|
|
1024
|
-
index = None
|
|
1025
1080
|
if color_capability == 'None':
|
|
1026
1081
|
return ''
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
index = (hash(string) % 14) + 31
|
|
1030
|
-
if index > 37:
|
|
1031
|
-
index += 52 # Bright colors (90-97)
|
|
1032
|
-
elif color_capability == '8':
|
|
1033
|
-
index = (hash(string) % 6) + 31
|
|
1034
|
-
r,g,b = hashable_to_color(string)
|
|
1035
|
-
if color_capability == '256':
|
|
1036
|
-
index = rgb_to_xterm_index(r,g,b)
|
|
1037
|
-
if index:
|
|
1038
|
-
if index == __previous_ansi_color_index:
|
|
1039
|
-
return string_to_unique_ansi_color(hash(string))
|
|
1040
|
-
__previous_ansi_color_index = index
|
|
1041
|
-
if color_capability == '256':
|
|
1042
|
-
return f'\033[38;5;{index}m'
|
|
1043
|
-
else:
|
|
1044
|
-
return f'\033[{index}m'
|
|
1082
|
+
if color_capability == '24bit':
|
|
1083
|
+
r, g, b = int_to_color(number)
|
|
1045
1084
|
else:
|
|
1046
|
-
|
|
1085
|
+
# for 256 colors and below, reduce brightness threshold as we do not have many color to work with
|
|
1086
|
+
r, g, b = int_to_color(number, min_brightness=70, max_brightness=190)
|
|
1087
|
+
if sum(abs(a - b) for a, b in zip((r, g, b), __previous_color_rgb)) <= 256:
|
|
1088
|
+
r, g, b = int_to_color(hash(str(number)))
|
|
1089
|
+
__previous_color_rgb = (r, g, b)
|
|
1090
|
+
return rgb_to_ansi_color_string(r, g, b)
|
|
1047
1091
|
|
|
1048
1092
|
#%% ------------ Compacting Hostnames ----------------
|
|
1049
1093
|
def __tokenize_hostname(hostname):
|
|
@@ -2795,7 +2839,7 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2795
2839
|
indexes = Counter({hostname: 0 for hostname in merging_hostnames})
|
|
2796
2840
|
working_index_keys = set(merging_hostnames)
|
|
2797
2841
|
previousBuddies = set()
|
|
2798
|
-
hostnameWrapper = textwrap.TextWrapper(width=line_length -
|
|
2842
|
+
hostnameWrapper = textwrap.TextWrapper(width=line_length -1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent=' ', subsequent_indent='- ')
|
|
2799
2843
|
hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
|
|
2800
2844
|
diff_display_item_count = max(1,int(max(map(len, outputs_by_hostname.values())) * (1 - diff_display_threshold)))
|
|
2801
2845
|
def get_multiset_index_for_hostname(hostname):
|
|
@@ -2830,6 +2874,13 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2830
2874
|
# futures[hostname] # ensure it's initialized
|
|
2831
2875
|
futures = {hostname: get_multiset_index_for_hostname(hostname) for hostname in merging_hostnames}
|
|
2832
2876
|
currentLines = defaultdict(set)
|
|
2877
|
+
color_cap = get_terminal_color_capability()
|
|
2878
|
+
if color_cap == 'None':
|
|
2879
|
+
green_str = ''
|
|
2880
|
+
reset_str = ''
|
|
2881
|
+
else:
|
|
2882
|
+
green_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('green', __DEFAULT_COLOR_PALETTE['green']))
|
|
2883
|
+
reset_str = '\033[0m'
|
|
2833
2884
|
for hostname in merging_hostnames:
|
|
2834
2885
|
currentLines[outputs_by_hostname[hostname][0]].add(hostname)
|
|
2835
2886
|
while indexes:
|
|
@@ -2867,12 +2918,18 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2867
2918
|
if buddy != previousBuddies:
|
|
2868
2919
|
hostnameStr = ','.join(compact_hostnames(buddy))
|
|
2869
2920
|
hostnameLines = hostnameWrapper.wrap(hostnameStr)
|
|
2870
|
-
hostnameLines = [line.ljust(line_length
|
|
2871
|
-
|
|
2872
|
-
|
|
2921
|
+
# hostnameLines = [line.ljust(line_length) for line in hostnameLines]
|
|
2922
|
+
if color_cap == 'None':
|
|
2923
|
+
hostnameLines[0] = f"■{hostnameLines[0]}"
|
|
2924
|
+
elif len(buddy) < len(merging_hostnames):
|
|
2925
|
+
color = int_to_unique_ansi_color(hash(hostnameStr))
|
|
2926
|
+
hostnameLines[0] = f"{color}■{hostnameLines[0]}"
|
|
2927
|
+
hostnameLines[-1] += reset_str
|
|
2928
|
+
else:
|
|
2929
|
+
hostnameLines[0] = f"{green_str}■{reset_str}{hostnameLines[0]}"
|
|
2873
2930
|
output.extend(hostnameLines)
|
|
2874
2931
|
previousBuddies = buddy
|
|
2875
|
-
output.append(lineToAdd
|
|
2932
|
+
output.append(lineToAdd)
|
|
2876
2933
|
currentLines[lineToAdd].difference_update(buddy)
|
|
2877
2934
|
if not currentLines[lineToAdd]:
|
|
2878
2935
|
del currentLines[lineToAdd]
|
|
@@ -2896,17 +2953,27 @@ def mergeOutput(merging_hostnames,outputs_by_hostname,output,diff_display_thresh
|
|
|
2896
2953
|
|
|
2897
2954
|
def mergeOutputs(outputs_by_hostname, merge_groups, remaining_hostnames, diff_display_threshold, line_length):
|
|
2898
2955
|
output = []
|
|
2899
|
-
|
|
2900
|
-
|
|
2956
|
+
color_cap = get_terminal_color_capability()
|
|
2957
|
+
if color_cap == 'None':
|
|
2958
|
+
color_line = ''
|
|
2959
|
+
color_reset = ''
|
|
2960
|
+
green_str = ''
|
|
2961
|
+
else:
|
|
2962
|
+
color_line = rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
|
|
2963
|
+
color_reset = '\033[0m'
|
|
2964
|
+
green_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('green', __DEFAULT_COLOR_PALETTE['green']))
|
|
2965
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2966
|
+
hostnameWrapper = textwrap.TextWrapper(width=line_length - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False, break_on_hyphens=False,initial_indent=' ', subsequent_indent='- ')
|
|
2901
2967
|
hostnameWrapper.wordsep_simple_re = re.compile(r'([,]+)')
|
|
2902
2968
|
for merging_hostnames in merge_groups:
|
|
2903
2969
|
mergeOutput(merging_hostnames, outputs_by_hostname, output, diff_display_threshold,line_length)
|
|
2904
|
-
output.append(
|
|
2970
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2905
2971
|
for hostname in remaining_hostnames:
|
|
2906
2972
|
hostnameLines = hostnameWrapper.wrap(','.join(compact_hostnames([hostname])))
|
|
2907
|
-
|
|
2908
|
-
output.extend(
|
|
2909
|
-
output.
|
|
2973
|
+
hostnameLines[0] = f"{green_str}■{color_reset}{hostnameLines[0]}"
|
|
2974
|
+
output.extend(hostnameLines)
|
|
2975
|
+
output.extend(outputs_by_hostname[hostname])
|
|
2976
|
+
output.append(color_line+'─'*(line_length)+color_reset)
|
|
2910
2977
|
if output:
|
|
2911
2978
|
output.pop()
|
|
2912
2979
|
# if output and output[0] and output[0].startswith('├'):
|
|
@@ -2931,26 +2998,40 @@ def get_host_raw_output(hosts, terminal_width):
|
|
|
2931
2998
|
outputs_by_hostname = {}
|
|
2932
2999
|
line_bag_by_hostname = {}
|
|
2933
3000
|
hostnames_by_line_bag_len = {}
|
|
2934
|
-
text_wrapper = textwrap.TextWrapper(width=terminal_width -
|
|
2935
|
-
initial_indent='
|
|
3001
|
+
text_wrapper = textwrap.TextWrapper(width=terminal_width - 1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
3002
|
+
initial_indent=' ', subsequent_indent='-')
|
|
2936
3003
|
max_length = 20
|
|
3004
|
+
color_cap = get_terminal_color_capability()
|
|
3005
|
+
if color_cap == 'None':
|
|
3006
|
+
color_reset_str = ''
|
|
3007
|
+
blue_str = ''
|
|
3008
|
+
cyan_str = ''
|
|
3009
|
+
green_str = ''
|
|
3010
|
+
red_str = ''
|
|
3011
|
+
else:
|
|
3012
|
+
color_reset_str = '\033[0m'
|
|
3013
|
+
blue_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('blue', __DEFAULT_COLOR_PALETTE['blue']))
|
|
3014
|
+
cyan_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_cyan', __DEFAULT_COLOR_PALETTE['bright_cyan']))
|
|
3015
|
+
green_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_green', __DEFAULT_COLOR_PALETTE['bright_green']))
|
|
3016
|
+
red_str = rgb_to_ansi_color_string(*COLOR_PALETTE.get('bright_red', __DEFAULT_COLOR_PALETTE['bright_red']))
|
|
2937
3017
|
hosts = pre_merge_hosts(hosts)
|
|
2938
3018
|
for host in hosts:
|
|
2939
3019
|
max_length = max(max_length, len(max(host.name.split(','), key=len)) + 3)
|
|
2940
|
-
hostPrintOut = ["
|
|
3020
|
+
hostPrintOut = [f"{cyan_str}█{color_reset_str} EXECUTED COMMAND:"]
|
|
2941
3021
|
for line in host.command.splitlines():
|
|
2942
3022
|
hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2943
3023
|
# hostPrintOut.extend(itertools.chain.from_iterable(text_wrapper.wrap(line) for line in host['command'].splitlines()))
|
|
2944
3024
|
lineBag = {(0,host.command)}
|
|
2945
3025
|
prevLine = host.command
|
|
2946
3026
|
if host.stdout:
|
|
2947
|
-
hostPrintOut.append('
|
|
3027
|
+
hostPrintOut.append(f'{blue_str}▓{color_reset_str} STDOUT:')
|
|
2948
3028
|
# for line in host.stdout:
|
|
2949
3029
|
# if len(line) < terminal_width - 2:
|
|
2950
|
-
# hostPrintOut.append(f"
|
|
3030
|
+
# hostPrintOut.append(f" {line}")
|
|
2951
3031
|
# else:
|
|
2952
3032
|
# hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2953
|
-
hostPrintOut.extend(f"
|
|
3033
|
+
hostPrintOut.extend(f" {line}" for line in host.stdout)
|
|
3034
|
+
max_length = max(max_length, max(map(len, host.stdout)))
|
|
2954
3035
|
# hostPrintOut.extend(text_wrapper.wrap(line) for line in host.stdout)
|
|
2955
3036
|
lineBag.add((prevLine,1))
|
|
2956
3037
|
lineBag.add((1,host.stdout[0]))
|
|
@@ -2966,22 +3047,26 @@ def get_host_raw_output(hosts, terminal_width):
|
|
|
2966
3047
|
elif host.stderr[-1].strip().endswith('No route to host'):
|
|
2967
3048
|
host.stderr[-1] = 'Cannot find host!'
|
|
2968
3049
|
if host.stderr:
|
|
2969
|
-
hostPrintOut.append('
|
|
3050
|
+
hostPrintOut.append(f'{red_str}▒{color_reset_str} STDERR:')
|
|
2970
3051
|
# for line in host.stderr:
|
|
2971
3052
|
# if len(line) < terminal_width - 2:
|
|
2972
|
-
# hostPrintOut.append(f"
|
|
3053
|
+
# hostPrintOut.append(f" {line}")
|
|
2973
3054
|
# else:
|
|
2974
3055
|
# hostPrintOut.extend(text_wrapper.wrap(line))
|
|
2975
|
-
hostPrintOut.extend(f"
|
|
3056
|
+
hostPrintOut.extend(f" {line}" for line in host.stderr)
|
|
3057
|
+
max_length = max(max_length, max(map(len, host.stderr)))
|
|
2976
3058
|
lineBag.add((prevLine,2))
|
|
2977
3059
|
lineBag.add((2,host.stderr[0]))
|
|
2978
3060
|
lineBag.update(host.stderr)
|
|
2979
3061
|
if len(host.stderr) > 1:
|
|
2980
3062
|
lineBag.update(zip(host.stderr, host.stderr[1:]))
|
|
2981
3063
|
prevLine = host.stderr[-1]
|
|
2982
|
-
|
|
3064
|
+
if host.returncode != 0:
|
|
3065
|
+
codeColor = red_str
|
|
3066
|
+
else:
|
|
3067
|
+
codeColor = green_str
|
|
3068
|
+
hostPrintOut.append(f"{codeColor}░{color_reset_str} RETURN CODE: {host.returncode}")
|
|
2983
3069
|
lineBag.add((prevLine,f"{host.returncode}"))
|
|
2984
|
-
max_length = max(max_length, max(map(len, hostPrintOut)))
|
|
2985
3070
|
outputs_by_hostname[host.name] = hostPrintOut
|
|
2986
3071
|
line_bag_by_hostname[host.name] = lineBag
|
|
2987
3072
|
hostnames_by_line_bag_len.setdefault(len(lineBag), set()).add(host.name)
|
|
@@ -3022,6 +3107,7 @@ def form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_le
|
|
|
3022
3107
|
return merge_groups, remaining_hostnames
|
|
3023
3108
|
|
|
3024
3109
|
def generate_output(hosts, usejson = False, greppable = False,quiet = False,encoding = _encoding,keyPressesIn = [[]]):
|
|
3110
|
+
color_cap = get_terminal_color_capability()
|
|
3025
3111
|
if quiet:
|
|
3026
3112
|
# remove hosts with returncode 0
|
|
3027
3113
|
hosts = [host for host in hosts if host.returncode != 0]
|
|
@@ -3029,7 +3115,10 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
|
|
|
3029
3115
|
if usejson:
|
|
3030
3116
|
return '{"Success": true}'
|
|
3031
3117
|
else:
|
|
3032
|
-
|
|
3118
|
+
if color_cap == 'None':
|
|
3119
|
+
return 'Success'
|
|
3120
|
+
else:
|
|
3121
|
+
return '\033[32mSuccess\033[0m'
|
|
3033
3122
|
if usejson:
|
|
3034
3123
|
# [print(dict(host)) for host in hosts]
|
|
3035
3124
|
#print(json.dumps([dict(host) for host in hosts],indent=4))
|
|
@@ -3064,23 +3153,32 @@ def generate_output(hosts, usejson = False, greppable = False,quiet = False,enco
|
|
|
3064
3153
|
except Exception:
|
|
3065
3154
|
eprint("Warning: diff_display_threshold should be a float between 0 and 1. Setting to default value of 0.9")
|
|
3066
3155
|
diff_display_threshold = 0.9
|
|
3156
|
+
|
|
3157
|
+
color_reset_str = '' if color_cap == 'None' else '\033[0m'
|
|
3158
|
+
white_str = '' if color_cap == 'None' else rgb_to_ansi_color_string(*COLOR_PALETTE.get('white', __DEFAULT_COLOR_PALETTE['white']))
|
|
3067
3159
|
terminal_length = get_terminal_size()[0]
|
|
3068
3160
|
outputs_by_hostname, line_bag_by_hostname, hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_length = get_host_raw_output(hosts,terminal_length)
|
|
3069
3161
|
merge_groups ,remaining_hostnames = form_merge_groups(hostnames_by_line_bag_len, sorted_hostnames_by_line_bag_len_keys, line_bag_by_hostname, diff_display_threshold)
|
|
3070
3162
|
outputs = mergeOutputs(outputs_by_hostname, merge_groups,remaining_hostnames, diff_display_threshold,line_length)
|
|
3071
3163
|
if keyPressesIn[-1]:
|
|
3072
3164
|
CMDsOut = [''.join(cmd).encode(encoding=encoding,errors='backslashreplace').decode(encoding=encoding,errors='backslashreplace').replace('\\n', '↵') for cmd in keyPressesIn if cmd]
|
|
3073
|
-
outputs.append("
|
|
3165
|
+
outputs.append(color_reset_str + "░ User Inputs:".ljust(line_length,'─'))
|
|
3074
3166
|
cmdOut = []
|
|
3075
3167
|
for line in CMDsOut:
|
|
3076
3168
|
cmdOut.extend(textwrap.wrap(line, width=line_length-1, tabsize=4, replace_whitespace=False, drop_whitespace=False,
|
|
3077
|
-
initial_indent='
|
|
3078
|
-
outputs.extend(
|
|
3169
|
+
initial_indent=' ', subsequent_indent='-'))
|
|
3170
|
+
outputs.extend(cmdOut)
|
|
3079
3171
|
keyPressesIn[-1].clear()
|
|
3080
3172
|
if not outputs:
|
|
3081
|
-
|
|
3173
|
+
if quiet:
|
|
3174
|
+
if color_cap == 'None':
|
|
3175
|
+
return 'Success'
|
|
3176
|
+
else:
|
|
3177
|
+
return '\033[32mSuccess\033[0m'
|
|
3178
|
+
else:
|
|
3179
|
+
rtnStr = ''
|
|
3082
3180
|
else:
|
|
3083
|
-
rtnStr = '\n'.join(outputs + [
|
|
3181
|
+
rtnStr = '\n'.join(outputs + [white_str + '─' * (line_length) + color_reset_str])
|
|
3084
3182
|
return rtnStr
|
|
3085
3183
|
|
|
3086
3184
|
def print_output(hosts,usejson = False,quiet = False,greppable = False):
|
|
@@ -3126,7 +3224,15 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
3126
3224
|
if total_sleeped > 0.1:
|
|
3127
3225
|
break
|
|
3128
3226
|
if any([host.returncode is None for host in hosts]):
|
|
3129
|
-
|
|
3227
|
+
try:
|
|
3228
|
+
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
3229
|
+
except Exception:
|
|
3230
|
+
try:
|
|
3231
|
+
curses.wrapper(curses_print, hosts, threads, min_char_len = curses_min_char_len, min_line_len = curses_min_line_len, single_window = single_window)
|
|
3232
|
+
except Exception as e:
|
|
3233
|
+
eprint(f"Curses print error: {e}")
|
|
3234
|
+
import traceback
|
|
3235
|
+
print(traceback.format_exc())
|
|
3130
3236
|
if not returnUnfinished:
|
|
3131
3237
|
# wait until all hosts have a return code
|
|
3132
3238
|
while any([host.returncode is None for host in hosts]):
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=Of3ONtNehRGAXWF3E3m8ao7OwmbgEu2YGv524CX0ytw,185227
|
|
2
|
+
multissh3-5.99.dist-info/METADATA,sha256=CLc8mLA6ufLHHF3Xip041K1nvdXcZwcwmsDsARshSoo,18093
|
|
3
|
+
multissh3-5.99.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
+
multissh3-5.99.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multissh3-5.99.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multissh3-5.99.dist-info/RECORD,,
|
multissh3-5.97.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
multiSSH3.py,sha256=AXZqfsQ3c0O0qIGUedwGChpLnkxPX_pDtDEtBj_L7Rk,181554
|
|
2
|
-
multissh3-5.97.dist-info/METADATA,sha256=26GBKHydWcT4nR5HO3St3ubhaH6zp2-_ajR6RvOX6rI,18093
|
|
3
|
-
multissh3-5.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
-
multissh3-5.97.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
-
multissh3-5.97.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
-
multissh3-5.97.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|