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 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,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(filename):
439
+ def script(output_file):
440
+ global p
432
441
  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
-
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
- output_file_path = get_output_file_path(command_id)
447
- with open(output_file_path, 'wb') as output_file:
448
- def read(fd):
449
- data = os.read(fd, 1024)
450
- if not data: # Check for EOF
451
- raise OSError("EOF reached")
452
- output_file.write(data)
453
- output_file.flush()
454
- return data
455
-
456
- def spawn_pty():
457
- pid, fd = pty.fork()
458
- if pid == 0: # Child process
459
- try:
460
- os.setsid()
461
- except:
462
- pass
463
- os.execvp(command, [command] + params)
464
- else: # Parent process
465
- update_command_status(command_id, 'running', pid=pid, user=user)
466
- while True:
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 {}
@@ -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.8'
16
- __version_tuple__ = version_tuple = (1, 3, 8)
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.8
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=JvzLrQujPfIr-lYBD3kjk6gcMmFhNkw0EzPGrUDSvn0,26570
3
- pywebexec/version.py,sha256=qiRBw-eWRP8Lkk4ISEcIe48Z7v6e-_S_a_GbGFURp50,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.8.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
24
- pywebexec-1.3.8.dist-info/METADATA,sha256=WuJ_CEfWtHwrYx-5Sx4DNabbdhpRF_DRbp4SCsvhG6g,7397
25
- pywebexec-1.3.8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
- pywebexec-1.3.8.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
27
- pywebexec-1.3.8.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
28
- pywebexec-1.3.8.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,,