pywebexec 1.1.11__tar.gz → 1.1.15__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 (28) hide show
  1. {pywebexec-1.1.11/pywebexec.egg-info → pywebexec-1.1.15}/PKG-INFO +1 -1
  2. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/pywebexec.py +3 -9
  3. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/css/style.css +26 -0
  4. pywebexec-1.1.15/pywebexec/static/js/script.js +256 -0
  5. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/templates/index.html +3 -0
  6. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/version.py +2 -2
  7. {pywebexec-1.1.11 → pywebexec-1.1.15/pywebexec.egg-info}/PKG-INFO +1 -1
  8. pywebexec-1.1.11/pywebexec/static/js/script.js +0 -198
  9. {pywebexec-1.1.11 → pywebexec-1.1.15}/.github/workflows/python-publish.yml +0 -0
  10. {pywebexec-1.1.11 → pywebexec-1.1.15}/.gitignore +0 -0
  11. {pywebexec-1.1.11 → pywebexec-1.1.15}/LICENSE +0 -0
  12. {pywebexec-1.1.11 → pywebexec-1.1.15}/README.md +0 -0
  13. {pywebexec-1.1.11 → pywebexec-1.1.15}/pyproject.toml +0 -0
  14. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/__init__.py +0 -0
  15. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/aborted.svg +0 -0
  16. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/copy.svg +0 -0
  17. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/copy_ok.svg +0 -0
  18. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/failed.svg +0 -0
  19. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/favicon.svg +0 -0
  20. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/running.gif +0 -0
  21. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/static/images/success.svg +0 -0
  22. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec/templates/__init__.py +0 -0
  23. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec.egg-info/SOURCES.txt +0 -0
  24. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec.egg-info/dependency_links.txt +0 -0
  25. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec.egg-info/entry_points.txt +0 -0
  26. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec.egg-info/requires.txt +0 -0
  27. {pywebexec-1.1.11 → pywebexec-1.1.15}/pywebexec.egg-info/top_level.txt +0 -0
  28. {pywebexec-1.1.11 → pywebexec-1.1.15}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.1.11
3
+ Version: 1.1.15
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -15,13 +15,8 @@ from gunicorn.app.base import Application
15
15
  import ipaddress
16
16
  from socket import gethostname, gethostbyname_ex
17
17
  import ssl
18
-
19
18
  if os.environ.get('PYWEBEXEC_LDAP_SERVER'):
20
- try:
21
- from ldap3 import Server, Connection, ALL, SIMPLE, SUBTREE, Tls
22
- except:
23
- print("Need to install ldap3: pip install ldap3", file=sys.stderr)
24
- sys.exit(1)
19
+ from ldap3 import Server, Connection, ALL, SIMPLE, SUBTREE, Tls
25
20
 
26
21
  app = Flask(__name__)
27
22
  app.secret_key = os.urandom(24) # Secret key for session management
@@ -42,8 +37,6 @@ if os.path.isdir(f"{CONFDIR}/.config"):
42
37
  CONFDIR += '/.config'
43
38
  CONFDIR += "/.pywebexec"
44
39
 
45
- if not os.path.exists(COMMAND_STATUS_DIR):
46
- os.makedirs(COMMAND_STATUS_DIR)
47
40
 
48
41
  # In-memory cache for command statuses
49
42
  command_status_cache = {}
@@ -271,7 +264,8 @@ def parseargs():
271
264
  else:
272
265
  print(f"Error: {args.dir} not found", file=sys.stderr)
273
266
  sys.exit(1)
274
-
267
+ if not os.path.exists(COMMAND_STATUS_DIR):
268
+ os.makedirs(COMMAND_STATUS_DIR)
275
269
  if args.gencert:
276
270
  hostname = resolve_hostname(gethostname())
277
271
  args.cert = args.cert or f"{CONFDIR}/pywebexec.crt"
