pywebexec 2.3.0__py3-none-any.whl → 2.3.2__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/host_ip.py CHANGED
@@ -41,6 +41,8 @@ def resolve(host_or_ip):
41
41
  def get_host_ip(host_or_ip='0.0.0.0'):
42
42
  if host_or_ip == '0.0.0.0':
43
43
  return resolve(gethostname())
44
+ if host_or_ip in ('localhost', '127.0.0.1'):
45
+ return ('localhost', '127.0.0.1')
44
46
  return resolve(host_or_ip)
45
47
 
46
48
  if __name__ == '__main__':
pywebexec/pywebexec.py CHANGED
@@ -22,7 +22,7 @@ if platform.system() != 'Windows':
22
22
  import termios
23
23
  else:
24
24
  from waitress import serve
25
- import winpty
25
+ from winpty import PtyProcess, WinptyError
26
26
  import ipaddress
27
27
  from socket import socket, AF_INET, SOCK_STREAM
28
28
  import ssl
@@ -72,6 +72,7 @@ CONFDIR += "/.pywebexec"
72
72
  term_command_id = str(uuid.uuid4())
73
73
  tty_cols = 125
74
74
  tty_rows = 30
75
+ os.environ["PYWEBEXEC"] = "true"
75
76
 
76
77
  # In-memory cache for command statuses
77
78
  status_cache = {}
@@ -364,6 +365,7 @@ def parseargs():
364
365
  parser.add_argument("-k", "--key", type=str, help="Path to https certificate key")
365
366
  parser.add_argument("-g", "--gencert", action="store_true", help="https server self signed cert")
366
367
  parser.add_argument("-T", "--tokenurl", action="store_true", help="generate safe url to access")
368
+ parser.add_argument("-n", "--notty", action="store_true", help="no span commands in tty")
367
369
  parser.add_argument("-C", "--cols", type=int, default=tty_cols, help="terminal columns")
368
370
  parser.add_argument("-R", "--rows", type=int, default=tty_rows, help="terminal rows")
369
371
  parser.add_argument("action", nargs="?", help="daemon action start/stop/restart/status/shareterm/term",
@@ -529,12 +531,21 @@ def run_command(fromip, user, command, params, command_id, rows, cols):
529
531
  })
530
532
  output_file_path = get_output_file_path(command_id)
531
533
  try:
532
- if platform.system() == 'Windows':
534
+ if args.notty:
535
+ os.environ["PYTHONIOENCODING"] = "utf-8"
536
+ os.environ["PYTHONLEGACYWINDOWSSTDIO"] = "utf-8"
537
+ with open(output_file_path, 'wb', buffering=0) as fd:
538
+ p = subprocess.Popen([sys.executable, "-u", command, *params], stdout=fd, stderr=fd, bufsize=1, text=False)
539
+ pid = p.pid
540
+ update_command_status(command_id, {
541
+ 'pid': pid,
542
+ })
543
+ p.wait()
544
+ status = p.returncode
545
+ elif platform.system() == 'Windows':
533
546
  # On Windows, use winpty
534
- cmdline = f"{sys.executable} -u {command} " + " ".join(shlex.quote(p) for p in params)
535
547
  with open(output_file_path, 'wb', buffering=0) as fd:
536
- p = winpty.PTY(cols, rows)
537
- p.spawn(cmdline)
548
+ p = PtyProcess.spawn([sys.executable, "-u", command, *params], dimensions=(rows, cols))
538
549
  pid = p.pid
