pywebexec 1.9.1__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 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}/dopopup/{command_id}{url_params}", flush=True)
310
- print(f"web popup: {protocol}://{ip}:{args.port}/dopopup/{command_id}{url_params}", flush=True)
311
- print(f"raw output: {protocol}://{hostname}:{args.port}/command_output_raw/{command_id}{url_params}", flush=True)
312
- print(f"raw output: {protocol}://{ip}:{args.port}/command_output_raw/{command_id}{url_params}", flush=True)
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('/stop_command/<command_id>', methods=['POST'])
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('/run_command', methods=['POST'])
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('/command_status/<command_id>', methods=['GET'])
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('/command_output/<command_id>', methods=['GET'])
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}command_output/{command_id}?offset={new_offset}&maxsize={maxsize}{token_param}'
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('/command_output_raw/<command_id>', methods=['GET'])
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('/popup/<command_id>')
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('/dopopup/<command_id>')
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('/popup/{command_id}{token_param}', '_blank', 'width=1000,height=600');
840
+ window.open('/commands/{command_id}/popup{token_param}', '_blank', 'width=1000,height=600');
842
841
  window.close();
843
842
  }};
844
843
  </script>
@@ -161,13 +161,14 @@ async function viewOutput(command_id) {
161
161
  slider.value = 1000;
162
162
  adjustOutputHeight();
163
163
  currentCommandId = command_id;
164
- nextOutputLink = `/command_output/${command_id}${urlToken}`;
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
- const response = await fetch(`/command_status/${command_id}${urlToken}`);
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(-1)[0];
260
+ const commandId = window.location.pathname.split('/').slice(-2)[0];
260
261
  viewOutput(commandId);
261
262
  });
262
263
 
@@ -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(`/run_command${urlToken}`, {
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 = `/command_output/${command_id}${urlToken}`;
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(`/command_status/${command_id}${urlToken}`);
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 = `/popup/${command_id}${urlToken}`;
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(`/command_status/${command_id}${urlToken}`);
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(`/run_command${urlToken}`, {
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(`/stop_command/${command_id}${urlToken}`, {
341
- method: 'POST'
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
- /run_command:
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
- /command_status/{command_id}:
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: "List commands"
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: "List of all commands"
77
+ description: "Command stopped successfully"
49
78
  /executables:
50
79
  get:
51
- summary: "List executables"
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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.9.1'
21
- __version_tuple__ = version_tuple = (1, 9, 1)
20
+ __version__ = version = '1.9.2'
21
+ __version_tuple__ = version_tuple = (1, 9, 2)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.9.1
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 | /command_output/command_id | offset: int | output: str<br>status: str<br>links: { next: str } |
209
- | GET | /command_output_raw/command_id | offset: int | output: stream raw output until end of command<br>curl -Ns http://srv/command_output_raw/command_id|
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,8 +1,8 @@
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=VJH7C8MI18Ybx0KHrPiBsVy17xpO4pfd_KXoNIkVBxg,33724
4
- pywebexec/swagger.yaml,sha256=0AI4KfQI5ezTF8PP4E_ER6dfiseBi3SiOk6OUj5DjcA,1253
5
- pywebexec/version.py,sha256=0TTXzdumH9yE7lSKUOdvHb1HJBaZX7fxyxuZNWj70Cg,511
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
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
@@ -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=pYBwMuGnOCz4O1O-LmxHNlqHiZCn55ozbYchQraJp6Q,9222
28
- pywebexec/static/js/script.js,sha256=iQcl-22b4GmGLw8lY2aviC-yd8dTYZKi8cN-KoaYwvE,18216
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.1.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
44
- pywebexec-1.9.1.dist-info/METADATA,sha256=xo0Wz-wbREaii01oK9ro9ObeOWIIV4Zf40_5a74EGkI,8186
45
- pywebexec-1.9.1.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
46
- pywebexec-1.9.1.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
47
- pywebexec-1.9.1.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
48
- pywebexec-1.9.1.dist-info/RECORD,,
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,,