pywebexec 1.2.4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,178 @@
1
+ body {
2
+ font-family: Arial, sans-serif;
3
+ overflow: hidden;
4
+ }
5
+ .table-container {
6
+ height: 270px;
7
+ overflow-y: auto;
8
+ position: relative;
9
+ border-radius: 10px;
10
+ border: 1px solid #aaa;
11
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.30);
12
+ }
13
+ table {
14
+ width: 100%;
15
+ border-collapse: collapse;
16
+ }
17
+ th, td {
18
+ padding: 8px;
19
+ text-align: left;
20
+ border-bottom: 1px solid #ddd;
21
+ white-space: nowrap;
22
+ }
23
+ th {
24
+ background-color: #444;
25
+ color: #eee;
26
+ position: sticky;
27
+ top: 0;
28
+ z-index: 1;
29
+ }
30
+ .outcol {
31
+ width: 100%;
32
+ }
33
+ select { /* Safari bug */
34
+ font-size: 15px;
35
+ border: #aaa solid 1px;
36
+ border-radius: 5px;
37
+ }
38
+ .output {
39
+ white-space: pre-wrap;
40
+ background: #111;
41
+ padding: 10px;
42
+ border: 1px solid #ccc;
43
+ font-family: monospace;
44
+ border-radius: 10px;
45
+ overflow-y: hidden;
46
+ }
47
+ .copy-icon { cursor: pointer; }
48
+ .monospace { font-family: monospace; }
49
+ .copied { color: green; margin-left: 5px; }
50
+ button {
51
+ -webkit-appearance: none;
52
+ -webkit-border-radius: none;
53
+ appearance: none;
54
+ border-radius: 15px;
55
+ padding-right: 13px;
56
+ border: 1px #555 solid;
57
+ height: 22px;
58
+ font-size: 13px;
59
+ outline: none;
60
+ text-indent: 10px;
61
+ background-color: #eee;
62
+ }
63
+ form {
64
+ padding-bottom: 15px;
65
+ }
66
+ .status-icon {
67
+ display: inline-block;
68
+ width: 16px;
69
+ height: 16px;
70
+ margin-right: 5px;
71
+ background-size: contain;
72
+ background-repeat: no-repeat;
73
+ vertical-align: middle;
74
+ }
75
+ .title-icon {
76
+ width: 30px;
77
+ height: 30px;
78
+ background-image: url("/static/images/favicon.svg");
79
+ vertical-align: bottom;
80
+ }
81
+ .status-running {
82
+ background-image: url("/static/images/running.gif")
83
+ }
84
+ .status-success {
85
+ background-image: url("/static/images/success.svg")
86
+ }
87
+ .status-failed {
88
+ background-image: url("/static/images/failed.svg")
89
+ }
90
+ .status-aborted {
91
+ background-image: url("/static/images/aborted.svg")
92
+ }
93
+ .copy_clip {
94
+ padding-right: 20px;
95
+ background-repeat: no-repeat;
96
+ background-position: right top;
97
+ background-size: 20px 12px;
98
+ white-space: nowrap;
99
+ }
100
+ .copy_clip:hover {
101
+ cursor: pointer;
102
+ background-image: url("/static/images/copy.svg");
103
+ }
104
+ .copy_clip_ok, .copy_clip_ok:hover {
105
+ background-image: url("/static/images/copy_ok.svg");
106
+ }
107
+ input {
108
+ width: 50%;
109
+ -webkit-appearance: none;
110
+ -webkit-border-radius: none;
111
+ appearance: none;
112
+ border-radius: 15px;
113
+ padding: 3px;
114
+ padding-right: 13px;
115
+ border: 1px #aaa solid;
116
+ height: 15px;
117
+ font-size: 15px;
118
+ outline: none;
119
+ text-indent: 5px;
120
+ background-color: white;
121
+ }
122
+ .currentcommand {
123
+ background-color: #eef;
124
+ }
125
+ .resizer {
126
+ width: 100%;
127
+ height: 5px;
128
+ border-radius: 5px;
129
+ background: #aaa;
130
+ cursor: ns-resize;
131
+ position: absolute;
132
+ bottom: 0;
133
+ left: 0;
134
+ }
135
+ .resizer-container {
136
+ position: relative;
137
+ height: 5px;
138
+ margin: 5px;
139
+ /*margin-bottom: 10px;*/
140
+ }
141
+ tr.clickable-row {
142
+ cursor: pointer;
143
+ }
144
+ body.dimmed {
145
+ background-color: rgba(0, 0, 0, 0.5);
146
+ pointer-events: none;
147
+ }
148
+ body.dimmed * {
149
+ pointer-events: none;
150
+ }
151
+ .dimmer {
152
+ display: none;
153
+ position: fixed;
154
+ top: 0;
155
+ left: 0;
156
+ width: 100%;
157
+ height: 100%;
158
+ background-color: rgba(0, 0, 0, 0.5);
159
+ z-index: 1000;
160
+ overflow-y: hidden;
161
+ }
162
+ .dimmer-text {
163
+ color: white;
164
+ font-size: 24px;
165
+ text-align: center;
166
+ position: absolute;
167
+ top: 50%;
168
+ left: 50%;
169
+ transform: translate(-50%, -50%);
170
+ }
171
+ .xterm-cursor {
172
+ display: none;
173
+ visibility: hidden;
174
+ height: 0px;
175
+ }
176
+ span { /* allow wide chars in terminal */
177
+ letter-spacing: unset !important;
178
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
+ * https://github.com/chjj/term.js
5
+ * @license MIT
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ *
25
+ * Originally forked from (with the author's permission):
26
+ * Fabrice Bellard's javascript vt100 for jslinux:
27
+ * http://bellard.org/jslinux/
28
+ * Copyright (c) 2011 Fabrice Bellard
29
+ * The original design remains. The terminal itself
30
+ * has been extended to include xterm CSI codes, among
31
+ * other features.
32
+ */
33
+
34
+ /**
35
+ * Default styles for xterm.js
36
+ */
37
+
38
+ .xterm {
39
+ cursor: text;
40
+ position: relative;
41
+ user-select: none;
42
+ -ms-user-select: none;
43
+ -webkit-user-select: none;
44
+ }
45
+
46
+ .xterm.focus,
47
+ .xterm:focus {
48
+ outline: none;
49
+ }
50
+
51
+ .xterm .xterm-helpers {
52
+ position: absolute;
53
+ top: 0;
54
+ /**
55
+ * The z-index of the helpers must be higher than the canvases in order for
56
+ * IMEs to appear on top.
57
+ */
58
+ z-index: 5;
59
+ }
60
+
61
+ .xterm .xterm-helper-textarea {
62
+ padding: 0;
63
+ border: 0;
64
+ margin: 0;
65
+ /* Move textarea out of the screen to the far left, so that the cursor is not visible */
66
+ position: absolute;
67
+ opacity: 0;
68
+ left: -9999em;
69
+ top: 0;
70
+ width: 0;
71
+ height: 0;
72
+ z-index: -5;
73
+ /** Prevent wrapping so the IME appears against the textarea at the correct position */
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ resize: none;
77
+ }
78
+
79
+ .xterm .composition-view {
80
+ /* TODO: Composition position got messed up somewhere */
81
+ background: #000;
82
+ color: #FFF;
83
+ display: none;
84
+ position: absolute;
85
+ white-space: nowrap;
86
+ z-index: 1;
87
+ }
88
+
89
+ .xterm .composition-view.active {
90
+ display: block;
91
+ }
92
+
93
+ .xterm .xterm-viewport {
94
+ /* On OS X this is required in order for the scroll bar to appear fully opaque */
95
+ background-color: #000;
96
+ overflow-y: scroll;
97
+ cursor: default;
98
+ position: absolute;
99
+ right: 0;
100
+ left: 0;
101
+ top: 0;
102
+ bottom: 0;
103
+ }
104
+
105
+ .xterm .xterm-screen {
106
+ position: relative;
107
+ }
108
+
109
+ .xterm .xterm-screen canvas {
110
+ position: absolute;
111
+ left: 0;
112
+ top: 0;
113
+ }
114
+
115
+ .xterm .xterm-scroll-area {
116
+ visibility: hidden;
117
+ }
118
+
119
+ .xterm-char-measure-element {
120
+ display: inline-block;
121
+ visibility: hidden;
122
+ position: absolute;
123
+ top: 0;
124
+ left: -9999em;
125
+ line-height: normal;
126
+ }
127
+
128
+ .xterm.enable-mouse-events {
129
+ /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
130
+ cursor: default;
131
+ }
132
+
133
+ .xterm.xterm-cursor-pointer,
134
+ .xterm .xterm-cursor-pointer {
135
+ cursor: pointer;
136
+ }
137
+
138
+ .xterm.column-select.focus {
139
+ /* Column selection mode */
140
+ cursor: crosshair;
141
+ }
142
+
143
+ .xterm .xterm-accessibility,
144
+ .xterm .xterm-message {
145
+ position: absolute;
146
+ left: 0;
147
+ top: 0;
148
+ bottom: 0;
149
+ right: 0;
150
+ z-index: 10;
151
+ color: transparent;
152
+ pointer-events: none;
153
+ }
154
+
155
+ .xterm .live-region {
156
+ position: absolute;
157
+ left: -9999px;
158
+ width: 1px;
159
+ height: 1px;
160
+ overflow: hidden;
161
+ }
162
+
163
+ .xterm-dim {
164
+ /* Dim should not apply to background, so the opacity of the foreground color is applied
165
+ * explicitly in the generated class and reset to 1 here */
166
+ opacity: 1 !important;
167
+ }
168
+
169
+ .xterm-underline-1 { text-decoration: underline; }
170
+ .xterm-underline-2 { text-decoration: double underline; }
171
+ .xterm-underline-3 { text-decoration: wavy underline; }
172
+ .xterm-underline-4 { text-decoration: dotted underline; }
173
+ .xterm-underline-5 { text-decoration: dashed underline; }
174
+
175
+ .xterm-overline {
176
+ text-decoration: overline;
177
+ }
178
+
179
+ .xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
180
+ .xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
181
+ .xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
182
+ .xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
183
+ .xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
184
+
185
+ .xterm-strikethrough {
186
+ text-decoration: line-through;
187
+ }
188
+
189
+ .xterm-screen .xterm-decoration-container .xterm-decoration {
190
+ z-index: 6;
191
+ position: absolute;
192
+ }
193
+
194
+ .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
195
+ z-index: 7;
196
+ }
197
+
198
+ .xterm-decoration-overview-ruler {
199
+ z-index: 8;
200
+ position: absolute;
201
+ top: 0;
202
+ right: 0;
203
+ pointer-events: none;
204
+ }
205
+
206
+ .xterm-decoration-top {
207
+ z-index: 2;
208
+ position: relative;
209
+ }
@@ -0,0 +1 @@
1
+ <svg viewBox="-1 -1 13 13" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill-rule="evenodd" clip-rule="evenodd" d="M6 12A6 6 0 106 0a6 6 0 000 12zM3 5a1 1 0 000 2h6a1 1 0 100-2H3z" fill="#ff641a"></path></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="img" viewBox="0 0 16 16" width="25" height="16" fill="currentColor"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" role="img" viewBox="0 0 16 16" width="25" height="16" fill="currentColor" style="display: inline-block; user-select: none; vertical-align: text-bottom; overflow: visible;"><path fill="#118811" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="-2 -2 34 34" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><title>cross-circle</title><desc>Created with Sketch Beta.</desc><defs></defs><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage"><g id="Icon-Set-Filled" sketch:type="MSLayerGroup" transform="translate(-570.000000, -1089.000000)" fill="#ca0000"><path d="M591.657,1109.24 C592.048,1109.63 592.048,1110.27 591.657,1110.66 C591.267,1111.05 590.633,1111.05 590.242,1110.66 L586.006,1106.42 L581.74,1110.69 C581.346,1111.08 580.708,1111.08 580.314,1110.69 C579.921,1110.29 579.921,1109.65 580.314,1109.26 L584.58,1104.99 L580.344,1100.76 C579.953,1100.37 579.953,1099.73 580.344,1099.34 C580.733,1098.95 581.367,1098.95 581.758,1099.34 L585.994,1103.58 L590.292,1099.28 C590.686,1098.89 591.323,1098.89 591.717,1099.28 C592.11,1099.68 592.11,1100.31 591.717,1100.71 L587.42,1105.01 L591.657,1109.24 L591.657,1109.24 Z M586,1089 C577.163,1089 570,1096.16 570,1105 C570,1113.84 577.163,1121 586,1121 C594.837,1121 602,1113.84 602,1105 C602,1096.16 594.837,1089 586,1089 L586,1089 Z" id="cross-circle" sketch:type="MSShapeGroup"></path></g></g></g></svg>
@@ -0,0 +1 @@
1
+ <svg fill="#000000" viewBox="-1 0 19 19" xmlns="http://www.w3.org/2000/svg" class="cf-icon-svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M16.5 9.5a8 8 0 1 1-8-8 8 8 0 0 1 8 8zm-2.97.006a5.03 5.03 0 1 0-5.03 5.03 5.03 5.03 0 0 0 5.03-5.03zm-7.383-.4H4.289a4.237 4.237 0 0 1 2.565-3.498q.1-.042.2-.079a7.702 7.702 0 0 0-.907 3.577zm0 .8a7.7 7.7 0 0 0 .908 3.577q-.102-.037-.201-.079a4.225 4.225 0 0 1-2.565-3.498zm.8-.8a9.04 9.04 0 0 1 .163-1.402 6.164 6.164 0 0 1 .445-1.415c.289-.615.66-1.013.945-1.013.285 0 .656.398.945 1.013a6.18 6.18 0 0 1 .445 1.415 9.078 9.078 0 0 1 .163 1.402zm3.106.8a9.073 9.073 0 0 1-.163 1.402 6.187 6.187 0 0 1-.445 1.415c-.289.616-.66 1.013-.945 1.013-.285 0-.656-.397-.945-1.013a6.172 6.172 0 0 1-.445-1.415 9.036 9.036 0 0 1-.163-1.402zm1.438-3.391a4.211 4.211 0 0 1 1.22 2.591h-1.858a7.698 7.698 0 0 0-.908-3.577q.102.037.201.08a4.208 4.208 0 0 1 1.345.906zm-.638 3.391h1.858a4.238 4.238 0 0 1-2.565 3.498q-.1.043-.2.08a7.697 7.697 0 0 0 .907-3.578z"></path></g></svg>
Binary file
@@ -0,0 +1 @@
1
+ <svg viewBox="0 75 949 949" xmlns="http://www.w3.org/2000/svg" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill="#00a600" d="M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896zm-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.272 38.272 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336L456.192 600.384z"></path></g></svg>
@@ -0,0 +1,280 @@
1
+ let currentCommandId = null;
2
+ let outputInterval = null;
3
+ let nextOutputLink = null;
4
+
5
+ const terminal = new Terminal({
6
+ cursorBlink: false,
7
+ cursorHidden: true,
8
+ disableStdin: true,
9
+ convertEol: true,
10
+ fontFamily: 'Consolas NF, monospace, courier-new, courier'
11
+ });
12
+ const fitAddon = new FitAddon.FitAddon();
13
+ terminal.loadAddon(fitAddon);
14
+ terminal.open(document.getElementById('output'));
15
+ fitAddon.fit();
16
+
17
+ terminal.onSelectionChange(() => {
18
+ const selectionText = terminal.getSelection();
19
+ if (selectionText) {
20
+ navigator.clipboard.writeText(selectionText).catch(err => {
21
+ console.error('Failed to copy text to clipboard:', err);
22
+ });
23
+ }
24
+ });
25
+
26
+ document.getElementById('launchForm').addEventListener('submit', async (event) => {
27
+ event.preventDefault();
28
+ const commandName = document.getElementById('commandName').value;
29
+ const params = document.getElementById('params').value.split(' ');
30
+ try {
31
+ const response = await fetch('/run_command', {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/json'
35
+ },
36
+ body: JSON.stringify({ command: commandName, params: params })
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error('Failed to launch command');
40
+ }
41
+ const data = await response.json();
42
+ await new Promise(r => setTimeout(r, 200));
43
+ fetchCommands();
44
+ viewOutput(data.command_id);
45
+ } catch (error) {
46
+ console.log('Error running command:', error);
47
+ }
48
+ });
49
+
50
+ async function fetchCommands() {
51
+ try {
52
+ const response = await fetch('/commands');
53
+ if (!response.ok) {
54
+ document.getElementById('dimmer').style.display = 'block';
55
+ return;
56
+ }
57
+ const commands = await response.json();
58
+ commands.sort((a, b) => new Date(b.start_time) - new Date(a.start_time));
59
+ const commandsTbody = document.getElementById('commands');
60
+ commandsTbody.innerHTML = '';
61
+ if (!currentCommandId && commands.length) {
62
+ currentCommandId = commands[0].command_id;
63
+ viewOutput(currentCommandId);
64
+ }
65
+ commands.forEach(command => {
66
+ const commandRow = document.createElement('tr');
67
+ commandRow.className = `clickable-row ${command.command_id === currentCommandId ? 'currentcommand' : ''}`;
68
+ commandRow.onclick = () => viewOutput(command.command_id);
69
+ commandRow.innerHTML = `
70
+ <td class="monospace">
71
+ ${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>`}
72
+ </td>
73
+ <td>${formatTime(command.start_time)}</td>
74
+ <td>${command.status === 'running' ? formatDuration(command.start_time, new Date().toISOString()) : formatDuration(command.start_time, command.end_time)}</td>
75
+ <td>${command.command.replace(/^\.\//, '')}</td>
76
+ <td>${command.exit_code}</td>
77
+ <td><span class="status-icon status-${command.status}"></span>${command.status}</td>
78
+ <td>
79
+ ${command.status === 'running' ? `<button onclick="stopCommand('${command.command_id}')">Stop</button>` : `<button onclick="relaunchCommand('${command.command_id}')">Run</button>`}
80
+ </td>
81
+ <td class="monospace outcol">${command.last_output_line || ''}</td>
82
+ `;
83
+ commandsTbody.appendChild(commandRow);
84
+ });
85
+ document.getElementById('dimmer').style.display = 'none';
86
+ } catch (error) {
87
+ console.log('Error fetching commands:', error);
88
+ document.getElementById('dimmer').style.display = 'block';
89
+ }
90
+ }
91
+
92
+ async function fetchExecutables() {
93
+ try {
94
+ const response = await fetch('/executables');
95
+ if (!response.ok) {
96
+ throw new Error('Failed to fetch command status');
97
+ }
98
+ const executables = await response.json();
99
+ const commandNameSelect = document.getElementById('commandName');
100
+ commandNameSelect.innerHTML = '';
101
+ executables.forEach(executable => {
102
+ const option = document.createElement('option');
103
+ option.value = executable;
104
+ option.textContent = executable;
105
+ commandNameSelect.appendChild(option);
106
+ });
107
+ } catch (error) {
108
+ console.log('Error fetching executables:', error);
109
+ alert("Failed to fetch executables");
110
+ }
111
+ }
112
+
113
+ async function fetchOutput(url) {
114
+ try {
115
+ const response = await fetch(url);
116
+ if (!response.ok) {
117
+ return;
118
+ }
119
+ const data = await response.json();
120
+ if (data.error) {
121
+ terminal.write(data.error);
122
+ clearInterval(outputInterval);
123
+ } else {
124
+ terminal.write(data.output);
125
+ nextOutputLink = data.links.next;
126
+ if (data.status != 'running') {
127
+ clearInterval(outputInterval);
128
+ }
129
+ }
130
+ } catch (error) {
131
+ console.log('Error fetching output:', error);
132
+ }
133
+ }
134
+
135
+ async function viewOutput(command_id) {
136
+ adjustOutputHeight();
137
+ currentCommandId = command_id;
138
+ nextOutputLink = `/command_output/${command_id}`;
139
+ clearInterval(outputInterval);
140
+ terminal.clear();
141
+ try {
142
+ const response = await fetch(`/command_status/${command_id}`);
143
+ if (!response.ok) {
144
+ return;
145
+ }
146
+ const data = await response.json();
147
+ if (data.status === 'running') {
148
+ fetchOutput(nextOutputLink);
149
+ outputInterval = setInterval(() => fetchOutput(nextOutputLink), 1000);
150
+ } else {
151
+ fetchOutput(nextOutputLink);
152
+ }
153
+ fetchCommands(); // Refresh the command list to highlight the current command
154
+ } catch (error) {
155
+ console.log('Error viewing output:', error);
156
+ }
157
+ }
158
+
159
+ async function relaunchCommand(command_id) {
160
+ try {
161
+ const response = await fetch(`/command_status/${command_id}`);
162
+ if (!response.ok) {
163
+ throw new Error('Failed to fetch command status');
164
+ }
165
+ const data = await response.json();
166
+ if (data.error) {
167
+ alert(data.error);
168
+ return;
169
+ }
170
+ const relaunchResponse = await fetch('/run_command', {
171
+ method: 'POST',
172
+ headers: {
173
+ 'Content-Type': 'application/json'
174
+ },
175
+ body: JSON.stringify({
176
+ command: data.command,
177
+ params: data.params
178
+ })
179
+ });
180
+ if (!relaunchResponse.ok) {
181
+ throw new Error('Failed to relaunch command');
182
+ }
183
+ const relaunchData = await relaunchResponse.json();
184
+ fetchCommands();
185
+ viewOutput(relaunchData.command_id);
186
+ } catch (error) {
187
+ console.log('Error relaunching command:', error);
188
+ alert('Failed to relaunch command. Please try again.');
189
+ }
190
+ }
191
+
192
+ async function stopCommand(command_id) {
193
+ try {
194
+ const response = await fetch(`/stop_command/${command_id}`, {
195
+ method: 'POST'
196
+ });
197
+ if (!response.ok) {
198
+ throw new Error('Failed to stop command');
199
+ }
200
+ const data = await response.json();
201
+ if (data.error) {
202
+ alert(data.error);
203
+ } else {
204
+ fetchCommands();
205
+ }
206
+ } catch (error) {
207
+ console.log('Error stopping command:', error);
208
+ alert('Failed to stop command. Please try again.');
209
+ }
210
+ }
211
+
212
+ function formatTime(time) {
213
+ if (!time || time === 'N/A') return 'N/A';
214
+ const date = new Date(time);
215
+ return date.toISOString().slice(0, 16).replace('T', ' ');
216
+ }
217
+
218
+ function formatDuration(startTime, endTime) {
219
+ if (!startTime || !endTime) return 'N/A';
220
+ const start = new Date(startTime);
221
+ const end = new Date(endTime);
222
+ const duration = (end - start) / 1000;
223
+ const hours = Math.floor(duration / 3600);
224
+ const minutes = Math.floor((duration % 3600) / 60);
225
+ const seconds = Math.floor(duration % 60);
226
+ return `${hours}h ${minutes}m ${seconds}s`;
227
+ }
228
+
229
+ function copyToClipboard(text, element, event) {
230
+ event.stopPropagation();
231
+ event.stopImmediatePropagation();
232
+ navigator.clipboard.writeText(text).then(() => {
233
+ element.classList.add('copy_clip_ok');
234
+ setTimeout(() => {
235
+ element.classList.remove('copy_clip_ok');
236
+ }, 1000);
237
+ });
238
+ }
239
+
240
+ function adjustOutputHeight() {
241
+ const outputDiv = document.getElementById('output');
242
+ const windowHeight = window.innerHeight;
243
+ const outputTop = outputDiv.getBoundingClientRect().top;
244
+ const maxHeight = windowHeight - outputTop - 30; // 20px for padding/margin
245
+ outputDiv.style.height = `${maxHeight}px`;
246
+ fitAddon.fit();
247
+ }
248
+
249
+ function initResizer() {
250
+ const resizer = document.getElementById('resizer');
251
+ const tableContainer = document.getElementById('tableContainer');
252
+ let startY, startHeight;
253
+
254
+ resizer.addEventListener('mousedown', (e) => {
255
+ startY = e.clientY;
256
+ startHeight = parseInt(document.defaultView.getComputedStyle(tableContainer).height, 10);
257
+ document.documentElement.addEventListener('mousemove', doDrag, false);
258
+ document.documentElement.addEventListener('mouseup', stopDrag, false);
259
+ });
260
+
261
+ function doDrag(e) {
262
+ tableContainer.style.height = `${startHeight + e.clientY - startY}px`;
263
+ adjustOutputHeight();
264
+ }
265
+
266
+ function stopDrag() {
267
+ document.documentElement.removeEventListener('mousemove', doDrag, false);
268
+ document.documentElement.removeEventListener('mouseup', stopDrag, false);
269
+ }
270
+ }
271
+
272
+ window.addEventListener('resize', adjustOutputHeight);
273
+ window.addEventListener('load', () => {
274
+ adjustOutputHeight();
275
+ initResizer();
276
+ });
277
+
278
+ fetchExecutables();
279
+ fetchCommands();
280
+ setInterval(fetchCommands, 5000);