539
550
  update_command_status(command_id, {
540
551
  'pid': pid,
@@ -543,16 +554,15 @@ def run_command(fromip, user, command, params, command_id, rows, cols):
543
554
  try:
544
555
  if not p.isalive():
545
556
  time.sleep(1)
546
- data = p.read(10485760, blocking=False)
557
+ data = p.read(10485760)
547
558
  fd.write(data.encode())
548
559
  if not p.isalive():
549
560
  break
550
561
  time.sleep(0.1)
551
- except (EOFError, winpty.WinptyError):
562
+ except (EOFError, WinptyError):
552
563
  break
553
- status = p.get_exitstatus()
554
- del p
555
- print("end", status)
564
+ status = p.exitstatus
565
+ p.close()
556
566
  else:
557
567
  # On Unix, use pexpect
558
568
  with open(output_file_path, 'wb') as fd:
@@ -55,6 +55,16 @@ th {
55
55
  overflow-y: hidden;
56
56
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.60);
57
57
  }
58
+ .outputhtml {
59
+ background: #fff;
60
+ padding: 0;
61
+ overflow: auto;
62
+ ul {
63
+ margin: 0;
64
+ padding: 0 15px;
65
+ width: fit-content;
66
+ }
67
+ }
58
68
  .copy-icon {
59
69
  cursor: pointer;
60
70
  }
@@ -104,6 +104,10 @@ function setCommandStatus(status) {
104
104
  document.getElementById("commandStatus").className = `status-icon status-${status}`;
105
105
  }
106
106
 
107
+ function extractHtml(text) {
108
+ const match = text.match(/<html[\s\S]*?<\/html>/);
109
+ return match ? match[0] : null;
110
+ }
107
111
 
108
112
  async function fetchOutput(url) {
109
113
  if (isPaused) return;
@@ -123,16 +127,33 @@ async function fetchOutput(url) {
123
127
  rows = data.rows;
124
128
  autoFit(false);
125
129
  }
126
- percentage = slider.value;
127
130
  fullOutput += data.output;
128
131
  if (fullOutput.length > maxSize)
129
132
  fullOutput = fullOutput.slice(-maxSize);
130
- if (percentage == 1000)
131
- terminal.write(data.output);
132
- else {
133
- percentage = Math.round((outputLength * 1000)/fullOutput.length);
134
- slider.value = percentage;
135
- document.getElementById('outputPercentage').innerText = `${Math.floor(percentage/10)}%`;
133
+
134
+ if (data.status != 'running') {
135
+ const htmlContent = extractHtml(fullOutput);
136
+ if (htmlContent) {
137
+ document.getElementById('output').innerHTML = htmlContent;
138
+ document.getElementById('output').classList.add('outputhtml');
139
+ } else {
140
+ document.getElementById('output').classList.remove('outputhtml');
141
+ if (slider.value == 1000)
142
+ terminal.write(data.output);
143
+ else {
144
+ percentage = Math.round((outputLength * 1000)/fullOutput.length);
145
+ slider.value = percentage;
146
+ document.getElementById('outputPercentage').innerText = `${Math.floor(percentage/10)}%`;
147
+ }
148
+ }
149
+ } else {
150
+ if (slider.value == 1000)
151
+ terminal.write(data.output);
152
+ else {
153
+ percentage = Math.round((outputLength * 1000)/fullOutput.length);
154
+ slider.value = percentage;
155
+ document.getElementById('outputPercentage').innerText = `${Math.floor(percentage/10)}%`;
156
+ }
136
157
  }
137
158
  nextOutputLink = data.links.next;
138
159
  if (data.status != 'running') {
@@ -162,7 +183,6 @@ async function viewOutput(command_id) {
162
183
  terminal.reset();
163
184
  fullOutput = '';
164
185
  try {
165
- // Updated endpoint below:
166
186
  const response = await fetch(`/commands/${command_id}`);
167
187
  if (!response.ok) {
168
188
  return;
@@ -186,8 +206,22 @@ async function viewOutput(command_id) {
186
206
  fetchOutput(nextOutputLink);
187
207
  outputInterval = setInterval(() => fetchOutput(nextOutputLink), 500);
188
208
  toggleButton.style.display = 'block';
209
+ document.getElementById('output').innerHTML = '';
210
+ document.getElementById('output').appendChild(terminal.element);
189
211
  } else {
190
- fetchOutput(nextOutputLink);
212
+ const outputResponse = await fetch(nextOutputLink);
213
+ const outputData = await outputResponse.json();
214
+ const output = outputData.output;
215
+ const htmlContent = extractHtml(output);
216
+ if (htmlContent) {
217
+ document.getElementById('output').innerHTML = htmlContent;
218
+ document.getElementById('output').classList.add('outputhtml');
219
+ } else {
220
+ document.getElementById('output').classList.remove('outputhtml');
221
+ document.getElementById('output').innerHTML = '';
222
+ document.getElementById('output').appendChild(terminal.element);
223
+ terminal.write(output);
224
+ }
191
225
  toggleButton.style.display = 'none';
192
226
  }
193
227
  } catch (error) {
@@ -196,13 +196,16 @@ async function fetchCommands(hide=false) {
196
196
  }
197
197
  }
198
198
 
199
+ function extractHtml(text) {
200
+ const match = text.match(/<html[\s\S]*?<\/html>/);
201
+ return match ? match[0] : null;
202
+ }
203
+
199
204
  async function fetchOutput(url) {
200
205
  if (isPaused) return;
201
206
  try {
202
207
  const response = await fetch(url);
203
- if (!response.ok) {
204
- return;
205
- }
208
+ if (!response.ok) return;
206
209
  const data = await response.json();
207
210
  if (data.error) {
208
211
  terminal.write(data.error);
@@ -216,12 +219,30 @@ async function fetchOutput(url) {
216
219
  fullOutput += data.output;
217
220
  if (fullOutput.length > maxSize)
218
221
  fullOutput = fullOutput.slice(-maxSize);
219
- if (slider.value == 1000)
220
- terminal.write(data.output);
221
- else {
222
- percentage = Math.round((outputLength * 1000)/fullOutput.length);
223
- slider.value = percentage;
224
- outputPercentage.innerText = `${Math.floor(percentage/10)}%`;
222
+
223
+ if (data.status != 'running') {
224
+ const htmlContent = extractHtml(fullOutput);
225
+ if (htmlContent) {
226
+ document.getElementById('output').innerHTML = htmlContent;
227
+ document.getElementById('output').classList.add('outputhtml');
228
+ } else {
229
+ document.getElementById('output').classList.remove('outputhtml');
230
+ if (slider.value == 1000)
231
+ terminal.write(data.output);
232
+ else {
233
+ percentage = Math.round((outputLength * 1000)/fullOutput.length);
234
+ slider.value = percentage;
235
+ outputPercentage.innerText = `${Math.floor(percentage/10)}%`;
236
+ }
237
+ }
238
+ } else {
239
+ if (slider.value == 1000)
240
+ terminal.write(data.output);
241
+ else {
242
+ percentage = Math.round((outputLength * 1000)/fullOutput.length);
243
+ slider.value = percentage;
244
+ outputPercentage.innerText = `${Math.floor(percentage/10)}%`;
245
+ }
225
246
  }
226
247
  nextOutputLink = data.links.next;
227
248
  if (data.status != 'running') {
@@ -276,8 +297,22 @@ async function viewOutput(command_id) {
276
297
  fetchOutput(nextOutputLink);
277
298
  outputInterval = setInterval(() => fetchOutput(nextOutputLink), 500);
278
299
  toggleButton.style.display = 'block';
300
+ document.getElementById('output').innerHTML = '';
301
+ document.getElementById('output').appendChild(terminal.element);
279
302
  } else {
280
- fetchOutput(nextOutputLink);
303
+ const outputResponse = await fetch(nextOutputLink);
304
+ const outputData = await outputResponse.json();
305
+ const output = outputData.output;
306
+ const htmlContent = extractHtml(output);
307
+ if (htmlContent) {
308
+ document.getElementById('output').innerHTML = htmlContent;
309
+ document.getElementById('output').classList.add('outputhtml');
310
+ } else {
311
+ document.getElementById('output').classList.remove('outputhtml');
312
+ document.getElementById('output').innerHTML = '';
313
+ document.getElementById('output').appendChild(terminal.element);
314
+ terminal.write(output);
315
+ }
281
316
  toggleButton.style.display = 'none';
282
317
  }
283
318
  fetchCommands(); // Refresh the command list to highlight the current command
pywebexec/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.3.0'
21
- __version_tuple__ = version_tuple = (2, 3, 0)
20
+ __version__ = version = '2.3.2'
21
+ __version_tuple__ = version_tuple = (2, 3, 2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebexec
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1,11 +1,11 @@
1
1
  pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
2
- pywebexec/host_ip.py,sha256=Ud_HTflWVQ8789aoQ2RZdT1wGI-ccvrwSWGz_c7T3TI,1241
3
- pywebexec/pywebexec.py,sha256=q2q4NVgWeMmAegM-iK8J9B175snHyHkW45WYPlHYJpQ,47727
2
+ pywebexec/host_ip.py,sha256=oiCMlo2o3AkkgXDarUSx8T3FWXKI0vk1-EPnx5FGBd8,1332
3
+ pywebexec/pywebexec.py,sha256=LD917ZeDVnOSMuLWPkl9JT1baaDvZ_e0Yk7RTs5yBfk,48273
4
4
  pywebexec/swagger.yaml,sha256=I_oLpp7Hqel8SDEEykvpmCT-Gv3ytGlziq9bvQOrtZY,7598
5
- pywebexec/version.py,sha256=U--yqU7RFo8hQQm8oopUGYLkafj4phNIVfkf5HFEal8,511
5
+ pywebexec/version.py,sha256=R9MtLgm19dJJGBULhjeOA69Z4XJij_vYIytb4O8GglA,511
6
6
  pywebexec/static/css/form.css,sha256=XC_0ES5yMHYz0S2OHR0RAboQN7fBUmg5ZIq8Qm5rHP0,5806
7
7
  pywebexec/static/css/markdown.css,sha256=br4-iK9wigTs54N2KHtjgZ4KLH0THVSvJo-XZAdMHiE,1970
8
- pywebexec/static/css/style.css,sha256=R1VOPNV2ztROKy9Fgf3tvUrtuKagY027tFJ8C866yWU,9991
8
+ pywebexec/static/css/style.css,sha256=3Vytr8BMVMvDJlRWQy0Jfnv1jyDxaYm9irwlZT2fAHs,10152
9
9
  pywebexec/static/css/swagger-ui.css,sha256=xhXN8fnUaIACGHuPIEIr9-qmyYr6Zx0k2wv4Qy7Bg1Y,154985
10
10
  pywebexec/static/css/swagger-ui.css.map,sha256=dJy-xBn_htK4BNupTMIl33ddse7BXsrCdDJWlTJodnw,258842
11
11
  pywebexec/static/css/xterm.css,sha256=uo5phWaUiJgcz0DAzv46uoByLLbJLeetYosL1xf68rY,5559
@@ -34,9 +34,9 @@ pywebexec/static/images/running.svg,sha256=fBCYwYb2O9K4N3waC2nURP25NRwZlqR4PbDZy
34
34
  pywebexec/static/images/success.svg,sha256=NVwezvVMplt46ElW798vqGfrL21Mw_DWHUp_qiD_FU8,489
35
35
  pywebexec/static/images/swagger-ui.svg,sha256=FR0yeOVwe4zCYKZAjCGcT_m0Mf25NexIVaSXifIkoU0,2117
36
36
  pywebexec/static/js/executables.js,sha256=cTgCFHr_F9bFCirtfG_uR32vOY3vNUr4Ih3Wglj5lFc,11988
37
- pywebexec/static/js/popup.js,sha256=O3DEWnyb5yGW9tjODYycc-ujWndyAfnJMxulaQeogtc,9700
37
+ pywebexec/static/js/popup.js,sha256=IaKmk2U2hEn-Nv6krf_PPW6LaG8NcpCkJKb7lUX0qZo,11457
38
38
  pywebexec/static/js/schemaform.js,sha256=NlFXFKJI53izxPXct3a5XiB1RhWGt0_EIp6o1HfsryU,9624
39
- pywebexec/static/js/script.js,sha256=yQP2dWwTObWbg6ROeKFxwYi1NfDd2gxEn2e3oL-FWL8,18379
39
+ pywebexec/static/js/script.js,sha256=V9uJVcF56e275zwRamoCGgQ-5B8DnpkiiaBslrczsPI,20158
40
40
  pywebexec/static/js/swagger-form.js,sha256=CLcSHMhk5P4-_2MIRBoJLgEnIj_9keDDSzUugXHZjio,4565
41
41
  pywebexec/static/js/js-yaml/LICENSE,sha256=oHvCRGi5ZUznalR9R6LbKC0HcztxXbTHOpi9Y5YflVA,1084
42
42
  pywebexec/static/js/js-yaml/js-yaml.min.js,sha256=Rdw90D3AegZwWiwpibjH9wkBPwS9U4bjJ51ORH8H69c,39430
@@ -67,9 +67,9 @@ pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
67
67
  pywebexec/templates/index.html,sha256=w18O2plH_yS8bqlPsu5hwFFmCj9H2hWLSV8B6ADcSwU,3900
68
68
  pywebexec/templates/popup.html,sha256=3kpMccKD_OLLhJ4Y9KRw6Ny8wQWjVaRrUfV9y5-bDiQ,1580
69
69
  pywebexec/templates/swagger_ui.html,sha256=MAPr-z96VERAecDvX37V8q2Nxph-O0fNDBul1x2w9SI,1147
70
- pywebexec-2.3.0.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
71
- pywebexec-2.3.0.dist-info/METADATA,sha256=y1F2WI5mfVSDdt_GznPWBmK07NrToa8qOjSqls1r0kM,13015
72
- pywebexec-2.3.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
73
- pywebexec-2.3.0.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
74
- pywebexec-2.3.0.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
75
- pywebexec-2.3.0.dist-info/RECORD,,
70
+ pywebexec-2.3.2.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
71
+ pywebexec-2.3.2.dist-info/METADATA,sha256=JeeyKi63mWtYod8YJjtNh8sTB9g8SPA1GZRJ4dLGfE8,13015
72
+ pywebexec-2.3.2.dist-info/WHEEL,sha256=DK49LOLCYiurdXXOXwGJm6U4DkHkg4lcxjhqwRa0CP4,91
73
+ pywebexec-2.3.2.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
74
+ pywebexec-2.3.2.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
75
+ pywebexec-2.3.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (78.0.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5