pywebexec 0.0.3__py3-none-any.whl → 0.0.6__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
@@ -16,7 +16,7 @@ app = Flask(__name__)
16
16
  auth = HTTPBasicAuth()
17
17
 
18
18
  # Directory to store the script status and output
19
- SCRIPT_STATUS_DIR = 'script_status'
19
+ SCRIPT_STATUS_DIR = '.web_status'
20
20
 
21
21
  if not os.path.exists(SCRIPT_STATUS_DIR):
22
22
  os.makedirs(SCRIPT_STATUS_DIR)
@@ -48,6 +48,7 @@ def start_gunicorn():
48
48
  options = {
49
49
  'bind': '%s:%s' % (args.listen, args.port),
50
50
  'workers': 4,
51
+ 'timeout': 600,
51
52
  'certfile': args.cert,
52
53
  'keyfile': args.key,
53
54
  }
@@ -99,7 +100,7 @@ def get_status_file_path(script_id):
99
100
  def get_output_file_path(script_id):
100
101
  return os.path.join(SCRIPT_STATUS_DIR, f'{script_id}_output.txt')
101
102
 
102
- def update_script_status(script_id, status, script_name=None, params=None, start_time=None, end_time=None, exit_code=None):
103
+ def update_script_status(script_id, status, script_name=None, params=None, start_time=None, end_time=None, exit_code=None, pid=None):
103
104
  status_file_path = get_status_file_path(script_id)
104
105
  status_data = read_script_status(script_id) or {}
105
106
  status_data['status'] = status
@@ -113,6 +114,8 @@ def update_script_status(script_id, status, script_name=None, params=None, start
113
114
  status_data['end_time'] = end_time
114
115
  if exit_code is not None:
115
116
  status_data['exit_code'] = exit_code
117
+ if pid is not None:
118
+ status_data['pid'] = pid
116
119
  with open(status_file_path, 'w') as f:
117
120
  json.dump(status_data, f)
118
121
 
@@ -134,6 +137,7 @@ def run_script(script_name, params, script_id):
134
137
  with open(output_file_path, 'w') as output_file:
135
138
  # Run the script with parameters and redirect stdout and stderr to the file
136
139
  process = subprocess.Popen([script_name] + params, stdout=output_file, stderr=output_file, text=True)
140
+ update_script_status(script_id, 'running', pid=process.pid)
137
141
  processes[script_id] = process
138
142
  process.wait()
139
143
  processes.pop(script_id, None)
@@ -193,25 +197,26 @@ def run_script_endpoint():
193
197
  @app.route('/stop_script/<script_id>', methods=['POST'])
194
198
  @auth_required
195
199
  def stop_script(script_id):
196
- process = processes.get(script_id)
200
+ status = read_script_status(script_id)
201
+ if not status or 'pid' not in status:
202
+ return jsonify({'error': 'Invalid script_id or script not running'}), 400
203
+
204
+ pid = status['pid']
197
205
  end_time = datetime.now().isoformat()
198
- if process and process.poll() is None:
199
- try:
200
- process.terminate()
201
- process.wait() # Ensure the process has terminated
202
- return jsonify({'message': 'Script aborted'})
203
- except Exception as e:
204
- status_data = read_script_status(script_id) or {}
205
- status_data['status'] = 'failed'
206
- status_data['end_time'] = end_time
207
- status_data['exit_code'] = 1
208
- with open(get_status_file_path(script_id), 'w') as f:
209
- json.dump(status_data, f)
210
- with open(get_output_file_path(script_id), 'a') as output_file:
211
- output_file.write(str(e))
212
- return jsonify({'error': 'Failed to terminate script'}), 500
213
- update_script_status(script_id, 'failed', end_time=end_time, exit_code=1)
214
- return jsonify({'error': 'Invalid script_id or script not running'}), 400
206
+ try:
207
+ os.kill(pid, 15) # Send SIGTERM
208
+ update_script_status(script_id, 'aborted', end_time=end_time, exit_code=-15)
209
+ return jsonify({'message': 'Script aborted'})
210
+ except Exception as e:
211
+ status_data = read_script_status(script_id) or {}
212
+ status_data['status'] = 'failed'
213
+ status_data['end_time'] = end_time
214
+ status_data['exit_code'] = 1
215
+ with open(get_status_file_path(script_id), 'w') as f:
216
+ json.dump(status_data, f)
217
+ with open(get_output_file_path(script_id), 'a') as output_file:
218
+ output_file.write(str(e))
219
+ return jsonify({'error': 'Failed to terminate script'}), 500
215
220
 
216
221
  @app.route('/script_status/<script_id>', methods=['GET'])
217
222
  @auth_required
@@ -276,9 +281,6 @@ def list_executables():
276
281
  def verify_password(username, password):
277
282
  return username == app.config['USER'] and password == app.config['PASSWORD']
278
283
 
279
-
280
-
281
-
282
284
  if __name__ == '__main__':
283
285
  start_gunicorn()
284
286
  #app.run(host='0.0.0.0', port=5000)
@@ -79,7 +79,12 @@
79
79
  .copy_clip_ok, .copy_clip_ok:hover {
80
80
  background-image: url("/static/images/copy_ok.svg");
81
81
  }
82
-
82
+ input {
83
+ width: 50%
84
+ }
85
+ .currentscript {
86
+ background-color: #eef;
87
+ }
83
88
  </style>
84
89
  </head>
85
90
  <body>
@@ -126,6 +131,7 @@
126
131
  });
