pywebexec 1.1.2__py3-none-any.whl → 1.1.13__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 +9 -2
- pywebexec/static/css/style.css +66 -11
- pywebexec/static/images/running.gif +0 -0
- pywebexec/static/js/script.js +155 -96
- pywebexec/templates/index.html +4 -1
- pywebexec/version.py +2 -2
- {pywebexec-1.1.2.dist-info → pywebexec-1.1.13.dist-info}/METADATA +11 -9
- pywebexec-1.1.13.dist-info/RECORD +20 -0
- pywebexec/static/images/running.svg +0 -1
- pywebexec-1.1.2.dist-info/RECORD +0 -20
- {pywebexec-1.1.2.dist-info → pywebexec-1.1.13.dist-info}/LICENSE +0 -0
- {pywebexec-1.1.2.dist-info → pywebexec-1.1.13.dist-info}/WHEEL +0 -0
- {pywebexec-1.1.2.dist-info → pywebexec-1.1.13.dist-info}/entry_points.txt +0 -0
- {pywebexec-1.1.2.dist-info → pywebexec-1.1.13.dist-info}/top_level.txt +0 -0
pywebexec/pywebexec.py
CHANGED
|
@@ -342,7 +342,10 @@ def read_command_status(command_id):
|
|
|
342
342
|
if not os.path.exists(status_file_path):
|
|
343
343
|
return None
|
|
344
344
|
with open(status_file_path, 'r') as f:
|
|
345
|
-
|
|
345
|
+
try:
|
|
346
|
+
status_data = json.load(f)
|
|
347
|
+
except json.JSONDecodeError:
|
|
348
|
+
return None
|
|
346
349
|
|
|
347
350
|
# Cache the status if it is not "running"
|
|
348
351
|
if status_data['status'] != 'running':
|
|
@@ -382,6 +385,8 @@ def run_command(command, params, command_id):
|
|
|
382
385
|
|
|
383
386
|
@app.before_request
|
|
384
387
|
def check_authentication():
|
|
388
|
+
if not app.config['USER'] and not app.config['LDAP_SERVER']:
|
|
389
|
+
return
|
|
385
390
|
if 'username' not in session and request.endpoint not in ['login', 'static']:
|
|
386
391
|
return auth.login_required(lambda: None)()
|
|
387
392
|
|
|
@@ -518,7 +523,7 @@ def list_commands():
|
|
|
518
523
|
params = shlex.join(status['params'])
|
|
519
524
|
except AttributeError:
|
|
520
525
|
params = " ".join([shlex.quote(p) if " " in p else p for p in status['params']])
|
|
521
|
-
command = status
|
|
526
|
+
command = status.get('command', '-') + ' ' + params
|
|
522
527
|
commands.append({
|
|
523
528
|
'command_id': command_id,
|
|
524
529
|
'status': status['status'],
|
|
@@ -539,6 +544,8 @@ def get_command_output(command_id):
|
|
|
539
544
|
with open(output_file_path, 'r') as output_file:
|
|
540
545
|
output = output_file.read()
|
|
541
546
|
status_data = read_command_status(command_id) or {}
|
|
547
|
+
if request.headers.get('Accept') == 'text/plain':
|
|
548
|
+
return f"{output}\nstatus: {status_data.get('status')}", 200, {'Content-Type': 'text/plain'}
|
|
542
549
|
return jsonify({'output': output, 'status': status_data.get("status")})
|
|
543
550
|
return jsonify({'error': 'Invalid command_id'}), 404
|
|
544
551
|
|
pywebexec/static/css/style.css
CHANGED
|
@@ -1,23 +1,46 @@
|
|
|
1
|
-
body {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
body {
|
|
2
|
+
font-family: Arial, sans-serif;
|
|
3
|
+
}
|
|
4
|
+
.table-container {
|
|
5
|
+
height: 270px;
|
|
6
|
+
overflow-y: auto;
|
|
7
|
+
position: relative;
|
|
8
|
+
border-radius: 10px;
|
|
9
|
+
border: 1px solid #aaa;
|
|
10
|
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.30);
|
|
11
|
+
}
|
|
12
|
+
table {
|
|
13
|
+
width: 100%;
|
|
14
|
+
border-collapse: collapse;
|
|
15
|
+
}
|
|
4
16
|
th, td {
|
|
5
17
|
padding: 8px;
|
|
6
18
|
text-align: left;
|
|
7
19
|
border-bottom: 1px solid #ddd;
|
|
8
20
|
white-space: nowrap;
|
|
9
21
|
}
|
|
10
|
-
th {
|
|
22
|
+
th {
|
|
23
|
+
background-color: #444;
|
|
24
|
+
color: #eee;
|
|
25
|
+
position: sticky;
|
|
26
|
+
top: 0;
|
|
27
|
+
z-index: 1;
|
|
28
|
+
}
|
|
11
29
|
.outcol {
|
|
12
30
|
width: 100%;
|
|
13
31
|
}
|
|
32
|
+
select { /* Safari bug */
|
|
33
|
+
font-size: 15px;
|
|
34
|
+
border: #aaa solid 1px;
|
|
35
|
+
border-radius: 5px;
|
|
36
|
+
}
|
|
14
37
|
.output {
|
|
15
38
|
white-space: pre-wrap;
|
|
16
39
|
background: #f0f0f0;
|
|
17
40
|
padding: 10px;
|
|
18
41
|
border: 1px solid #ccc;
|
|
19
42
|
font-family: monospace;
|
|
20
|
-
border-radius:
|
|
43
|
+
border-radius: 10px;
|
|
21
44
|
overflow-y: auto;
|
|
22
45
|
}
|
|
23
46
|
.copy-icon { cursor: pointer; }
|
|
@@ -28,7 +51,6 @@ button {
|
|
|
28
51
|
-webkit-border-radius: none;
|
|
29
52
|
appearance: none;
|
|
30
53
|
border-radius: 15px;
|
|
31
|
-
padding: 3px;
|
|
32
54
|
padding-right: 13px;
|
|
33
55
|
border: 1px #555 solid;
|
|
34
56
|
height: 22px;
|
|
@@ -49,8 +71,13 @@ form {
|
|
|
49
71
|
background-repeat: no-repeat;
|
|
50
72
|
vertical-align: middle;
|
|
51
73
|
}
|
|
74
|
+
.title-icon {
|
|
75
|
+
width: 30px;
|
|
76
|
+
height: 30px;
|
|
77
|
+
background-image: url("/static/images/favicon.svg")
|
|
78
|
+
}
|
|
52
79
|
.status-running {
|
|
53
|
-
background-image: url("/static/images/running.
|
|
80
|
+
background-image: url("/static/images/running.gif")
|
|
54
81
|
}
|
|
55
82
|
.status-success {
|
|
56
83
|
background-image: url("/static/images/success.svg")
|
|
@@ -62,10 +89,10 @@ form {
|
|
|
62
89
|
background-image: url("/static/images/aborted.svg")
|
|
63
90
|
}
|
|
64
91
|
.copy_clip {
|
|
65
|
-
padding-right:
|
|
92
|
+
padding-right: 20px;
|
|
66
93
|
background-repeat: no-repeat;
|
|
67
94
|
background-position: right top;
|
|
68
|
-
background-size:
|
|
95
|
+
background-size: 20px 12px;
|
|
69
96
|
white-space: nowrap;
|
|
70
97
|
}
|
|
71
98
|
.copy_clip:hover {
|
|
@@ -87,7 +114,7 @@ input {
|
|
|
87
114
|
height: 15px;
|
|
88
115
|
font-size: 15px;
|
|
89
116
|
outline: none;
|
|
90
|
-
text-indent:
|
|
117
|
+
text-indent: 5px;
|
|
91
118
|
background-color: white;
|
|
92
119
|
}
|
|
93
120
|
.currentcommand {
|
|
@@ -96,6 +123,7 @@ input {
|
|
|
96
123
|
.resizer {
|
|
97
124
|
width: 100%;
|
|
98
125
|
height: 5px;
|
|
126
|
+
border-radius: 5px;
|
|
99
127
|
background: #aaa;
|
|
100
128
|
cursor: ns-resize;
|
|
101
129
|
position: absolute;
|
|
@@ -105,8 +133,35 @@ input {
|
|
|
105
133
|
.resizer-container {
|
|
106
134
|
position: relative;
|
|
107
135
|
height: 5px;
|
|
108
|
-
margin
|
|
136
|
+
margin: 5px;
|
|
137
|
+
/*margin-bottom: 10px;*/
|
|
109
138
|
}
|
|
110
139
|
tr.clickable-row {
|
|
111
140
|
cursor: pointer;
|
|
112
141
|
}
|
|
142
|
+
body.dimmed {
|
|
143
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
}
|
|
146
|
+
body.dimmed * {
|
|
147
|
+
pointer-events: none;
|
|
148
|
+
}
|
|
149
|
+
.dimmer {
|
|
150
|
+
display: none;
|
|
151
|
+
position: fixed;
|
|
152
|
+
top: 0;
|
|
153
|
+
left: 0;
|
|
154
|
+
width: 100%;
|
|
155
|
+
height: 100%;
|
|
156
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
157
|
+
z-index: 1000;
|
|
158
|
+
}
|
|
159
|
+
.dimmer-text {
|
|
160
|
+
color: white;
|
|
161
|
+
font-size: 24px;
|
|
162
|
+
text-align: center;
|
|
163
|
+
position: absolute;
|
|
164
|
+
top: 50%;
|
|
165
|
+
left: 50%;
|
|
166
|
+
transform: translate(-50%, -50%);
|
|
167
|
+
}
|
|
Binary file
|
pywebexec/static/js/script.js
CHANGED
|
@@ -5,77 +5,108 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
|
|
|
5
5
|
event.preventDefault();
|
|
6
6
|
const commandName = document.getElementById('commandName').value;
|
|
7
7
|
const params = document.getElementById('params').value.split(' ');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch('/run_command', {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify({ command: commandName, params: params })
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error('Failed to launch command');
|
|
18
|
+
}
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
fetchCommands();
|
|
21
|
+
viewOutput(data.command_id);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.log('Error running command:', error);
|
|
24
|
+
}
|
|
18
25
|
});
|
|
19
26
|
|
|
20
27
|
async function fetchCommands() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch('/commands');
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
document.getElementById('dimmer').style.display = 'block';
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const commands = await response.json();
|
|
35
|
+
commands.sort((a, b) => new Date(b.start_time) - new Date(a.start_time));
|
|
36
|
+
const commandsTbody = document.getElementById('commands');
|
|
37
|
+
commandsTbody.innerHTML = '';
|
|
38
|
+
if (!currentCommandId && commands.length) {
|
|
39
|
+
currentCommandId = commands[0].command_id;
|
|
40
|
+
viewOutput(currentCommandId);
|
|
41
|
+
}
|
|
42
|
+
commands.forEach(command => {
|
|
43
|
+
const commandRow = document.createElement('tr');
|
|
44
|
+
commandRow.className = `clickable-row ${command.command_id === currentCommandId ? 'currentcommand' : ''}`;
|
|
45
|
+
commandRow.onclick = () => viewOutput(command.command_id);
|
|
46
|
+
commandRow.innerHTML = `
|
|
47
|
+
<td class="monospace">
|
|
48
|
+
${navigator.clipboard == undefined ? `${command.command_id}` : `<span class="copy_clip" onclick="copyToClipboard('${command.command_id.slice(0, 8)}', this, event)">${command.command_id.slice(0, 8)}</span>`}
|
|
49
|
+
</td>
|
|
50
|
+
<td><span class="status-icon status-${command.status}"></span>${command.status}</td>
|
|
51
|
+
<td>${formatTime(command.start_time)}</td>
|
|
52
|
+
<td>${command.status === 'running' ? formatDuration(command.start_time, new Date().toISOString()) : formatDuration(command.start_time, command.end_time)}</td>
|
|
53
|
+
<td>${command.exit_code}</td>
|
|
54
|
+
<td>${command.command.replace(/^\.\//, '')}</td>
|
|
55
|
+
<td>
|
|
56
|
+
${command.status === 'running' ? `<button onclick="stopCommand('${command.command_id}')">Stop</button>` : `<button onclick="relaunchCommand('${command.command_id}')">Run</button>`}
|
|
57
|
+
</td>
|
|
58
|
+
<td class="monospace outcol">${command.last_output_line || ''}</td>
|
|
59
|
+
`;
|
|
60
|
+
commandsTbody.appendChild(commandRow);
|
|
61
|
+
});
|
|
62
|
+
document.getElementById('dimmer').style.display = 'none';
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.log('Error fetching commands:', error);
|
|
65
|
+
document.getElementById('dimmer').style.display = 'block';
|
|
29
66
|
}
|
|
30
|
-
commands.forEach(command => {
|
|
31
|
-
const commandRow = document.createElement('tr');
|
|
32
|
-
commandRow.className = `clickable-row ${command.command_id === currentCommandId ? 'currentcommand' : ''}`;
|
|
33
|
-
commandRow.onclick = () => viewOutput(command.command_id);
|
|
34
|
-
commandRow.innerHTML = `
|
|
35
|
-
<td class="monospace">
|
|
36
|
-
<span class="copy_clip" onclick="copyToClipboard('${command.command_id}', this)">${command.command_id.slice(0, 8)}</span>
|
|
37
|
-
</td>
|
|
38
|
-
<td><span class="status-icon status-${command.status}"></span>${command.status}</td>
|
|
39
|
-
<td>${formatTime(command.start_time)}</td>
|
|
40
|
-
<td>${command.status === 'running' ? formatDuration(command.start_time, new Date().toISOString()) : formatDuration(command.start_time, command.end_time)}</td>
|
|
41
|
-
<td>${command.exit_code}</td>
|
|
42
|
-
<td>${command.command.replace(/^\.\//, '')}</td>
|
|
43
|
-
<td>
|
|
44
|
-
${command.status === 'running' ? `<button onclick="stopCommand('${command.command_id}')">Stop</button>` : ` <button onclick="relaunchCommand('${command.command_id}')">Run</button>
|
|
45
|
-
`}
|
|
46
|
-
</td>
|
|
47
|
-
<td class="monospace outcol">${command.last_output_line || ''}</td>
|
|
48
|
-
`;
|
|
49
|
-
commandsTbody.appendChild(commandRow);
|
|
50
|
-
});
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
async function fetchExecutables() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch('/executables');
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error('Failed to fetch command status');
|
|
74
|
+
}
|
|
75
|
+
const executables = await response.json();
|
|
76
|
+
const commandNameSelect = document.getElementById('commandName');
|
|
77
|
+
commandNameSelect.innerHTML = '';
|
|
78
|
+
executables.forEach(executable => {
|
|
79
|
+
const option = document.createElement('option');
|
|
80
|
+
option.value = executable;
|
|
81
|
+
option.textContent = executable;
|
|
82
|
+
commandNameSelect.appendChild(option);
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.log('Error fetching executables:', error);
|
|
86
|
+
alert("Failed to fetch executables");
|
|
87
|
+
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
async function fetchOutput(command_id) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (data.status != 'running') {
|
|
91
|
+
try {
|
|
92
|
+
const outputDiv = document.getElementById('output');
|
|
93
|
+
const response = await fetch(`/command_output/${command_id}`);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
if (data.error) {
|
|
99
|
+
outputDiv.innerHTML = data.error;
|
|
77
100
|
clearInterval(outputInterval);
|
|
101
|
+
} else {
|
|
102
|
+
outputDiv.innerHTML = data.output;
|
|
103
|
+
outputDiv.scrollTop = outputDiv.scrollHeight;
|
|
104
|
+
if (data.status != 'running') {
|
|
105
|
+
clearInterval(outputInterval);
|
|
106
|
+
}
|
|
78
107
|
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.log('Error fetching output:', error);
|
|
79
110
|
}
|
|
80
111
|
}
|
|
81
112
|
|
|
@@ -83,49 +114,75 @@ async function viewOutput(command_id) {
|
|
|
83
114
|
adjustOutputHeight();
|
|
84
115
|
currentCommandId = command_id;
|
|
85
116
|
clearInterval(outputInterval);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(`/command_status/${command_id}`);
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
if (data.status === 'running') {
|
|
124
|
+
fetchOutput(command_id);
|
|
125
|
+
outputInterval = setInterval(() => fetchOutput(command_id), 1000);
|
|
126
|
+
} else {
|
|
127
|
+
fetchOutput(command_id);
|
|
128
|
+
}
|
|
129
|
+
fetchCommands(); // Refresh the command list to highlight the current command
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.log('Error viewing output:', error);
|
|
93
132
|
}
|
|
94
|
-
fetchCommands(); // Refresh the command list to highlight the current command
|
|
95
133
|
}
|
|
96
134
|
|
|
97
135
|
async function relaunchCommand(command_id) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
136
|
+
try {
|
|
137
|
+
const response = await fetch(`/command_status/${command_id}`);
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
throw new Error('Failed to fetch command status');
|
|
140
|
+
}
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
if (data.error) {
|
|
143
|
+
alert(data.error);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const relaunchResponse = await fetch('/run_command', {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json'
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
command: data.command,
|
|
153
|
+
params: data.params
|
|
154
|
+
})
|
|
155
|
+
});
|
|
156
|
+
if (!relaunchResponse.ok) {
|
|
157
|
+
throw new Error('Failed to relaunch command');
|
|
158
|
+
}
|
|
159
|
+
const relaunchData = await relaunchResponse.json();
|
|
160
|
+
fetchCommands();
|
|
161
|
+
viewOutput(relaunchData.command_id);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.log('Error relaunching command:', error);
|
|
164
|
+
alert('Failed to relaunch command. Please try again.');
|
|
103
165
|
}
|
|
104
|
-
const relaunchResponse = await fetch('/run_command', {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
headers: {
|
|
107
|
-
'Content-Type': 'application/json'
|
|
108
|
-
},
|
|
109
|
-
body: JSON.stringify({
|
|
110
|
-
command: data.command,
|
|
111
|
-
params: data.params
|
|
112
|
-
})
|
|
113
|
-
});
|
|
114
|
-
const relaunchData = await relaunchResponse.json();
|
|
115
|
-
fetchCommands();
|
|
116
|
-
viewOutput(relaunchData.command_id);
|
|
117
166
|
}
|
|
118
167
|
|
|
119
168
|
async function stopCommand(command_id) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(`/stop_command/${command_id}`, {
|
|
171
|
+
method: 'POST'
|
|
172
|
+
});
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
throw new Error('Failed to stop command');
|
|
175
|
+
}
|
|
176
|
+
const data = await response.json();
|
|
177
|
+
if (data.error) {
|
|
178
|
+
alert(data.error);
|
|
179
|
+
} else {
|
|
180
|
+
alert(data.message);
|
|
181
|
+
fetchCommands();
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.log('Error stopping command:', error);
|
|
185
|
+
alert('Failed to stop command. Please try again.');
|
|
129
186
|
}
|
|
130
187
|
}
|
|
131
188
|
|
|
@@ -146,12 +203,14 @@ function formatDuration(startTime, endTime) {
|
|
|
146
203
|
return `${hours}h ${minutes}m ${seconds}s`;
|
|
147
204
|
}
|
|
148
205
|
|
|
149
|
-
function copyToClipboard(text, element) {
|
|
206
|
+
function copyToClipboard(text, element, event) {
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
event.stopImmediatePropagation();
|
|
150
209
|
navigator.clipboard.writeText(text).then(() => {
|
|
151
210
|
element.classList.add('copy_clip_ok');
|
|
152
211
|
setTimeout(() => {
|
|
153
212
|
element.classList.remove('copy_clip_ok');
|
|
154
|
-
},
|
|
213
|
+
}, 1000);
|
|
155
214
|
});
|
|
156
215
|
}
|
|
157
216
|
|
pywebexec/templates/index.html
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
<link rel="stylesheet" href="/static/css/style.css">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
|
-
<
|
|
10
|
+
<div id="dimmer" class="dimmer">
|
|
11
|
+
<div class="dimmer-text">Server not reachable</div>
|
|
12
|
+
</div>
|
|
13
|
+
<h2><span class="status-icon title-icon"></span>{{ title }}</h2>
|
|
11
14
|
<form id="launchForm">
|
|
12
15
|
<label for="commandName">Command</label>
|
|
13
16
|
<select id="commandName" name="commandName"></select>
|
pywebexec/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pywebexec
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.13
|
|
4
4
|
Summary: Simple Python HTTP Exec Server
|
|
5
5
|
Home-page: https://github.com/joknarf/pywebexec
|
|
6
6
|
Author: Franck Jouvanceau
|
|
@@ -30,7 +30,7 @@ License: MIT License
|
|
|
30
30
|
Project-URL: Homepage, https://github.com/joknarf/pywebexec
|
|
31
31
|
Project-URL: Documentation, https://github.com/joknarf/pywebexec/blob/main/README.md
|
|
32
32
|
Project-URL: Repository, https://github.com/joknarf/pywebexec.git
|
|
33
|
-
Keywords: http,
|
|
33
|
+
Keywords: http,server,remote commands,api,website
|
|
34
34
|
Classifier: Development Status :: 5 - Production/Stable
|
|
35
35
|
Classifier: Intended Audience :: System Administrators
|
|
36
36
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -63,7 +63,7 @@ Requires-Dist: ldap3>=2.9.1
|
|
|
63
63
|
[](https://pypi.org/project/pywebexec/)
|
|
64
64
|

|
|
65
65
|
[](https://shields.io/)
|
|
66
|
-
[](https://pepy.tech/
|
|
66
|
+
[](https://pepy.tech/projects/pywebexec)
|
|
67
67
|
[](https://shields.io/)
|
|
68
68
|
|
|
69
69
|
# pywebexec
|
|
@@ -83,7 +83,7 @@ $ pywebexec
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
* Launch commands with params/view live output/Status using browser
|
|
86
|
-

|
|
87
87
|
|
|
88
88
|
all commands output / statuses are available in the executables directory in subdirectory `.web_status`
|
|
89
89
|
|
|
@@ -99,8 +99,8 @@ all commands output / statuses are available in the executables directory in sub
|
|
|
99
99
|
* Basic Auth
|
|
100
100
|
* LDAP(S)
|
|
101
101
|
* Can be started as a daemon (POSIX)
|
|
102
|
-
*
|
|
103
|
-
*
|
|
102
|
+
* Uses gunicorn to serve http/https
|
|
103
|
+
* Linux/MacOS compatible
|
|
104
104
|
|
|
105
105
|
## Customize server
|
|
106
106
|
```shell
|
|
@@ -122,7 +122,7 @@ Generated password is given if no `--pasword` option
|
|
|
122
122
|
$ export PYWEBEXEC_LDAP_SERVER=ldap://ldap.forumsys.com:389
|
|
123
123
|
$ export PYWEBEXEC_LDAP_BIND_DN="cn=read-only-admin,dc=example,dc=com"
|
|
124
124
|
$ export PYWEBEXEC_LDAP_BIND_PASSWORD="password"
|
|
125
|
-
$ export PYWEBEXEC_LDAP_GROUPS=ou=mathematicians,ou=scientists
|
|
125
|
+
$ export PYWEBEXEC_LDAP_GROUPS="ou=mathematicians,ou=scientists"
|
|
126
126
|
$ export PYWEBEXEC_LDAP_USER_ID="uid"
|
|
127
127
|
$ export PYWEBEXEC_LDAP_BASE_DN="dc=example,dc=com"
|
|
128
128
|
$ pywebexec
|
|
@@ -148,12 +148,14 @@ $ pywebexec start
|
|
|
148
148
|
$ pywebexec status
|
|
149
149
|
$ pywebexec stop
|
|
150
150
|
```
|
|
151
|
-
* log of server are stored in directory
|
|
151
|
+
* log of server are stored in directory `~/[.config/].pywebexec/pywebexec_<listen>:<port>.log`
|
|
152
152
|
|
|
153
153
|
## Launch command through API
|
|
154
154
|
|
|
155
155
|
```shell
|
|
156
|
-
$ curl http://myhost:8080/run_script -H 'Content-Type: application/json' -X POST -d '{ "script_name":"myscript", "
|
|
156
|
+
$ curl http://myhost:8080/run_script -H 'Content-Type: application/json' -X POST -d '{ "script_name":"myscript", "params":["param1", ...]}
|
|
157
|
+
$ curl http://myhost:8080/command_status/<command_id>
|
|
158
|
+
$ curl http://myhost:8080/command_output/<command_id> -H "Accept: text/plain"
|
|
157
159
|
```
|
|
158
160
|
|
|
159
161
|
## API reference
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
|
|
2
|
+
pywebexec/pywebexec.py,sha256=nXo0K6dBS9zKRUEP1hFyGWpL3BV2sUku3CFeGi0QQ-w,21479
|
|
3
|
+
pywebexec/version.py,sha256=TwfOeVDk7-YdURlWNHTinaP_27Y41bPwJIh1LndxWvc,413
|
|
4
|
+
pywebexec/static/css/style.css,sha256=BFiRkGhGrNoEmD2M3grfQXwsdI_bJWgB7GPHDoyT7R4,3374
|
|
5
|
+
pywebexec/static/images/aborted.svg,sha256=_mP43hU5QdRLFZIknBgjx-dIXrHgQG23-QV27ApXK2A,381
|
|
6
|
+
pywebexec/static/images/copy.svg,sha256=d9OwtGh5GzzZHzYcDrLfNxZYLth1Q64x7bRyYxu4Px0,622
|
|
7
|
+
pywebexec/static/images/copy_ok.svg,sha256=mEqUVUhSq8xaJK2msQkxRawnz_KwlCZ-tok8QS6hJ3g,451
|
|
8
|
+
pywebexec/static/images/failed.svg,sha256=ADZ7IKrUyOXtqpivnz3VcH0-Wru-I5MOi3OJAkI3hxk,1439
|
|
9
|
+
pywebexec/static/images/favicon.svg,sha256=ti80IfuDZwIvQcmJxkOeUaB1iMsiyOPmQmVO-h0y1IU,1126
|
|
10
|
+
pywebexec/static/images/running.gif,sha256=iYuzQGkMxrakSIwt6gPieKCImGZoSAHmU5MUNZa7cpw,25696
|
|
11
|
+
pywebexec/static/images/success.svg,sha256=PJDcCSTevJh7rkfSFLtc7P0pbeh8PVQBS8DaOLQemmc,489
|
|
12
|
+
pywebexec/static/js/script.js,sha256=glaAXtJkH2pZGak3AWRExZnaojO1vPoyvYxDQqZ2fQ0,9328
|
|
13
|
+
pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
pywebexec/templates/index.html,sha256=X0TsF1-fEuanpGPQo0GpCvvE87BWUEkYyrWA7IOu20Q,1454
|
|
15
|
+
pywebexec-1.1.13.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
|
16
|
+
pywebexec-1.1.13.dist-info/METADATA,sha256=2HTctXivbcyeIDet0bxmPb4JHB9nYssTHCX1uXZCEwM,6906
|
|
17
|
+
pywebexec-1.1.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
18
|
+
pywebexec-1.1.13.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
|
19
|
+
pywebexec-1.1.13.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
|
20
|
+
pywebexec-1.1.13.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M661.333333 170.666667l253.866667 34.133333-209.066667 209.066667zM362.666667 853.333333L108.8 819.2l209.066667-209.066667zM170.666667 362.666667L204.8 108.8l209.066667 209.066667z" fill="#4949d1"></path><path d="M198.4 452.266667l-89.6 17.066666c-2.133333 14.933333-2.133333 27.733333-2.133333 42.666667 0 98.133333 34.133333 192 98.133333 264.533333l64-55.466666C219.733333 663.466667 192 588.8 192 512c0-19.2 2.133333-40.533333 6.4-59.733333zM512 106.666667c-115.2 0-217.6 49.066667-292.266667 125.866666l59.733334 59.733334C339.2 230.4 420.266667 192 512 192c19.2 0 40.533333 2.133333 59.733333 6.4l14.933334-83.2C563.2 108.8 537.6 106.666667 512 106.666667zM825.6 571.733333l89.6-17.066666c2.133333-14.933333 2.133333-27.733333 2.133333-42.666667 0-93.866667-32-185.6-91.733333-258.133333l-66.133333 53.333333c46.933333 57.6 72.533333 130.133333 72.533333 202.666667 0 21.333333-2.133333 42.666667-6.4 61.866666zM744.533333 731.733333C684.8 793.6 603.733333 832 512 832c-19.2 0-40.533333-2.133333-59.733333-6.4l-14.933334 83.2c25.6 4.266667 51.2 6.4 74.666667 6.4 115.2 0 217.6-49.066667 292.266667-125.866667l-59.733334-57.6z" fill="#4949d1"></path><path d="M853.333333 661.333333l-34.133333 253.866667-209.066667-209.066667z" fill="#4949d1"></path></g></svg>
|
pywebexec-1.1.2.dist-info/RECORD
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
|
|
2
|
-
pywebexec/pywebexec.py,sha256=mRvTAK8O9VtGPqUdQxIeYBzpjLYkYVru1rzWmS3Ui5Q,21149
|
|
3
|
-
pywebexec/version.py,sha256=nmVMEP2kpLDIzjgS-U1taCdJbVrmv_k8ao6RojGcvRg,411
|
|
4
|
-
pywebexec/static/css/style.css,sha256=NiBoOzZ35eBM1ZP2HFNda-dzOsAv4xRPh3vsvVgLL9c,2513
|
|
5
|
-
pywebexec/static/images/aborted.svg,sha256=_mP43hU5QdRLFZIknBgjx-dIXrHgQG23-QV27ApXK2A,381
|
|
6
|
-
pywebexec/static/images/copy.svg,sha256=d9OwtGh5GzzZHzYcDrLfNxZYLth1Q64x7bRyYxu4Px0,622
|
|
7
|
-
pywebexec/static/images/copy_ok.svg,sha256=mEqUVUhSq8xaJK2msQkxRawnz_KwlCZ-tok8QS6hJ3g,451
|
|
8
|
-
pywebexec/static/images/failed.svg,sha256=ADZ7IKrUyOXtqpivnz3VcH0-Wru-I5MOi3OJAkI3hxk,1439
|
|
9
|
-
pywebexec/static/images/favicon.svg,sha256=ti80IfuDZwIvQcmJxkOeUaB1iMsiyOPmQmVO-h0y1IU,1126
|
|
10
|
-
pywebexec/static/images/running.svg,sha256=vBpiG6ClNUNCArkwsyqK7O-qhIKJX1NI7MSjclNSp_8,1537
|
|
11
|
-
pywebexec/static/images/success.svg,sha256=PJDcCSTevJh7rkfSFLtc7P0pbeh8PVQBS8DaOLQemmc,489
|
|
12
|
-
pywebexec/static/js/script.js,sha256=RrlozMlUx_7MJTf7IG98rzJsLgwEMyEV9bSW6QpQdZw,7088
|
|
13
|
-
pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
pywebexec/templates/index.html,sha256=Q3tubZjBzq4v1aV_BDFSvAKnoim-wKo3EkE_pkA2jm4,1302
|
|
15
|
-
pywebexec-1.1.2.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
|
16
|
-
pywebexec-1.1.2.dist-info/METADATA,sha256=7f5boEeNnoqAijinCKAqx-gPL_tGLOxdLe1Jdp9xYVI,6736
|
|
17
|
-
pywebexec-1.1.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
18
|
-
pywebexec-1.1.2.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
|
19
|
-
pywebexec-1.1.2.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
|
20
|
-
pywebexec-1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|