pywebexec 1.1.11__py3-none-any.whl → 1.1.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pywebexec/pywebexec.py CHANGED
@@ -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
+ }
@@ -5,76 +5,108 @@ document.getElementById('launchForm').addEventListener('submit', async (event) =
5
5
  event.preventDefault();
6
6
  const commandName = document.getElementById('commandName').value;
7
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);
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
+ }
18
25
  });
19
26
 
20
27
  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);
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';
29
66
  }
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
67
  }
51
68
 
52
69
  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
- });
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
+ }
63
88
  }
64
89
 
65
90
  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') {
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;
76
100
  clearInterval(outputInterval);
101
+ } else {
102
+ outputDiv.innerHTML = data.output;
103
+ outputDiv.scrollTop = outputDiv.scrollHeight;
104
+ if (data.status != 'running') {
105
+ clearInterval(outputInterval);
106
+ }
77
107
  }
108
+ } catch (error) {
109
+ console.log('Error fetching output:', error);
78
110
  }
79
111
  }
80
112
 
@@ -82,49 +114,75 @@ async function viewOutput(command_id) {
82
114
  adjustOutputHeight();
83
115
  currentCommandId = command_id;
84
116
  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);
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);
92
132
  }
93
- fetchCommands(); // Refresh the command list to highlight the current command
94
133
  }
95
134
 
96
135
  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;
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.');
102
165
  }
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
166
  }
117
167
 
118
168
  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();
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.');
128
186
  }
129
187
  }
130
188
 
@@ -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>
pywebexec/version.py CHANGED
@@ -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,7 +1,7 @@
1
1
  pywebexec/__init__.py,sha256=4spIsVaF8RJt8S58AG_wWoORRNkws9Iwqprj27C3ljM,99
2
- pywebexec/pywebexec.py,sha256=nXo0K6dBS9zKRUEP1hFyGWpL3BV2sUku3CFeGi0QQ-w,21479
3
- pywebexec/version.py,sha256=9JSmVS_AC0iwzCn3SBDWoTqahTE6DWQZLNs5YmBeMMk,413
4
- pywebexec/static/css/style.css,sha256=rYpzhBftpZ9pw_FTZFwcAnGC9-EhVOOSy6w56Ybe-Wk,2902
2
+ pywebexec/pywebexec.py,sha256=DQJ1uc8otLP0IOa3kk14oAadi27VzZDphtagciy7jXs,21365
3
+ pywebexec/version.py,sha256=lhzTCd4R60DxNLLFyeDP7u5gfCa_r796diruMLGD-PY,413
4
+ pywebexec/static/css/style.css,sha256=BFiRkGhGrNoEmD2M3grfQXwsdI_bJWgB7GPHDoyT7R4,3374
5
5
  pywebexec/static/images/aborted.svg,sha256=_mP43hU5QdRLFZIknBgjx-dIXrHgQG23-QV27ApXK2A,381
6
6
  pywebexec/static/images/copy.svg,sha256=d9OwtGh5GzzZHzYcDrLfNxZYLth1Q64x7bRyYxu4Px0,622
7
7
  pywebexec/static/images/copy_ok.svg,sha256=mEqUVUhSq8xaJK2msQkxRawnz_KwlCZ-tok8QS6hJ3g,451
@@ -9,12 +9,12 @@ pywebexec/static/images/failed.svg,sha256=ADZ7IKrUyOXtqpivnz3VcH0-Wru-I5MOi3OJAk
9
9
  pywebexec/static/images/favicon.svg,sha256=ti80IfuDZwIvQcmJxkOeUaB1iMsiyOPmQmVO-h0y1IU,1126
10
10
  pywebexec/static/images/running.gif,sha256=iYuzQGkMxrakSIwt6gPieKCImGZoSAHmU5MUNZa7cpw,25696
11
11
  pywebexec/static/images/success.svg,sha256=PJDcCSTevJh7rkfSFLtc7P0pbeh8PVQBS8DaOLQemmc,489
12
- pywebexec/static/js/script.js,sha256=fbxMRP_BsRNYlFRJjVipaahV9_7letVITJD0ic4IwfA,7230
12
+ pywebexec/static/js/script.js,sha256=OZlD4iowqDqY9qeOU_g4zc8SQMzTfNnMUfSN9MVCzUc,9328
13
13
  pywebexec/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- pywebexec/templates/index.html,sha256=7dFHAmHXGMrK1-M6PIAbMS0bv7Pi5-6vDoyUk3irnQc,1346
15
- pywebexec-1.1.11.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
16
- pywebexec-1.1.11.dist-info/METADATA,sha256=fc3XLIyoELPSQnOZsUFyFrGCb1OCfrB8AeX3J1ytMqE,6906
17
- pywebexec-1.1.11.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
- pywebexec-1.1.11.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
19
- pywebexec-1.1.11.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
20
- pywebexec-1.1.11.dist-info/RECORD,,
14
+ pywebexec/templates/index.html,sha256=X0TsF1-fEuanpGPQo0GpCvvE87BWUEkYyrWA7IOu20Q,1454
15
+ pywebexec-1.1.15.dist-info/LICENSE,sha256=gRJf0JPT_wsZJsUGlWPTS8Vypfl9vQ1qjp6sNbKykuA,1064
16
+ pywebexec-1.1.15.dist-info/METADATA,sha256=__yydXcA3rMRIxdF8IbxFu9do3Vlf6Ymo5AZr-pIAyg,6906
17
+ pywebexec-1.1.15.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
+ pywebexec-1.1.15.dist-info/entry_points.txt,sha256=l52GBkPCXRkmlHfEyoVauyfBdg8o-CAtC8qQpOIjJK0,55
19
+ pywebexec-1.1.15.dist-info/top_level.txt,sha256=vHoHyzngrfGdm_nM7Xn_5iLmaCrf10XO1EhldgNLEQ8,10
20
+ pywebexec-1.1.15.dist-info/RECORD,,