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 +25 -23
- pywebexec/templates/index.html +10 -2
- pywebexec/version.py +2 -2
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/METADATA +5 -4
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/RECORD +9 -9
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/WHEEL +1 -1
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/LICENSE +0 -0
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/entry_points.txt +0 -0
- {pywebexec-0.0.3.dist-info → pywebexec-0.0.6.dist-info}/top_level.txt +0 -0
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 = '
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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)
|
pywebexec/templates/index.html
CHANGED
|
@@ -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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pywebexec
|
|
3
|
-
Version: 0.0.
|
|
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
|
|
81
|
+
* Launch commands with params/view live output/Status using browser
|
|
82
|
+

|
|
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=
|
|
3
|
-
pywebexec/version.py,sha256=
|
|
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=
|
|
12
|
-
pywebexec-0.0.
|
|
13
|
-
pywebexec-0.0.
|
|
14
|
-
pywebexec-0.0.
|
|
15
|
-
pywebexec-0.0.
|
|
16
|
-
pywebexec-0.0.
|
|
17
|
-
pywebexec-0.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|