pywebexec 2.3.8__py3-none-any.whl → 2.3.10__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 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")
@@ -44,7 +44,7 @@
44
44
  }
45
45
  .btn-primary {
46
46
  position: relative;
47
- margin-bottom: 10px;
47
+ margin-bottom: 20px;
48
48
  height: 24px;
49
49
  top: 20px;
50
50
  color: #fff;
@@ -224,13 +224,17 @@
224
224
  }
225
225
  ul ~ ._jsonform-array-buttons {
226
226
  display: block;
227
+ }
228
+ li > fieldset {
227
229
  width: 100%;
230
+ margin-bottom: 3px;
228
231
  }
229
232
  ._jsonform-array-buttons {
230
- /* display: block; */
231
- /* width: 100%; */
232
233
  text-align: right;
233
234
  }
235
+ ul._jsonform-array-ul > li > div.form-group {
236
+ width: 100%;
237
+ }
234
238
  i[title="Delete current"] {
235
239
  top: 5px;
236
240
  }
@@ -253,7 +257,7 @@
253
257
  }
254
258
  .alert {
255
259
  padding: 5px 10px;
256
- margin-bottom: 2px;
260
+ margin-top: 10px;
257
261
  border: 1px solid transparent;
258
262
  border-radius: 15px;
259
263
  font-size: 14px;
@@ -277,11 +281,22 @@
277
281
  color: #333;
278
282
  padding: 3px 13px;
279
283
  }
284
+ legend {
285
+ font-weight: bold;
286
+ }
287
+ .expandable > legend::before {
288
+ content: '\25B8';
289
+ padding-right: 5px;
290
+ }
291
+ .expanded > legend::before {
292
+ content: '\25BE';
293
+ }
294
+
280
295
  fieldset {
281
296
  border: 1px solid #ccc;
282
297
  border-radius: 5px;
283
298
  padding-top: 9px;
284
- width: 100%;
299
+ /* width: 100%; */
285
300
  label {
286
301
  padding-right: 5px;
287
302
  }
@@ -290,6 +305,11 @@
290
305
  /* display: block ruby; */
291
306
  padding-bottom: 5px;
292
307
  }
308
+ fieldset.expandable > div {
309
+ display: inline-flex;
310
+ gap: 0px 5px;
311
+ padding-bottom: 5px;
312
+ }
293
313
  }
294
314
 
