pywebexec 2.3.14__py3-none-any.whl → 2.4.0__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.
@@ -4,6 +4,27 @@
4
4
  src: url('../fonts/glyphicons-halflings-regular.eot');
5
5
  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
6
6
  }
7
+ .glyphicon-plus-sign:before {
8
+ content: "\e081";
9
+ }
10
+ .glyphicon-minus-sign:before {
11
+ content: "\e082";
12
+ }
13
+ .glyphicon-search:before {
14
+ content: "\e003";
15
+ }
16
+ .glyphicon {
17
+ position: relative;
18
+ top: 1px;
19
+ display: inline-block;
20
+ font-family: 'Glyphicons Halflings';
21
+ font-style: normal;
22
+ font-weight: normal;
23
+ line-height: 1;
24
+ -webkit-font-smoothing: antialiased;
25
+ -moz-osx-font-smoothing: grayscale;
26
+ }
27
+
7
28
 
8
29
  #schemaForm, .schema-form {
9
30
  font-size: 14px;
@@ -12,24 +33,6 @@
12
33
  div {
13
34
  scrollbar-width: thin;
14
35
  }
15
- .glyphicon-plus-sign:before {
16
- content: "\e081";
17
- }
18
- .glyphicon-minus-sign:before {
19
- content: "\e082";
20
- }
21
-
22
- .glyphicon {
23
- position: relative;
24
- top: 1px;
25
- display: inline-block;
26
- font-family: 'Glyphicons Halflings';
27
- font-style: normal;
28
- font-weight: normal;
29
- line-height: 1;
30
- -webkit-font-smoothing: antialiased;
31
- -moz-osx-font-smoothing: grayscale;
32
- }
33
36
 
34
37
  .btn {
35
38
  font-size: 14px;
@@ -71,6 +74,7 @@
71
74
  display: block;
72
75
  margin-top: 5px;
73
76
  margin-bottom: 10px;
77
+ max-width: 300px;
74
78
  }
75
79
  .form-control {
76
80
  font-family: Arial, Helvetica, sans-serif;
@@ -317,4 +321,5 @@
317
321
  .swagger-ui textarea {
318
322
  min-height: 130px;
319
323
  overflow-y: hidden;
320
- }
324
+ }
325
+
@@ -46,6 +46,57 @@ th {
46
46
  position: sticky;
47
47
  top: 0;
48
48
  z-index: 1;
49
+ vertical-align: top;
50
+ }
51
+ .th-content {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 5px;
55
+ white-space: nowrap;
56
+ }
57
+ .sort-btn {
58
+ color: #aaa;
59
+ font-size: 14px;
60
+ user-select: none;
61
+ display: inline-block;
62
+ width: 7px;
63
+ text-align: center;
64
+ flex-shrink: 0;
65
+ }
66
+ .sort-btn[data-sort-order="asc"] {
67
+ color: #5a5;
68
+ }
69
+ .sort-btn[data-sort-order="desc"] {
70
+ color: #5a5;
71
+ }
72
+ .column-filter {
73
+ display: block;
74
+ width: 90%;
75
+ max-width: 300px;
76
+ margin: 5px 0px 0px 0px;
77
+ padding: 2px 5px;
78
+ text-indent: 5px;
79
+ border: 1px solid #666;
80
+ border-radius: 15px;
81
+ font-size: 12px;
82
+ background: #444;
83
+ color: #eee
84
+ }
85
+ .column-filter:focus {
86
+ outline: none;
87
+ border-color: #666;
88
+ background: #ddd;
89
+ color: #222;
90
+ }
91
+ .column-filter:focus::placeholder {
92
+ color: transparent;
93
+ }
94
+ .column-filter::placeholder {
95
+ font-family: 'Glyphicons Halflings';
96
+ /* content: "\e003"; */
97
+ font-size: 12px;
98
+ /* text-align: right; */
99
+ color: #888;
49
100
  }
