pywebexec 1.9.1__py3-none-any.whl → 1.9.3__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/js/commands.js +1 -1
- pywebexec/static/js/popup.js +4 -8
- pywebexec/static/js/script.js +9 -16
- pywebexec/swagger.yaml +35 -6
- pywebexec/version.py +2 -2
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.dist-info}/METADATA +7 -7
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.dist-info}/RECORD +12 -12
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.dist-info}/LICENSE +0 -0
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.dist-info}/WHEEL +0 -0
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.9.1.dist-info → pywebexec-1.9.3.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/js/commands.js
CHANGED
@@ -216,7 +216,7 @@ window.addEventListener('load', () => {
|
|
216
216
|
|
217
217
|
async function fetchExecutables() {
|
218
218
|
try {
|
219
|
-
const response = await fetch(`/executables
|
219
|
+
const response = await fetch(`/executables`);
|
220
220
|
if (!response.ok) {
|
221
221
|
throw new Error('Failed to fetch command status');
|
222
222
|
}
|
pywebexec/static/js/popup.js
CHANGED
@@ -98,11 +98,6 @@ function autoFit(scroll=true) {
|
|
98
98
|
if (scroll) terminal.scrollToBottom();
|
99
99
|
}
|
100
100
|
|
101
|
-
function getTokenParam() {
|
102
|
-
const urlParams = new URLSearchParams(window.location.search);
|
103
|
-
return urlParams.get('token') ? `?token=${urlParams.get('token')}` : '';
|
104
|
-
}
|
105
|
-
const urlToken = getTokenParam();
|
106
101
|
|
107
102
|
|
108
103
|
function setCommandStatus(status) {
|
@@ -161,13 +156,14 @@ async function viewOutput(command_id) {
|
|
161
156
|
slider.value = 1000;
|
162
157
|
adjustOutputHeight();
|
163
158
|
currentCommandId = command_id;
|
164
|
-
nextOutputLink = `/
|
159
|
+
nextOutputLink = `/commands/${command_id}/output`;
|
165
160
|
clearInterval(outputInterval);
|
166
161
|
terminal.clear();
|
167
162
|
terminal.reset();
|
168
163
|
fullOutput = '';
|
169
164
|
try {
|
170
|
-
|
165
|
+
// Updated endpoint below:
|
166
|
+
const response = await fetch(`/commands/${command_id}`);
|
171
167
|
if (!response.ok) {
|
172
168
|
return;
|
173
169
|
}
|
@@ -256,7 +252,7 @@ window.addEventListener('load', () => {
|
|
256
252
|
slider = document.getElementById('outputSlider');
|
257
253
|
slider.addEventListener('input', sliderUpdateOutput);
|
258
254
|
adjustOutputHeight();
|
259
|
-
const commandId = window.location.pathname.split('/').slice(-
|
255
|
+
const commandId = window.location.pathname.split('/').slice(-2)[0];
|
260
256
|
viewOutput(commandId);
|
261
257
|
});
|
262
258
|
|
pywebexec/static/js/script.js
CHANGED
@@ -108,12 +108,6 @@ function autoFit(scroll=true) {
|
|
108
108
|
if (scroll) terminal.scrollToBottom();
|
109
109
|
}
|
110
110
|
|
111
|
-
function getTokenParam() {
|
112
|
-
const urlParams = new URLSearchParams(window.location.search);
|
113
|
-
return urlParams.get('token') ? `?token=${urlParams.get('token')}` : '';
|
114
|
-
}
|
115
|
-
const urlToken = getTokenParam();
|
116
|
-
|
117
111
|
|
118
112
|
document.getElementById('launchForm').addEventListener('submit', async (event) => {
|
119
113
|
event.preventDefault();
|
@@ -122,7 +116,7 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
122
116
|
fitAddon.fit();
|
123
117
|
terminal.clear();
|
124
118
|
try {
|
125
|
-
const response = await fetch(`/
|
119
|
+
const response = await fetch(`/commands`, {
|
126
120
|
method: 'POST',
|
127
121
|
headers: {
|
128
122
|
'Content-Type': 'application/json'
|
@@ -133,7 +127,6 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
133
127
|
throw new Error('Failed to launch command');
|
134
128
|
}
|
135
129
|
const data = await response.json();
|
136
|
-
//await new Promise(r => setTimeout(r, 300));// not ok
|
137
130
|
viewOutput(data.command_id);
|
138
131
|
fetchCommands();
|
139
132
|
commandInput.focus()
|
@@ -145,7 +138,7 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
145
138
|
|
146
139
|
async function fetchCommands(hide=false) {
|
147
140
|
try {
|
148
|
-
const response = await fetch(`/commands
|
141
|
+
const response = await fetch(`/commands`);
|
149
142
|
if (!response.ok) {
|
150
143
|
document.getElementById('dimmer').style.display = 'block';
|
151
144
|
return;
|
@@ -253,13 +246,13 @@ async function viewOutput(command_id) {
|
|
253
246
|
outputPercentage.innerText = '100%';
|
254
247
|
adjustOutputHeight();
|
255
248
|
currentCommandId = command_id;
|
256
|
-
nextOutputLink = `/
|
249
|
+
nextOutputLink = `/commands/${command_id}/output`;
|
257
250
|
clearInterval(outputInterval);
|
258
251
|
terminal.clear();
|
259
252
|
terminal.reset();
|
260
253
|
fullOutput = '';
|
261
254
|
try {
|
262
|
-
const response = await fetch(`/
|
255
|
+
const response = await fetch(`/commands/${command_id}`);
|
263
256
|
if (!response.ok) {
|
264
257
|
outputInterval = setInterval(() => fetchOutput(nextOutputLink), 500);
|
265
258
|
}
|
@@ -290,7 +283,7 @@ async function viewOutput(command_id) {
|
|
290
283
|
async function openPopup(command_id, event) {
|
291
284
|
event.stopPropagation();
|
292
285
|
event.stopImmediatePropagation();
|
293
|
-
const popupUrl = `/
|
286
|
+
const popupUrl = `/commands/${command_id}/popup`;
|
294
287
|
window.open(popupUrl, '_blank', 'width=1000,height=600');
|
295
288
|
}
|
296
289
|
|
@@ -298,7 +291,7 @@ async function relaunchCommand(command_id, event) {
|
|
298
291
|
event.stopPropagation();
|
299
292
|
event.stopImmediatePropagation();
|
300
293
|
try {
|
301
|
-
const response = await fetch(`/
|
294
|
+
const response = await fetch(`/commands/${command_id}`);
|
302
295
|
if (!response.ok) {
|
303
296
|
throw new Error('Failed to fetch command status');
|
304
297
|
}
|
@@ -309,7 +302,7 @@ async function relaunchCommand(command_id, event) {
|
|
309
302
|
}
|
310
303
|
fitAddon.fit();
|
311
304
|
terminal.clear();
|
312
|
-
const relaunchResponse = await fetch(`/
|
305
|
+
const relaunchResponse = await fetch(`/commands`, {
|
313
306
|
method: 'POST',
|
314
307
|
headers: {
|
315
308
|
'Content-Type': 'application/json'
|
@@ -337,8 +330,8 @@ async function stopCommand(command_id, event) {
|
|
337
330
|
event.stopPropagation();
|
338
331
|
event.stopImmediatePropagation();
|
339
332
|
try {
|
340
|
-
const response = await fetch(`/
|
341
|
-
method: '
|
333
|
+
const response = await fetch(`/commands/${command_id}/stop`, {
|
334
|
+
method: 'PATCH'
|
342
335
|
});
|
343
336
|
if (!response.ok) {
|
344
337
|
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.3
|
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,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=
|
4
|
-
pywebexec/swagger.yaml,sha256
|
5
|
-
pywebexec/version.py,sha256=
|
3
|
+
pywebexec/pywebexec.py,sha256=uGOdA0nQ4Na9F12vZYXVBr11Uk9XoYuhdM8lJxEUC0Y,33679
|
4
|
+
pywebexec/swagger.yaml,sha256=-uafngZxQFHLdnWY-9SFCdgotO5wynFN2sTEyuBpQ_Q,1998
|
5
|
+
pywebexec/version.py,sha256=9iRyJjXIu4ospHHgY6jThnBbx_i0E46WrhiFzzJdLrE,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
|
@@ -23,9 +23,9 @@ pywebexec/static/images/popup.svg,sha256=0Bl9A_v5cBsMPn6FnOlVWlAQKgd2zqiWQbhjcL9
|
|
23
23
|
pywebexec/static/images/resume.svg,sha256=99LP1Ya2JXakRCO9kW8JMuT_4a_CannF65EiuwtvK4A,607
|
24
24
|
pywebexec/static/images/running.svg,sha256=fBCYwYb2O9K4N3waC2nURP25NRwZlqR4PbDZy6JQMww,610
|
25
25
|
pywebexec/static/images/success.svg,sha256=NVwezvVMplt46ElW798vqGfrL21Mw_DWHUp_qiD_FU8,489
|
26
|
-
pywebexec/static/js/commands.js,sha256=
|
27
|
-
pywebexec/static/js/popup.js,sha256=
|
28
|
-
pywebexec/static/js/script.js,sha256=
|
26
|
+
pywebexec/static/js/commands.js,sha256=VhfRJsMqXIwrTRCkEhGzDuaG5WslmTGCcrEo_eqfl1w,8409
|
27
|
+
pywebexec/static/js/popup.js,sha256=0fr3pp4j9D2fXEVnHyQrx2bPWFHfgbb336dbewgH1d8,9023
|
28
|
+
pywebexec/static/js/script.js,sha256=ZtVBu2CtH5XgHIF9nflGNw-Aq26LXzYGmyd51MHrAHY,17851
|
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.3.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
44
|
+
pywebexec-1.9.3.dist-info/METADATA,sha256=AaRXJqL-SrwHOVX5XQ4Rdxdpw0h46jL2YHE4awGcFnU,8154
|
45
|
+
pywebexec-1.9.3.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
46
|
+
pywebexec-1.9.3.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
47
|
+
pywebexec-1.9.3.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
48
|
+
pywebexec-1.9.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|