pywebexec 0.0.3__tar.gz → 0.0.7__tar.gz

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.
Files changed (25) hide show
  1. {pywebexec-0.0.3/pywebexec.egg-info → pywebexec-0.0.7}/PKG-INFO +5 -4
  2. {pywebexec-0.0.3 → pywebexec-0.0.7}/README.md +3 -2
  3. {pywebexec-0.0.3 → pywebexec-0.0.7}/pyproject.toml +1 -1
  4. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/pywebexec.py +67 -24
  5. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/templates/index.html +10 -2
  6. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/version.py +2 -2
  7. {pywebexec-0.0.3 → pywebexec-0.0.7/pywebexec.egg-info}/PKG-INFO +5 -4
  8. pywebexec-0.0.7/pywebexec.egg-info/entry_points.txt +2 -0
  9. pywebexec-0.0.3/pywebexec.egg-info/entry_points.txt +0 -2
  10. {pywebexec-0.0.3 → pywebexec-0.0.7}/.github/workflows/python-publish.yml +0 -0
  11. {pywebexec-0.0.3 → pywebexec-0.0.7}/.gitignore +0 -0
  12. {pywebexec-0.0.3 → pywebexec-0.0.7}/LICENSE +0 -0
  13. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/__init__.py +0 -0
  14. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/aborted.svg +0 -0
  15. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/copy.svg +0 -0
  16. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/copy_ok.svg +0 -0
  17. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/failed.svg +0 -0
  18. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/running.svg +0 -0
  19. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/static/images/success.svg +0 -0
  20. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec/templates/__init__.py +0 -0
  21. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec.egg-info/SOURCES.txt +0 -0
  22. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec.egg-info/dependency_links.txt +0 -0
  23. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec.egg-info/requires.txt +0 -0
  24. {pywebexec-0.0.3 → pywebexec-0.0.7}/pywebexec.egg-info/top_level.txt +0 -0
  25. {pywebexec-0.0.3 → pywebexec-0.0.7}/setup.cfg +0 -0
@@ -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.7
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
@@ -19,12 +19,13 @@ $ pip install pywebexec
19
19
  $ pywebexec
20
20
  ```
21
21
 
22
- * Launch commands with params/view live output/Status using browser `http://<yourserver>:8080`
22
+ * Launch commands with params/view live output/Status using browser
23
+ ![image](https://github.com/user-attachments/assets/921da56f-6d4b-46e3-b16c-e01a2dc9accf)
23
24
 
24
25
  ## features
25
26
 
26
27
  * Serve executables in current directory
27
- * Launch commands with params from web browser
28
+ * Launch commands with params from web browser or API call
28
29
  * Follow live output
29
30
  * Stop command
30
31
  * Relaunch command
@@ -53,4 +53,4 @@ build-backend = "setuptools.build_meta"
53
53
  version_file = "pywebexec/version.py"
54
54
 
55
55
  [project.scripts]
56
- pywebexec = "pywebexec.pywebexec:start_gunicorn"
56
+ pywebexec = "pywebexec.pywebexec:main"
@@ -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,11 +48,46 @@ 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
  }
54
55
  StandaloneApplication(app, options=options).run()
55
56
 
57
+ def daemon_d(action, pidfilepath, hostname=None, args=None):
58
+ """start/stop daemon"""
59
+ import signal
60
+ import daemon, daemon.pidfile
61
+
62
+ pidfile = daemon.pidfile.TimeoutPIDLockFile(pidfilepath+".pid", acquire_timeout=30)
63
+ if action == "stop":
64
+ if pidfile.is_locked():
65
+ pid = pidfile.read_pid()
66
+ print(f"Stopping server pid {pid}")
67
+ try:
68
+ os.kill(pid, signal.SIGINT)
69
+ except:
70
+ return False
71
+ return True
72
+ elif action == "status":
73
+ status = pidfile.is_locked()
74
+ if status:
75
+ print(f"pywebexec running pid {pidfile.read_pid()}")
76
+ return True
77
+ print("pywebexec not running")
78
+ return False
79
+ elif action == "start":
80
+ print(f"Starting server")
81
+ log = open(pidfilepath + ".log", "ab+")
82
+ daemon_context = daemon.DaemonContext(
83
+ stderr=log,
84
+ pidfile=pidfile,
85
+ umask=0o077,
86
+ working_directory=os.getcwd(),
87
+ )
88
+ with daemon_context:
89
+ start_gunicorn()
90
+
56
91
  def parseargs():
