pywebexec 1.3.7__py3-none-any.whl → 1.4.0__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.
- pywebexec/pywebexec.py +45 -61
- pywebexec/static/js/script.js +14 -3
- pywebexec/version.py +2 -2
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/METADATA +1 -1
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/RECORD +9 -9
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/LICENSE +0 -0
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/WHEEL +0 -0
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.3.7.dist-info → pywebexec-1.4.0.dist-info}/top_level.txt +0 -0
pywebexec/pywebexec.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
import sys
|
2
2
|
from flask import Flask, request, jsonify, render_template, session, redirect, url_for
|
3
3
|
from flask_httpauth import HTTPBasicAuth
|
4
|
-
import subprocess
|
5
4
|
import threading
|
6
5
|
import os
|
7
6
|
import json
|
@@ -18,8 +17,12 @@ import ssl
|
|
18
17
|
import re
|
19
18
|
import pwd
|
20
19
|
from secrets import token_urlsafe
|
21
|
-
import
|
22
|
-
import
|
20
|
+
import pexpect
|
21
|
+
import signal
|
22
|
+
import fcntl
|
23
|
+
import termios
|
24
|
+
import struct
|
25
|
+
|
23
26
|
|
24
27
|
if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
|
25
28
|
from ldap3 import Server, Connection, ALL, SIMPLE, SUBTREE, Tls
|
@@ -239,7 +242,6 @@ def start_gunicorn(daemonized=False, baselog=None):
|
|
239
242
|
|
240
243
|
def daemon_d(action, pidfilepath, silent=False, hostname=None, args=None):
|
241
244
|
"""start/stop daemon"""
|
242
|
-
import signal
|
243
245
|
import daemon, daemon.pidfile
|
244
246
|
|
245
247
|
pidfile = daemon.pidfile.TimeoutPIDLockFile(pidfilepath+".pid", acquire_timeout=30)
|
@@ -427,66 +429,52 @@ def read_command_status(command_id):
|
|
427
429
|
|
428
430
|
return status_data
|
429
431
|
|
432
|
+
def sigwinch_passthrough(sig, data):
|
433
|
+
s = struct.pack("HHHH", 0, 0, 0, 0)
|
434
|
+
a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s))
|
435
|
+
global p
|
436
|
+
p.setwinsize(a[0], a[1])
|
430
437
|
|
431
|
-
def script(filename):
|
432
|
-
shell = os.environ.get('SHELL', 'sh')
|
433
|
-
with open(filename, 'wb') as s:
|
434
|
-
def read(fd):
|
435
|
-
data = os.read(fd, 1024)
|
436
|
-
s.write(data)
|
437
|
-
s.flush()
|
438
|
-
return data
|
439
|
-
return pty.spawn(shell, read)
|
440
438
|
|
439
|
+
def script(output_file):
|
440
|
+
global p
|
441
|
+
shell = os.environ.get('SHELL', 'sh')
|
442
|
+
with open(output_file, 'wb') as fd:
|
443
|
+
p = pexpect.spawn(shell, echo=True)
|
444
|
+
p.logfile_read = fd
|
445
|
+
# Set the window size
|
446
|
+
sigwinch_passthrough(None, None)
|
447
|
+
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
448
|
+
p.interact()
|
441
449
|
|
442
|
-
|
450
|
+
|
443
451
|
|
444
452
|
def run_command(command, params, command_id, user):
|
445
453
|
start_time = datetime.now().isoformat()
|
446
454
|
update_command_status(command_id, 'running', command=command, params=params, start_time=start_time, user=user)
|
455
|
+
output_file_path = get_output_file_path(command_id)
|
447
456
|
try:
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
while True:
|
470
|
-
r, _, _ = select.select([fd], [], [], 1.0) # Use select to monitor the file descriptor
|
471
|
-
if fd in r:
|
472
|
-
try:
|
473
|
-
read(fd)
|
474
|
-
except OSError:
|
475
|
-
break
|
476
|
-
(pid, status) = os.waitpid(pid, 0)
|
477
|
-
return status
|
478
|
-
|
479
|
-
status = spawn_pty()
|
480
|
-
end_time = datetime.now().isoformat()
|
481
|
-
# Update the status based on the result
|
482
|
-
if os.WIFEXITED(status):
|
483
|
-
exit_code = os.WEXITSTATUS(status)
|
484
|
-
if exit_code == 0:
|
485
|
-
update_command_status(command_id, 'success', end_time=end_time, exit_code=exit_code, user=user)
|
486
|
-
elif exit_code == -15:
|
487
|
-
update_command_status(command_id, 'aborted', end_time=end_time, exit_code=exit_code, user=user)
|
488
|
-
else:
|
489
|
-
update_command_status(command_id, 'failed', end_time=end_time, exit_code=exit_code, user=user)
|
457
|
+
def spawn_tty():
|
458
|
+
with open(output_file_path, 'wb') as fd:
|
459
|
+
p = pexpect.spawn(command, params, ignore_sighup=True, timeout=None)
|
460
|
+
update_command_status(command_id, 'running', pid=p.pid, user=user)
|
461
|
+
p.setwinsize(24, 120)
|
462
|
+
p.logfile = fd
|
463
|
+
p.expect(pexpect.EOF)
|
464
|
+
fd.flush()
|
465
|
+
status = p.wait()
|
466
|
+
end_time = datetime.now().isoformat()
|
467
|
+
# Update the status based on the result
|
468
|
+
if status is None:
|
469
|
+
exit_code = -15
|
470
|
+
update_command_status(command_id, 'aborted', end_time=end_time, exit_code=exit_code, user=user)
|
471
|
+
else:
|
472
|
+
exit_code = status
|
473
|
+
if exit_code == 0:
|
474
|
+
update_command_status(command_id, 'success', end_time=end_time, exit_code=exit_code, user=user)
|
475
|
+
else:
|
476
|
+
update_command_status(command_id, 'failed', end_time=end_time, exit_code=exit_code, user=user)
|
477
|
+
spawn_tty()
|
490
478
|
except Exception as e:
|
491
479
|
end_time = datetime.now().isoformat()
|
492
480
|
update_command_status(command_id, 'failed', end_time=end_time, exit_code=1, user=user)
|
@@ -502,12 +490,8 @@ def stop_command(command_id):
|
|
502
490
|
pid = status['pid']
|
503
491
|
end_time = datetime.now().isoformat()
|
504
492
|
try:
|
505
|
-
update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
|
493
|
+
#update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
|
506
494
|
os.killpg(os.getpgid(pid), 15) # Send SIGTERM to the process group
|
507
|
-
try:
|
508
|
-
os.waitpid(pid, 0) # Wait for the process to terminate
|
509
|
-
except ChildProcessError:
|
510
|
-
pass # Ignore if the process has already been reaped
|
511
495
|
return jsonify({'message': 'Command aborted'})
|
512
496
|
except Exception as e:
|
513
497
|
status_data = read_command_status(command_id) or {}
|
pywebexec/static/js/script.js
CHANGED
@@ -128,8 +128,18 @@ async function fetchOutput(url) {
|
|
128
128
|
terminal.write(data.error);
|
129
129
|
clearInterval(outputInterval);
|
130
130
|
} else {
|
131
|
+
slider = document.getElementById('outputSlider')
|
132
|
+
percentage = slider.value;
|
133
|
+
if (percentage == 100)
|
134
|
+
terminal.write(data.output);
|
135
|
+
else {
|
136
|
+
length = Math.floor((fullOutput.length * percentage) / 100);
|
137
|
+
newlength = fullOutput.length + data.output.length;
|
138
|
+
percentage = Math.floor(length/newlength * 100);
|
139
|
+
slider.value = percentage;
|
140
|
+
document.getElementById('outputPercentage').innerText = `${percentage}%`;
|
141
|
+
}
|
131
142
|
fullOutput += data.output;
|
132
|
-
updateTerminalOutput();
|
133
143
|
nextOutputLink = data.links.next;
|
134
144
|
if (data.status != 'running') {
|
135
145
|
clearInterval(outputInterval);
|
@@ -284,7 +294,8 @@ function initResizer() {
|
|
284
294
|
}
|
285
295
|
}
|
286
296
|
|
287
|
-
function
|
297
|
+
function sliderUpdateOutput()
|
298
|
+
{
|
288
299
|
const slider = document.getElementById('outputSlider');
|
289
300
|
const percentage = slider.value;
|
290
301
|
const outputLength = Math.floor((fullOutput.length * percentage) / 100);
|
@@ -295,7 +306,7 @@ function updateTerminalOutput() {
|
|
295
306
|
document.getElementById('outputPercentage').innerText = `${percentage}%`;
|
296
307
|
}
|
297
308
|
|
298
|
-
document.getElementById('outputSlider').addEventListener('input',
|
309
|
+
document.getElementById('outputSlider').addEventListener('input', sliderUpdateOutput);
|
299
310
|
|
300
311
|
window.addEventListener('resize', adjustOutputHeight);
|
301
312
|
window.addEventListener('load', initResizer);
|
pywebexec/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
|
2
|
-
pywebexec/pywebexec.py,sha256=
|
3
|
-
pywebexec/version.py,sha256=
|
2
|
+
pywebexec/pywebexec.py,sha256=O3_iTeNw6ekF-f7-xO_wL0MDMm72v_onrcrB4repxk4,26124
|
3
|
+
pywebexec/version.py,sha256=R8-T9fmURjcuoxYpHTAjyNAhgJPDtI2jogCjqYYkfCU,411
|
4
4
|
pywebexec/static/css/Consolas NF.ttf,sha256=DJEOzF0eqZ-kxu3Gs_VE8X0NJqiobBzmxWDGpdgGRxI,1313900
|
5
5
|
pywebexec/static/css/style.css,sha256=iLX6k1hoWLinZWyqtbH50U-0hND2M-5_Zr1U1UC_gos,5578
|
6
6
|
pywebexec/static/css/xterm.css,sha256=gy8_LGA7Q61DUf8ElwFQzHqHMBQnbbEmpgZcbdgeSHI,5383
|
@@ -13,16 +13,16 @@ pywebexec/static/images/favicon.svg,sha256=ti80IfuDZwIvQcmJxkOeUaB1iMsiyOPmQmVO-
|
|
13
13
|
pywebexec/static/images/running.gif,sha256=iYuzQGkMxrakSIwt6gPieKCImGZoSAHmU5MUNZa7cpw,25696
|
14
14
|
pywebexec/static/images/success.svg,sha256=PJDcCSTevJh7rkfSFLtc7P0pbeh8PVQBS8DaOLQemmc,489
|
15
15
|
pywebexec/static/js/commands.js,sha256=VdMeCop9V5KwsR2v1J_OY1xFE7tJUYgcMg_lh2VGNjs,7476
|
16
|
-
pywebexec/static/js/script.js,sha256=
|
16
|
+
pywebexec/static/js/script.js,sha256=C7n41Isu3HNi2NTaXFYeztFMGOx-R6TCK24N0dZ_Azw,11429
|
17
17
|
pywebexec/static/js/xterm/LICENSE,sha256=EU1P4eXTull-_T9I80VuwnJXubB-zLzUl3xpEYj2T1M,1083
|
18
18
|
pywebexec/static/js/xterm/ansi_up.min.js,sha256=KNGV0vEr30hNqKQimTAvGVy-icD5A1JqMQTtvYtKR2Y,13203
|
19
19
|
pywebexec/static/js/xterm/xterm-addon-fit.js,sha256=Pprm9pZe4SadVXS5Bc8b9VnC9Ex4QlWwA0pxOH53Gck,1460
|
20
20
|
pywebexec/static/js/xterm/xterm.js,sha256=Bzka76jZwEhVt_LlS0e0qMw7ryGa1p5qfxFyeohphBo,283371
|
21
21
|
pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
pywebexec/templates/index.html,sha256=DYtT555wSNhnFtzzHhPMWJireynCJNnAuTytpoORQeE,2321
|
23
|
-
pywebexec-1.
|
24
|
-
pywebexec-1.
|
25
|
-
pywebexec-1.
|
26
|
-
pywebexec-1.
|
27
|
-
pywebexec-1.
|
28
|
-
pywebexec-1.
|
23
|
+
pywebexec-1.4.0.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
24
|
+
pywebexec-1.4.0.dist-info/METADATA,sha256=DPx9rM-cTxia4_yYTq0oB-lVtZONKe-f2gWn329n2oc,7397
|
25
|
+
pywebexec-1.4.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
26
|
+
pywebexec-1.4.0.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
27
|
+
pywebexec-1.4.0.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
28
|
+
pywebexec-1.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|