meerschaum 2.7.8__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/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/core/Pipe/_sync.py +7 -7
- meerschaum/core/Pipe/_verify.py +1 -1
- meerschaum/utils/daemon/Daemon.py +15 -9
- meerschaum/utils/dtypes/__init__.py +9 -0
- meerschaum/utils/dtypes/sql.py +19 -13
- meerschaum/utils/formatting/_pprint.py +1 -1
- meerschaum/utils/misc.py +16 -0
- meerschaum/utils/venv/__init__.py +10 -14
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/METADATA +1 -1
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/RECORD +32 -32
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.8.dist-info → meerschaum-2.7.9.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.8.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/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 | 
             
            )
         |