multiSSH3 5.27__tar.gz → 5.30__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.
- {multissh3-5.27 → multissh3-5.30}/PKG-INFO +1 -1
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.py +302 -39
- {multissh3-5.27 → multissh3-5.30}/setup.py +1 -1
- {multissh3-5.27 → multissh3-5.30}/LICENSE +0 -0
- {multissh3-5.27 → multissh3-5.30}/README.md +0 -0
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/SOURCES.txt +0 -0
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-5.27 → multissh3-5.30}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-5.27 → multissh3-5.30}/setup.cfg +0 -0
|
@@ -24,6 +24,7 @@ import shutil
|
|
|
24
24
|
import getpass
|
|
25
25
|
import uuid
|
|
26
26
|
import tempfile
|
|
27
|
+
import math
|
|
27
28
|
|
|
28
29
|
try:
|
|
29
30
|
# Check if functiools.cache is available
|
|
@@ -36,7 +37,7 @@ except AttributeError:
|
|
|
36
37
|
# If neither is available, use a dummy decorator
|
|
37
38
|
def cache_decorator(func):
|
|
38
39
|
return func
|
|
39
|
-
version = '5.
|
|
40
|
+
version = '5.30'
|
|
40
41
|
VERSION = version
|
|
41
42
|
|
|
42
43
|
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
@@ -179,12 +180,17 @@ class Host:
|
|
|
179
180
|
self.uuid = uuid
|
|
180
181
|
self.identity_file = identity_file
|
|
181
182
|
self.ip = ip if ip else getIP(name)
|
|
183
|
+
self.current_color_pair = [-1, -1, 1]
|
|
182
184
|
|
|
183
185
|
def __iter__(self):
|
|
184
186
|
return zip(['name', 'command', 'returncode', 'stdout', 'stderr'], [self.name, self.command, self.returncode, self.stdout, self.stderr])
|
|
185
187
|
def __repr__(self):
|
|
186
188
|
# return the complete data structure
|
|
187
|
-
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr},
|
|
189
|
+
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr}, \
|
|
190
|
+
output={self.output}, printedLines={self.printedLines}, files={self.files}, ipmi={self.ipmi}, \
|
|
191
|
+
interface_ip_prefix={self.interface_ip_prefix}, scp={self.scp}, gatherMode={self.gatherMode}, \
|
|
192
|
+
extraargs={self.extraargs}, resolvedName={self.resolvedName}, i={self.i}, uuid={self.uuid}), \
|
|
193
|
+
identity_file={self.identity_file}, ip={self.ip}, current_color_pair={self.current_color_pair}"
|
|
188
194
|
def __str__(self):
|
|
189
195
|
return f"Host(name={self.name}, command={self.command}, returncode={self.returncode}, stdout={self.stdout}, stderr={self.stderr})"
|
|
190
196
|
|
|
@@ -341,7 +347,30 @@ if True:
|
|
|
341
347
|
__keyPressesIn = [[]]
|
|
342
348
|
_emo = False
|
|
343
349
|
_etc_hosts = __configs_from_file.get('_etc_hosts', __build_in_default_config['_etc_hosts'])
|
|
344
|
-
|
|
350
|
+
__curses_global_color_pairs = {(-1,-1):1}
|
|
351
|
+
__curses_current_color_pair_index = 2 # Start from 1, as 0 is the default color pair
|
|
352
|
+
__curses_color_table = {}
|
|
353
|
+
__curses_current_color_index = 10
|
|
354
|
+
|
|
355
|
+
# Mapping of ANSI 4-bit colors to curses colors
|
|
356
|
+
ANSI_TO_CURSES_COLOR = {
|
|
357
|
+
30: curses.COLOR_BLACK,
|
|
358
|
+
31: curses.COLOR_RED,
|
|
359
|
+
32: curses.COLOR_GREEN,
|
|
360
|
+
33: curses.COLOR_YELLOW,
|
|
361
|
+
34: curses.COLOR_BLUE,
|
|
362
|
+
35: curses.COLOR_MAGENTA,
|
|
363
|
+
36: curses.COLOR_CYAN,
|
|
364
|
+
37: curses.COLOR_WHITE,
|
|
365
|
+
90: curses.COLOR_BLACK, # Bright Black (usually gray)
|
|
366
|
+
91: curses.COLOR_RED, # Bright Red
|
|
367
|
+
92: curses.COLOR_GREEN, # Bright Green
|
|
368
|
+
93: curses.COLOR_YELLOW, # Bright Yellow
|
|
369
|
+
94: curses.COLOR_BLUE, # Bright Blue
|
|
370
|
+
95: curses.COLOR_MAGENTA, # Bright Magenta
|
|
371
|
+
96: curses.COLOR_CYAN, # Bright Cyan
|
|
372
|
+
97: curses.COLOR_WHITE # Bright White
|
|
373
|
+
}
|
|
345
374
|
# ------------ Exportable Help Functions ----------------
|
|
346
375
|
# check if command sshpass is available
|
|
347
376
|
_binPaths = {}
|
|
@@ -998,9 +1027,6 @@ def __expand_hostnames(hosts) -> dict:
|
|
|
998
1027
|
username, host = host.split('@',1)
|
|
999
1028
|
username = username.strip()
|
|
1000
1029
|
host = host.strip()
|
|
1001
|
-
username, host = host.split('@',1)
|
|
1002
|
-
username = username.strip()
|
|
1003
|
-
host = host.strip()
|
|
1004
1030
|
# first we check if the hostname is an range of IP addresses
|
|
1005
1031
|
# This is done by checking if the hostname follows four fields of
|
|
1006
1032
|
# "(((\d{1,3}|x|\*|\?)(-(\d{1,3}|x|\*|\?))?)|(\[(\d{1,3}|x|\*|\?)(-(\d{1,3}|x|\*|\?))?\]))"
|
|
@@ -1424,6 +1450,227 @@ def start_run_on_hosts(hosts, timeout=60,password=None,max_connections=4 * os.cp
|
|
|
1424
1450
|
return threads
|
|
1425
1451
|
|
|
1426
1452
|
# ------------ Display Block ----------------
|
|
1453
|
+
def __approximate_color_8bit(color):
|
|
1454
|
+
"""
|
|
1455
|
+
Approximate an 8-bit color (0-255) to the nearest curses color.
|
|
1456
|
+
|
|
1457
|
+
Args:
|
|
1458
|
+
color: 8-bit color code
|
|
1459
|
+
|
|
1460
|
+
Returns:
|
|
1461
|
+
Curses color code
|
|
1462
|
+
"""
|
|
1463
|
+
if color < 8: # Standard and bright colors
|
|
1464
|
+
return ANSI_TO_CURSES_COLOR.get(color % 8 + 30, curses.COLOR_WHITE)
|
|
1465
|
+
elif 8 <= color < 16: # Bright colors
|
|
1466
|
+
return ANSI_TO_CURSES_COLOR.get(color % 8 + 90, curses.COLOR_WHITE)
|
|
1467
|
+
elif 16 <= color <= 231: # Color cube
|
|
1468
|
+
# Convert 216-color cube index to RGB
|
|
1469
|
+
color -= 16
|
|
1470
|
+
r = (color // 36) % 6 * 51
|
|
1471
|
+
g = (color // 6) % 6 * 51
|
|
1472
|
+
b = color % 6 * 51
|
|
1473
|
+
return __approximate_color_24bit(r, g, b) # Map to the closest curses color
|
|
1474
|
+
elif 232 <= color <= 255: # Grayscale
|
|
1475
|
+
gray = (color - 232) * 10 + 8
|
|
1476
|
+
return __approximate_color_24bit(gray, gray, gray)
|
|
1477
|
+
else:
|
|
1478
|
+
return curses.COLOR_WHITE # Fallback to white for unexpected values
|
|
1479
|
+
|
|
1480
|
+
def __approximate_color_24bit(r, g, b):
|
|
1481
|
+
"""
|
|
1482
|
+
Approximate a 24-bit RGB color to the nearest curses color.
|
|
1483
|
+
Will initiate a curses color if curses.can_change_color() is True.
|
|
1484
|
+
|
|
1485
|
+
Globals:
|
|
1486
|
+
__curses_color_table: Dictionary of RGB color to curses color code
|
|
1487
|
+
__curses_current_color_index: Current index of the
|
|
1488
|
+
|
|
1489
|
+
Args:
|
|
1490
|
+
r: Red component (0-255)
|
|
1491
|
+
g: Green component (0-255)
|
|
1492
|
+
b: Blue component (0-255)
|
|
1493
|
+
|
|
1494
|
+
Returns:
|
|
1495
|
+
Curses color code
|
|
1496
|
+
"""
|
|
1497
|
+
if curses.can_change_color():
|
|
1498
|
+
global __curses_color_table,__curses_current_color_index
|
|
1499
|
+
# Initiate a new color if it does not exist
|
|
1500
|
+
if (r, g, b) not in __curses_color_table:
|
|
1501
|
+
if __curses_current_color_index >= curses.COLORS:
|
|
1502
|
+
eprint("Warning: Maximum number of colors reached. Wrapping around.")
|
|
1503
|
+
__curses_current_color_index = 10
|
|
1504
|
+
curses.init_color(__curses_current_color_index, int(r/255*1000), int(g/255*1000), int(b/255*1000))
|
|
1505
|
+
__curses_color_table[(r, g, b)] = __curses_current_color_index
|
|
1506
|
+
__curses_current_color_index += 1
|
|
1507
|
+
return __curses_color_table[(r, g, b)]
|
|
1508
|
+
# Fallback to 8-bit color approximation
|
|
1509
|
+
colors = {
|
|
1510
|
+
curses.COLOR_BLACK: (0, 0, 0),
|
|
1511
|
+
curses.COLOR_RED: (255, 0, 0),
|
|
1512
|
+
curses.COLOR_GREEN: (0, 255, 0),
|
|
1513
|
+
curses.COLOR_YELLOW: (255, 255, 0),
|
|
1514
|
+
curses.COLOR_BLUE: (0, 0, 255),
|
|
1515
|
+
curses.COLOR_MAGENTA: (255, 0, 255),
|
|
1516
|
+
curses.COLOR_CYAN: (0, 255, 255),
|
|
1517
|
+
curses.COLOR_WHITE: (255, 255, 255),
|
|
1518
|
+
}
|
|
1519
|
+
best_match = curses.COLOR_WHITE
|
|
1520
|
+
min_distance = float("inf")
|
|
1521
|
+
for color, (cr, cg, cb) in colors.items():
|
|
1522
|
+
distance = math.sqrt((r - cr) ** 2 + (g - cg) ** 2 + (b - cb) ** 2)
|
|
1523
|
+
if distance < min_distance:
|
|
1524
|
+
min_distance = distance
|
|
1525
|
+
best_match = color
|
|
1526
|
+
return best_match
|
|
1527
|
+
|
|
1528
|
+
def __parse_ansi_escape_sequence_to_curses_color(escape_code):
|
|
1529
|
+
"""
|
|
1530
|
+
Parse ANSI escape codes to extract foreground and background colors.
|
|
1531
|
+
|
|
1532
|
+
Args:
|
|
1533
|
+
escape_code: ANSI escape sequence for color
|
|
1534
|
+
|
|
1535
|
+
Returns:
|
|
1536
|
+
Tuple of (foreground, background) curses color pairs.
|
|
1537
|
+
If the escape code is a reset code, return (-1, -1).
|
|
1538
|
+
None values indicate that the color should not be changed.
|
|
1539
|
+
"""
|
|
1540
|
+
if not escape_code:
|
|
1541
|
+
return None, None
|
|
1542
|
+
color_match = re.match(r"\x1b\[(\d+)(?:;(\d+))?(?:;(\d+))?(?:;(\d+);(\d+);(\d+))?m", escape_code)
|
|
1543
|
+
if color_match:
|
|
1544
|
+
params = color_match.groups()
|
|
1545
|
+
if params[0] == "0" and not any(params[1:]): # Reset code
|
|
1546
|
+
return -1, -1
|
|
1547
|
+
if params[0] == "38" and params[1] == "5": # 8-bit foreground
|
|
1548
|
+
return __approximate_color_8bit(int(params[2])), None
|
|
1549
|
+
elif params[0] == "38" and params[1] == "2": # 24-bit foreground
|
|
1550
|
+
return __approximate_color_24bit(int(params[3]), int(params[4]), int(params[5])), None
|
|
1551
|
+
elif params[0] == "48" and params[1] == "5": # 8-bit background
|
|
1552
|
+
return None , __approximate_color_8bit(int(params[2]))
|
|
1553
|
+
elif params[0] == "48" and params[1] == "2": # 24-bit background
|
|
1554
|
+
return None, __approximate_color_24bit(int(params[3]), int(params[4]), int(params[5]))
|
|
1555
|
+
else:
|
|
1556
|
+
fg = None
|
|
1557
|
+
bg = None
|
|
1558
|
+
if params[0] and params[0].isdigit(): # 4-bit color
|
|
1559
|
+
fg = ANSI_TO_CURSES_COLOR.get(int(params[0]), curses.COLOR_WHITE)
|
|
1560
|
+
if params[1] and params[1].isdigit():
|
|
1561
|
+
bg = ANSI_TO_CURSES_COLOR.get(int(params[1]), curses.COLOR_BLACK)
|
|
1562
|
+
return fg, bg
|
|
1563
|
+
return None, None
|
|
1564
|
+
|
|
1565
|
+
def __get_curses_color_pair(fg, bg):
|
|
1566
|
+
"""
|
|
1567
|
+
Use curses color int values to create a curses color pair.
|
|
1568
|
+
|
|
1569
|
+
Globals:
|
|
1570
|
+
__curses_global_color_pairs: Dictionary of color pairs
|
|
1571
|
+
__curses_current_color_pair_index: Current index of the color pair
|
|
1572
|
+
|
|
1573
|
+
Args:
|
|
1574
|
+
fg: Foreground color code
|
|
1575
|
+
bg: Background color code
|
|
1576
|
+
|
|
1577
|
+
Returns:
|
|
1578
|
+
Curses color pair code
|
|
1579
|
+
"""
|
|
1580
|
+
global __curses_global_color_pairs, __curses_current_color_pair_index
|
|
1581
|
+
if (fg, bg) not in __curses_global_color_pairs:
|
|
1582
|
+
if __curses_current_color_pair_index >= curses.COLOR_PAIRS:
|
|
1583
|
+
print("Warning: Maximum number of color pairs reached, wrapping around.")
|
|
1584
|
+
__curses_current_color_pair_index = 1
|
|
1585
|
+
curses.init_pair(__curses_current_color_pair_index, fg, bg)
|
|
1586
|
+
__curses_global_color_pairs[(fg, bg)] = __curses_current_color_pair_index
|
|
1587
|
+
__curses_current_color_pair_index += 1
|
|
1588
|
+
return curses.color_pair(__curses_global_color_pairs[(fg, bg)])
|
|
1589
|
+
|
|
1590
|
+
def _curses_add_string_to_window(window, line, y = 0, x = 0, number_of_char_to_write = -1, color_pair_list = [-1,-1,1],fill_char=' ',parse_ascii_colors = True,centered = False,lead_str = '', trail_str = '',box_ansi_color = None):
|
|
1591
|
+
"""
|
|
1592
|
+
Add a string to a curses window with / without ANSI color escape sequences translated to curses color pairs.
|
|
1593
|
+
|
|
1594
|
+
Args:
|
|
1595
|
+
window: curses window object
|
|
1596
|
+
line: The line to add
|
|
1597
|
+
y: Line position in the window. Use -1 to scroll the window up 1 line and add the line at the bottom
|
|
1598
|
+
x: Column position in the window
|
|
1599
|
+
number_of_char_to_write: Number of characters to write. -1 for all remaining space in line, 0 for no characters, and a positive integer for a specific number of characters.
|
|
1600
|
+
color_pair_list: List of [foreground, background, color_pair] curses color pair values
|
|
1601
|
+
fill_char: Character to fill the remaining space in the line
|
|
1602
|
+
parse_ascii_colors: Parse ASCII color codes
|
|
1603
|
+
centered: Center the text in the window
|
|
1604
|
+
lead_str: Leading string to add to the line
|
|
1605
|
+
trail_str: Trailing string to add to the line
|
|
1606
|
+
|
|
1607
|
+
Returns:
|
|
1608
|
+
None
|
|
1609
|
+
"""
|
|
1610
|
+
if window.getmaxyx()[0] == 0 or window.getmaxyx()[1] == 0 or x >= window.getmaxyx()[1]:
|
|
1611
|
+
return
|
|
1612
|
+
if x < 0:
|
|
1613
|
+
x = window.getmaxyx()[1] + x
|
|
1614
|
+
if number_of_char_to_write == -1:
|
|
1615
|
+
numChar = window.getmaxyx()[1] - x -1
|
|
1616
|
+
elif number_of_char_to_write == 0:
|
|
1617
|
+
return
|
|
1618
|
+
elif number_of_char_to_write + x > window.getmaxyx()[1]:
|
|
1619
|
+
numChar = window.getmaxyx()[1] - x -1
|
|
1620
|
+
else:
|
|
1621
|
+
numChar = number_of_char_to_write
|
|
1622
|
+
if numChar < 0:
|
|
1623
|
+
return
|
|
1624
|
+
if y < 0 or y >= window.getmaxyx()[0]:
|
|
1625
|
+
window.move(0, 0)
|
|
1626
|
+
window.deleteln()
|
|
1627
|
+
y = window.getmaxyx()[0] - 1
|
|
1628
|
+
if parse_ascii_colors:
|
|
1629
|
+
segments = re.split(r"(\x1b\[[\d;]*m)", line) # Split line by ANSI escape codes
|
|
1630
|
+
else:
|
|
1631
|
+
segments = [line]
|
|
1632
|
+
charsWritten = 0
|
|
1633
|
+
boxFrontColor, boxBackColor = color_pair_list[0], color_pair_list[1]
|
|
1634
|
+
newBoxFrontColor, newBoxBackColor = __parse_ansi_escape_sequence_to_curses_color(box_ansi_color)
|
|
1635
|
+
if newBoxFrontColor:
|
|
1636
|
+
boxFrontColor = newBoxFrontColor
|
|
1637
|
+
if newBoxBackColor:
|
|
1638
|
+
boxBackColor = newBoxBackColor
|
|
1639
|
+
boxColorPair = __get_curses_color_pair(boxFrontColor, boxBackColor)
|
|
1640
|
+
# first add the lead_str
|
|
1641
|
+
window.addnstr(y, x, lead_str, numChar, boxColorPair)
|
|
1642
|
+
charsWritten = min(len(lead_str), numChar)
|
|
1643
|
+
# process centering
|
|
1644
|
+
if centered:
|
|
1645
|
+
fill_length = numChar - len(lead_str) - len(trail_str) - sum([len(segment) for segment in segments if not segment.startswith("\x1b[")])
|
|
1646
|
+
window.addnstr(y, x + charsWritten, fill_char * (fill_length // 2), numChar - charsWritten, boxColorPair)
|
|
1647
|
+
charsWritten += min(fill_length // 2, numChar - charsWritten)
|
|
1648
|
+
# add the segments
|
|
1649
|
+
for segment in segments:
|
|
1650
|
+
if parse_ascii_colors and segment.startswith("\x1b["):
|
|
1651
|
+
# Parse ANSI escape sequence
|
|
1652
|
+
newFrontColor, newBackColor = __parse_ansi_escape_sequence_to_curses_color(segment)
|
|
1653
|
+
if newFrontColor is not None:
|
|
1654
|
+
color_pair_list[0] = newFrontColor
|
|
1655
|
+
if newBackColor is not None:
|
|
1656
|
+
color_pair_list[1] = newBackColor
|
|
1657
|
+
color_pair_list[2] = __get_curses_color_pair(color_pair_list[0], color_pair_list[1])
|
|
1658
|
+
#window.addnstr(y, x + charsWritten, str(color_pair_list[2]), numChar - charsWritten, color_pair_list[2])
|
|
1659
|
+
#charsWritten += min(len(str(color_pair_list[2])), numChar - charsWritten)
|
|
1660
|
+
else:
|
|
1661
|
+
# Add text with current color
|
|
1662
|
+
if charsWritten < numChar:
|
|
1663
|
+
window.addnstr(y, x + charsWritten, segment, numChar - charsWritten, color_pair_list[2])
|
|
1664
|
+
charsWritten += min(len(segment), numChar - charsWritten)
|
|
1665
|
+
# if we have finished printing segments but we still have space, we will fill it with fill_char
|
|
1666
|
+
if charsWritten + len(trail_str) < numChar:
|
|
1667
|
+
fillStr = fill_char * (numChar - charsWritten - len(trail_str))
|
|
1668
|
+
#fillStr = f'{color_pair_list}'
|
|
1669
|
+
window.addnstr(y, x + charsWritten, fillStr + trail_str, numChar - charsWritten, boxColorPair)
|
|
1670
|
+
charsWritten += numChar - charsWritten
|
|
1671
|
+
else:
|
|
1672
|
+
window.addnstr(y, x + charsWritten, trail_str, numChar - charsWritten, boxColorPair)
|
|
1673
|
+
|
|
1427
1674
|
def _get_hosts_to_display (hosts, max_num_hosts, hosts_to_display = None):
|
|
1428
1675
|
'''
|
|
1429
1676
|
Generate a list for the hosts to be displayed on the screen. This is used to display as much relevant information as possible.
|
|
@@ -1519,6 +1766,7 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1519
1766
|
stdscr.nodelay(True)
|
|
1520
1767
|
# we generate a stats window at the top of the screen
|
|
1521
1768
|
stat_window = curses.newwin(1, max_x, 0, 0)
|
|
1769
|
+
stat_window.leaveok(True)
|
|
1522
1770
|
# We create a window for each host
|
|
1523
1771
|
host_windows = []
|
|
1524
1772
|
for i, host in enumerate(hosts_to_display):
|
|
@@ -1529,13 +1777,18 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1529
1777
|
#print(f"Creating a window at {y},{x}")
|
|
1530
1778
|
# We create the window
|
|
1531
1779
|
host_window = curses.newwin(host_window_height, host_window_width, y, x)
|
|
1780
|
+
host_window.idlok(True)
|
|
1781
|
+
host_window.scrollok(True)
|
|
1782
|
+
host_window.leaveok(True)
|
|
1532
1783
|
host_windows.append(host_window)
|
|
1533
1784
|
# If there is space left, we will draw the bottom border
|
|
1534
1785
|
bottom_border = None
|
|
1535
1786
|
if y + host_window_height < org_dim[0]:
|
|
1536
1787
|
bottom_border = curses.newwin(1, max_x, y + host_window_height, 0)
|
|
1788
|
+
bottom_border.leaveok(True)
|
|
1537
1789
|
#bottom_border.clear()
|
|
1538
|
-
bottom_border.
|
|
1790
|
+
#bottom_border.addnstr(0, 0, '-' * (max_x - 1), max_x - 1)
|
|
1791
|
+
_curses_add_string_to_window(window=bottom_border, y=0, line='-' * (max_x - 1),fill_char='-')
|
|
1539
1792
|
bottom_border.refresh()
|
|
1540
1793
|
while host_stats['running'] > 0 or host_stats['waiting'] > 0:
|
|
1541
1794
|
# Check for keypress
|
|
@@ -1637,11 +1890,13 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1637
1890
|
# encodedLine = encodedLine[:curserPosition] + ' ' + encodedLine[curserPosition:]
|
|
1638
1891
|
stats = '┍'+ f"Send CMD: {encodedLine}"[:max_x - 2].center(max_x - 2, "━")
|
|
1639
1892
|
if bottom_border:
|
|
1640
|
-
|
|
1893
|
+
target_length = max_x - 2 + len('\x1b[33m\x1b[0m\x1b[31m\x1b[0m\x1b[32m\x1b[0m')
|
|
1894
|
+
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, "─")
|
|
1641
1895
|
if bottom_stats != old_bottom_stat:
|
|
1642
1896
|
old_bottom_stat = bottom_stats
|
|
1643
1897
|
#bottom_border.clear()
|
|
1644
|
-
bottom_border.
|
|
1898
|
+
#bottom_border.addnstr(0, 0, bottom_stats, max_x - 1)
|
|
1899
|
+
_curses_add_string_to_window(window=bottom_border, y=0, line=bottom_stats)
|
|
1645
1900
|
bottom_border.refresh()
|
|
1646
1901
|
if stats != old_stat or curserPosition != old_cursor_position:
|
|
1647
1902
|
old_stat = stats
|
|
@@ -1654,9 +1909,9 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1654
1909
|
#stat_window.clear()
|
|
1655
1910
|
#stat_window.addstr(0, 0, stats)
|
|
1656
1911
|
# add the line with curser that inverses the color at the curser position
|
|
1657
|
-
stat_window.addstr(0, 0, stats[:curserPositionStats]
|
|
1658
|
-
stat_window.
|
|
1659
|
-
stat_window.
|
|
1912
|
+
stat_window.addstr(0, 0, stats[:curserPositionStats])
|
|
1913
|
+
stat_window.addch(0,curserPositionStats, stats[curserPositionStats], curses.A_REVERSE)
|
|
1914
|
+
stat_window.addnstr(0, curserPositionStats + 1, stats[curserPositionStats + 1:], max_x - 1 - curserPositionStats)
|
|
1660
1915
|
stat_window.refresh()
|
|
1661
1916
|
# set the maximum refresh rate to 100 Hz
|
|
1662
1917
|
if time.perf_counter() - last_refresh_time < 0.01:
|
|
@@ -1670,17 +1925,19 @@ def __generate_display(stdscr, hosts, lineToDisplay = -1,curserPosition = 0, min
|
|
|
1670
1925
|
#host_window.clear()
|
|
1671
1926
|
# we will try to center the name of the host with ┼ at the beginning and end and ─ in between
|
|
1672
1927
|
linePrintOut = f'┼{(host.name+":["+host.command+"]")[:host_window_width - 2].center(host_window_width - 1, "─")}'.replace('\n', ' ').replace('\r', ' ').strip()
|
|
1673
|
-
host_window.
|
|
1928
|
+
host_window.addnstr(0, 0, linePrintOut, host_window_width - 1)
|
|
1929
|
+
#_add_line_with_ascii_colors(window=host_window, y=0, x=0, line=linePrintOut, n=host_window_width - 1, color_pair_list = host.current_color_pair)
|
|
1674
1930
|
# we will display the latest outputs of the host as much as we can
|
|
1675
1931
|
for i, line in enumerate(host.output[-(host_window_height - 1):]):
|
|
1676
1932
|
# print(f"Printng a line at {i + 1} with length of {len('│'+line[:host_window_width - 1])}")
|
|
1677
1933
|
# time.sleep(10)
|
|
1678
|
-
linePrintOut = ('│'+line[:host_window_width - 2].replace('\n', ' ').replace('\r', ' ')).strip()
|
|
1679
|
-
host_window.
|
|
1934
|
+
linePrintOut = ('│'+line[:host_window_width - 2].replace('\n', ' ').replace('\r', ' ')).strip()
|
|
1935
|
+
#host_window.addnstr(i + 1, 0, linePrintOut, host_window_width - 1)
|
|
1936
|
+
_curses_add_string_to_window(window=host_window, y=i + 1, line=linePrintOut, color_pair_list=host.current_color_pair)
|
|
1680
1937
|
# we draw the rest of the available lines
|
|
1681
1938
|
for i in range(len(host.output), host_window_height - 1):
|
|
1682
1939
|
# print(f"Printng a line at {i + 1} with length of {len('│')}")
|
|
1683
|
-
host_window.
|
|
1940
|
+
host_window.addnstr(i + 1, 0, '│'.ljust(host_window_width - 1, ' '), host_window_width - 1)
|
|
1684
1941
|
host.printedLines = len(host.output)
|
|
1685
1942
|
host_window.refresh()
|
|
1686
1943
|
except Exception as e:
|
|
@@ -1710,33 +1967,39 @@ def curses_print(stdscr, hosts, threads, min_char_len = DEFAULT_CURSES_MINIMUM_C
|
|
|
1710
1967
|
'''
|
|
1711
1968
|
# We create all the windows we need
|
|
1712
1969
|
# We initialize the color pair
|
|
1713
|
-
curses.start_color()
|
|
1714
1970
|
curses.curs_set(0)
|
|
1715
|
-
curses.
|
|
1716
|
-
curses.
|
|
1717
|
-
curses.init_pair(
|
|
1718
|
-
curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK)
|
|
1719
|
-
curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
|
1720
|
-
curses.init_pair(6, curses.COLOR_BLUE, curses.COLOR_BLACK)
|
|
1721
|
-
curses.init_pair(7, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
|
|
1722
|
-
curses.init_pair(8, curses.COLOR_CYAN, curses.COLOR_BLACK)
|
|
1723
|
-
curses.init_pair(9, curses.COLOR_WHITE, curses.COLOR_RED)
|
|
1724
|
-
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_GREEN)
|
|
1725
|
-
curses.init_pair(11, curses.COLOR_WHITE, curses.COLOR_YELLOW)
|
|
1726
|
-
curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_BLUE)
|
|
1727
|
-
curses.init_pair(13, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
|
|
1728
|
-
curses.init_pair(14, curses.COLOR_WHITE, curses.COLOR_CYAN)
|
|
1729
|
-
curses.init_pair(15, curses.COLOR_BLACK, curses.COLOR_RED)
|
|
1730
|
-
curses.init_pair(16, curses.COLOR_BLACK, curses.COLOR_GREEN)
|
|
1731
|
-
curses.init_pair(17, curses.COLOR_BLACK, curses.COLOR_YELLOW)
|
|
1732
|
-
curses.init_pair(18, curses.COLOR_BLACK, curses.COLOR_BLUE)
|
|
1733
|
-
curses.init_pair(19, curses.COLOR_BLACK, curses.COLOR_MAGENTA)
|
|
1734
|
-
curses.init_pair(20, curses.COLOR_BLACK, curses.COLOR_CYAN)
|
|
1735
|
-
|
|
1971
|
+
curses.start_color()
|
|
1972
|
+
curses.use_default_colors()
|
|
1973
|
+
curses.init_pair(1, -1, -1)
|
|
1736
1974
|
# do not generate display if the output window have a size of zero
|
|
1737
1975
|
if stdscr.getmaxyx()[0] < 2 or stdscr.getmaxyx()[1] < 2:
|
|
1738
1976
|
return
|
|
1739
|
-
|
|
1977
|
+
stdscr.idlok(True)
|
|
1978
|
+
stdscr.scrollok(True)
|
|
1979
|
+
stdscr.leaveok(True)
|
|
1980
|
+
# generate some debug information before display initialization
|
|
1981
|
+
try:
|
|
1982
|
+
stdscr.clear()
|
|
1983
|
+
_curses_add_string_to_window(window=stdscr, y=0, line='Initializing display...', n=stdscr.getmaxyx()[1] - 1)
|
|
1984
|
+
# print the size
|
|
1985
|
+
_curses_add_string_to_window(window=stdscr, y=1, line=f"Terminal size: {stdscr.getmaxyx()}", n=stdscr.getmaxyx()[1] - 1)
|
|
1986
|
+
# print the number of hosts
|
|
1987
|
+
_curses_add_string_to_window(window=stdscr, y=2, line=f"Number of hosts: {len(hosts)}", n=stdscr.getmaxyx()[1] - 1)
|
|
1988
|
+
# print the number of threads
|
|
1989
|
+
_curses_add_string_to_window(window=stdscr, y=3, line=f"Number of threads: {len(threads)}", n=stdscr.getmaxyx()[1] - 1)
|
|
1990
|
+
# print the minimum character length
|
|
1991
|
+
_curses_add_string_to_window(window=stdscr, y=4, line=f"Minimum character length: {min_char_len}", n=stdscr.getmaxyx()[1] - 1)
|
|
1992
|
+
# print the minimum line length
|
|
1993
|
+
_curses_add_string_to_window(window=stdscr, y=5, line=f"Minimum line length: {min_line_len}", n=stdscr.getmaxyx()[1] - 1)
|
|
1994
|
+
# print the single window mode
|
|
1995
|
+
_curses_add_string_to_window(window=stdscr, y=6, line=f"Single window mode: {single_window}", n=stdscr.getmaxyx()[1] - 1)
|
|
1996
|
+
# print COLORS and COLOR_PAIRS count
|
|
1997
|
+
_curses_add_string_to_window(window=stdscr, y=7, line=f"len(COLORS): {curses.COLORS} len(COLOR_PAIRS): {curses.COLOR_PAIRS}", n=stdscr.getmaxyx()[1] - 1)
|
|
1998
|
+
# print if can change color
|
|
1999
|
+
_curses_add_string_to_window(window=stdscr, y=8, line=f"Real color capability: {curses.can_change_color()}", n=stdscr.getmaxyx()[1] - 1)
|
|
2000
|
+
stdscr.refresh()
|
|
2001
|
+
except:
|
|
2002
|
+
pass
|
|
1740
2003
|
params = (-1,0 , min_char_len, min_line_len, single_window,'new config')
|
|
1741
2004
|
while params:
|
|
1742
2005
|
params = __generate_display(stdscr, hosts, *params)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|