meerschaum 2.7.7__py3-none-any.whl → 2.7.9__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.
- meerschaum/_internal/term/TermPageHandler.py +54 -4
- meerschaum/_internal/term/__init__.py +13 -5
- meerschaum/_internal/term/tools.py +41 -6
- meerschaum/actions/copy.py +1 -0
- meerschaum/actions/start.py +25 -10
- meerschaum/api/dash/callbacks/dashboard.py +43 -2
- meerschaum/api/dash/components.py +13 -6
- meerschaum/api/dash/keys.py +82 -108
- meerschaum/api/dash/pages/dashboard.py +17 -17
- meerschaum/api/dash/sessions.py +1 -0
- meerschaum/api/dash/webterm.py +17 -6
- meerschaum/api/resources/static/js/terminado.js +0 -2
- meerschaum/api/resources/templates/termpage.html +47 -4
- meerschaum/api/routes/_webterm.py +15 -11
- meerschaum/config/_default.py +6 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +2 -2
- meerschaum/connectors/sql/_SQLConnector.py +2 -9
- meerschaum/connectors/sql/_fetch.py +5 -30
- meerschaum/connectors/sql/_pipes.py +7 -4
- meerschaum/connectors/sql/_sql.py +56 -31
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -2
- meerschaum/core/Pipe/_fetch.py +4 -0
- meerschaum/core/Pipe/_sync.py +22 -15
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/utils/daemon/Daemon.py +24 -11
- meerschaum/utils/daemon/RotatingFile.py +3 -3
- meerschaum/utils/dataframe.py +42 -12
- meerschaum/utils/dtypes/__init__.py +153 -24
- meerschaum/utils/dtypes/sql.py +58 -9
- meerschaum/utils/formatting/__init__.py +2 -2
- meerschaum/utils/formatting/_pprint.py +13 -12
- meerschaum/utils/misc.py +32 -18
- meerschaum/utils/prompt.py +1 -1
- meerschaum/utils/sql.py +26 -8
- meerschaum/utils/venv/__init__.py +10 -14
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/METADATA +1 -1
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/RECORD +44 -44
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.7.dist-info → meerschaum-2.7.9.dist-info}/zip-safe +0 -0
@@ -7,14 +7,64 @@ Define the terminal page handler class.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
from
|
11
|
-
|
10
|
+
from typing import Any
|
11
|
+
|
12
|
+
import meerschaum as mrsm
|
13
|
+
from meerschaum.utils.warnings import warn
|
14
|
+
tornado_web = mrsm.attempt_import('tornado.web', lazy=False)
|
15
|
+
terminado = mrsm.attempt_import('terminado', lazy=False)
|
16
|
+
|
17
|
+
tmux_suffix = mrsm.get_config('system', 'webterm', 'tmux', 'session_suffix')
|
18
|
+
|
12
19
|
|
13
20
|
class TermPageHandler(tornado_web.RequestHandler):
|
14
|
-
|
21
|
+
|
22
|
+
def get(self, term_name):
|
23
|
+
term_manager = self.application.settings['term_manager']
|
24
|
+
shell_command = [
|
25
|
+
cmd.replace('MRSM_SESSION', term_name + tmux_suffix)
|
26
|
+
for cmd in term_manager.shell_command
|
27
|
+
]
|
28
|
+
terminal = term_manager.new_terminal(shell_command=shell_command)
|
29
|
+
terminal.term_name = term_name
|
30
|
+
term_manager.terminals[term_name] = terminal
|
31
|
+
term_manager.start_reading(terminal)
|
15
32
|
return self.render(
|
16
33
|
"termpage.html",
|
17
34
|
static=self.static_url,
|
18
|
-
ws_url_path="/
|
35
|
+
ws_url_path=f"/_websocket/{term_name}",
|
19
36
|
)
|
20
37
|
|
38
|
+
|
39
|
+
class CustomTermSocket(terminado.TermSocket):
|
40
|
+
|
41
|
+
def open(self, url_component: Any = None) -> None:
|
42
|
+
super(terminado.TermSocket, self).open(url_component)
|
43
|
+
url_component = (
|
44
|
+
url_component.decode("utf-8")
|
45
|
+
if isinstance(url_component, bytes)
|
46
|
+
else url_component
|
47
|
+
)
|
48
|
+
self.term_name = url_component
|
49
|
+
shell_command = [
|
50
|
+
cmd.replace('MRSM_SESSION', self.term_name + tmux_suffix)
|
51
|
+
for cmd in self.term_manager.shell_command
|
52
|
+
]
|
53
|
+
if self.term_name not in self.term_manager.terminals:
|
54
|
+
self.terminal = self.term_manager.new_terminal(shell_command=shell_command)
|
55
|
+
self.terminal.term_name = self.term_name
|
56
|
+
self.term_manager.terminals[self.term_name] = self.terminal
|
57
|
+
self.term_manager.start_reading(self.terminal)
|
58
|
+
else:
|
59
|
+
self.terminal = self.term_manager.terminals[self.term_name]
|
60
|
+
self.terminal.clients.append(self)
|
61
|
+
self.send_json_message(["setup", {}])
|
62
|
+
buffered = ""
|
63
|
+
preopen_buffer = self.terminal.read_buffer.copy()
|
64
|
+
while True:
|
65
|
+
if not preopen_buffer:
|
66
|
+
break
|
67
|
+
s = preopen_buffer.popleft()
|
68
|
+
buffered += s
|
69
|
+
if buffered:
|
70
|
+
self.on_pty_read(buffered)
|
@@ -9,10 +9,14 @@ Build the web console virtual terminal using Tornado and xterm.js.
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
11
|
from typing import Optional, Tuple
|
12
|
+
|
13
|
+
import meerschaum as mrsm
|
12
14
|
from meerschaum.utils.packages import attempt_import
|
13
|
-
from meerschaum._internal.term.TermPageHandler import TermPageHandler
|
15
|
+
from meerschaum._internal.term.TermPageHandler import TermPageHandler, CustomTermSocket
|
14
16
|
from meerschaum.config._paths import API_TEMPLATES_PATH, API_STATIC_PATH
|
15
17
|
from meerschaum.utils.venv import venv_executable
|
18
|
+
from meerschaum.utils.misc import is_tmux_available
|
19
|
+
from meerschaum.utils.daemon._names import get_new_daemon_name
|
16
20
|
|
17
21
|
tornado, tornado_ioloop, terminado = attempt_import(
|
18
22
|
'tornado', 'tornado.ioloop', 'terminado', lazy=False,
|
@@ -40,16 +44,19 @@ def get_webterm_app_and_manager(
|
|
40
44
|
"from meerschaum._internal.entry import get_shell; "
|
41
45
|
f"get_shell({shell_kwargs_str}).cmdloop()"
|
42
46
|
]
|
47
|
+
webterm_cf = mrsm.get_config('system', 'webterm')
|
48
|
+
if webterm_cf.get('tmux', {}).get('enabled', False) and is_tmux_available():
|
49
|
+
commands = ['tmux', 'new-session', '-A', '-s', 'MRSM_SESSION'] + commands
|
43
50
|
|
44
|
-
term_manager = terminado.
|
51
|
+
term_manager = terminado.NamedTermManager(shell_command=commands)
|
45
52
|
handlers = [
|
46
53
|
(
|
47
|
-
r"/
|
48
|
-
|
54
|
+
r"/_websocket/(.+)/?",
|
55
|
+
CustomTermSocket,
|
49
56
|
{'term_manager': term_manager}
|
50
57
|
),
|
51
58
|
(
|
52
|
-
r"/",
|
59
|
+
r"/webterm/(.+)/?",
|
53
60
|
TermPageHandler
|
54
61
|
),
|
55
62
|
]
|
@@ -57,5 +64,6 @@ def get_webterm_app_and_manager(
|
|
57
64
|
handlers,
|
58
65
|
static_path=API_STATIC_PATH,
|
59
66
|
template_path=API_TEMPLATES_PATH,
|
67
|
+
term_manager=term_manager,
|
60
68
|
)
|
61
69
|
return tornado_app, term_manager
|
@@ -6,16 +6,21 @@
|
|
6
6
|
Utility functions regarding the webterm.
|
7
7
|
"""
|
8
8
|
|
9
|
-
|
9
|
+
import meerschaum as mrsm
|
10
|
+
from meerschaum.utils.typing import List
|
10
11
|
|
11
|
-
|
12
|
+
|
13
|
+
def is_webterm_running(
|
14
|
+
host: str,
|
15
|
+
port: int,
|
16
|
+
protocol: str = 'http',
|
17
|
+
session_id: str = 'mrsm',
|
18
|
+
) -> int:
|
12
19
|
"""
|
13
20
|
Determine whether the webterm service is running on a given host and port.
|
14
21
|
"""
|
15
|
-
|
16
|
-
|
17
|
-
requests = attempt_import('requests')
|
18
|
-
url = f'{protocol}://{host}:{port}'
|
22
|
+
requests = mrsm.attempt_import('requests', lazy=False)
|
23
|
+
url = f'{protocol}://{host}:{port}/webterm/{session_id}'
|
19
24
|
try:
|
20
25
|
r = requests.get(url, timeout=3)
|
21
26
|
except Exception as e:
|
@@ -23,3 +28,33 @@ def is_webterm_running(host: str, port: int, protocol: str = 'http') -> int:
|
|
23
28
|
if not r:
|
24
29
|
return False
|
25
30
|
return '<title>Meerschaum Shell</title>' in r.text
|
31
|
+
|
32
|
+
|
33
|
+
def kill_tmux_session(session: str) -> bool:
|
34
|
+
"""
|
35
|
+
Kill a tmux session if it exists.
|
36
|
+
"""
|
37
|
+
from meerschaum.utils.process import run_process
|
38
|
+
command = ['tmux', 'kill-session', '-t', session]
|
39
|
+
return run_process(command, capture_output=True) == 0
|
40
|
+
|
41
|
+
|
42
|
+
def get_mrsm_tmux_sessions() -> List[str]:
|
43
|
+
"""
|
44
|
+
Return a list of tmux sessions created by Meerschaum.
|
45
|
+
"""
|
46
|
+
from meerschaum.utils.process import run_process
|
47
|
+
tmux_suffix = mrsm.get_config('system', 'webterm', 'tmux', 'session_suffix')
|
48
|
+
command = ['tmux', 'ls']
|
49
|
+
proc = run_process(command, capture_output=True, as_proc=True)
|
50
|
+
if proc.returncode != 0:
|
51
|
+
return []
|
52
|
+
sessions = [
|
53
|
+
line.split(':', maxsplit=1)[0]
|
54
|
+
for line in proc.stdout.read().decode('utf-8').split('\n')
|
55
|
+
]
|
56
|
+
return [
|
57
|
+
session
|
58
|
+
for session in sessions
|
59
|
+
if session.endswith(tmux_suffix)
|
60
|
+
]
|
meerschaum/actions/copy.py
CHANGED
meerschaum/actions/start.py
CHANGED
@@ -314,6 +314,11 @@ def _start_gui(
|
|
314
314
|
"""
|
315
315
|
Start the Meerschaum GUI application.
|
316
316
|
"""
|
317
|
+
import json
|
318
|
+
import time
|
319
|
+
import uuid
|
320
|
+
import platform
|
321
|
+
|
317
322
|
from meerschaum.utils.venv import venv_exec
|
318
323
|
from meerschaum.utils.packages import attempt_import
|
319
324
|
from meerschaum.utils.warnings import warn
|
@@ -321,17 +326,14 @@ def _start_gui(
|
|
321
326
|
from meerschaum.utils.networking import find_open_ports
|
322
327
|
from meerschaum.connectors.parse import parse_instance_keys
|
323
328
|
from meerschaum._internal.term.tools import is_webterm_running
|
324
|
-
import platform
|
325
329
|
webview, requests = attempt_import('webview', 'requests')
|
326
|
-
import json
|
327
|
-
import time
|
328
330
|
|
329
331
|
success, msg = True, "Success"
|
330
332
|
host = '127.0.0.1'
|
331
333
|
if port is None:
|
332
334
|
port = 8765
|
333
335
|
|
334
|
-
if not is_webterm_running(host, port):
|
336
|
+
if not is_webterm_running(host, port, session_id='mrsm'):
|
335
337
|
port = next(find_open_ports(port, 9000))
|
336
338
|
|
337
339
|
api_kw = {
|
@@ -350,7 +352,7 @@ def _start_gui(
|
|
350
352
|
)
|
351
353
|
if debug:
|
352
354
|
print(start_tornado_code)
|
353
|
-
base_url = 'http://' + api_kw['host'] + ':' + str(api_kw['port'])
|
355
|
+
base_url = 'http://' + api_kw['host'] + ':' + str(api_kw['port']) + '/webterm/mrsm'
|
354
356
|
|
355
357
|
process = venv_exec(
|
356
358
|
start_tornado_code, as_proc=True, debug=debug, capture_output=(not debug)
|
@@ -375,7 +377,7 @@ def _start_gui(
|
|
375
377
|
try:
|
376
378
|
webview.create_window(
|
377
379
|
'Meerschaum Shell',
|
378
|
-
f'http://127.0.0.1:{port}',
|
380
|
+
f'http://127.0.0.1:{port}/webterm/mrsm',
|
379
381
|
height=650,
|
380
382
|
width=1000
|
381
383
|
)
|
@@ -415,8 +417,13 @@ def _start_webterm(
|
|
415
417
|
- `-i`, '--instance'
|
416
418
|
The default instance to use for the Webterm shell.
|
417
419
|
"""
|
420
|
+
import uuid
|
418
421
|
from meerschaum._internal.term import get_webterm_app_and_manager, tornado_ioloop
|
419
|
-
from meerschaum._internal.term.tools import
|
422
|
+
from meerschaum._internal.term.tools import (
|
423
|
+
is_webterm_running,
|
424
|
+
get_mrsm_tmux_sessions,
|
425
|
+
kill_tmux_session,
|
426
|
+
)
|
420
427
|
from meerschaum.utils.networking import find_open_ports
|
421
428
|
from meerschaum.utils.warnings import info
|
422
429
|
|
@@ -426,19 +433,23 @@ def _start_webterm(
|
|
426
433
|
port = 8765
|
427
434
|
if sysargs is None:
|
428
435
|
sysargs = ['start', 'webterm']
|
436
|
+
session_id = 'mrsm'
|
429
437
|
tornado_app, term_manager = get_webterm_app_and_manager(instance_keys=mrsm_instance)
|
430
438
|
|
431
|
-
if is_webterm_running(host, port):
|
439
|
+
if is_webterm_running(host, port, session_id=session_id):
|
432
440
|
if force:
|
433
441
|
port = next(find_open_ports(port + 1, 9000))
|
434
442
|
else:
|
435
443
|
return False, (
|
436
|
-
f"The webterm is already running at http://{host}:{port}\n\n"
|
444
|
+
f"The webterm is already running at http://{host}:{port}/webterm/{session_id}\n\n"
|
437
445
|
+ " Include `-f` to start another server on a new port\n"
|
438
446
|
+ " or specify a different port with `-p`."
|
439
447
|
)
|
440
448
|
if not nopretty:
|
441
|
-
info(
|
449
|
+
info(
|
450
|
+
f"Starting the webterm at http://{host}:{port}/webterm/{session_id} ..."
|
451
|
+
"\n Press CTRL+C to quit."
|
452
|
+
)
|
442
453
|
tornado_app.listen(port, host)
|
443
454
|
loop = tornado_ioloop.IOLoop.instance()
|
444
455
|
try:
|
@@ -451,6 +462,10 @@ def _start_webterm(
|
|
451
462
|
term_manager.shutdown()
|
452
463
|
loop.close()
|
453
464
|
|
465
|
+
sessions = get_mrsm_tmux_sessions()
|
466
|
+
for session in sessions:
|
467
|
+
kill_tmux_session(session)
|
468
|
+
|
454
469
|
return True, "Success"
|
455
470
|
|
456
471
|
|
@@ -190,6 +190,7 @@ def update_page_layout_div(
|
|
190
190
|
Input('go-button', 'n_clicks'),
|
191
191
|
Input('get-pipes-button', 'n_clicks'),
|
192
192
|
Input('get-jobs-button', 'n_clicks'),
|
193
|
+
Input('show-webterm-button', 'n_clicks'),
|
193
194
|
Input('get-plugins-button', 'n_clicks'),
|
194
195
|
Input('get-users-button', 'n_clicks'),
|
195
196
|
Input('get-graphs-button', 'n_clicks'),
|
@@ -229,7 +230,7 @@ def update_content(*args):
|
|
229
230
|
### NOTE: functions MUST return a list of content and a list of alerts
|
230
231
|
triggers = {
|
231
232
|
'go-button': lambda x: ([], []),
|
232
|
-
'
|
233
|
+
'show-webterm-button': lambda x: ([], []),
|
233
234
|
'get-pipes-button': get_pipes_cards,
|
234
235
|
'get-jobs-button': get_jobs_cards,
|
235
236
|
'get-plugins-button': get_plugins_cards,
|
@@ -251,7 +252,12 @@ def update_content(*args):
|
|
251
252
|
webterm_style = {
|
252
253
|
'display': (
|
253
254
|
'none'
|
254
|
-
if trigger not in (
|
255
|
+
if trigger not in (
|
256
|
+
'instance-select',
|
257
|
+
'cancel-button',
|
258
|
+
'go-button',
|
259
|
+
'show-webterm-button',
|
260
|
+
)
|
255
261
|
else 'block'
|
256
262
|
)
|
257
263
|
}
|
@@ -650,6 +656,41 @@ dash_app.clientside_callback(
|
|
650
656
|
State('mrsm-location', 'href'),
|
651
657
|
)
|
652
658
|
|
659
|
+
dash_app.clientside_callback(
|
660
|
+
"""
|
661
|
+
function(n_clicks, url){
|
662
|
+
if (!n_clicks) { return dash_clientside.no_update; }
|
663
|
+
iframe = document.getElementById('webterm-iframe');
|
664
|
+
if (!iframe){ return dash_clientside.no_update; }
|
665
|
+
|
666
|
+
iframe.contentWindow.postMessage(
|
667
|
+
{
|
668
|
+
action: "__TMUX_NEW_WINDOW"
|
669
|
+
},
|
670
|
+
url
|
671
|
+
);
|
672
|
+
return dash_clientside.no_update;
|
673
|
+
}
|
674
|
+
""",
|
675
|
+
Output('mrsm-location', 'href'),
|
676
|
+
Input('webterm-new-tab-button', 'n_clicks'),
|
677
|
+
State('mrsm-location', 'href'),
|
678
|
+
)
|
679
|
+
|
680
|
+
dash_app.clientside_callback(
|
681
|
+
"""
|
682
|
+
function(n_clicks, url){
|
683
|
+
if (!n_clicks) { return dash_clientside.no_update; }
|
684
|
+
iframe = document.getElementById('webterm-iframe');
|
685
|
+
if (!iframe){ return dash_clientside.no_update; }
|
686
|
+
iframe.src = iframe.src;
|
687
|
+
return dash_clientside.no_update;
|
688
|
+
}
|
689
|
+
""",
|
690
|
+
Output('mrsm-location', 'href'),
|
691
|
+
Input('webterm-refresh-button', 'n_clicks'),
|
692
|
+
State('mrsm-location', 'href'),
|
693
|
+
)
|
653
694
|
|
654
695
|
@dash_app.callback(
|
655
696
|
Output(component_id='connector-keys-input', component_property='value'),
|
@@ -23,7 +23,7 @@ dex = attempt_import('dash_extensions', lazy=False, check_update=CHECK_UPDATE)
|
|
23
23
|
dash_ace = attempt_import('dash_ace', lazy=False, check_update=CHECK_UPDATE)
|
24
24
|
_load_builtin_custom_connectors()
|
25
25
|
|
26
|
-
go_button = dbc.Button('
|
26
|
+
go_button = dbc.Button('Exec', id='go-button', color='primary', style={'width': '100%'})
|
27
27
|
test_button = dbc.Button('Test', id='test-button', color='danger', style={'display': 'none'})
|
28
28
|
get_items_menu = dbc.DropdownMenu(
|
29
29
|
label='More', id='get-items-menu', children=[
|
@@ -50,19 +50,26 @@ show_jobs_button = dbc.Button(
|
|
50
50
|
style={'width': '100%'},
|
51
51
|
)
|
52
52
|
cancel_button = dbc.Button(
|
53
|
-
'
|
53
|
+
'Term',
|
54
54
|
id='cancel-button',
|
55
55
|
color='dark',
|
56
56
|
style={'width': '100%', 'background-color': 'black', 'display': 'none'},
|
57
57
|
)
|
58
|
+
show_webterm_button = dbc.Button(
|
59
|
+
'Term',
|
60
|
+
id='show-webterm-button',
|
61
|
+
color='dark',
|
62
|
+
style={'width': '100%', 'background-color': 'black'},
|
63
|
+
)
|
58
64
|
bottom_buttons_content = dbc.Card(
|
59
65
|
dbc.CardBody(
|
60
66
|
dbc.Row([
|
61
|
-
dbc.Col(go_button, lg=3, md=
|
62
|
-
dbc.Col(show_pipes_button, lg=3, md=
|
63
|
-
dbc.Col(show_jobs_button, lg=3, md=
|
67
|
+
dbc.Col(go_button, xl=2, lg=3, md=3, sm=12),
|
68
|
+
dbc.Col(show_pipes_button, xl=2, lg=3, md=3, sm=12),
|
69
|
+
dbc.Col(show_jobs_button, xl=2, lg=3, md=3, sm=12),
|
70
|
+
dbc.Col(show_webterm_button, xl=2, lg=3, md=3, sm=12),
|
64
71
|
dbc.Col(lg=True, md=False, sm=False),
|
65
|
-
dbc.Col(get_items_menu,
|
72
|
+
dbc.Col(get_items_menu, xl=2, lg=12, md=12, sm=12),
|
66
73
|
])
|
67
74
|
)
|
68
75
|
)
|