57
92
  global app, args
58
93
  parser = argparse.ArgumentParser(description='Run the script execution server.')
@@ -76,6 +111,7 @@ def parseargs():
76
111
  )
77
112
  parser.add_argument("-c", "--cert", type=str, help="Path to https certificate")
78
113
  parser.add_argument("-k", "--key", type=str, help="Path to https certificate key")
114
+ parser.add_argument("action", nargs="?", help="daemon action start/stop/restart/status", choices=["start","stop","restart","status"])
79
115
 
80
116
  args = parser.parse_args()
81
117
 
@@ -99,7 +135,7 @@ def get_status_file_path(script_id):
99
135
  def get_output_file_path(script_id):
100
136
  return os.path.join(SCRIPT_STATUS_DIR, f'{script_id}_output.txt')
101
137
 
102
- def update_script_status(script_id, status, script_name=None, params=None, start_time=None, end_time=None, exit_code=None):
138
+ def update_script_status(script_id, status, script_name=None, params=None, start_time=None, end_time=None, exit_code=None, pid=None):
103
139
  status_file_path = get_status_file_path(script_id)
104
140
  status_data = read_script_status(script_id) or {}
105
141
  status_data['status'] = status
@@ -113,6 +149,8 @@ def update_script_status(script_id, status, script_name=None, params=None, start
113
149
  status_data['end_time'] = end_time
114
150
  if exit_code is not None:
115
151
  status_data['exit_code'] = exit_code
152
+ if pid is not None:
153
+ status_data['pid'] = pid
116
154
  with open(status_file_path, 'w') as f:
117
155
  json.dump(status_data, f)
118
156
 
@@ -134,6 +172,7 @@ def run_script(script_name, params, script_id):
134
172
  with open(output_file_path, 'w') as output_file:
135
173
  # Run the script with parameters and redirect stdout and stderr to the file
136
174
  process = subprocess.Popen([script_name] + params, stdout=output_file, stderr=output_file, text=True)
175
+ update_script_status(script_id, 'running', pid=process.pid)
137
176
  processes[script_id] = process
138
177
  process.wait()
139
178
  processes.pop(script_id, None)
@@ -193,25 +232,26 @@ def run_script_endpoint():
193
232
  @app.route('/stop_script/<script_id>', methods=['POST'])
194
233
  @auth_required
195
234
  def stop_script(script_id):
196
- process = processes.get(script_id)
235
+ status = read_script_status(script_id)
236
+ if not status or 'pid' not in status:
237
+ return jsonify({'error': 'Invalid script_id or script not running'}), 400
238
+
239
+ pid = status['pid']
197
240
  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
241
+ try:
242
+ os.kill(pid, 15) # Send SIGTERM
243
+ update_script_status(script_id, 'aborted', end_time=end_time, exit_code=-15)
244
+ return jsonify({'message': 'Script aborted'})
245
+ except Exception as e:
246
+ status_data = read_script_status(script_id) or {}
247
+ status_data['status'] = 'failed'
248
+ status_data['end_time'] = end_time
249
+ status_data['exit_code'] = 1
250
+ with open(get_status_file_path(script_id), 'w') as f:
251
+ json.dump(status_data, f)
252
+ with open(get_output_file_path(script_id), 'a') as output_file:
253
+ output_file.write(str(e))
254
+ return jsonify({'error': 'Failed to terminate script'}), 500
215
255
 
216
256
  @app.route('/script_status/<script_id>', methods=['GET'])
217
257
  @auth_required
@@ -276,9 +316,12 @@ def list_executables():
276
316
  def verify_password(username, password):
277
317
  return username == app.config['USER'] and password == app.config['PASSWORD']
278
318
 
279
-
280
-
319
+ def main():
320
+ if args.action:
321
+ daemon_d(args.action, pidfilepath=SCRIPT_STATUS_DIR+'/pywebexec')
322
+ else:
323
+ start_gunicorn()
281
324
 
282
325
  if __name__ == '__main__':
283
- start_gunicorn()
284
- #app.run(host='0.0.0.0', port=5000)
326
+ main()
327
+ # 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) {
@@ -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.7'
16
+ __version_tuple__ = version_tuple = (0, 0, 7)
@@ -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.7
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
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pywebexec = pywebexec.pywebexec:main
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- pywebexec = pywebexec.pywebexec:start_gunicorn
File without changes
File without changes
File without changes