295
315
  .swagger-ui textarea {
@@ -8,7 +8,12 @@ function adjustInputWidth(input) {
8
8
  } else {
9
9
  delta = 3;
10
10
  }
11
- input.style.width = `${input.scrollWidth + delta}px`;
11
+ if (input.scrollWidth > 0) {
12
+ input.style.width = `${input.scrollWidth + delta}px`;
13
+ } else {
14
+ input.style.width = `${input.value.length * 10 + 20}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 (formoptions[k]) {
42
- foptions = formoptions[k];
49
+ if (formkeys[k]) {
50
+ foptions = formkeys[k];
43
51
  } else {
44
52
  foptions = {};
45
53
  }
@@ -136,26 +144,31 @@ function validateSchemaForm(form, formDesc, schema, values, schemaName) {
136
144
  }
137
145
 
138
146
  function createSchemaForm($form, schema, onSubmit, schemaName) {
147
+ schema_options = undefined;
148
+ schema_params_options = undefined;
139
149
  if (schema && schema.schema_options) {
140
150
  schema_options = schema.schema_options;
141
- } else {
142
- schema_options = {};
143
151
  }
144
152
  if (schema && schema.properties && schema.properties.params && schema.properties.params.schema_options) {
145
153
  schema_params_options = schema.properties.params.schema_options;
146
- } else {
147
- schema_params_options = {};
148
154
  }
149
155
 
156
+ formkeys = {};
150
157
  formoptions = {};
151
- if (schema_options && schema_options.form) {
152
- formoptions = schema.schema_options.form;
153
- } else if (schema_params_options && schema_params_options.form) {
154
- for (let key in schema_params_options.form) {
155
- formoptions[`params.${key}`] = schema_params_options.form[key];
158
+ if (schema_options) {
159
+ formkeys = schema_options.form || {}
160
+ formoptions = schema_options.formoptions || {};
161
+ } else if (schema_params_options) {
162
+ let fkeys = schema_params_options.form || {};
163
+ let foptions = schema_params_options.formoptions || {};
164
+ for (let key in fkeys) {
165
+ formkeys[`params.${key}`] = fkeys[key];
166
+ }
167
+ for (let key in foptions) {
168
+ formoptions[`params.${key}`] = foptions[key];
156
169
  }
157
170
  }
158
- formDesc = extractKeysAndPlaceholders(schema, formoptions);
171
+ formDesc = extractKeysAndPlaceholders(schema, formkeys, formoptions);
159
172
  if (schemaValues[schemaName]) {
160
173
  value = schemaValues[schemaName];
161
174
  // convert array for textarea formDesc type to string separated by newlines
@@ -233,6 +246,22 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
233
246
  }
234
247
  }
235
248
  }
249
+ if (Object.keys(formoptions).length) {
250
+ items = [];
251
+ for (let key in formoptions) {
252
+ items.push({
253
+ key: key,
254
+ ... formoptions[key],
255
+ });
256
+ }
257
+ formDesc.push({
258
+ type: 'fieldset',
259
+ title: 'Options',
260
+ fieldHtmlClass: 'fieldsetoptions',
261
+ expandable: true,
262
+ items: items,
263
+ });
264
+ }
236
265
  formDesc.push({
237
266
  type: 'actions',
238
267
  items: [
@@ -246,7 +275,6 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
246
275
  title: 'Reset',
247
276
  id: 'reset-form',
248
277
  onClick: function (evt) {
249
- console.log('reset');
250
278
  evt.preventDefault();
251
279
  evt.stopPropagation();
252
280
  evt.stopImmediatePropagation();
@@ -274,6 +302,22 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
274
302
  }
275
303
  }
276
304
  }
305
+ if (formoptions) {
306
+ items = [];
307
+ for (let key in formoptions) {
308
+ items.push({
309
+ key: key,
310
+ ... formoptions[key],
311
+ });
312
+ }
313
+ formDesc.push({
314
+ type: 'fieldset',
315
+ title: 'Options',
316
+ fieldHtmlClass: 'fieldsetoptions',
317
+ expandable: true,
318
+ items: items,
319
+ });
320
+ }
277
321
  }
278
322
  // schemaForm.classList.add('form-inline');
279
323
  jsform = $form.jsonForm({
@@ -312,6 +356,7 @@ function createSchemaForm($form, schema, onSubmit, schemaName) {
312
356
  adjustInputWidth(e.target);
313
357
  }
314
358
  });
359
+ divopt = schemaForm.querySelector("fieldset.expandable > div");
315
360
  formInputHandle();
316
361
  return jsform;
317
362
  }
pywebexec/version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.3.8'
21
- __version_tuple__ = version_tuple = (2, 3, 8)
20
+ __version__ = version = '2.3.10'
21
+ __version_tuple__ = version_tuple = (2, 3, 10)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebexec
3
- Version: 2.3.8
3
+ Version: 2.3.10
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -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=3Oc3thyKkSSxKGqu1ruTNsjrUTMaCkmwZoi_w-3OL9M,511
6
- pywebexec/static/css/form.css,sha256=qzBSq95h3s4qPZEbUD8l2bMBfdWgSWTpk4TEJKZeFxg,6872
7
+ pywebexec/version.py,sha256=DnoODDuhoEPNHCrRl_hTa5YLRN14el-BkmsHbGdVxYQ,513
8
+ pywebexec/static/css/form.css,sha256=Wv73mrLgvuo22ugmb6Uh3Bo8nUfzZ3cEV3UtMYvZaZI,7224
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=57FeWNWJT1zW9sG3ujZEOG7vLoiHif5ROQZori2QNMg,11131
40
+ pywebexec/static/js/schemaform.js,sha256=OdHhwhSRi-ps_LiQX14vAeyNlnKgzKw3fXUl9nzoT6c,12231
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.8.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
71
- pywebexec-2.3.8.dist-info/METADATA,sha256=3Gotnq8hyz6QPF1A6XwAdQX1lRCtTRjXVayFLglXZIo,13015
72
- pywebexec-2.3.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
73
- pywebexec-2.3.8.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
74
- pywebexec-2.3.8.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
75
- pywebexec-2.3.8.dist-info/RECORD,,
72
+ pywebexec-2.3.10.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
73
+ pywebexec-2.3.10.dist-info/METADATA,sha256=JaOMNA_myIeVZiI6s8T4sefU7_wDHDVX3-24mzHYeRc,13016
74
+ pywebexec-2.3.10.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
75
+ pywebexec-2.3.10.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
76
+ pywebexec-2.3.10.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
77
+ pywebexec-2.3.10.dist-info/RECORD,,