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 +2 -0
- pywebexec/pywebexec.py +20 -10
- pywebexec/static/css/style.css +10 -0
- pywebexec/static/js/popup.js +43 -9
- pywebexec/static/js/script.js +45 -10
- pywebexec/version.py +2 -2
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/METADATA +1 -1
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/RECORD +12 -12
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/WHEEL +1 -1
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/entry_points.txt +0 -0
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/licenses/LICENSE +0 -0
- {pywebexec-2.3.0.dist-info → pywebexec-2.3.2.dist-info}/top_level.txt +0 -0
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
|
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
|
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 =
|
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
|
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,
|
562
|
+
except (EOFError, WinptyError):
|
552
563
|
break
|
553
|
-
status = p.
|
554
|
-
|
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:
|
pywebexec/static/css/style.css
CHANGED
@@ -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
|
}
|
pywebexec/static/js/popup.js
CHANGED
@@ -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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
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) {
|
pywebexec/static/js/script.js
CHANGED
@@ -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
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
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
@@ -1,11 +1,11 @@
|
|
1
1
|
pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
|
2
|
-
pywebexec/host_ip.py,sha256=
|
3
|
-
pywebexec/pywebexec.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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.
|
71
|
-
pywebexec-2.3.
|
72
|
-
pywebexec-2.3.
|
73
|
-
pywebexec-2.3.
|
74
|
-
pywebexec-2.3.
|
75
|
-
pywebexec-2.3.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|