127
132
  const data = await response.json();
128
133
  fetchScripts();
134
+ viewOutput(data.script_id)
129
135
  });
130
136
 
131
137
  async function fetchScripts() {
@@ -136,6 +142,7 @@
136
142
  scriptsTbody.innerHTML = '';
137
143
  scripts.forEach(script => {
138
144
  const scriptRow = document.createElement('tr');
145
+ scriptRow.className = script.script_id === currentScriptId ? 'currentscript' : '';
139
146
  scriptRow.innerHTML = `
140
147
  <td class="monospace">
141
148
  <span class="copy_clip" onclick="copyToClipboard('${script.script_id}', this)">${script.script_id.slice(0, 8)}</span>
@@ -194,6 +201,7 @@
194
201
  } else {
195
202
  fetchOutput(script_id);
196
203
  }
204
+ fetchScripts(); // Refresh the script list to highlight the current script
197
205
  }
198
206
 
199
207
  async function relaunchScript(script_id) {
@@ -214,8 +222,8 @@
214
222
  })
215
223
  });
216
224
  const relaunchData = await relaunchResponse.json();
217
- alert(relaunchData.message);
218
225
  fetchScripts();
226
+ viewOutput(relaunchData.script_id)
219
227
  }
220
228
 
221
229
  async function stopScript(script_id) {
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 = '0.0.3'
16
- __version_tuple__ = version_tuple = (0, 0, 3)
15
+ __version__ = version = '0.0.6'
16
+ __version_tuple__ = version_tuple = (0, 0, 6)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 0.0.3
3
+ Version: 0.0.6
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -78,12 +78,13 @@ $ pip install pywebexec
78
78
  $ pywebexec
79
79
  ```
80
80
 
81
- * Launch commands with params/view live output/Status using browser `http://<yourserver>:8080`
81
+ * Launch commands with params/view live output/Status using browser
82
+ ![image](https://github.com/user-attachments/assets/921da56f-6d4b-46e3-b16c-e01a2dc9accf)
82
83
 
83
84
  ## features
84
85
 
85
86
  * Serve executables in current directory
86
- * Launch commands with params from web browser
87
+ * Launch commands with params from web browser or API call
87
88
  * Follow live output
88
89
  * Stop command
89
90
  * Relaunch command
@@ -1,6 +1,6 @@
1
1
  pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
2
- pywebexec/pywebexec.py,sha256=mnnlYkRg8HpWdfcVamqEw0zFqy-ZVNVEVXYttecRTyg,10212
3
- pywebexec/version.py,sha256=hB095avW4HuDZxn8qPHRG1UMzSSonb8ZDAsLxt9hmk8,411
2
+ pywebexec/pywebexec.py,sha256=1LUPOuZXoXqM17-Cu-rYL05lOJy_5HqtIGqhxVyzdZY,10306
3
+ pywebexec/version.py,sha256=c6ZQWSJeXXzGZ3WoZWjkA-MiNkBFXMIRV9kZPo4MQ_M,411
4
4
  pywebexec/static/images/aborted.svg,sha256=_mP43hU5QdRLFZIknBgjx-dIXrHgQG23-QV27ApXK2A,381
5
5
  pywebexec/static/images/copy.svg,sha256=d9OwtGh5GzzZHzYcDrLfNxZYLth1Q64x7bRyYxu4Px0,622
6
6
  pywebexec/static/images/copy_ok.svg,sha256=mEqUVUhSq8xaJK2msQkxRawnz_KwlCZ-tok8QS6hJ3g,451
@@ -8,10 +8,10 @@ pywebexec/static/images/failed.svg,sha256=ADZ7IKrUyOXtqpivnz3VcH0-Wru-I5MOi3OJAk
8
8
  pywebexec/static/images/running.svg,sha256=vBpiG6ClNUNCArkwsyqK7O-qhIKJX1NI7MSjclNSp_8,1537
9
9
  pywebexec/static/images/success.svg,sha256=PJDcCSTevJh7rkfSFLtc7P0pbeh8PVQBS8DaOLQemmc,489
10
10
  pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- pywebexec/templates/index.html,sha256=tS2uVjto2iPpx-0j3aVxAfE564dJJKMzY0_dBC-vGjE,9813
12
- pywebexec-0.0.3.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
13
- pywebexec-0.0.3.dist-info/METADATA,sha256=9CAAkMxOY5LxHf8boAIwPj2FPgu7G2r3PMda-4U8xrw,4623
14
- pywebexec-0.0.3.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
15
- pywebexec-0.0.3.dist-info/entry_points.txt,sha256=-6--c27U7RARJe0BiW5CkTuKljf6pRtnDzE0wfmD9TM,65
16
- pywebexec-0.0.3.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
17
- pywebexec-0.0.3.dist-info/RECORD,,
11
+ pywebexec/templates/index.html,sha256=2peDmBnxZQSw6OjDCjNqRCx1_grDlI_Xr1NMgAGv2OI,10163
12
+ pywebexec-0.0.6.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
13
+ pywebexec-0.0.6.dist-info/METADATA,sha256=DQZhxqwFQhVWYOnPU6CmyNgIPZm4owp5O4AJx_ZSZWQ,4698
14
+ pywebexec-0.0.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
15
+ pywebexec-0.0.6.dist-info/entry_points.txt,sha256=-6--c27U7RARJe0BiW5CkTuKljf6pRtnDzE0wfmD9TM,65
16
+ pywebexec-0.0.6.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
17
+ pywebexec-0.0.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.7.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5