pywebexec 1.1.18__tar.gz → 1.2.0__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.
- {pywebexec-1.1.18/pywebexec.egg-info → pywebexec-1.2.0}/PKG-INFO +1 -1
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/pywebexec.py +33 -5
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/version.py +2 -2
- {pywebexec-1.1.18 → pywebexec-1.2.0/pywebexec.egg-info}/PKG-INFO +1 -1
- {pywebexec-1.1.18 → pywebexec-1.2.0}/.github/workflows/python-publish.yml +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/.gitignore +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/LICENSE +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/README.md +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pyproject.toml +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/__init__.py +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/css/style.css +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/aborted.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/copy.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/copy_ok.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/failed.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/favicon.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/running.gif +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/images/success.svg +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/static/js/script.js +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/templates/__init__.py +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec/templates/index.html +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec.egg-info/SOURCES.txt +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec.egg-info/dependency_links.txt +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec.egg-info/entry_points.txt +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec.egg-info/requires.txt +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/pywebexec.egg-info/top_level.txt +0 -0
- {pywebexec-1.1.18 → pywebexec-1.2.0}/setup.cfg +0 -0
@@ -15,6 +15,7 @@ from gunicorn.app.base import Application
|
|
15
15
|
import ipaddress
|
16
16
|
from socket import gethostname, gethostbyname_ex
|
17
17
|
import ssl
|
18
|
+
import re
|
18
19
|
if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
|
19
20
|
from ldap3 import Server, Connection, ALL, SIMPLE, SUBTREE, Tls
|
20
21
|
|
@@ -136,10 +137,28 @@ class StandaloneApplication(Application):
|
|
136
137
|
return self.application
|
137
138
|
|
138
139
|
|
140
|
+
def strip_ansi_control_chars(text):
|
141
|
+
"""Remove ANSI and control characters from the text."""
|
142
|
+
# To clean
|
143
|
+
# ansi_escape = re.compile(r'''
|
144
|
+
# (?:\x1B[@-_]| # ANSI ESCape sequences
|
145
|
+
# \x1B\[.*?[ -/]*[@-~]| # ANSI CSI sequences
|
146
|
+
# \x1B\].*?\x07| # ANSI OSC sequences
|
147
|
+
# \x1B=P| # ANSI DCS sequences
|
148
|
+
# \x1B\\| # ANSI ST sequences
|
149
|
+
# \x1B\^| # ANSI PM sequences
|
150
|
+
# \x1B_.*?\x1B\\| # ANSI APC sequences
|
151
|
+
# [\x00-\x1F\x7F]) # Control characters
|
152
|
+
# ''', re.VERBOSE)
|
153
|
+
# ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|[(]B)|>')
|
154
|
+
ansi_escape = re.compile(br'(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~]|\x1B([(]B|>))')
|
155
|
+
return ansi_escape.sub(b'', text)
|
156
|
+
|
157
|
+
|
139
158
|
def decode_line(line: bytes) -> str:
|
140
159
|
"""try decode line exception on binary"""
|
141
160
|
try:
|
142
|
-
return line.decode()
|
161
|
+
return strip_ansi_control_chars(line).decode().strip(" ")
|
143
162
|
except UnicodeDecodeError:
|
144
163
|
return ""
|
145
164
|
|
@@ -149,8 +168,11 @@ def last_line(fd, maxline=1000):
|
|
149
168
|
line = "\n"
|
150
169
|
fd.seek(0, os.SEEK_END)
|
151
170
|
size = 0
|
152
|
-
|
171
|
+
last_pos = 0
|
172
|
+
while line in ["", "\n", "\r"] and size < maxline:
|
153
173
|
try: # catch if file empty / only empty lines
|
174
|
+
if last_pos:
|
175
|
+
fd.seek(last_pos-2, os.SEEK_SET)
|
154
176
|
while fd.read(1) not in [b"\n", b"\r"]:
|
155
177
|
fd.seek(-2, os.SEEK_CUR)
|
156
178
|
size += 1
|
@@ -158,8 +180,8 @@ def last_line(fd, maxline=1000):
|
|
158
180
|
fd.seek(0)
|
159
181
|
line = decode_line(fd.readline())
|
160
182
|
break
|
183
|
+
last_pos = fd.tell()
|
161
184
|
line = decode_line(fd.readline())
|
162
|
-
fd.seek(-4, os.SEEK_CUR)
|
163
185
|
return line.strip()
|
164
186
|
|
165
187
|
|
@@ -317,6 +339,7 @@ def update_command_status(command_id, status, command=None, params=None, start_t
|
|
317
339
|
if status != 'running':
|
318
340
|
output_file_path = get_output_file_path(command_id)
|
319
341
|
if os.path.exists(output_file_path):
|
342
|
+
print(output_file_path)
|
320
343
|
status_data['last_output_line'] = get_last_non_empty_line_of_file(output_file_path)
|
321
344
|
with open(status_file_path, 'w') as f:
|
322
345
|
json.dump(status_data, f)
|
@@ -474,7 +497,7 @@ def stop_command(command_id):
|
|
474
497
|
end_time = datetime.now().isoformat()
|
475
498
|
try:
|
476
499
|
os.kill(pid, 15) # Send SIGTERM
|
477
|
-
update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
|
500
|
+
#update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
|
478
501
|
return jsonify({'message': 'Command aborted'})
|
479
502
|
except Exception as e:
|
480
503
|
status_data = read_command_status(command_id) or {}
|
@@ -518,6 +541,11 @@ def list_commands():
|
|
518
541
|
except AttributeError:
|
519
542
|
params = " ".join([shlex.quote(p) if " " in p else p for p in status['params']])
|
520
543
|
command = status.get('command', '-') + ' ' + params
|
544
|
+
last_line = status.get('last_output_line')
|
545
|
+
if last_line is None:
|
546
|
+
output_file_path = get_output_file_path(command_id)
|
547
|
+
if os.path.exists(output_file_path):
|
548
|
+
last_line = get_last_non_empty_line_of_file(output_file_path)
|
521
549
|
commands.append({
|
522
550
|
'command_id': command_id,
|
523
551
|
'status': status['status'],
|
@@ -525,7 +553,7 @@ def list_commands():
|
|
525
553
|
'end_time': status.get('end_time', 'N/A'),
|
526
554
|
'command': command,
|
527
555
|
'exit_code': status.get('exit_code', 'N/A'),
|
528
|
-
'last_output_line':
|
556
|
+
'last_output_line': last_line,
|
529
557
|
})
|
530
558
|
# Sort commands by start_time in descending order
|
531
559
|
commands.sort(key=lambda x: x['start_time'], reverse=True)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|