pywebexec 1.3.8__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 +46 -58
- pywebexec/static/js/script.js +14 -3
- pywebexec/version.py +2 -2
- {pywebexec-1.3.8.dist-info → pywebexec-1.4.0.dist-info}/METADATA +1 -1
- {pywebexec-1.3.8.dist-info → pywebexec-1.4.0.dist-info}/RECORD +9 -9
- {pywebexec-1.3.8.dist-info → pywebexec-1.4.0.dist-info}/LICENSE +0 -0
- {pywebexec-1.3.8.dist-info → pywebexec-1.4.0.dist-info}/WHEEL +0 -0
- {pywebexec-1.3.8.dist-info → pywebexec-1.4.0.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.3.8.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,62 +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])
|
437
|
+
|
430
438
|
|
431
|
-
def script(
|
439
|
+
def script(output_file):
|
440
|
+
global p
|
432
441
|
shell = os.environ.get('SHELL', 'sh')
|
433
|
-
with open(
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
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()
|
449
|
+
|
450
|
+
|
441
451
|
|
442
452
|
def run_command(command, params, command_id, user):
|
443
453
|
start_time = datetime.now().isoformat()
|
444
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)
|
445
456
|
try:
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
try:
|
468
|
-
read(fd)
|
469
|
-
except OSError:
|
470
|
-
break
|
471
|
-
(pid, status) = os.waitpid(pid, 0)
|
472
|
-
return status
|
473
|
-
|
474
|
-
status = spawn_pty()
|
475
|
-
end_time = datetime.now().isoformat()
|
476
|
-
# Update the status based on the result
|
477
|
-
if os.WIFSIGNALED(status):
|
478
|
-
exit_code = -os.WTERMSIG(status)
|
479
|
-
update_command_status(command_id, 'aborted', end_time=end_time, exit_code=exit_code, user=user)
|
480
|
-
else:
|
481
|
-
exit_code = os.WEXITSTATUS(status)
|
482
|
-
if exit_code == 0:
|
483
|
-
update_command_status(command_id, 'success', end_time=end_time, exit_code=exit_code, user=user)
|
484
|
-
else:
|
485
|
-
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()
|
486
478
|
except Exception as e:
|
487
479
|
end_time = datetime.now().isoformat()
|
488
480
|
update_command_status(command_id, 'failed', end_time=end_time, exit_code=1, user=user)
|
@@ -498,12 +490,8 @@ def stop_command(command_id):
|
|
498
490
|
pid = status['pid']
|
499
491
|
end_time = datetime.now().isoformat()
|
500
492
|
try:
|
501
|
-
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)
|
502
494
|
os.killpg(os.getpgid(pid), 15) # Send SIGTERM to the process group
|
503
|
-
try:
|
504
|
-
os.waitpid(pid, 0) # Wait for the process to terminate
|
505
|
-
except ChildProcessError:
|
506
|
-
pass # Ignore if the process has already been reaped
|
507
495
|
return jsonify({'message': 'Command aborted'})
|
508
496
|
except Exception as e:
|
509
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
|