pywebexec 2.3.7__py3-none-any.whl → 2.3.9__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/cmdscript.py +150 -0
- pywebexec/cmdscript.py.ok +85 -0
- pywebexec/static/css/form.css +18 -2
- pywebexec/static/js/schemaform.js +46 -12
- pywebexec/version.py +2 -2
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/METADATA +1 -1
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/RECORD +11 -9
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/WHEEL +0 -0
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/entry_points.txt +0 -0
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/licenses/LICENSE +0 -0
- {pywebexec-2.3.7.dist-info → pywebexec-2.3.9.dist-info}/top_level.txt +0 -0
pywebexec/cmdscript.py
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import msvcrt
|
4
|
+
import shutil
|
5
|
+
import time
|
6
|
+
import signal
|
7
|
+
from datetime import datetime
|
8
|
+
from winpty import PTY, WinptyError
|
9
|
+
|
10
|
+
class CmdInteractive:
|
11
|
+
def __init__(self, logfile=None):
|
12
|
+
self.logfile = logfile
|
13
|
+
cols, rows = shutil.get_terminal_size()
|
14
|
+
self.pty = PTY(cols, rows)
|
15
|
+
# Set up Ctrl+C handling
|
16
|
+
signal.signal(signal.SIGINT, self._handle_sigint)
|
17
|
+
self.pid = self.pty.spawn('cmd.exe')
|
18
|
+
if not self.pid:
|
19
|
+
raise RuntimeError("Failed to spawn cmd.exe")
|
20
|
+
self.command_buffer = []
|
21
|
+
self.history = []
|
22
|
+
self.history_index = 0
|
23
|
+
self.cursor_pos = 0 # Track cursor position in command buffer
|
24
|
+
|
25
|
+
def _handle_sigint(self, signum, frame):
|
26
|
+
"""Handle Ctrl+C by forwarding it to the child process"""
|
27
|
+
try:
|
28
|
+
self.pty.write('\x03')
|
29
|
+
except:
|
30
|
+
pass
|
31
|
+
|
32
|
+
def log(self, text, direction='>>'):
|
33
|
+
if self.logfile:
|
34
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
35
|
+
with open(self.logfile, 'a', encoding='utf-8') as f:
|
36
|
+
f.write(f'{timestamp} {direction} {text}')
|
37
|
+
|
38
|
+
def handle_special_key(self, char):
|
39
|
+
"""Handle special key sequences"""
|
40
|
+
if char == b'\xe0': # Extended key
|
41
|
+
key = msvcrt.getch()
|
42
|
+
if key == b'H': # Up arrow
|
43
|
+
if self.history and self.history_index > 0:
|
44
|
+
self.history_index -= 1
|
45
|
+
cmd = self.history[self.history_index]
|
46
|
+
self._replace_line(cmd)
|
47
|
+
elif key == b'P': # Down arrow
|
48
|
+
if self.history_index < len(self.history) - 1:
|
49
|
+
self.history_index += 1
|
50
|
+
cmd = self.history[self.history_index]
|
51
|
+
self._replace_line(cmd)
|
52
|
+
elif key == b'K': # Left arrow
|
53
|
+
if self.cursor_pos > 0:
|
54
|
+
self.cursor_pos -= 1
|
55
|
+
self.pty.write('\x1b[D')
|
56
|
+
elif key == b'M': # Right arrow
|
57
|
+
if self.cursor_pos < len(self.command_buffer):
|
58
|
+
self.cursor_pos += 1
|
59
|
+
self.pty.write('\x1b[C')
|
60
|
+
return True
|
61
|
+
return False
|
62
|
+
|
63
|
+
def _replace_line(self, new_text):
|
64
|
+
"""Replace current line with new text"""
|
65
|
+
# Clear current line
|
66
|
+
self.pty.write('\r' + ' ' * len(self.command_buffer) + '\r')
|
67
|
+
# Write new line
|
68
|
+
self.command_buffer = list(new_text)
|
69
|
+
self.cursor_pos = len(self.command_buffer)
|
70
|
+
self.pty.write(''.join(self.command_buffer))
|
71
|
+
|
72
|
+
def interact(self):
|
73
|
+
try:
|
74
|
+
while True:
|
75
|
+
try:
|
76
|
+
try:
|
77
|
+
output = self.pty.read()
|
78
|
+
if output:
|
79
|
+
if isinstance(output, str):
|
80
|
+
output = output.encode('utf-8')
|
81
|
+
sys.stdout.buffer.write(output)
|
82
|
+
sys.stdout.buffer.flush()
|
83
|
+
self.log(output.decode('utf-8', errors='replace'), '>>')
|
84
|
+
except (EOFError, WinptyError):
|
85
|
+
break
|
86
|
+
|
87
|
+
if msvcrt.kbhit():
|
88
|
+
char = msvcrt.getch()
|
89
|
+
if self.handle_special_key(char):
|
90
|
+
continue
|
91
|
+
|
92
|
+
if char == b'\r': # Enter
|
93
|
+
self.pty.write('\r\n')
|
94
|
+
if self.command_buffer:
|
95
|
+
cmd = ''.join(self.command_buffer)
|
96
|
+
self.history.append(cmd)
|
97
|
+
self.history_index = len(self.history)
|
98
|
+
self.command_buffer = []
|
99
|
+
self.cursor_pos = 0
|
100
|
+
elif char == b'\x08': # Backspace
|
101
|
+
if self.cursor_pos > 0:
|
102
|
+
# Remove character at cursor position
|
103
|
+
self.command_buffer.pop(self.cursor_pos - 1)
|
104
|
+
self.cursor_pos -= 1
|
105
|
+
# Rewrite the line from cursor position
|
106
|
+
remain = ''.join(self.command_buffer[self.cursor_pos:])
|
107
|
+
self.pty.write('\x08' + remain + ' ')
|
108
|
+
# Move cursor back to position
|
109
|
+
if remain:
|
110
|
+
self.pty.write('\x1b[' + str(len(remain)) + 'D')
|
111
|
+
elif char == b'\x03': # Ctrl+C
|
112
|
+
self.pty.write('\x03')
|
113
|
+
self.command_buffer = []
|
114
|
+
self.cursor_pos = 0
|
115
|
+
continue
|
116
|
+
else:
|
117
|
+
# Insert character at cursor position
|
118
|
+
if isinstance(char, bytes):
|
119
|
+
char = char.decode('cp437', errors='replace')
|
120
|
+
self.command_buffer.insert(self.cursor_pos, char)
|
121
|
+
self.cursor_pos += 1
|
122
|
+
# Write new char and remaining text
|
123
|
+
remain = ''.join(self.command_buffer[self.cursor_pos-1:])
|
124
|
+
self.pty.write(remain)
|
125
|
+
# Move cursor back if needed
|
126
|
+
if self.cursor_pos < len(self.command_buffer):
|
127
|
+
self.pty.write('\x1b[' + str(len(remain)-1) + 'D')
|
128
|
+
self.log(char, '<<')
|
129
|
+
|
130
|
+
except (IOError, OSError) as e:
|
131
|
+
if "handle is closed" in str(e):
|
132
|
+
break
|
133
|
+
raise
|
134
|
+
|
135
|
+
except KeyboardInterrupt:
|
136
|
+
pass
|
137
|
+
finally:
|
138
|
+
self.close()
|
139
|
+
|
140
|
+
def close(self):
|
141
|
+
if hasattr(self, 'pty'):
|
142
|
+
del self.pty
|
143
|
+
|
144
|
+
if __name__ == '__main__':
|
145
|
+
log_file = 'cmd_session.log'
|
146
|
+
try:
|
147
|
+
cmd = CmdInteractive(log_file)
|
148
|
+
cmd.interact()
|
149
|
+
except ImportError:
|
150
|
+
print("Please install pywinpty: pip install pywinpty")
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import msvcrt
|
4
|
+
import shutil
|
5
|
+
import time
|
6
|
+
import signal
|
7
|
+
from datetime import datetime
|
8
|
+
from winpty import PTY, WinptyError
|
9
|
+
|
10
|
+
class CmdInteractive:
|
11
|
+
def __init__(self, logfile=None):
|
12
|
+
self.logfile = logfile
|
13
|
+
cols, rows = shutil.get_terminal_size()
|
14
|
+
self.pty = PTY(cols, rows)
|
15
|
+
# Set up Ctrl+C handling
|
16
|
+
signal.signal(signal.SIGINT, self._handle_sigint)
|
17
|
+
self.pid = self.pty.spawn('cmd.exe')
|
18
|
+
if not self.pid:
|
19
|
+
raise RuntimeError("Failed to spawn cmd.exe")
|
20
|
+
|
21
|
+
def _handle_sigint(self, signum, frame):
|
22
|
+
"""Handle Ctrl+C by forwarding it to the child process"""
|
23
|
+
try:
|
24
|
+
self.pty.write('\x03')
|
25
|
+
except:
|
26
|
+
pass
|
27
|
+
|
28
|
+
def log(self, text, direction='>>'):
|
29
|
+
if self.logfile:
|
30
|
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
31
|
+
with open(self.logfile, 'a', encoding='utf-8') as f:
|
32
|
+
f.write(f'{timestamp} {direction} {text}')
|
33
|
+
|
34
|
+
def interact(self):
|
35
|
+
try:
|
36
|
+
while True:
|
37
|
+
try:
|
38
|
+
try:
|
39
|
+
output = self.pty.read()
|
40
|
+
if output:
|
41
|
+
if isinstance(output, str):
|
42
|
+
output = output.encode('utf-8')
|
43
|
+
sys.stdout.buffer.write(output)
|
44
|
+
sys.stdout.buffer.flush()
|
45
|
+
self.log(output.decode('utf-8', errors='replace'), '>>')
|
46
|
+
except (EOFError, WinptyError):
|
47
|
+
break
|
48
|
+
|
49
|
+
if msvcrt.kbhit():
|
50
|
+
char = msvcrt.getch()
|
51
|
+
if char == b'\x03': # Ctrl+C
|
52
|
+
# Forward Ctrl+C to child process
|
53
|
+
self.pty.write('\x03')
|
54
|
+
continue
|
55
|
+
if char == b'\x1d': # Ctrl+]
|
56
|
+
break
|
57
|
+
if isinstance(char, bytes):
|
58
|
+
char = char.decode('cp437', errors='replace')
|
59
|
+
try:
|
60
|
+
self.pty.write(char)
|
61
|
+
self.log(char, '<<')
|
62
|
+
except (EOFError, WinptyError):
|
63
|
+
break
|
64
|
+
|
65
|
+
except (IOError, OSError) as e:
|
66
|
+
if "handle is closed" in str(e):
|
67
|
+
break
|
68
|
+
raise
|
69
|
+
|
70
|
+
except KeyboardInterrupt:
|
71
|
+
pass
|
72
|
+
finally:
|
73
|
+
self.close()
|
74
|
+
|
75
|
+
def close(self):
|
76
|
+
if hasattr(self, 'pty'):
|
77
|
+
del self.pty
|
78
|
+
|
79
|
+
if __name__ == '__main__':
|
80
|
+
log_file = 'cmd_session.log'
|
81
|
+
try:
|
82
|
+
cmd = CmdInteractive(log_file)
|
83
|
+
cmd.interact()
|
84
|
+
except ImportError:
|
85
|
+
print("Please install pywinpty: pip install pywinpty")
|
pywebexec/static/css/form.css
CHANGED
@@ -241,7 +241,7 @@
|
|
241
241
|
.controls > .checkbox input {
|
242
242
|
top: 5px;
|
243
243
|
}
|
244
|
-
.checkbox {
|
244
|
+
.checkbox, .radio {
|
245
245
|
label {
|
246
246
|
font-weight: normal;
|
247
247
|
}
|
@@ -277,11 +277,22 @@
|
|
277
277
|
color: #333;
|
278
278
|
padding: 3px 13px;
|
279
279
|
}
|
280
|
+
legend {
|
281
|
+
font-weight: bold;
|
282
|
+
}
|
283
|
+
.expandable > legend::before {
|
284
|
+
content: '\25B8';
|
285
|
+
padding-right: 5px;
|
286
|
+
}
|
287
|
+
.expanded > legend::before {
|
288
|
+
content: '\25BE';
|
289
|
+
}
|
290
|
+
|
280
291
|
fieldset {
|
281
292
|
border: 1px solid #ccc;
|
282
293
|
border-radius: 5px;
|
283
294
|
padding-top: 9px;
|
284
|
-
width: 100%;
|
295
|
+
/* width: 100%; */
|
285
296
|
label {
|
286
297
|
padding-right: 5px;
|
287
298
|
}
|
@@ -290,6 +301,11 @@
|
|
290
301
|
/* display: block ruby; */
|
291
302
|
padding-bottom: 5px;
|
292
303
|
}
|
304
|
+
fieldset.expandable > div {
|
305
|
+
display: inline-flex;
|
306
|
+
gap: 0px 5px;
|
307
|
+
padding-bottom: 5px;
|
308
|
+
}
|
293
309
|
}
|
294
310
|
|
295
311
|
.swagger-ui textarea {
|
@@ -8,7 +8,12 @@ function adjustInputWidth(input) {
|
|
8
8
|
} else {
|
9
9
|
delta = 3;
|
10
10
|
}
|
11
|
-
|
11
|
+
if (input.scrollWidth > 0) {
|
12
|
+
input.style.width = `${input.scrollWidth + delta}px`;
|
13
|
+
} else {
|
14
|
+
input.style.width = `${input.value.length * 11 + delta}px`;
|
15
|
+
}
|
16
|
+
|
12
17
|
}
|
13
18
|
|
14
19
|
function formInputHandle() {
|
@@ -30,16 +35,19 @@ function formInputHandle() {
|
|
30
35
|
});
|
31
36
|
}
|
32
37
|
|
33
|
-
function extractKeysAndPlaceholders(obj, formoptions, prefix = '') {
|
38
|
+
function extractKeysAndPlaceholders(obj, formkeys, formoptions, prefix = '') {
|
34
39
|
let result = [];
|
35
40
|
|
36
41
|
for (let key in obj.properties) {
|
37
42
|
k = prefix ? `${prefix}.${key}` : key;
|
43
|
+
if (formoptions[k]) {
|
44
|
+
continue;
|
45
|
+
}
|
38
46
|
if (obj.properties[key].type === 'object' && obj.properties[key].properties) {
|
39
|
-
result = result.concat(extractKeysAndPlaceholders(obj.properties[key], formoptions, k));
|
47
|
+
result = result.concat(extractKeysAndPlaceholders(obj.properties[key], formkeys, formoptions, k));
|
40
48
|
} else {
|
41
|
-
if (
|
42
|
-
foptions =
|
49
|
+
if (formkeys[k]) {
|
50
|
+
foptions = formkeys[k];
|
43
51
|
} else {
|
44
52
|
foptions = {};
|
45
53
|
}
|
@@ -147,15 +155,25 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
|
|
147
155
|
schema_params_options = {};
|
148
156
|
}
|
149
157
|
|
158
|
+
formkeys = {};
|
150
159
|
formoptions = {};
|
151
|
-
if (schema_options
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
160
|
+
if (schema_options) {
|
161
|
+
formkeys = schema.schema_options.form || {};
|
162
|
+
formoptions = schema.schema_options.formoptions || {};
|
163
|
+
} else {
|
164
|
+
if (schema_params_options) {
|
165
|
+
let fkeys = schema_params_options.form || {};
|
166
|
+
let foptions = schema_params_options.formoptions || {};
|
167
|
+
for (let key in fkeys) {
|
168
|
+
formkeys[`params.${key}`] = fkeys[key];
|
169
|
+
}
|
170
|
+
for (let key in foptions) {
|
171
|
+
formoptions[`params.${key}`] = foptions[key];
|
172
|
+
}
|
156
173
|
}
|
157
174
|
}
|
158
|
-
|
175
|
+
|
176
|
+
formDesc = extractKeysAndPlaceholders(schema, formkeys, formoptions);
|
159
177
|
if (schemaValues[schemaName]) {
|
160
178
|
value = schemaValues[schemaName];
|
161
179
|
// convert array for textarea formDesc type to string separated by newlines
|
@@ -233,6 +251,22 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
|
|
233
251
|
}
|
234
252
|
}
|
235
253
|
}
|
254
|
+
if (formoptions) {
|
255
|
+
items = [];
|
256
|
+
for (let key in formoptions) {
|
257
|
+
items.push({
|
258
|
+
key: key,
|
259
|
+
... formoptions[key],
|
260
|
+
});
|
261
|
+
}
|
262
|
+
formDesc.push({
|
263
|
+
type: 'fieldset',
|
264
|
+
title: 'Options',
|
265
|
+
fieldHtmlClass: 'fieldsetoptions',
|
266
|
+
expandable: true,
|
267
|
+
items: items,
|
268
|
+
});
|
269
|
+
}
|
236
270
|
formDesc.push({
|
237
271
|
type: 'actions',
|
238
272
|
items: [
|
@@ -246,7 +280,6 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
|
|
246
280
|
title: 'Reset',
|
247
281
|
id: 'reset-form',
|
248
282
|
onClick: function (evt) {
|
249
|
-
console.log('reset');
|
250
283
|
evt.preventDefault();
|
251
284
|
evt.stopPropagation();
|
252
285
|
evt.stopImmediatePropagation();
|
@@ -312,6 +345,7 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
|
|
312
345
|
adjustInputWidth(e.target);
|
313
346
|
}
|
314
347
|
});
|
348
|
+
divopt = schemaForm.querySelector("fieldset.expandable > div");
|
315
349
|
formInputHandle();
|
316
350
|
return jsform;
|
317
351
|
}
|
pywebexec/version.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
|
2
|
+
pywebexec/cmdscript.py,sha256=TCYfWTPYn5LEWiMPm0zc9iyHm2AV4uH5E-gOL3hGoDc,6182
|
3
|
+
pywebexec/cmdscript.py.ok,sha256=nWB11LXjQahCUwJ8jK0XuU1ekoMyN9Zu_FaOdVTpOwk,2859
|
2
4
|
pywebexec/host_ip.py,sha256=oiCMlo2o3AkkgXDarUSx8T3FWXKI0vk1-EPnx5FGBd8,1332
|
3
5
|
pywebexec/pywebexec.py,sha256=1M5CtxKr5YkEMBhuVSrwnMNcVRuf7iQf-uxF4Lf0ouQ,48375
|
4
6
|
pywebexec/swagger.yaml,sha256=I_oLpp7Hqel8SDEEykvpmCT-Gv3ytGlziq9bvQOrtZY,7598
|
5
|
-
pywebexec/version.py,sha256=
|
6
|
-
pywebexec/static/css/form.css,sha256=
|
7
|
+
pywebexec/version.py,sha256=xD7sq7tXCC-QKVs2SPi2bsTgQhH6dVLkP6dvZaxMKcA,511
|
8
|
+
pywebexec/static/css/form.css,sha256=jScx_gUwW0RyrYZJsn6z6vBsBAAgV-zva63folDPNtA,7160
|
7
9
|
pywebexec/static/css/markdown.css,sha256=br4-iK9wigTs54N2KHtjgZ4KLH0THVSvJo-XZAdMHiE,1970
|
8
10
|
pywebexec/static/css/style.css,sha256=pUmylXwbFIoXrdaJRVOUohlKIhOIilapH97NyIlgGV4,10343
|
9
11
|
pywebexec/static/css/swagger-ui.css,sha256=xhXN8fnUaIACGHuPIEIr9-qmyYr6Zx0k2wv4Qy7Bg1Y,154985
|
@@ -35,7 +37,7 @@ pywebexec/static/images/success.svg,sha256=NVwezvVMplt46ElW798vqGfrL21Mw_DWHUp_q
|
|
35
37
|
pywebexec/static/images/swagger-ui.svg,sha256=FR0yeOVwe4zCYKZAjCGcT_m0Mf25NexIVaSXifIkoU0,2117
|
36
38
|
pywebexec/static/js/executables.js,sha256=cTgCFHr_F9bFCirtfG_uR32vOY3vNUr4Ih3Wglj5lFc,11988
|
37
39
|
pywebexec/static/js/popup.js,sha256=IaKmk2U2hEn-Nv6krf_PPW6LaG8NcpCkJKb7lUX0qZo,11457
|
38
|
-
pywebexec/static/js/schemaform.js,sha256=
|
40
|
+
pywebexec/static/js/schemaform.js,sha256=oo4G8XqtL-iDCuq_HULgmYx5YOwY-c9166Wi7C1iAWQ,11919
|
39
41
|
pywebexec/static/js/script.js,sha256=TI3TSylgBxh_a6QvYWlg4CyJ6LMPxnhFl8WRtRDGD0Y,20810
|
40
42
|
pywebexec/static/js/swagger-form.js,sha256=CLcSHMhk5P4-_2MIRBoJLgEnIj_9keDDSzUugXHZjio,4565
|
41
43
|
pywebexec/static/js/js-yaml/LICENSE,sha256=oHvCRGi5ZUznalR9R6LbKC0HcztxXbTHOpi9Y5YflVA,1084
|
@@ -67,9 +69,9 @@ pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
67
69
|
pywebexec/templates/index.html,sha256=w18O2plH_yS8bqlPsu5hwFFmCj9H2hWLSV8B6ADcSwU,3900
|
68
70
|
pywebexec/templates/popup.html,sha256=3kpMccKD_OLLhJ4Y9KRw6Ny8wQWjVaRrUfV9y5-bDiQ,1580
|
69
71
|
pywebexec/templates/swagger_ui.html,sha256=MAPr-z96VERAecDvX37V8q2Nxph-O0fNDBul1x2w9SI,1147
|
70
|
-
pywebexec-2.3.
|
71
|
-
pywebexec-2.3.
|
72
|
-
pywebexec-2.3.
|
73
|
-
pywebexec-2.3.
|
74
|
-
pywebexec-2.3.
|
75
|
-
pywebexec-2.3.
|
72
|
+
pywebexec-2.3.9.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
|
73
|
+
pywebexec-2.3.9.dist-info/METADATA,sha256=mapKSx2-T9t0PuRG5sCWF0yaGGLzg081Q8FD75-5hDQ,13015
|
74
|
+
pywebexec-2.3.9.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
75
|
+
pywebexec-2.3.9.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
|
76
|
+
pywebexec-2.3.9.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
|
77
|
+
pywebexec-2.3.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|