pywebexec 0.0.7__tar.gz → 0.0.9__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 (24) hide show
  1. {pywebexec-0.0.7/pywebexec.egg-info → pywebexec-0.0.9}/PKG-INFO +3 -2
  2. {pywebexec-0.0.7 → pywebexec-0.0.9}/README.md +1 -1
  3. {pywebexec-0.0.7 → pywebexec-0.0.9}/pyproject.toml +1 -0
  4. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/pywebexec.py +40 -7
  5. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/templates/index.html +63 -6
  6. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/version.py +2 -2
  7. {pywebexec-0.0.7 → pywebexec-0.0.9/pywebexec.egg-info}/PKG-INFO +3 -2
  8. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec.egg-info/requires.txt +1 -0
  9. {pywebexec-0.0.7 → pywebexec-0.0.9}/.github/workflows/python-publish.yml +0 -0
  10. {pywebexec-0.0.7 → pywebexec-0.0.9}/.gitignore +0 -0
  11. {pywebexec-0.0.7 → pywebexec-0.0.9}/LICENSE +0 -0
  12. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/__init__.py +0 -0
  13. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/aborted.svg +0 -0
  14. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/copy.svg +0 -0
  15. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/copy_ok.svg +0 -0
  16. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/failed.svg +0 -0
  17. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/running.svg +0 -0
  18. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/static/images/success.svg +0 -0
  19. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec/templates/__init__.py +0 -0
  20. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec.egg-info/SOURCES.txt +0 -0
  21. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec.egg-info/dependency_links.txt +0 -0
  22. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec.egg-info/entry_points.txt +0 -0
  23. {pywebexec-0.0.7 → pywebexec-0.0.9}/pywebexec.egg-info/top_level.txt +0 -0
  24. {pywebexec-0.0.7 → pywebexec-0.0.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -53,6 +53,7 @@ Classifier: Topic :: System :: Systems Administration
53
53
  Requires-Python: >=3.6
54
54
  Description-Content-Type: text/markdown
55
55
  License-File: LICENSE
56
+ Requires-Dist: python-daemon>=2.3.2
56
57
  Requires-Dist: cryptography>=40.0.2
57
58
  Requires-Dist: Flask>=3.0.3
58
59
  Requires-Dist: Flask-HTTPAuth>=4.8.0
@@ -127,5 +128,5 @@ $ pywebexec start
127
128
  $ pywebexec status
128
129
  $ pywebexec stop
129
130
  ```
130
- * log of server are stored in current directory `.web_status/pwexec_<listen>:<port>.log`
131
+ * log of server are stored in directory `[.config/].pywebexec/pywebexec_<listen>:<port>.log`
131
132
 
@@ -68,5 +68,5 @@ $ pywebexec start
68
68
  $ pywebexec status
69
69
  $ pywebexec stop
70
70
  ```
71
- * log of server are stored in current directory `.web_status/pwexec_<listen>:<port>.log`
71
+ * log of server are stored in directory `[.config/].pywebexec/pywebexec_<listen>:<port>.log`
72
72
 
@@ -9,6 +9,7 @@ maintainers = [
9
9
 
10
10
  description = "Simple Python HTTP Exec Server"
11
11
  dependencies = [
12
+ "python-daemon>=2.3.2",
12
13
  "cryptography>=40.0.2",
13
14
  "Flask>=3.0.3",
14
15
  "Flask-HTTPAuth>=4.8.0",
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from flask import Flask, request, jsonify, render_template
2
3
  from flask_httpauth import HTTPBasicAuth
3
4
  import subprocess
@@ -10,13 +11,17 @@ import random
10
11
  import string
11
12
  from datetime import datetime
12
13
  import shlex
13
- from gunicorn.app.base import BaseApplication
14
+ from gunicorn.app.base import BaseApplication, Application
14
15
 
15
16
  app = Flask(__name__)
16
17
  auth = HTTPBasicAuth()
17
18
 
18
19
  # Directory to store the script status and output
19
20
  SCRIPT_STATUS_DIR = '.web_status'
21
+ CONFDIR = os.path.expanduser("~/")
22
+ if os.path.isdir(f"{CONFDIR}/.config"):
23
+ CONFDIR += '/.config'
24
+ CONFDIR += "/.pywebexec"
20
25
 
21
26
  if not os.path.exists(SCRIPT_STATUS_DIR):
22
27
  os.makedirs(SCRIPT_STATUS_DIR)
@@ -25,7 +30,7 @@ def generate_random_password(length=12):
25
30
  characters = string.ascii_letters + string.digits + string.punctuation
26
31
  return ''.join(random.choice(characters) for i in range(length))
27
32
 
28
- class StandaloneApplication(BaseApplication):
33
+ class StandaloneApplication(Application):
29
34
 
30
35
  def __init__(self, app, options=None):
31
36
  self.options = options or {}
@@ -44,13 +49,25 @@ class StandaloneApplication(BaseApplication):
44
49
  return self.application
45
50
 
46
51
 
47
- def start_gunicorn():
52
+ def start_gunicorn(daemon=False, baselog=None):
53
+ if daemon:
54
+ errorlog = f"{baselog}.log"
55
+ accesslog = None # f"{baselog}.access.log"
56
+ pidfile = f"{baselog}.pid"
57
+ else:
58
+ errorlog = "-"
59
+ accesslog = "-"
60
+ pidfile = None
48
61
  options = {
49
62
  'bind': '%s:%s' % (args.listen, args.port),
50
63
  'workers': 4,
51
64
  'timeout': 600,
52
65
  'certfile': args.cert,
53
66
  'keyfile': args.key,
67
+ 'daemon': daemon,
68
+ 'errorlog': errorlog,
69
+ 'accesslog': accesslog,
70
+ 'pidfile': pidfile,
54
71
  }
55
72
  StandaloneApplication(app, options=options).run()
56
73
 
@@ -86,7 +103,10 @@ def daemon_d(action, pidfilepath, hostname=None, args=None):
86
103
  working_directory=os.getcwd(),
87
104
  )
88
105
  with daemon_context:
89
- start_gunicorn()
106
+ try:
107
+ start_gunicorn()
108
+ except Exception as e:
109
+ print(e)
90
110
 
91
111
  def parseargs():
92
112
  global app, args
@@ -114,6 +134,15 @@ def parseargs():
114
134
  parser.add_argument("action", nargs="?", help="daemon action start/stop/restart/status", choices=["start","stop","restart","status"])
115
135
 
116
136
  args = parser.parse_args()
137
+ if os.path.isdir(args.dir):
138
+ try:
139
+ os.chdir(args.dir)
140
+ except OSError:
141
+ print(f"Error: cannot chdir {args.dir}", file=sys.stderr)
142
+ sys.exit(1)
143
+ else:
144
+ print(f"Error: {args.dir} not found", file=sys.stderr)
145
+ sys.exit(1)
117
146
 
118
147
  if args.user:
119
148
  app.config['USER'] = args.user
@@ -317,10 +346,14 @@ def verify_password(username, password):
317
346
  return username == app.config['USER'] and password == app.config['PASSWORD']
318
347
 
319
348
  def main():
349
+ basef = f"{CONFDIR}/pywebexec_{args.listen}:{args.port}"
350
+ if not os.path.exists(CONFDIR):
351
+ os.mkdir(CONFDIR, mode=0o700)
352
+ if args.action == "start":
353
+ return start_gunicorn(daemon=True, baselog=basef)
320
354
  if args.action:
321
- daemon_d(args.action, pidfilepath=SCRIPT_STATUS_DIR+'/pywebexec')
322
- else:
323
- start_gunicorn()
355
+ return daemon_d(args.action, pidfilepath=basef)
356
+ return start_gunicorn()
324
357
 
325
358
  if __name__ == '__main__':
326
359
  main()
@@ -5,7 +5,7 @@
5
5
  <title>pywebexec</title>
6
6
  <style>
7
7
  body { font-family: Arial, sans-serif; }
8
- .table-container { max-height: 385px; overflow-y: auto; }
8
+ .table-container { height: 380px; overflow-y: auto; position: relative; }
9
9
  table { width: 100%; border-collapse: collapse; }
10
10
  th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
11
11
  th { background-color: #f2f2f2; position: sticky; top: 0; z-index: 1; }
@@ -16,6 +16,7 @@
16
16
  border: 1px solid #ccc;
17
17
  font-family: monospace;
18
18
  border-radius: 15px;
19
+ overflow-y: auto;
19
20
  }
20
21
  .copy-icon { cursor: pointer; }
21
22
  .monospace { font-family: monospace; }
@@ -85,6 +86,20 @@
85
86
  .currentscript {
86
87
  background-color: #eef;
87
88
  }
89
+ .resizer {
90
+ width: 100%;
91
+ height: 5px;
92
+ background: #aaa;
93
+ cursor: ns-resize;
94
+ position: absolute;
95
+ bottom: 0;
96
+ left: 0;
97
+ }
98
+ .resizer-container {
99
+ position: relative;
100
+ height: 5px;
101
+ margin-bottom: 10px;
102
+ }
88
103
  </style>
89
104
  </head>
90
105
  <body>
@@ -96,7 +111,7 @@
96
111
  <input type="text" id="params" name="params">
97
112
  <button type="submit">Launch</button>
98
113
  </form>
99
- <div class="table-container">
114
+ <div class="table-container" id="tableContainer">
100
115
  <table>
101
116
  <thead>
102
117
  <tr>
@@ -112,6 +127,9 @@
112
127
  <tbody id="scripts"></tbody>
113
128
  </table>
114
129
  </div>
130
+ <div class="resizer-container">
131
+ <div class="resizer" id="resizer"></div>
132
+ </div>
115
133
  <div id="output" class="output"></div>
116
134
 
117
135
  <script>
@@ -131,7 +149,7 @@
131
149
  });
