pywebexec 1.9.0__py3-none-any.whl → 1.9.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/pywebexec.py +13 -14
- pywebexec/static/css/style.css +4 -2
- pywebexec/static/js/popup.js +4 -3
- pywebexec/static/js/script.js +8 -9
- pywebexec/swagger.yaml +35 -6
- pywebexec/version.py +2 -2
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/METADATA +7 -7
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/RECORD +12 -12
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/LICENSE +0 -0
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/WHEEL +0 -0
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.9.0.dist-info → pywebexec-1.9.2.dist-info}/top_level.txt +0 -0
pywebexec/pywebexec.py
CHANGED
@@ -306,10 +306,10 @@ def print_urls(command_id=None):
|
|
306
306
|
if token:
|
307
307
|
url_params = f"?token={token}"
|
308
308
|
if command_id:
|
309
|
-
print(f"web popup: {protocol}://{hostname}:{args.port}/
|
310
|
-
print(f"web popup: {protocol}://{ip}:{args.port}/
|
311
|
-
print(f"raw output: {protocol}://{hostname}:{args.port}/
|
312
|
-
print(f"raw output: {protocol}://{ip}:{args.port}/
|
309
|
+
print(f"web popup: {protocol}://{hostname}:{args.port}/commands/{command_id}/dopopup{url_params}", flush=True)
|
310
|
+
print(f"web popup: {protocol}://{ip}:{args.port}/commands/{command_id}/dopopup{url_params}", flush=True)
|
311
|
+
print(f"raw output: {protocol}://{hostname}:{args.port}/commands/{command_id}/output_raw{url_params}", flush=True)
|
312
|
+
print(f"raw output: {protocol}://{ip}:{args.port}/commands/{command_id}/output_raw{url_params}", flush=True)
|
313
313
|
else:
|
314
314
|
print(f"web commands: {protocol}://{hostname}:{args.port}{url_params}", flush=True)
|
315
315
|
print(f"web commands: {protocol}://{ip}:{args.port}{url_params}", flush=True)
|
@@ -619,7 +619,7 @@ def log_error(fromip, user, message):
|
|
619
619
|
def log_request(message):
|
620
620
|
log_info(request.remote_addr, session.get('username', '-'), message)
|
621
621
|
|
622
|
-
@app.route('/
|
622
|
+
@app.route('/commands/<command_id>/stop', methods=['PATCH'])
|
623
623
|
def stop_command(command_id):
|
624
624
|
log_request(f"stop_command {command_id}")
|
625
625
|
status = read_command_status(command_id)
|
@@ -629,7 +629,6 @@ def stop_command(command_id):
|
|
629
629
|
pid = status['pid']
|
630
630
|
end_time = datetime.now(timezone.utc).isoformat()
|
631
631
|
try:
|
632
|
-
#update_command_status(command_id, 'aborted', end_time=end_time, exit_code=-15)
|
633
632
|
os.killpg(os.getpgid(pid), 15) # Send SIGTERM to the process group
|
634
633
|
return jsonify({'message': 'Command aborted'})
|
635
634
|
except Exception as e:
|
@@ -704,7 +703,7 @@ def verify_ldap(username, password):
|
|
704
703
|
print(f"LDAP authentication failed: {e}")
|
705
704
|
return False
|
706
705
|
|
707
|
-
@app.route('/
|
706
|
+
@app.route('/commands', methods=['POST'])
|
708
707
|
def run_command_endpoint():
|
709
708
|
data = request.json
|
710
709
|
command = data.get('command')
|
@@ -745,7 +744,7 @@ def run_command_endpoint():
|
|
745
744
|
|
746
745
|
return jsonify({'message': 'Command is running', 'command_id': command_id})
|
747
746
|
|
748
|
-
@app.route('/
|
747
|
+
@app.route('/commands/<command_id>', methods=['GET'])
|
749
748
|
def get_command_status(command_id):
|
750
749
|
status = read_command_status(command_id)
|
751
750
|
if not status:
|
@@ -763,7 +762,7 @@ def list_commands():
|
|
763
762
|
commands.sort(key=lambda x: x['start_time'], reverse=True)
|
764
763
|
return jsonify(commands)
|
765
764
|
|
766
|
-
@app.route('/
|
765
|
+
@app.route('/commands/<command_id>/output', methods=['GET'])
|
767
766
|
def get_command_output(command_id):
|
768
767
|
offset = int(request.args.get('offset', 0))
|
769
768
|
maxsize = int(request.args.get('maxsize', 10485760))
|
@@ -787,7 +786,7 @@ def get_command_output(command_id):
|
|
787
786
|
'cols': status_data.get("cols"),
|
788
787
|
'rows': status_data.get("rows"),
|
789
788
|
'links': {
|
790
|
-
'next': f'{request.url_root}
|
789
|
+
'next': f'{request.url_root}commands/{command_id}/output?offset={new_offset}&maxsize={maxsize}{token_param}'
|
791
790
|
}
|
792
791
|
}
|
793
792
|
if request.headers.get('Accept') == 'text/plain':
|
@@ -795,7 +794,7 @@ def get_command_output(command_id):
|
|
795
794
|
return jsonify(response)
|
796
795
|
return jsonify({'error': 'Invalid command_id'}), 404
|
797
796
|
|
798
|
-
@app.route('/
|
797
|
+
@app.route('/commands/<command_id>/output_raw', methods=['GET'])
|
799
798
|
def get_command_output_raw(command_id):
|
800
799
|
offset = int(request.args.get('offset', 0))
|
801
800
|
@stream_with_context
|
@@ -825,11 +824,11 @@ def list_executables():
|
|
825
824
|
executables.sort() # Sort the list of executables alphabetically
|
826
825
|
return jsonify(executables)
|
827
826
|
|
828
|
-
@app.route('/
|
827
|
+
@app.route('/commands/<command_id>/popup')
|
829
828
|
def popup(command_id):
|
830
829
|
return render_template('popup.html', command_id=command_id)
|
831
830
|
|
832
|
-
@app.route('/
|
831
|
+
@app.route('/commands/<command_id>/dopopup')
|
833
832
|
def do_popup(command_id):
|
834
833
|
token = request.args.get('token', '')
|
835
834
|
token_param = f'?token={token}' if token else ''
|
@@ -838,7 +837,7 @@ def do_popup(command_id):
|
|
838
837
|
<head>
|
839
838
|
<script type="text/javascript">
|
840
839
|
window.onload = function() {{
|
841
|
-
window.open('/
|
840
|
+
window.open('/commands/{command_id}/popup{token_param}', '_blank', 'width=1000,height=600');
|
842
841
|
window.close();
|
843
842
|
}};
|
844
843
|
</script>
|
pywebexec/static/css/style.css
CHANGED
@@ -91,11 +91,13 @@ form {
|
|
91
91
|
vertical-align: middle;
|
92
92
|
}
|
93
93
|
.title-icon {
|
94
|
-
|
94
|
+
display: inline;
|
95
95
|
height: 30px;
|
96
96
|
background-image: url("/static/images/favicon.svg");
|
97
|
+
background-size: 30px 30px;
|
98
|
+
background-repeat: no-repeat;
|
97
99
|
vertical-align: bottom;
|
98
|
-
|
100
|
+
padding-left: 35px;
|
99
101
|
}
|
100
102
|
#thStatus {
|
101
103
|
cursor: pointer;
|
pywebexec/static/js/popup.js
CHANGED
@@ -161,13 +161,14 @@ async function viewOutput(command_id) {
|
|
161
161
|
slider.value = 1000;
|
162
162
|
adjustOutputHeight();
|
163
163
|
currentCommandId = command_id;
|
164
|
-
nextOutputLink = `/
|
164
|
+
nextOutputLink = `/commands/${command_id}/output${urlToken}`;
|
165
165
|
clearInterval(outputInterval);
|
166
166
|
terminal.clear();
|
167
167
|
terminal.reset();
|
168
168
|
fullOutput = '';
|
169
169
|
try {
|
170
|
-
|
170
|
+
// Updated endpoint below:
|
171
|
+
const response = await fetch(`/commands/${command_id}${urlToken}`);
|
171
172
|
if (!response.ok) {
|
172
173
|
return;
|
173
174
|
}
|
@@ -256,7 +257,7 @@ window.addEventListener('load', () => {
|
|
256
257
|
slider = document.getElementById('outputSlider');
|
257
258
|
slider.addEventListener('input', sliderUpdateOutput);
|
258
259
|
adjustOutputHeight();
|
259
|
-
const commandId = window.location.pathname.split('/').slice(-
|
260
|
+
const commandId = window.location.pathname.split('/').slice(-2)[0];
|
260
261
|
viewOutput(commandId);
|
261
262
|
});
|
262
263
|
|
pywebexec/static/js/script.js
CHANGED
@@ -122,7 +122,7 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
122
122
|
fitAddon.fit();
|
123
123
|
terminal.clear();
|
124
124
|
try {
|
125
|
-
const response = await fetch(`/
|
125
|
+
const response = await fetch(`/commands${urlToken}`, {
|
126
126
|
method: 'POST',
|
127
127
|
headers: {
|
128
128
|
'Content-Type': 'application/json'
|
@@ -133,7 +133,6 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
133
133
|
throw new Error('Failed to launch command');
|
134
134
|
}
|
135
135
|
const data = await response.json();
|
136
|
-
//await new Promise(r => setTimeout(r, 300));// not ok
|
137
136
|
viewOutput(data.command_id);
|
138
137
|
fetchCommands();
|
139
138
|
commandInput.focus()
|
@@ -253,13 +252,13 @@ async function viewOutput(command_id) {
|
|
253
252
|
outputPercentage.innerText = '100%';
|
254
253
|
adjustOutputHeight();
|
255
254
|
currentCommandId = command_id;
|
256
|
-
nextOutputLink = `/
|
255
|
+
nextOutputLink = `/commands/${command_id}/output${urlToken}`; // updated URL
|
257
256
|
clearInterval(outputInterval);
|
258
257
|
terminal.clear();
|
259
258
|
terminal.reset();
|
260
259
|
fullOutput = '';
|
261
260
|
try {
|
262
|
-
const response = await fetch(`/
|
261
|
+
const response = await fetch(`/commands/${command_id}${urlToken}`);
|
263
262
|
if (!response.ok) {
|
264
263
|
outputInterval = setInterval(() => fetchOutput(nextOutputLink), 500);
|
265
264
|
}
|
@@ -290,7 +289,7 @@ async function viewOutput(command_id) {
|
|
290
289
|
async function openPopup(command_id, event) {
|
291
290
|
event.stopPropagation();
|
292
291
|
event.stopImmediatePropagation();
|
293
|
-
const popupUrl = `/
|
292
|
+
const popupUrl = `/commands/${command_id}/popup${urlToken}`;
|
294
293
|
window.open(popupUrl, '_blank', 'width=1000,height=600');
|
295
294
|
}
|
296
295
|
|
@@ -298,7 +297,7 @@ async function relaunchCommand(command_id, event) {
|
|
298
297
|
event.stopPropagation();
|
299
298
|
event.stopImmediatePropagation();
|
300
299
|
try {
|
301
|
-
const response = await fetch(`/
|
300
|
+
const response = await fetch(`/commands/${command_id}${urlToken}`);
|
302
301
|
if (!response.ok) {
|
303
302
|
throw new Error('Failed to fetch command status');
|
304
303
|
}
|
@@ -309,7 +308,7 @@ async function relaunchCommand(command_id, event) {
|
|
309
308
|
}
|
310
309
|
fitAddon.fit();
|
311
310
|
terminal.clear();
|
312
|
-
const relaunchResponse = await fetch(`/
|
311
|
+
const relaunchResponse = await fetch(`/commands${urlToken}`, {
|
313
312
|
method: 'POST',
|
314
313
|
headers: {
|
315
314
|
'Content-Type': 'application/json'
|
@@ -337,8 +336,8 @@ async function stopCommand(command_id, event) {
|
|
337
336
|
event.stopPropagation();
|
338
337
|
event.stopImmediatePropagation();
|
339
338
|
try {
|
340
|
-
const response = await fetch(`/
|
341
|
-
method: '
|
339
|
+
const response = await fetch(`/commands/${command_id}/stop${urlToken}`, {
|
340
|
+
method: 'PATCH'
|
342
341
|
});
|
343
342
|
if (!response.ok) {
|
344
343
|
throw new Error('Failed to stop command');
|
pywebexec/swagger.yaml
CHANGED
@@ -3,7 +3,12 @@ info:
|
|
3
3
|
title: PyWebExec API
|
4
4
|
version: "1.0"
|
5
5
|
paths:
|
6
|
-
/
|
6
|
+
/commands:
|
7
|
+
get:
|
8
|
+
summary: "List commands status"
|
9
|
+
responses:
|
10
|
+
"200":
|
11
|
+
description: "List of all commands status"
|
7
12
|
post:
|
8
13
|
summary: "Run a command"
|
9
14
|
consumes:
|
@@ -29,7 +34,7 @@ paths:
|
|
29
34
|
responses:
|
30
35
|
"200":
|
31
36
|
description: "Command started"
|
32
|
-
/
|
37
|
+
/commands/{command_id}:
|
33
38
|
get:
|
34
39
|
summary: "Get command status"
|
35
40
|
parameters:
|
@@ -40,15 +45,39 @@ paths:
|
|
40
45
|
responses:
|
41
46
|
"200":
|
42
47
|
description: "Command status returned"
|
43
|
-
/commands:
|
48
|
+
/commands/{command_id}/output:
|
44
49
|
get:
|
45
|
-
summary: "
|
50
|
+
summary: "Get command output"
|
51
|
+
parameters:
|
52
|
+
- in: path
|
53
|
+
name: command_id
|
54
|
+
required: true
|
55
|
+
type: string
|
56
|
+
- in: query
|
57
|
+
name: offset
|
58
|
+
type: integer
|
59
|
+
default: 0
|
60
|
+
- in: query
|
61
|
+
name: maxsize
|
62
|
+
type: integer
|
63
|
+
default: 10485760
|
64
|
+
responses:
|
65
|
+
"200":
|
66
|
+
description: "Command output returned"
|
67
|
+
/commands/{command_id}/stop:
|
68
|
+
patch:
|
69
|
+
summary: "Stop a running command"
|
70
|
+
parameters:
|
71
|
+
- in: path
|
72
|
+
name: command_id
|
73
|
+
required: true
|
74
|
+
type: string
|
46
75
|
responses:
|
47
76
|
"200":
|
48
|
-
description: "
|
77
|
+
description: "Command stopped successfully"
|
49
78
|
/executables:
|
50
79
|
get:
|
51
|
-
summary: "List
|
80
|
+
summary: "List available executable commands"
|
52
81
|
responses:
|
53
82
|
"200":
|
54
83
|
description: "List of executables returned as an array of executable names"
|
pywebexec/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: pywebexec
|
3
|
-
Version: 1.9.
|
3
|
+
Version: 1.9.2
|
4
4
|
Summary: Simple Python HTTP Exec Server
|
5
5
|
Home-page: https://github.com/joknarf/pywebexec
|
6
6
|
Author: Franck Jouvanceau
|
@@ -200,10 +200,10 @@ $ curl http://myhost:8080/command_output/<command_id> -H "Accept: text/plain"
|
|
200
200
|
|
201
201
|
| method | route | params/payload | returns
|
202
202
|
|-----------|-----------------------------|--------------------|---------------------|
|
203
|
-
| POST | /run_command | command: str<br>params: array[str]<br>rows: int<br>cols: int | command_id: uuid<br>message: str |
|
204
|
-
| POST | /stop_command/command_id | | message: str |
|
205
|
-
| GET | /command_status/command_id | | command_id: uuid<br>command: str<br>params: array[str]<br>start_time: isotime<br>end_time: isotime<br>status: str<br>exit_code: int<br>last_output_line: str |
|
206
|
-
| GET | /commands | | array of<br>command_id: uuid<br>command: str<br>start_time: isotime<br>end_time: isotime<br>status: str<br>exit_code: int<br>last_output_line: str |
|
207
203
|
| GET | /executables | | array of str |
|
208
|
-
| GET | /
|
209
|
-
| GET | /
|
204
|
+
| GET | /commands | | array of<br>command_id: uuid<br>command: str<br>start_time: isotime<br>end_time: isotime<br>status: str<br>exit_code: int<br>last_output_line: str |
|
205
|
+
| GET | /commands/{id} | | command_id: uuid<br>command: str<br>params: array[str]<br>start_time: isotime<br>end_time: isotime<br>status: str<br>exit_code: int<br>last_output_line: str |
|
206
|
+
| GET | /commands/{id}/output | offset: int | output: str<br>status: str<br>links: { next: str } |
|
207
|
+
| GET | /commands/{id}/output_raw | offset: int | output: stream raw output until end of command<br>curl -Ns http://srv/commands/{id}/output_raw|
|
208
|
+
| POST | /commands | command: str<br>params: array[str]<br>rows: int<br>cols: int | command_id: uuid<br>message: str |
|
209
|
+
| PATCH | /commands/{id}/stop | | message: str |
|
@@ -1,9 +1,9 @@
|
|
1
1
|
pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
|
2
2
|
pywebexec/host_ip.py,sha256=Ud_HTflWVQ8789aoQ2RZdT1wGI-ccvrwSWGz_c7T3TI,1241
|
3
|
-
pywebexec/pywebexec.py,sha256=
|
4
|
-
pywebexec/swagger.yaml,sha256
|
5
|
-
pywebexec/version.py,sha256=
|
6
|
-
pywebexec/static/css/style.css,sha256=
|
3
|
+
pywebexec/pywebexec.py,sha256=uGOdA0nQ4Na9F12vZYXVBr11Uk9XoYuhdM8lJxEUC0Y,33679
|
4
|
+
pywebexec/swagger.yaml,sha256=-uafngZxQFHLdnWY-9SFCdgotO5wynFN2sTEyuBpQ_Q,1998
|
5
|
+
pywebexec/version.py,sha256=Bx58trLhK_vl5EzDfK18POHZa_BoHwqv52T5hR9tbaA,511
|
6
|
+
pywebexec/static/css/style.css,sha256=SuOU_USRh8BiAxEJ1LDYIx3asf3lETu_evWzA54gsBo,8145
|
7
7
|
pywebexec/static/css/xterm.css,sha256=uo5phWaUiJgcz0DAzv46uoByLLbJLeetYosL1xf68rY,5559
|
8
8
|
pywebexec/static/fonts/CommitMonoNerdFontMono-Regular.ttf,sha256=v6nZdSx5cs_TIic8Fujrjzg9u9glWjorDIr7RlwNceM,2370228
|
9
9
|
pywebexec/static/fonts/LICENSE,sha256=gsBdbFPfoMkCWYXBnjcYEAILdO0sYdUdNw8qirJQbVI,4395
|
@@ -24,8 +24,8 @@ pywebexec/static/images/resume.svg,sha256=99LP1Ya2JXakRCO9kW8JMuT_4a_CannF65Eiuw
|
|
24
24
|
pywebexec/static/images/running.svg,sha256=fBCYwYb2O9K4N3waC2nURP25NRwZlqR4PbDZy6JQMww,610
|
25
25
|
pywebexec/static/images/success.svg,sha256=NVwezvVMplt46ElW798vqGfrL21Mw_DWHUp_qiD_FU8,489
|
26
26
|
pywebexec/static/js/commands.js,sha256=TmfcauQlfIeAeC8pwQvKspc4PA_VYLbPTnVCDVBZ87I,8420
|
27
|
-
pywebexec/static/js/popup.js,sha256=
|
28
|
-
pywebexec/static/js/script.js,sha256=
|
27
|
+
pywebexec/static/js/popup.js,sha256=2Ku0h8vtM4hw4wyd_agKgf8vMFf-hQBKueREV8Y0Sio,9252
|
28
|
+
pywebexec/static/js/script.js,sha256=D7pPWiLv3ki_V4o4k0bE1wdBQrx0I2W39EmEJu-T_10,18162
|
29
29
|
pywebexec/static/js/xterm/LICENSE,sha256=EU1P4eXTull-_T9I80VuwnJXubB-zLzUl3xpEYj2T1M,1083
|
30
30
|
pywebexec/static/js/xterm/addon-canvas.js,sha256=ez6QTVvsmLVNJmdJlM-ZQ5bErwlxAQ_9DUmDIptl2TM,94607
|
31
31
|
pywebexec/static/js/xterm/addon-canvas.js.map,sha256=ECBA4B-BqUpdFeRzlsEWLSQnudnhLP-yPQJ8_hKquMo,379537
|
@@ -40,9 +40,9 @@ pywebexec/static/js/xterm/xterm.js.map,sha256=Y7O2Pb-fIS7Z8AC1D5s04_aiW_Jf1f4mCf
|
|
40
40
|
pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
41
|
pywebexec/templates/index.html,sha256=VLcuC0RUkwefDugXWcXsjd5C3owKk5wCJoYIo48xbgk,3106
|
42
42
|
pywebexec/templates/popup.html,sha256=3kpMccKD_OLLhJ4Y9KRw6Ny8wQWjVaRrUfV9y5-bDiQ,1580
|
43
|
-
pywebexec-1.9.
|
44
|
-
pywebexec-1.9.
|
45
|
-
pywebexec-1.9.
|
46
|
-
pywebexec-1.9.
|
47
|
-
pywebexec-1.9.
|
48
|
-
pywebexec-1.9.
|
43
|
+
pywebexec-1.9.2.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
44
|
+
pywebexec-1.9.2.dist-info/METADATA,sha256=-y8vqPEpfH7uCerUz2wdRlB2GTpJThP0HmFNEfrvKKo,8154
|
45
|
+
pywebexec-1.9.2.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
46
|
+
pywebexec-1.9.2.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
47
|
+
pywebexec-1.9.2.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
48
|
+
pywebexec-1.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|