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 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 pty
22
- import select
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
- import fcntl
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
- output_file_path = get_output_file_path(command_id)
449
- with open(output_file_path, 'wb') as output_file:
450
- def read(fd):
451
- data = os.read(fd, 1024)
452
- if not data: # Check for EOF
453
- raise OSError("EOF reached")
454
- output_file.write(data)
455
- output_file.flush()
456
- return data
457
-
458
- def spawn_pty():
459
- pid, fd = pty.fork()
460
- if pid == 0: # Child process
461
- try:
462
- os.setsid()
463
- except:
464
- pass
465
- os.execvp(command, [command] + params)
466
- else: # Parent process
467
- update_command_status(command_id, 'running', pid=pid, user=user)
468
- fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK) # Set the file descriptor to non-blocking mode
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 {}
@@ -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 updateTerminalOutput() {
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', updateTerminalOutput);
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.3.7'
16
- __version_tuple__ = version_tuple = (1, 3, 7)
15
+ __version__ = version = '1.4.0'
16
+ __version_tuple__ = version_tuple = (1, 4, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.3.7
3
+ Version: 1.4.0
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1,6 +1,6 @@
1
1
  pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
2
- pywebexec/pywebexec.py,sha256=ks64NNrtgs1ivwiUVDD1At69_hDlaU6mT7YZ1ndHtCE,26832
3
- pywebexec/version.py,sha256=wi-wdjHlnbEGXkbINL3u9-DfMuMikQsUE4upTqz0SJE,411
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=NCjnVd-i8zUE3qvu7UqUHBt_IcbnnUUKVGlp7XbFzic,10913
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.3.7.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
- pywebexec-1.3.7.dist-info/METADATA,sha256=zJ1dlHWVBDz4QgRQmpcJ3K-7j61TIIOufhJTSqcArW4,7397
25
- pywebexec-1.3.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
- pywebexec-1.3.7.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
- pywebexec-1.3.7.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
- pywebexec-1.3.7.dist-info/RECORD,,
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,,