@@ -139,3 +139,29 @@ input {
139
139
  tr.clickable-row {
140
140
  cursor: pointer;
141
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
+ }
@@ -0,0 +1,256 @@
1
+ let currentCommandId = null;
2
+ let outputInterval = null;
3
+
4
+ document.getElementById('launchForm').addEventListener('submit', async (event) => {
5
+ event.preventDefault();
6
+ const commandName = document.getElementById('commandName').value;
7
+ const params = document.getElementById('params').value.split(' ');
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
+ }
25
+ });
26
+
27
+ async function fetchCommands() {
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.slice(0, 8)}` : `<span class="copy_clip" onclick="copyToClipboard('${command.command_id}', 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';
66
+ }
67
+ }
68
+
69
+ async function fetchExecutables() {
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
+ }
88
+ }
89
+
90
+ async function fetchOutput(command_id) {
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;
100
+ clearInterval(outputInterval);
101
+ } else {
102
+ outputDiv.innerHTML = data.output;
103
+ outputDiv.scrollTop = outputDiv.scrollHeight;
104
+ if (data.status != 'running') {
105
+ clearInterval(outputInterval);
106
+ }
107
+ }
108
+ } catch (error) {
109
+ console.log('Error fetching output:', error);
110
+ }
111
+ }
112
+
113
+ async function viewOutput(command_id) {
114
+ adjustOutputHeight();
115
+ currentCommandId = command_id;
116
+ clearInterval(outputInterval);
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);
132
+ }
133
+ }
134
+
135
+ async function relaunchCommand(command_id) {
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.');
165
+ }
166
+ }
167
+
168
+ async function stopCommand(command_id) {
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.');
186
+ }
187
+ }
188
+
189
+ function formatTime(time) {
190
+ if (!time || time === 'N/A') return 'N/A';
191
+ const date = new Date(time);
192
+ return date.toISOString().slice(0, 16).replace('T', ' ');
193
+ }
194
+
195
+ function formatDuration(startTime, endTime) {
196
+ if (!startTime || !endTime) return 'N/A';
197
+ const start = new Date(startTime);
198
+ const end = new Date(endTime);
199
+ const duration = (end - start) / 1000;
200
+ const hours = Math.floor(duration / 3600);
201
+ const minutes = Math.floor((duration % 3600) / 60);
202
+ const seconds = Math.floor(duration % 60);
203
+ return `${hours}h ${minutes}m ${seconds}s`;
204
+ }
205
+
206
+ function copyToClipboard(text, element, event) {
207
+ event.stopPropagation();
208
+ event.stopImmediatePropagation();
209
+ navigator.clipboard.writeText(text).then(() => {
210
+ element.classList.add('copy_clip_ok');
211
+ setTimeout(() => {
212
+ element.classList.remove('copy_clip_ok');
213
+ }, 1000);
214
+ });
215
+ }
216
+
217
+ function adjustOutputHeight() {
218
+ const outputDiv = document.getElementById('output');
219
+ const windowHeight = window.innerHeight;
220
+ const outputTop = outputDiv.getBoundingClientRect().top;
221
+ const maxHeight = windowHeight - outputTop - 30; // 20px for padding/margin
222
+ outputDiv.style.maxHeight = `${maxHeight}px`;
223
+ }
224
+
225
+ function initResizer() {
226
+ const resizer = document.getElementById('resizer');
227
+ const tableContainer = document.getElementById('tableContainer');
228
+ let startY, startHeight;
229
+
230
+ resizer.addEventListener('mousedown', (e) => {
231
+ startY = e.clientY;
232
+ startHeight = parseInt(document.defaultView.getComputedStyle(tableContainer).height, 10);
233
+ document.documentElement.addEventListener('mousemove', doDrag, false);
234
+ document.documentElement.addEventListener('mouseup', stopDrag, false);
235
+ });
236
+
237
+ function doDrag(e) {
238
+ tableContainer.style.height = `${startHeight + e.clientY - startY}px`;
239
+ adjustOutputHeight();
240
+ }
241
+
242
+ function stopDrag() {
243
+ document.documentElement.removeEventListener('mousemove', doDrag, false);
244
+ document.documentElement.removeEventListener('mouseup', stopDrag, false);
245
+ }
246
+ }
247
+
248
+ window.addEventListener('resize', adjustOutputHeight);
249
+ window.addEventListener('load', () => {
250
+ adjustOutputHeight();
251
+ initResizer();
252
+ });
253
+
254
+ fetchExecutables();
255
+ fetchCommands();
256
+ setInterval(fetchCommands, 5000);
@@ -7,6 +7,9 @@
7
7
  <link rel="stylesheet" href="/static/css/style.css">
