pywebexec 2.1.19__tar.gz → 2.2.1__tar.gz

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.
Files changed (84) hide show
  1. {pywebexec-2.1.19/pywebexec.egg-info → pywebexec-2.2.1}/PKG-INFO +1 -1
  2. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/pywebexec.py +23 -22
  3. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/markdown.css +1 -1
  4. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/style.css +1 -0
  5. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/executables.js +30 -36
  6. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/popup.js +24 -8
  7. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/schemaform.js +86 -9
  8. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/script.js +22 -6
  9. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/swagger-form.js +40 -20
  10. pywebexec-2.2.1/pywebexec/swagger.yaml +278 -0
  11. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/version.py +2 -2
  12. {pywebexec-2.1.19 → pywebexec-2.2.1/pywebexec.egg-info}/PKG-INFO +1 -1
  13. pywebexec-2.1.19/pywebexec/swagger.yaml +0 -258
  14. {pywebexec-2.1.19 → pywebexec-2.2.1}/.github/workflows/python-publish.yml +0 -0
  15. {pywebexec-2.1.19 → pywebexec-2.2.1}/.gitignore +0 -0
  16. {pywebexec-2.1.19 → pywebexec-2.2.1}/LICENSE +0 -0
  17. {pywebexec-2.1.19 → pywebexec-2.2.1}/README.md +0 -0
  18. {pywebexec-2.1.19 → pywebexec-2.2.1}/pyproject.toml +0 -0
  19. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/__init__.py +0 -0
  20. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/host_ip.py +0 -0
  21. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/form.css +0 -0
  22. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/swagger-ui.css +0 -0
  23. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/swagger-ui.css.map +0 -0
  24. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/css/xterm.css +0 -0
  25. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/CommitMonoNerdFontMono-Regular.ttf +0 -0
  26. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/LICENSE +0 -0
  27. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/glyphicons-halflings-regular.eot +0 -0
  28. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/glyphicons-halflings-regular.svg +0 -0
  29. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/glyphicons-halflings-regular.ttf +0 -0
  30. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/glyphicons-halflings-regular.woff +0 -0
  31. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/fonts/glyphicons-halflings-regular.woff2 +0 -0
  32. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/aborted.svg +0 -0
  33. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/copy.svg +0 -0
  34. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/copy_ok.svg +0 -0
  35. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/down-arrow.svg +0 -0
  36. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/failed.svg +0 -0
  37. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/favicon.svg +0 -0
  38. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/fit-tty.svg +0 -0
  39. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/fit-win.svg +0 -0
  40. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/font-decrease.svg +0 -0
  41. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/font-increase.svg +0 -0
  42. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/norun.svg +0 -0
  43. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/pause.svg +0 -0
  44. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/popup.svg +0 -0
  45. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/resume.svg +0 -0
  46. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/running.svg +0 -0
  47. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/success.svg +0 -0
  48. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/images/swagger-ui.svg +0 -0
  49. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/js-yaml/LICENSE +0 -0
  50. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/js-yaml/js-yaml.min.js +0 -0
  51. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/marked/LICENSE.md +0 -0
  52. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/marked/marked.min.js +0 -0
  53. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/swagger-ui/LICENSE +0 -0
  54. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/swagger-ui/swagger-ui-bundle.js +0 -0
  55. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/swagger-ui/swagger-ui-standalone-preset.js +0 -0
  56. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/LICENSE +0 -0
  57. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-canvas.js +0 -0
  58. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-canvas.js.map +0 -0
  59. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-fit.js +0 -0
  60. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-fit.js.map +0 -0
  61. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-unicode-graphemes.js +0 -0
  62. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-unicode-graphemes.js.map +0 -0
  63. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-unicode11.js +0 -0
  64. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/addon-unicode11.js.map +0 -0
  65. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/xterm.js +0 -0
  66. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/js/xterm/xterm.js.map +0 -0
  67. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/LICENSE +0 -0
  68. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/deps/README.md +0 -0
  69. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/deps/img/glyphicons-halflings.png +0 -0
  70. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/deps/jquery.min.js +0 -0
  71. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/deps/jsv.js +0 -0
  72. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/deps/underscore.js +0 -0
  73. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/static/jsonform/lib/jsonform.js +0 -0
  74. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/templates/__init__.py +0 -0
  75. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/templates/index.html +0 -0
  76. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/templates/popup.html +0 -0
  77. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec/templates/swagger_ui.html +0 -0
  78. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec.egg-info/SOURCES.txt +0 -0
  79. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec.egg-info/dependency_links.txt +0 -0
  80. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec.egg-info/entry_points.txt +0 -0
  81. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec.egg-info/requires.txt +0 -0
  82. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexec.egg-info/top_level.txt +0 -0
  83. {pywebexec-2.1.19 → pywebexec-2.2.1}/pywebexecdev +0 -0
  84. {pywebexec-2.1.19 → pywebexec-2.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 2.1.19
3
+ Version: 2.2.1
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1038,15 +1038,12 @@ def swagger_yaml():
1038
1038
  swagger_spec = yaml.safe_load(swagger_spec_str)
1039
1039
  # Update existing POST /commands enum if present
1040
1040
  executables = get_executables()
1041
- post_cmd = swagger_spec.get('paths', {}).get('/commands', {}).get('post')
1042
- if post_cmd:
1043
- params_list = post_cmd.get('parameters', [])
1044
- for param in params_list:
1045
- if param.get('in') == 'body' and 'schema' in param:
1046
- props = param['schema'].get('properties', {})
1047
- if 'command' in props:
1048
- props['command']['enum'] = [e['command'] for e in executables]
1049
- # Add dynamic paths for each
1041
+ post_cmd = swagger_spec.get('paths', {}).get('/commands', {}).get('post', {})
1042
+ if post_cmd and 'requestBody' in post_cmd:
1043
+ schema = post_cmd['requestBody'].get('content', {}).get('application/json', {}).get('schema', {})
1044
+ if 'properties' in schema and 'command' in schema['properties']:
1045
+ schema['properties']['command']['enum'] = [e['command'] for e in executables]
1046
+
1050
1047
  # Add dynamic paths for each executable:
1051
1048
  for exe in executables:
1052
1049
  dynamic_path = "/commands/" + exe["command"]
@@ -1065,28 +1062,32 @@ def swagger_yaml():
1065
1062
  "parallel": {"type": "integer", "description": 'nb parallel jobs', "default": 1, "required": True, "minimum": 1, "maximum": 100},
1066
1063
  "delay": {"type": "number", "description": "initial delay in s between jobs", "default": 10, "required": True, "minimum": 0, "maximum": 600},
1067
1064
  })