132
150
  const data = await response.json();
133
151
  fetchScripts();
134
- viewOutput(data.script_id)
152
+ viewOutput(data.script_id);
135
153
  });
136
154
 
137
155
  async function fetchScripts() {
@@ -184,13 +202,15 @@
184
202
  clearInterval(outputInterval);
185
203
  } else {
186
204
  outputDiv.innerHTML = data.output;
205
+ outputDiv.scrollTop = outputDiv.scrollHeight;
187
206
  if (data.status != 'running') {
188
- clearInterval(outputInterval)
207
+ clearInterval(outputInterval);
189
208
  }
190
209
  }
191
210
  }
192
211
 
193
212
  async function viewOutput(script_id) {
213
+ adjustOutputHeight();
194
214
  currentScriptId = script_id;
195
215
  clearInterval(outputInterval);
196
216
  const response = await fetch(`/script_status/${script_id}`);
@@ -223,7 +243,7 @@
223
243
  });
224
244
  const relaunchData = await relaunchResponse.json();
225
245
  fetchScripts();
226
- viewOutput(relaunchData.script_id)
246
+ viewOutput(relaunchData.script_id);
227
247
  }
228
248
 
229
249
  async function stopScript(script_id) {
@@ -258,13 +278,50 @@
258
278
 
259
279
  function copyToClipboard(text, element) {
260
280
  navigator.clipboard.writeText(text).then(() => {
261
- element.classList.add('copy_clip_ok')
281
+ element.classList.add('copy_clip_ok');
262
282
  setTimeout(() => {
263
283
  element.classList.remove('copy_clip_ok');
264
284
  }, 2000);
265
285
  });
266
286
  }
267
287
 
288
+ function adjustOutputHeight() {
289
+ const outputDiv = document.getElementById('output');
290
+ const windowHeight = window.innerHeight;
291
+ const outputTop = outputDiv.getBoundingClientRect().top;
292
+ const maxHeight = windowHeight - outputTop - 30; // 20px for padding/margin
293
+ outputDiv.style.maxHeight = `${maxHeight}px`;
294
+ }
295
+
296
+ function initResizer() {
297
+ const resizer = document.getElementById('resizer');
298
+ const tableContainer = document.getElementById('tableContainer');
299
+ let startY, startHeight;
300
+
301
+ resizer.addEventListener('mousedown', (e) => {
302
+ startY = e.clientY;
303
+ startHeight = parseInt(document.defaultView.getComputedStyle(tableContainer).height, 10);
304
+ document.documentElement.addEventListener('mousemove', doDrag, false);
305
+ document.documentElement.addEventListener('mouseup', stopDrag, false);
306
+ });
307
+
308
+ function doDrag(e) {
309
+ tableContainer.style.height = `${startHeight + e.clientY - startY}px`;
310
+ adjustOutputHeight();
311
+ }
312
+
313
+ function stopDrag() {
314
+ document.documentElement.removeEventListener('mousemove', doDrag, false);
315
+ document.documentElement.removeEventListener('mouseup', stopDrag, false);
316
+ }
317
+ }
318
+
319
+ window.addEventListener('resize', adjustOutputHeight);
320
+ window.addEventListener('load', () => {
321
+ adjustOutputHeight();
322
+ initResizer();
323
+ });
324
+
268
325
  fetchScripts();
269
326
  fetchExecutables();
270
327
  setInterval(fetchScripts, 5000);
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.0.7'
16
- __version_tuple__ = version_tuple = (0, 0, 7)
15
+ __version__ = version = '0.0.9'
16
+ __version_tuple__ = version_tuple = (0, 0, 9)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pywebexec
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: Simple Python HTTP Exec Server
5
5
  Home-page: https://github.com/joknarf/pywebexec
6
6
  Author: Franck Jouvanceau
@@ -53,6 +53,7 @@ Classifier: Topic :: System :: Systems Administration
53
53
  Requires-Python: >=3.6
54
54
  Description-Content-Type: text/markdown
55
55
  License-File: LICENSE
56
+ Requires-Dist: python-daemon>=2.3.2
56
57
  Requires-Dist: cryptography>=40.0.2
57
58
  Requires-Dist: Flask>=3.0.3
58
59
  Requires-Dist: Flask-HTTPAuth>=4.8.0
@@ -127,5 +128,5 @@ $ pywebexec start
127
128
  $ pywebexec status
128
129
  $ pywebexec stop
129
130
  ```
130
- * log of server are stored in current directory `.web_status/pwexec_<listen>:<port>.log`
131
+ * log of server are stored in directory `[.config/].pywebexec/pywebexec_<listen>:<port>.log`
131
132
 
@@ -1,3 +1,4 @@
1
+ python-daemon>=2.3.2
1
2
  cryptography>=40.0.2
2
3
  Flask>=3.0.3
3
4
  Flask-HTTPAuth>=4.8.0
File without changes
File without changes
File without changes