8
8
  </head>
9
9
  <body>
10
+ <div id="dimmer" class="dimmer">
11
+ <div class="dimmer-text">Server not reachable</div>
12
+ </div>
10
13
  <h2><span class="status-icon title-icon"></span>{{ title }}</h2>
11
14
  <form id="launchForm">
12
15
  <label for="commandName">Command</label>
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.1.11'
16
- __version_tuple__ = version_tuple = (1, 1, 11)
15
+ __version__ = version = '1.1.15'
16
+ __version_tuple__ = version_tuple = (1, 1, 15)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 1.1.11
3
+ Version: 1.1.15
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -1,198 +0,0 @@
1
- let currentCommandId = null;
2
- let outputInterval = null;
3
-
4
- document.getElementById('launchForm').addEventListener('submit', async (event) => {
5
- event.preventDefault();
6
- const commandName = document.getElementById('commandName').value;
7
- const params = document.getElementById('params').value.split(' ');
8
- const response = await fetch('/run_command', {
9
- method: 'POST',
10
- headers: {
11
- 'Content-Type': 'application/json'
12
- },
13
- body: JSON.stringify({ command: commandName, params: params })
14
- });
15
- const data = await response.json();
16
- fetchCommands();
17
- viewOutput(data.command_id);
18
- });
19
-
20
- async function fetchCommands() {
21
- const response = await fetch('/commands');
22
- const commands = await response.json();
23
- commands.sort((a, b) => new Date(b.start_time) - new Date(a.start_time));
24
- const commandsTbody = document.getElementById('commands');
25
- commandsTbody.innerHTML = '';
26
- if (!currentCommandId && commands.length) {
27
- currentCommandId = commands[0].command_id;
28
- viewOutput(currentCommandId);
29
- }
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
- ${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>`}
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
- </td>
46
- <td class="monospace outcol">${command.last_output_line || ''}</td>
47
- `;
48
- commandsTbody.appendChild(commandRow);
49
- });
50
- }
51
-
52
- async function fetchExecutables() {
53
- const response = await fetch('/executables');
54
- const executables = await response.json();
55
- const commandNameSelect = document.getElementById('commandName');
56
- commandNameSelect.innerHTML = '';
57
- executables.forEach(executable => {
58
- const option = document.createElement('option');
59
- option.value = executable;
60
- option.textContent = executable;
61
- commandNameSelect.appendChild(option);
62
- });
63
- }
64
-
65
- async function fetchOutput(command_id) {
66
- const outputDiv = document.getElementById('output');
67
- const response = await fetch(`/command_output/${command_id}`);
68
- const data = await response.json();
69
- if (data.error) {
70
- outputDiv.innerHTML = data.error;
71
- clearInterval(outputInterval);
72
- } else {
73
- outputDiv.innerHTML = data.output;
74
- outputDiv.scrollTop = outputDiv.scrollHeight;
75
- if (data.status != 'running') {
76
- clearInterval(outputInterval);
77
- }
78
- }
79
- }
80
-
81
- async function viewOutput(command_id) {
82
- adjustOutputHeight();
83
- currentCommandId = command_id;
84
- clearInterval(outputInterval);
85
- const response = await fetch(`/command_status/${command_id}`);
86
- const data = await response.json();
87
- if (data.status === 'running') {
88
- fetchOutput(command_id);
89
- outputInterval = setInterval(() => fetchOutput(command_id), 1000);
90
- } else {
91
- fetchOutput(command_id);
92
- }
93
- fetchCommands(); // Refresh the command list to highlight the current command
94
- }
95
-
96
- async function relaunchCommand(command_id) {
97
- const response = await fetch(`/command_status/${command_id}`);
98
- const data = await response.json();
99
- if (data.error) {
100
- alert(data.error);
101
- return;
102
- }
103
- const relaunchResponse = await fetch('/run_command', {
104
- method: 'POST',
105
- headers: {
106
- 'Content-Type': 'application/json'
107
- },
108
- body: JSON.stringify({
109
- command: data.command,
110
- params: data.params
111
- })
112
- });
113
- const relaunchData = await relaunchResponse.json();
114
- fetchCommands();
115
- viewOutput(relaunchData.command_id);
116
- }
117
-
118
- async function stopCommand(command_id) {
119
- const response = await fetch(`/stop_command/${command_id}`, {
120
- method: 'POST'
121
- });
122
- const data = await response.json();
123
- if (data.error) {
124
- alert(data.error);
125
- } else {
126
- alert(data.message);
127
- fetchCommands();
128
- }
129
- }
130
-
131
- function formatTime(time) {
132
- if (!time || time === 'N/A') return 'N/A';
133
- const date = new Date(time);
134
- return date.toISOString().slice(0, 16).replace('T', ' ');
135
- }
136
-
137
- function formatDuration(startTime, endTime) {
138
- if (!startTime || !endTime) return 'N/A';
139
- const start = new Date(startTime);
140
- const end = new Date(endTime);
141
- const duration = (end - start) / 1000;
142
- const hours = Math.floor(duration / 3600);
143
- const minutes = Math.floor((duration % 3600) / 60);
144
- const seconds = Math.floor(duration % 60);
145
- return `${hours}h ${minutes}m ${seconds}s`;
146
- }
147
-
148
- function copyToClipboard(text, element, event) {
149
- event.stopPropagation();
150
- event.stopImmediatePropagation();
151
- navigator.clipboard.writeText(text).then(() => {
152
- element.classList.add('copy_clip_ok');
153
- setTimeout(() => {
154
- element.classList.remove('copy_clip_ok');
155
- }, 1000);
156
- });
157
- }
158
-
159
- function adjustOutputHeight() {
160
- const outputDiv = document.getElementById('output');
161
- const windowHeight = window.innerHeight;
162
- const outputTop = outputDiv.getBoundingClientRect().top;
163
- const maxHeight = windowHeight - outputTop - 30; // 20px for padding/margin
164
- outputDiv.style.maxHeight = `${maxHeight}px`;
165
- }
166
-
167
- function initResizer() {
168
- const resizer = document.getElementById('resizer');
169
- const tableContainer = document.getElementById('tableContainer');
170
- let startY, startHeight;
171
-
172
- resizer.addEventListener('mousedown', (e) => {
173
- startY = e.clientY;
174
- startHeight = parseInt(document.defaultView.getComputedStyle(tableContainer).height, 10);
175
- document.documentElement.addEventListener('mousemove', doDrag, false);
176
- document.documentElement.addEventListener('mouseup', stopDrag, false);
177
- });
178
-
179
- function doDrag(e) {
180
- tableContainer.style.height = `${startHeight + e.clientY - startY}px`;
181
- adjustOutputHeight();
182
- }
183
-
184
- function stopDrag() {
185
- document.documentElement.removeEventListener('mousemove', doDrag, false);
186
- document.documentElement.removeEventListener('mouseup', stopDrag, false);
187
- }
188
- }
189
-
190
- window.addEventListener('resize', adjustOutputHeight);
191
- window.addEventListener('load', () => {
192
- adjustOutputHeight();
193
- initResizer();
194
- });
195
-
196
- fetchExecutables();
197
- fetchCommands();
198
- setInterval(fetchCommands, 5000);
File without changes
File without changes
File without changes
File without changes
File without changes