1065
+
1068
1066
  swagger_spec.setdefault("paths", {})[dynamic_path] = {
1069
1067
  "post": {
1070
1068
  "summary": f"Run command {exe['command']}",
1071
1069
  "tags": ["run_commands"],
1072
1070
  "description": f"{exe['help']}",
1073
- "consumes": ["application/json"],
1074
- "produces": ["application/json"],
1075
- "parameters": [
1076
- {
1077
- "in": "body",
1078
- "name": "requestBody",
1079
- "schema": cmd_schema
1071
+ "requestBody": {
1072
+ "required": True,
1073
+ "content": {
1074
+ "application/json": {
1075
+ "schema": cmd_schema
1076
+ }
1080
1077
  }
1081
- ],
1078
+ },
1082
1079
  "responses": {
1083
1080
  "200": {
1084
1081
  "description": "Command started",
1085
- "schema": {
1086
- "type": "object",
1087
- "properties": {
1088
- "message": {"type": "string"},
1089
- "command_id": {"type": "string"}
1082
+ "content": {
1083
+ "application/json": {
1084
+ "schema": {
1085
+ "type": "object",
1086
+ "properties": {
1087
+ "message": {"type": "string"},
1088
+ "command_id": {"type": "string"}
1089
+ }
1090
+ }
1090
1091
  }
1091
1092
  }
1092
1093
  }
@@ -1,5 +1,5 @@
1
1
 
2
- .markdown, .swagger-ui .markdown {
2
+ .markdown, .swagger-ui .markdown, .swagger-ui .renderedMarkdown {
3
3
  /* background-color: #e0e0ff; */
4
4
  border: 1px solid #ccc;
5
5
  padding: 0px 8px 8px 8px;
@@ -449,6 +449,7 @@ body.dimmed * {
449
449
  .command-line:hover {
450
450
  text-overflow: unset;
451
451
  max-width: unset;
452
+ white-space: pre;
452
453
  }
453
454
  .nbrunning {
454
455
  display: inline-block;
@@ -264,44 +264,38 @@ paramsInput.addEventListener('focus', () => {
264
264
  }
265
265
  if (gExecutables[currentCmd] && gExecutables[currentCmd].schema && gExecutables[currentCmd].schema.properties && paramsContainer.style.display == 'none') {
266
266
  createSchemaForm($('#schemaForm'), gExecutables[currentCmd].schema, async function (errors, values) {
267
- if (errors) {
268
- console.log(errors);
269
- alert(errors[0].message);
270
- return false;
271
- } else {
272
- const commandName = commandInput.value;
273
- fitAddon.fit();
274
- terminal.clear();
275
- payload = { params: values, rows: terminal.rows, cols: terminal.cols }
276
- if ('parallel' in values) {
277
- payload['parallel'] = values['parallel'];
278
- payload['delay'] = values['delay'];
279
- delete payload['params']['parallel'];
280
- delete payload['params']['delay'];
281
- }
282
- try {
283
- const response = await fetch(`/commands/${commandName}`, {
284
- method: 'POST',
285
- headers: {
286
- 'Content-Type': 'application/json'
287
- },
288
- body: JSON.stringify(payload)
289
- });
290
-
291
- if (!response.ok) {
292
- throw new Error('Failed to launch command');
293
- }
294
-
295
- const data = await response.json();
296
- viewOutput(data.command_id);
297
- fetchCommands();
298
- commandInput.focus();
299
- commandInput.setSelectionRange(0, commandInput.value.length);
300
- } catch (error) {
301
- console.log('Error running command:', error);
267
+ const commandName = commandInput.value;
268
+ fitAddon.fit();
269
+ terminal.clear();
270
+ payload = { params: values, rows: terminal.rows, cols: terminal.cols }
271
+ if ('parallel' in values) {
272
+ payload['parallel'] = values['parallel'];
273
+ payload['delay'] = values['delay'];
274
+ delete payload['params']['parallel'];
275
+ delete payload['params']['delay'];
276
+ }
277
+ try {
278
+ const response = await fetch(`/commands/${commandName}`, {
279
+ method: 'POST',
280
+ headers: {
281
+ 'Content-Type': 'application/json'
282
+ },
283
+ body: JSON.stringify(payload)
284
+ });
285
+
286
+ if (!response.ok) {
287
+ throw new Error('Failed to launch command');
302
288
  }
289
+
290
+ const data = await response.json();
291
+ viewOutput(data.command_id);
292
+ fetchCommands();
293
+ commandInput.focus();
294
+ commandInput.setSelectionRange(0, commandInput.value.length);
295
+ } catch (error) {
296
+ console.log('Error running command:', error);
303
297
  }
304
- }, currentCmd.toString());
298
+ }, currentCmd);
305
299
  setHelpDivPosition();
306
300
  paramsContainer.style.display = 'block';
307
301
  const input1 = schemaFormPW.querySelector('input, select, textarea');
@@ -1,6 +1,6 @@
1
1
  const maxScrollback = 99999;
2
2
  const maxSize = 10485760; // 10MB
3
- let fontSize = 14;
3
+ let fontSize = +localStorage.getItem('popupFontSize') || 14;
4
4
  let terminal = new Terminal({
5
5
  cursorBlink: false,
6
6
  cursorInactiveStyle: 'none',
@@ -75,8 +75,8 @@ let slider = null;
75
75
  let isPaused = false;
76
76
  let cols = 0;
77
77
  let rows = 0;
78
- let fitWindow = false;
79
-
78
+ let fitWindow = localStorage.getItem('popupFitWindow') === 'false' ? false : true;
79
+ console.log(localStorage.getItem('popupFitWindow'));
80
80
  const toggleButton = document.getElementById('toggleFetch');
81
81
  const pausedMessage = document.getElementById('pausedMessage');
82
82
  const toggleFitButton = document.getElementById('toggleFit');
@@ -169,9 +169,13 @@ async function viewOutput(command_id) {
169
169
  }
170
170
  const data = await response.json();
171
171
  const commandInfo = document.getElementById('commandInfo');
172
- const command = `${data.command.replace(/^\.\//, '')} ${data.params.join(' ')}`;
172
+ if (data.command.endsWith('/run-para')) {
173
+ command = `${data.params.join(' ').replace(/^.* -- run .\//, 'batch ')}`;
174
+ } else {
175
+ command = `${data.command.replace(/^\.\//, '')} ${data.params.join(' ')}`;
176
+ }
173
177
  setCommandStatus(data.status);
174
- commandInfo.innerText = command;
178
+ commandInfo.innerHTML = command;
175
179
  commandInfo.setAttribute('title', command);
176
180
  document.title = `${data.command} ${data.params.join(' ')} - [${data.status}]`;
177
181
  if (data.command == 'term')
@@ -232,21 +236,31 @@ function toggleFetchOutput() {
232
236
  }
233
237
  isPaused = !isPaused;
234
238
  }
235
- function toggleFit() {
236
- fitWindow = ! fitWindow;
239
+ function setFitIcon()
240
+ {
241
+ console.log(fitWindow);
237
242
  if (fitWindow) {
243
+ toggleFitButton.classList.remove('fit-window');
238
244
  toggleFitButton.classList.add('fit-tty');
239
245
  toggleFitButton.setAttribute('title', 'terminal fit tty');
240
246
  } else {
241
247
  toggleFitButton.classList.remove('fit-tty');
248
+ toggleFitButton.classList.add('fit-window');
242
249
  toggleFitButton.setAttribute('title', 'terminal fit window');
243
- }
250
+ }
251
+ }
252
+
253
+ function toggleFit() {
254
+ fitWindow = ! fitWindow;
255
+ setFitIcon();
256
+ localStorage.setItem('popupFitWindow', fitWindow);
244
257
  autoFit();
245
258
  viewOutput(currentCommandId);
246
259
  }
247
260
 
248
261
  toggleButton.addEventListener('click', toggleFetchOutput);
249
262
  toggleFitButton.addEventListener('click', toggleFit);
263
+ setFitIcon();
250
264
  window.addEventListener('resize', adjustOutputHeight);
251
265
  window.addEventListener('load', () => {
252
266
  slider = document.getElementById('outputSlider');
@@ -259,11 +273,13 @@ window.addEventListener('load', () => {
259
273
  document.getElementById('decreaseFontSize').addEventListener('click', () => {
260
274
  fontSize = Math.max(8, fontSize - 1);
261
275
  terminal.options.fontSize = fontSize;
276
+ localStorage.setItem('popupFontSize', fontSize);
262
277
  autoFit();
263
278
  });
264
279
 
265
280
  document.getElementById('increaseFontSize').addEventListener('click', () => {
266
281
  fontSize = Math.min(32, fontSize + 1);
267
282
  terminal.options.fontSize = fontSize;
283
+ localStorage.setItem('popupFontSize', fontSize);
268
284
  autoFit();
269
285
  });
@@ -11,7 +11,7 @@ function adjustInputWidth(input) {
11
11
  function formInputHandle() {
12
12
  schemaForm.querySelectorAll('input[type="text"], input[type="number"]').forEach(input => {
13
13
  if (! inputHandlers.includes(input)) {
14
- val = input.value || input.placeholder;
14
+ val = input.placeholder;
15
15
  if (val) {
16
16
  size = Math.max(val.length, 2)
17
17
  if (input.type== 'number') {
@@ -54,12 +54,46 @@ function extractKeysAndPlaceholders(obj, formoptions, prefix = '') {
54
54
  return result;
55
55
  }
56
56
 
57
- function createSchemaForm(form, schema, onSubmit, schemaName) {
58
- if (schemaValues[schemaName]) {
59
- value = schemaValues[schemaName];
60
- } else {
61
- value = {};
57
+ // ...existing code...
58
+
59
+ function convertTextareaToArray(values, formDesc, schema) {
60
+ // Helper function to get schema type for a key path
61
+ function getSchemaType(schema, keyPath) {
62
+ const keys = keyPath.split('.');
63
+ let current = schema.properties;
64
+ for (const key of keys) {
65
+ if (!current || !current[key] || !current[key].properties) {
66
+ return current?.[key]?.type;
67
+ }
68
+ current = current[key].properties;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ // Convert textarea values to arrays if schema type matches
74
+ for (let i = 0; i < formDesc.length; i++) {
75
+ if (formDesc[i].type == 'textarea') {
76
+ const schemaType = getSchemaType(schema, formDesc[i].key);
77
+ if (schemaType === 'array') {
78
+ const keys = formDesc[i].key.split('.');
79
+ let obj = values;
80
+ for (let j = 0; j < keys.length - 1; j++) {
81
+ obj = obj[keys[j]];
82
+ }
83
+ const lastKey = keys[keys.length - 1];
84
+ const val = obj[lastKey];
85
+ if (val) {
86
+ obj[lastKey] = val.trim().split(/[\s\r,]+/).filter(x => x);
87
+ }
88
+ }
89
+ }
62
90
  }
91
+ return values;
92
+ }
93
+
94
+ // ...existing code...
95
+
96
+ function createSchemaForm(form, schema, onSubmit, schemaName) {
63
97
  if (schema && schema.schema_options) {
64
98
  schema_options = schema.schema_options;
65
99
  } else {
@@ -80,6 +114,33 @@ function createSchemaForm(form, schema, onSubmit, schemaName) {
80
114
  }
81
115
  }
82
116
  formDesc = extractKeysAndPlaceholders(schema, formoptions);
117
+ if (schemaValues[schemaName]) {
118
+ value = schemaValues[schemaName];
119
+ // convert array for textarea formDesc type to string separated by newlines
120
+ // if in formDesc a key has type textarea, convert the value to string separated by newlines
121
+ // formDesc=[{key: 'db.sid', type: 'textarea'}]
122
+ // value = {db: {sid: ['AA', 'BB']}}
123
+ // convert to
124
+ // value = {db: {sid: 'AA\nBB'}}
125
+ for (let i = 0; i < formDesc.length; i++) {
126
+ if (formDesc[i].type === 'textarea') {
127
+ const keys = formDesc[i].key.split('.');
128
+ let obj = value;
129
+ for (let j = 0; j < keys.length - 1; j++) {
130
+ if (!(keys[j] in obj)) obj[keys[j]] = {};
131
+ obj = obj[keys[j]];
132
+ }
133
+ const lastKey = keys[keys.length - 1];
134
+ const val = obj[lastKey];
135
+ if (val && Array.isArray(val)) {
136
+ obj[lastKey] = val.join('\n');
137
+ }
138
+ }
139
+ }
140
+ } else {
141
+ value = {};
142
+ }
143
+
83
144
  schemaForm = form[0];
84
145
  if (onSubmit != null) {
85
146
  if (schema_options && schema_options.batch_param) {
@@ -149,9 +210,20 @@ function createSchemaForm(form, schema, onSubmit, schemaName) {
149
210
  form[0].classList.add('form-inline');
150
211
  jsform = form.jsonForm({
151
212
  schema: schema,
152
- onSubmit: onSubmit,
213
+ onSubmit: function (errors, values) {
214
+ convertTextareaToArray(values, formDesc, schema);
215
+ env = JSV.createEnvironment();
216
+ report = env.validate(values, schema);
217
+ errors = report.errors;
218
+ if (errors.length > 0) {
219
+ alert(errors[0].message);
220
+ return false;
221
+ }
222
+ onSubmit(errors, values);
223
+ },
153
224
  form: formDesc,
154
225
  value: value,
226
+ validate: false,
155
227
  // params: {
156
228
  // fieldHtmlClass: "input-small",
157
229
  // }
@@ -169,15 +241,20 @@ function createSchemaForm(form, schema, onSubmit, schemaName) {
169
241
  txt.addEventListener("input", () => adjustTxtHeight(txt));
170
242
  });
171
243
  form[0].addEventListener('input', () => {
172
- schemaValues[schemaName] = jsform.root.getFormValues();
244
+ schemaValues[schemaName] = convertTextareaToArray(jsform.root.getFormValues(), formDesc, schema);
173
245
  localStorage.setItem('schemaValues', JSON.stringify(schemaValues));
174
246
  });
175
247
 
176
248
  return jsform;
177
249
  }
178
250
  function adjustTxtHeight(txt) {
251
+ if (txt.value.includes('\n')) {
252
+ delta = 2;
253
+ } else {
254
+ delta = 0;
255
+ }
179
256
  txt.style.height = "0";
180
- txt.style.height = txt.scrollHeight + "px";
257
+ txt.style.height = `${txt.scrollHeight+delta}px`;
181
258
  }
182
259
  async function getSwaggerSpec() {
183
260
  const response = await fetch('/swagger.yaml');
@@ -7,11 +7,11 @@ let fullOutput = '';
7
7
  let outputLength = 0;
8
8
  const maxScrollback = 99999;
9
9
  const maxSize = 10485760; // 10MB
10
- let fontSize = 14;
10
+ let fontSize = +localStorage.getItem('fontSize') || 14;
11
11
  let isPaused = false;
12
12
  let showRunningOnly = false;
13
13
  let hiddenCommandIds = [];
14
- let fitWindow = false;
14
+ let fitWindow = localStorage.getItem('fitWindow') === 'false' ? false : true;
15
15
  let cols = 0;
16
16
  let rows = 0;
17
17
 
@@ -260,7 +260,11 @@ async function viewOutput(command_id) {
260
260
  }
261
261
  const data = await response.json();
262
262
  const commandInfo = document.getElementById('commandInfo');
263
- const command = `${data.command.replace(/^\.\//, '')} ${data.params.join(' ')}`;
263
+ if (data.command.endsWith('/run-para')) {
264
+ command = `${data.params.join(' ').replace(/^.* -- run .\//, 'batch ')}`;
265
+ } else {
266
+ command = `${data.command.replace(/^\.\//, '')} ${data.params.join(' ')}`;
267
+ }
264
268
  setCommandStatus(data.status)
265
269
  commandInfo.innerHTML = command;
266
270
  commandInfo.setAttribute('title', command);
@@ -415,12 +419,14 @@ slider.addEventListener('input', sliderUpdateOutput);
415
419
  document.getElementById('decreaseFontSize').addEventListener('click', () => {
416
420
  fontSize = Math.max(8, fontSize - 1);
417
421
  terminal.options.fontSize = fontSize;
422
+ localStorage.setItem('fontSize', fontSize);
418
423
  autoFit();
419
424
  });
420
425
 
421
426
  document.getElementById('increaseFontSize').addEventListener('click', () => {
422
427
  fontSize = Math.min(32, fontSize + 1);
423
428
  terminal.options.fontSize = fontSize;
429
+ localStorage.setItem('fontSize', fontSize);
424
430
  autoFit();
425
431
  });
426
432
 
@@ -451,21 +457,31 @@ function toggleFetchOutput() {
451
457
  }
452
458
  isPaused = !isPaused;
453
459
  }
454
- function toggleFit() {
455
- fitWindow = ! fitWindow;
460
+
461
+ function setFitIcon()
462
+ {
456
463
  if (fitWindow) {
464
+ toggleFitButton.classList.remove('fit-window');
457
465
  toggleFitButton.classList.add('fit-tty');
458
466
  toggleFitButton.setAttribute('title', 'terminal fit tty');
459
467
  } else {
460
468
  toggleFitButton.classList.remove('fit-tty');
469
+ toggleFitButton.classList.add('fit-window');
461
470
  toggleFitButton.setAttribute('title', 'terminal fit window');
462
- }
471
+ }
472
+ }
473
+
474
+ function toggleFit() {
475
+ fitWindow = ! fitWindow;
476
+ setFitIcon();
477
+ localStorage.setItem('fitWindow', fitWindow);
463
478
  autoFit();
464
479
  viewOutput(currentCommandId);
465
480
  }
466
481
 
467
482
  toggleButton.addEventListener('click', toggleFetchOutput);
468
483
  toggleFitButton.addEventListener('click', toggleFit);
484
+ setFitIcon();
469
485
 
470
486
  document.getElementById('thStatus').addEventListener('click', () => {
471
487
  showRunningOnly = !showRunningOnly;
@@ -1,8 +1,9 @@
1
1
  let ui;
2
2
  let swaggerSchemas = {};
3
+
3
4
  function addFormInputListener(textArea, jsform){
4
5
  return function (event) {
5
- jsonString = JSON.stringify(jsform.root.getFormValues(), null, 2);
6
+ jsonString = JSON.stringify(convertTextareaToArray(jsform.root.getFormValues(), jsform.formDesc.form, jsform.formDesc.schema), null, 2);
6
7
  textArea.value = jsonString;
7
8
 
8
9
  // Find the React fiber node
@@ -26,6 +27,28 @@ function addFormInputListener(textArea, jsform){
26
27
  };
27
28
  }
28
29
 
30
+ async function getPostParametersSchema() {
31
+ const swaggerSpec = await getSwaggerSpec();
32
+ const result = {};
33
+ for (const path in swaggerSpec.paths) {
34
+ const pathItem = swaggerSpec.paths[path];
35
+ if (pathItem.post) {
36
+ const postDef = pathItem.post;
37
+ // Look for requestBody with JSON schema in OpenAPI 3.0
38
+ if (postDef.requestBody && postDef.requestBody.content && postDef.requestBody.content['application/json']) {
39
+ result[path] = postDef.requestBody.content['application/json'].schema;
40
+ } else {
41
+ result[path] = null;
42
+ }
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ function adjustTxtHeight2(paramtext) {
48
+ paramtext.style.height = "0";
49
+ paramtext.style.height = `${paramtext.scrollHeight+2}px`;
50
+ }
51
+
29
52
  window.onload = function() {
30
53
  ui = SwaggerUIBundle({
31
54
  url: "/swagger.yaml",
@@ -69,29 +92,26 @@ window.onload = function() {
69
92
  const observer = new MutationObserver((mutations) => {
70
93
  mutations.forEach(mutation => {
71
94
  mutation.addedNodes.forEach(node => {
72
- if (node.classList && (node.classList.contains("highlight-code") ||
73
- node.classList.contains("body-param__text"))) {
74
- // Retrieve the data-path attribute from the first opblock-summary-path element
75
- const routePath = $(node).closest('.opblock').find('.opblock-summary-path').first().attr('data-path');
76
- const routePathId = `schemaForm${routePath.replaceAll("/", "_")}`;
77
- const prevForm = node.parentNode.querySelector(`#${routePathId}`)
78
- if (prevForm) {
79
- prevForm.remove();
80
- }
81
- if (node.classList.contains("body-param__text")) {
82
- node.addEventListener("input", (e) => {
83
- e.target.style.height = "0"
84
- e.target.style.height = e.target.scrollHeight + "px";
85
- });
86
- node.style.height = "0"
87
- node.style.height = node.scrollHeight + "px";
95
+ if (node.nodeType === Node.ELEMENT_NODE) {
96
+ const paramtext = node.querySelector(".body-param__text");
97
+ if (paramtext) {
98
+ // Retrieve the data-path attribute from the first opblock-summary-path element
99
+ const routePath = $(node).closest('.opblock').find('.opblock-summary-path').first().attr('data-path');
100
+ const routePathId = `schemaForm${routePath.replaceAll("/", "_")}`;
101
+ const prevForm = paramtext.parentNode.querySelector(`#${routePathId}`)
102
+ if (prevForm) {
103
+ prevForm.remove();
104
+ }
105
+ paramtext.addEventListener("input", () => adjustTxtHeight2(paramtext));
106
+ setTimeout(() => adjustTxtHeight2(paramtext), 100);
88
107
  const form = document.createElement("form");
89
108
  form.id = routePathId;
90
109
  form.classList.add("schema-form");
91
110
  jsform = createSchemaForm($(form), swaggerSchemas[routePath], null, routePath);
92
- // form.addEventListener("input", formInput(node, jsform));
93
- form.addEventListener("input", addFormInputListener(node, jsform));
94
- node.parentNode.insertBefore(form, node.nextSibling);
111
+ // form.addEventListener("input", formInput(node, jsform));
112
+ setTimeout(() => addFormInputListener(paramtext, jsform)(), 100);
113
+ form.addEventListener("input", addFormInputListener(paramtext, jsform));
114
+ paramtext.parentNode.insertBefore(form, paramtext.nextSibling);
95
115
  item1 = form.querySelector("input, select, textarea");
96
116
  if (item1) {
97
117
  item1.focus();