50
101
  .outcol {
51
102
  width: 100%;
@@ -133,6 +184,7 @@ form {
133
184
  width: 16px;
134
185
  height: 16px;
135
186
  min-width: 16px;
187
+ margin-left: 5px;
136
188
  margin-right: 5px;
137
189
  background-size: contain;
138
190
  background-repeat: no-repeat;
@@ -254,7 +306,7 @@ code {
254
306
  font-size: 13px;
255
307
  }
256
308
 
257
- .show-command-list-button .arrow {
309
+ .show-command-list-button .ar- {
258
310
  visibility: hidden; /* Hide arrow by default */
259
311
  }
260
312
 
@@ -469,13 +521,24 @@ body.dimmed * {
469
521
  }
470
522
  .nbrunning {
471
523
  display: inline-block;
472
- font-size: 13px;
524
+ font-size: 10px;
473
525
  font-weight: normal;
474
526
  border: 1px solid #aaa;
475
527
  border-radius: 17px;
476
528
  min-width: 17px;
477
529
  text-align: center;
478
530
  }
531
+ .row-count {
532
+ display: inline-block;
533
+ font-size: 11px;
534
+ font-weight: normal;
535
+ border: 1px solid #aaa;
536
+ border-radius: 17px;
537
+ /* min-width: 17px; */
538
+ text-align: center;
539
+ padding: 0px 4px;
540
+ margin-left: auto;
541
+ }
479
542
 
480
543
  .xterm-accessibility {
481
544
  display: none;
@@ -483,4 +546,8 @@ body.dimmed * {
483
546
  .swaggerlink {
484
547
  text-decoration: none;
485
548
  font-size: 10px;
486
- }
549
+ }
550
+
551
+ #statusRunning > span {
552
+ vertical-align: text-top;
553
+ }
@@ -181,14 +181,19 @@ async function fetchCommands(hide=false) {
181
181
  </td>
182
182
  `;
183
183
  commandsTbody.appendChild(commandRow);
184
+ const commandsTable = document.getElementById('commandsTable');
184
185
  });
185
186
  if (runningCommands.length) {
186
- document.getElementById('thStatus').innerHTML=`<span class="status-icon status-running"></span>Running <span class="system-font nbrunning">${runningCommands.length}</span>`;
187
- document.getElementById('thStatus').setAttribute('title', runningCommands.join("\n"));
187
+ const thStatus = document.getElementById('statusRunning');
188
+ thStatus.innerHTML = `Running <span class="status-icon status-running"></span><span class="system-font nbrunning">${runningCommands.length}</span>`;
189
+ thStatus.setAttribute('title', runningCommands.join("\n"));
188
190
  } else {
189
- document.getElementById('thStatus').innerHTML=`<span class="status-icon status-norun"></span>Status`;
190
- document.getElementById('thStatus').setAttribute('title', "no command running");
191
+ const thStatus = document.getElementById('statusRunning');
192
+ thStatus.innerHTML = `Status <span class="status-icon status-norun"></span>`;
193
+ thStatus.setAttribute('title', "no command running");
191
194
  }
195
+ // Apply filters after table update
196
+ applyFilters(document.getElementById('commandsTable'));
192
197
  document.getElementById('dimmer').style.display = 'none';
193
198
  } catch (error) {
194
199
  console.log('Error fetching commands:', error);
@@ -227,6 +232,10 @@ async function fetchOutput(url) {
227
232
  if (htmlContent) {
228
233
  document.getElementById('output').innerHTML = htmlContent;
229
234
  document.getElementById('output').classList.add('outputhtml');
235
+ const table = document.getElementById('output').querySelector('table');
236
+ if (table != undefined && table != null) {
237
+ initTableFilters(table);
238
+ }
230
239
  } else {
231
240
  if (slider.value == 1000)
232
241
  terminal.write(data.output);
@@ -321,6 +330,11 @@ async function viewOutput(command_id) {
321
330
  if (htmlContent) {
322
331
  document.getElementById('output').innerHTML = htmlContent;
323
332
  document.getElementById('output').classList.add('outputhtml');
333
+ const table = document.getElementById('output').querySelector('table');
334
+ console.log(table);
335
+ if (table != undefined && table != null) {
336
+ initTableFilters(table);
337
+ }
324
338
  } else {
325
339
  terminal.write(output);
326
340
  }
@@ -535,7 +549,7 @@ toggleButton.addEventListener('click', toggleFetchOutput);
535
549
  toggleFitButton.addEventListener('click', toggleFit);
536
550
  setFitIcon();
537
551
 
538
- document.getElementById('thStatus').addEventListener('click', () => {
552
+ document.getElementById('statusRunning').addEventListener('click', () => {
539
553
  showRunningOnly = !showRunningOnly;
540
554
  hiddenCommandIds = [];
541
555
  fetchCommands(showRunningOnly);
@@ -0,0 +1,149 @@
1
+ function initTableFilters(table) {
2
+ const headers = table.querySelectorAll('thead th');
3
+ headers.forEach((header, index) => {
4
+ if (index !== 4 || table!==commandsTable) { // Skip Action column
5
+ const contentSpan = document.createElement('span');
6
+ contentSpan.className = 'th-content';
7
+
8
+ // Add sort button first
9
+ const sortBtn = document.createElement('span');
10
+ sortBtn.className = 'sort-btn';
11
+ sortBtn.innerHTML = '⇕';
12
+ sortBtn.style.cursor = 'pointer';
13
+ sortBtn.setAttribute('data-sort-order', '');
14
+ sortBtn.onclick = () => toggleSort(table, index, sortBtn);
15
+
16
+ // Move existing elements into the content span
17
+ while (header.firstChild) {
18
+ contentSpan.appendChild(header.firstChild);
19
+ }
20
+
21
+ // Add sort button at the beginning
22
+ contentSpan.insertBefore(sortBtn, contentSpan.firstChild);
23
+
24
+ // Add row counter for last column
25
+ if (index === headers.length - 1) {
26
+ const rowCount = document.createElement('span');
27
+ rowCount.className = 'row-count';
28
+ rowCount.classList.add('system-font');
29
+ contentSpan.appendChild(rowCount);
30
+ }
31
+
32
+ header.appendChild(contentSpan);
33
+
34
+ // Add filter input
35
+ const input = document.createElement('input');
36
+ input.type = 'search';
37
+ input.className = 'column-filter';
38
+ input.placeholder = ''; // Unicode for magnifying glass
39
+ input.addEventListener('input', () => applyFilters(table));
40
+ header.appendChild(input);
41
+ }
42
+ });
43
+ // Initialize row count
44
+ updateRowCount(table, table.querySelectorAll('tbody tr').length);
45
+ }
46
+
47
+ function updateRowCount(table, count) {
48
+ const rowCount = table.querySelector('.row-count');
49
+ if (rowCount) {
50
+ rowCount.textContent = `${count}`;
51
+ }
52
+ }
53
+
54
+ function toggleSort(table, colIndex, sortBtn) {
55
+ // Reset other sort buttons
56
+ table.querySelectorAll('.sort-btn').forEach(btn => {
57
+ if (btn !== sortBtn) {
58
+ btn.setAttribute('data-sort-order', '');
59
+ btn.innerHTML = '⇕';
60
+ }
61
+ });
62
+
63
+ // Toggle sort order
64
+ const currentOrder = sortBtn.getAttribute('data-sort-order');
65
+ let newOrder = 'asc';
66
+ if (currentOrder === 'asc') {
67
+ newOrder = 'desc';
68
+ sortBtn.innerHTML = '⇓';
69
+ } else if (currentOrder === 'desc') {
70
+ newOrder = '';
71
+ sortBtn.innerHTML = '⇕';
72
+ } else {
73
+ sortBtn.innerHTML = '⇑';
74
+ }
75
+ sortBtn.setAttribute('data-sort-order', newOrder);
76
+ sortBtn.setAttribute('data-col-index', colIndex); // Store column index on the button
77
+ applyFilters(table);
78
+ }
79
+
80
+ function applyFilters(table) {
81
+ const rows = Array.from(table.querySelectorAll('tbody tr'));
82
+ const filters = Array.from(table.querySelectorAll('.column-filter'))
83
+ .map(filter => ({
84
+ value: filter.value.toLowerCase(),
85
+ index: filter.parentElement.cellIndex,
86
+ regexp: filter.value ? (() => {
87
+ try { return new RegExp(filter.value, 'i'); }
88
+ catch(e) { return null; }
89
+ })() : null
90
+ }));
91
+
92
+ // First apply filters
93
+ const filteredRows = rows.filter(row => {
94
+ // If no filters are active, show all rows
95
+ if (filters.every(f => !f.value)) {
96
+ row.style.display = '';
97
+ return true;
98
+ }
99
+ const cells = row.cells;
100
+ const shouldShow = !filters.some(filter => {
101
+ if (!filter.value) return false;
102
+ const cellText = cells[filter.index]?.innerText || '';
103
+ if (filter.regexp) return !filter.regexp.test(cellText);
104
+ return !cellText.toLowerCase().includes(filter.value);
105
+ });
106
+ row.style.display = shouldShow ? '' : 'none';
107
+ return shouldShow;
108
+ });
109
+
110
+ // Update row count
111
+ updateRowCount(table, filteredRows.length);
112
+
113
+ // Then apply sorting if active
114
+ const sortBtn = table.querySelector('.sort-btn[data-sort-order]:not([data-sort-order=""])');
115
+ if (sortBtn) {
116
+ const colIndex = parseInt(sortBtn.getAttribute('data-col-index'));
117
+ const sortOrder = sortBtn.getAttribute('data-sort-order');
118
+
119
+ filteredRows.sort((a, b) => {
120
+ const aVal = a.cells[colIndex]?.innerText.trim() || '';
121
+ const bVal = b.cells[colIndex]?.innerText.trim() || '';
122
+
123
+ // Check if both values are numeric
124
+ const aNum = !isNaN(aVal) && !isNaN(parseFloat(aVal));
125
+ const bNum = !isNaN(bVal) && !isNaN(parseFloat(bVal));
126
+
127
+ if (aNum && bNum) {
128
+ // Numeric comparison
129
+ return sortOrder === 'asc'
130
+ ? parseFloat(aVal) - parseFloat(bVal)
131
+ : parseFloat(bVal) - parseFloat(aVal);
132
+ }
133
+
134
+ // Fallback to string comparison
135
+ if (aVal < bVal) return sortOrder === 'asc' ? -1 : 1;
136
+ if (aVal > bVal) return sortOrder === 'asc' ? 1 : -1;
137
+ return 0;
138
+ });
139
+
140
+ // Reorder visible rows
141
+ const tbody = table.querySelector('tbody');
142
+ filteredRows.forEach(row => tbody.appendChild(row));
143
+ }
144
+ }
145
+
146
+ let commandsTable = document.querySelector('#commandsTable');
147
+ document.addEventListener('DOMContentLoaded', () => {
148
+ if (commandsTable) initTableFilters(commandsTable);
149
+ });
@@ -33,13 +33,13 @@
33
33
  <div id="paramsHelp" class="markdown"></div>
34
34
  </div>
35
35
  <div class="table-container" id="tableContainer">
36
- <table>
36
+ <table id="commandsTable">
37
37
  <thead>
38
38
  <tr>
39
39
  <th>ID</th>
40
40
  <th>Start Time</th>
41
41
  <th>Duration</th>
42
- <th id="thStatus"><span class="status-icon status-norun"></span>Status</th>
42
+ <th id="thStatus"><div id="statusRunning"></div></th>
43
43
  <th>Action</th>
44
44
  <th>Command</th>
45
45
  <th>Output</th>
@@ -75,6 +75,7 @@
75
75
  <script type="text/javascript" src="/static/js/script.js"></script>
76
76
  <script type="text/javascript" src="/static/js/executables.js"></script>
77
77
  <script type="text/javascript" src="/static/js/schemaform.js"></script>
78
+ <script type="text/javascript" src="/static/js/tablefilter.js"></script>
78
79
 
79
80
 
80
81
  </body>
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.14'
21
- __version_tuple__ = version_tuple = (2, 3, 14)
20
+ __version__ = version = '2.4.0'
21
+ __version_tuple__ = version_tuple = (2, 4, 0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywebexec
3
- Version: 2.3.14
3
+ Version: 2.4.0
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -2,10 +2,10 @@ pywebexec/__init__.py,sha256=197fHJy0UDBwTTpGCGortZRr-w2kTaD7MxqdbVmTEi0,61
2
2
  pywebexec/host_ip.py,sha256=oiCMlo2o3AkkgXDarUSx8T3FWXKI0vk1-EPnx5FGBd8,1332
3
3
  pywebexec/pywebexec.py,sha256=NT4f7Xd4qMkAgjgwguqKKbUkOTCqT7ArRYlsW57Pfwg,48477
4
4
  pywebexec/swagger.yaml,sha256=I_oLpp7Hqel8SDEEykvpmCT-Gv3ytGlziq9bvQOrtZY,7598
5
- pywebexec/version.py,sha256=Aelhd_nWH-KFOx_jUKbvGukd7h0Q1WZNZKS7Tu4SHi0,513
6
- pywebexec/static/css/form.css,sha256=riFFi02xtUXusTTZOU3RSZykutJWMFfK65-eR5RbhY8,7252
5
+ pywebexec/version.py,sha256=KyHVhKcn4Ob8_JP09Buz8x5uB4b7RJZLl3FBlZ8sWSY,511
6
+ pywebexec/static/css/form.css,sha256=2JUhraeL46JaiNoD_MSKA2JdouHkXaamhd77DnCqG8k,7291
7
7
  pywebexec/static/css/markdown.css,sha256=br4-iK9wigTs54N2KHtjgZ4KLH0THVSvJo-XZAdMHiE,1970
8
- pywebexec/static/css/style.css,sha256=pUmylXwbFIoXrdaJRVOUohlKIhOIilapH97NyIlgGV4,10343
8
+ pywebexec/static/css/style.css,sha256=mdLDqKIGLxJtR9tSaThL8po0CXoEkm9P0k_gS8_wPcA,11652
9
9
  pywebexec/static/css/swagger-ui.css,sha256=xhXN8fnUaIACGHuPIEIr9-qmyYr6Zx0k2wv4Qy7Bg1Y,154985
10
10
  pywebexec/static/css/swagger-ui.css.map,sha256=dJy-xBn_htK4BNupTMIl33ddse7BXsrCdDJWlTJodnw,258842
11
11
  pywebexec/static/css/xterm.css,sha256=uo5phWaUiJgcz0DAzv46uoByLLbJLeetYosL1xf68rY,5559
@@ -36,8 +36,9 @@ pywebexec/static/images/swagger-ui.svg,sha256=FR0yeOVwe4zCYKZAjCGcT_m0Mf25NexIVa
36
36
  pywebexec/static/js/executables.js,sha256=cTgCFHr_F9bFCirtfG_uR32vOY3vNUr4Ih3Wglj5lFc,11988
37
37
  pywebexec/static/js/popup.js,sha256=IaKmk2U2hEn-Nv6krf_PPW6LaG8NcpCkJKb7lUX0qZo,11457
38
38
  pywebexec/static/js/schemaform.js,sha256=2AIjwdjSDTE2ide8UMmQt4tS-7-JKqidKdopq9mNzvo,12458
39
- pywebexec/static/js/script.js,sha256=TI3TSylgBxh_a6QvYWlg4CyJ6LMPxnhFl8WRtRDGD0Y,20810
39
+ pywebexec/static/js/script.js,sha256=SpNmskHKJHza0Au7QWrb17EKqiMPbMz5CDmaLt_i3M4,21548
40
40
  pywebexec/static/js/swagger-form.js,sha256=CLcSHMhk5P4-_2MIRBoJLgEnIj_9keDDSzUugXHZjio,4565
41
+ pywebexec/static/js/tablefilter.js,sha256=ml6oGCik11W61ifGWqLLmY_sC7gOH1tsqOFXFQ336lo,5590
41
42
  pywebexec/static/js/js-yaml/LICENSE,sha256=oHvCRGi5ZUznalR9R6LbKC0HcztxXbTHOpi9Y5YflVA,1084
42
43
  pywebexec/static/js/js-yaml/js-yaml.min.js,sha256=Rdw90D3AegZwWiwpibjH9wkBPwS9U4bjJ51ORH8H69c,39430
43
44
  pywebexec/static/js/marked/LICENSE.md,sha256=jjo_gvWaYJWPVsoI9EVkfDKkcz3HymwsRvbriYRxq5w,2942
@@ -64,12 +65,12 @@ pywebexec/static/jsonform/deps/underscore.js,sha256=SzKOQsVYGX1bmddyfPzGC6yXY_rW
64
65
  pywebexec/static/jsonform/deps/img/glyphicons-halflings.png,sha256=hpJM0AbbMLnU8UGOBs172D7vK-dooQ8n0s_ybml3zO0,13826
65
66
  pywebexec/static/jsonform/lib/jsonform.js,sha256=U-BvOgq5gCvSUo36qSAK7Y91RPKOq7vZShkIYpzwlkk,138525
66
67
  pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
- pywebexec/templates/index.html,sha256=w18O2plH_yS8bqlPsu5hwFFmCj9H2hWLSV8B6ADcSwU,3900
68
+ pywebexec/templates/index.html,sha256=29-MBjeJ5mgYdrkRB27WiY0okQLYcFFycJvdCMuwvV0,3974
68
69
  pywebexec/templates/popup.html,sha256=3kpMccKD_OLLhJ4Y9KRw6Ny8wQWjVaRrUfV9y5-bDiQ,1580
69
70
  pywebexec/templates/swagger_ui.html,sha256=MAPr-z96VERAecDvX37V8q2Nxph-O0fNDBul1x2w9SI,1147
70
- pywebexec-2.3.14.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
71
- pywebexec-2.3.14.dist-info/METADATA,sha256=8PDed7Qx0Rfvb3OExe836rpjUEZDoGgTcqQFHhhpvWA,13016
72
- pywebexec-2.3.14.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
73
- pywebexec-2.3.14.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
74
- pywebexec-2.3.14.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
75
- pywebexec-2.3.14.dist-info/RECORD,,
71
+ pywebexec-2.4.0.dist-info/licenses/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
72
+ pywebexec-2.4.0.dist-info/METADATA,sha256=of_DSpdHxXl41y6oQxQWaTvioxOEdYan-96zOHRIjz4,13015
73
+ pywebexec-2.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
74
+ pywebexec-2.4.0.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
75
+ pywebexec-2.4.0.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
76
+ pywebexec-2.4.0.dist-info/RECORD,,