Exemplo n.º 1
0
def initialize(webapp, root_dir, connection_url, settings):
    if os.name == 'nt':
        default_shell = 'powershell.exe'
    else:
        default_shell = which('sh')
    shell_override = settings.get('shell_command')
    shell = (
        [os.environ.get('SHELL') or default_shell]
        if shell_override is None
        else shell_override
    )
    # When the notebook server is not running in a terminal (e.g. when
    # it's launched by a JupyterHub spawner), it's likely that the user
    # environment hasn't been fully set up. In that case, run a login
    # shell to automatically source /etc/profile and the like, unless
    # the user has specifically set a preferred shell command.
    if os.name != 'nt' and shell_override is None and not sys.stdout.isatty():
        shell.append('-l')
    terminal_manager = webapp.settings['terminal_manager'] = TerminalManager(
        shell_command=shell,
        extra_env={'JUPYTER_SERVER_ROOT': root_dir,
                   'JUPYTER_SERVER_URL': connection_url,
                   },
        parent=webapp.settings['serverapp'],
    )
    terminal_manager.log = webapp.settings['serverapp'].log
    base_url = webapp.settings['base_url']
    handlers = [
        (ujoin(base_url, r"/terminals/websocket/(\w+)"), TermSocket,
             {'term_manager': terminal_manager}),
        (ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler),
        (ujoin(base_url, r"/api/terminals/(\w+)"), api_handlers.TerminalHandler),
    ]
    webapp.add_handlers(".*$", handlers)
Exemplo n.º 2
0
def initialize(webapp, root_dir, connection_url, settings):
    if os.name == 'nt':
        default_shell = 'powershell.exe'
    else:
        default_shell = which('sh')
    shell = settings.get('shell_command',
                         [os.environ.get('SHELL') or default_shell])
    # Enable login mode - to automatically source the /etc/profile script
    if os.name != 'nt':
        shell.append('-l')
    terminal_manager = webapp.settings['terminal_manager'] = NamedTermManager(
        shell_command=shell,
        extra_env={
            'JUPYTER_SERVER_ROOT': root_dir,
            'JUPYTER_SERVER_URL': connection_url,
        },
    )
    terminal_manager.log = app_log
    base_url = webapp.settings['base_url']
    handlers = [
        (ujoin(base_url, r"/terminals/(\w+)"), TerminalHandler),
        (ujoin(base_url, r"/terminals/websocket/(\w+)"), TermSocket, {
            'term_manager': terminal_manager
        }),
        (ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler),
        (ujoin(base_url,
               r"/api/terminals/(\w+)"), api_handlers.TerminalHandler),
    ]
    webapp.add_handlers(".*$", handlers)
Exemplo n.º 3
0
    def get(self):
        """Get the main page for the application's interface."""
        # Options set here can be read with PageConfig.getOption
        mathjax_config = self.settings.get("mathjax_config", "TeX-AMS_HTML-full,Safe")
        mathjax_url = self.settings.get(
            "mathjax_url", "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js"
        )

        config_data = {
            # Use camelCase here, since that's what the lab components expect
            "baseUrl": self.base_url,
            "token": self.settings["token"],
            "notebookPath": "test.ipynb",
            "fullStaticUrl": ujoin(self.base_url, "static", self.name),
            "frontendUrl": ujoin(self.base_url, "example/"),
            "mathjaxUrl": mathjax_url,
            "mathjaxConfig": mathjax_config,
        }
        return self.write(
            self.render_template(
                "index.html",
                static=self.static_url,
                base_url=self.base_url,
                token=self.settings["token"],
                page_config=config_data,
            )
        )
Exemplo n.º 4
0
    def initialize_templates(self):
        # Determine which model to run JupyterLab
        if self.core_mode or self.app_dir.startswith(HERE + os.sep):
            self.core_mode = True
            self.log.info("Running JupyterLab in core mode")

        if self.dev_mode or self.app_dir.startswith(DEV_DIR + os.sep):
            self.dev_mode = True
            self.log.info("Running JupyterLab in dev mode")

        if self.watch and self.core_mode:
            self.log.warning("Cannot watch in core mode, did you mean --dev-mode?")
            self.watch = False

        if self.core_mode and self.dev_mode:
            self.log.warning("Conflicting modes, choosing dev_mode over core_mode")
            self.core_mode = False

        # Set the paths based on JupyterLab's mode.
        if self.dev_mode:
            dev_static_dir = ujoin(DEV_DIR, "static")
            self.static_paths = [dev_static_dir]
            self.template_paths = [dev_static_dir]
            if not self.extensions_in_dev_mode:
                self.labextensions_path = []
                self.extra_labextensions_path = []
        elif self.core_mode:
            dev_static_dir = ujoin(HERE, "static")
            self.static_paths = [dev_static_dir]
            self.template_paths = [dev_static_dir]
            self.labextensions_path = []
            self.extra_labextensions_path = []
        else:
            self.static_paths = [self.static_dir]
            self.template_paths = [self.templates_dir]
Exemplo n.º 5
0
    async def get(self, path=None):
        """
        Display appropriate page for given path.

        - A directory listing is shown if path is a directory
        - Redirected to notebook page if path is a notebook
        - Render the raw file if path is any other file
        """
        path = path.strip('/')
        cm = self.contents_manager

        if await maybe_future(cm.dir_exists(path=path)):
            if await maybe_future(cm.is_hidden(path)) and not cm.allow_hidden:
                self.log.info(
                    "Refusing to serve hidden directory, via 404 Error")
                raise web.HTTPError(404)

            # Set treePath for routing to the directory
            page_config = self.get_page_config()
            page_config['treePath'] = path

            tpl = self.render_template("tree.html", page_config=page_config)
            return self.write(tpl)
        elif await maybe_future(cm.file_exists(path)):
            # it's not a directory, we have redirecting to do
            model = await maybe_future(cm.get(path, content=False))
            if model['type'] == 'notebook':
                url = ujoin(self.base_url, 'notebooks', url_escape(path))
            else:
                # Return raw content if file is not a notebook
                url = ujoin(self.base_url, 'files', url_escape(path))
            self.log.debug("Redirecting %s to %s", self.request.path, url)
            self.redirect(url)
        else:
            raise web.HTTPError(404)
Exemplo n.º 6
0
def initialize(webapp, root_dir, connection_url, settings):
    if os.name == "nt":
        default_shell = "powershell.exe"
    else:
        default_shell = which("sh")
    shell = settings.get("shell_command", [os.environ.get("SHELL") or default_shell])
    # Enable login mode - to automatically source the /etc/profile script
    if os.name != "nt":
        shell.append("-l")
    terminal_manager = webapp.settings["terminal_manager"] = NamedTermManager(
        shell_command=shell,
        extra_env={
            "JUPYTER_SERVER_ROOT": root_dir,
            "JUPYTER_SERVER_URL": connection_url,
        },
    )
    terminal_manager.log = app_log
    base_url = webapp.settings["base_url"]
    handlers = [
        (ujoin(base_url, r"/terminals/(\w+)"), TerminalHandler),
        (
            ujoin(base_url, r"/terminals/websocket/(\w+)"),
            TermSocket,
            {"term_manager": terminal_manager},
        ),
        (ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler),
        (ujoin(base_url, r"/api/terminals/(\w+)"), api_handlers.TerminalHandler),
    ]
    webapp.add_handlers(".*$", handlers)
Exemplo n.º 7
0
def _load_jupyter_server_extension(nbapp):
    # Set up handlers picked up via config
    base_url = nbapp.web_app.settings['base_url']
    serverproxy = ServerProxy(parent=nbapp)

    server_processes = [
        make_server_process(k, v) for k, v in serverproxy.servers.items()
    ]
    server_processes += get_entrypoint_server_processes()
    server_handlers = make_handlers(base_url, server_processes)
    nbapp.web_app.add_handlers('.*', server_handlers)

    # Set up default handler
    setup_handlers(nbapp.web_app, serverproxy.host_allowlist)

    launcher_entries = []
    icons = {}
    for sp in server_processes:
        if sp.launcher_entry.enabled and sp.launcher_entry.icon_path:
            icons[sp.name] = sp.launcher_entry.icon_path

    nbapp.web_app.add_handlers(
        '.*',
        [(ujoin(base_url, 'server-proxy/servers-info'), ServersInfoHandler, {
            'server_processes': server_processes
        }),
         (ujoin(base_url, 'server-proxy/icon/(.*)'), IconHandler, {
             'icons': icons
         })])
Exemplo n.º 8
0
def _load_jupyter_server_extension(nbapp):
    # Set up handlers picked up via config
    base_url = nbapp.web_app.settings['base_url']
    serverproxy_config = ServerProxyConfig(parent=nbapp)

    server_processes = [
        make_server_process(name, server_process_config, serverproxy_config)
        for name, server_process_config in serverproxy_config.servers.items()
    ]
    server_processes += get_entrypoint_server_processes(serverproxy_config)
    server_handlers = make_handlers(base_url, server_processes)
    nbapp.web_app.add_handlers('.*', server_handlers)

    # Set up default non-server handler
    setup_handlers(
        nbapp.web_app,
        serverproxy_config,
    )

    icons = {}
    for sp in server_processes:
        if sp.launcher_entry.enabled and sp.launcher_entry.icon_path:
            icons[sp.name] = sp.launcher_entry.icon_path

    nbapp.web_app.add_handlers('.*', [
        (ujoin(base_url, 'server-proxy/servers-info'), ServersInfoHandler, {'server_processes': server_processes}),
        (ujoin(base_url, 'server-proxy/icon/(.*)'), IconHandler, {'icons': icons}),
    ])
Exemplo n.º 9
0
    async def start_copy(self, name, starter, path, body):
        """start a copy starter"""
        root = self.resolve_src(starter)

        if root is None:
            return None

        root_uri = root.as_uri()

        dest_tmpl_str = starter.get("dest")

        if dest_tmpl_str is not None:
            dest_tmpl = self.jinja_env.from_string(dest_tmpl_str)
            dest = ujoin(path, dest_tmpl.render(**(body or {})))
        else:
            dest = ujoin(path, root.name)

        await self.save_one(root, dest)

        for child in iter_not_ignored(root, starter.get("ignore")):
            await self.save_one(
                child,
                unquote(ujoin(dest, child.as_uri().replace(root_uri, ""))),
            )

        return {
            "body": body,
            "name": name,
            "path": dest,
            "starter": starter,
            "status": Status.DONE,
        }
Exemplo n.º 10
0
    def get(self):
        """Get the main page for the application's interface."""
        # Options set here can be read with PageConfig.getOption
        mathjax_config = self.settings.get('mathjax_config',
                                           'TeX-AMS_HTML-full,Safe')
        mathjax_url = self.settings.get(
            'mathjax_url',
            'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js')

        config_data = {
            # Use camelCase here, since that's what the lab components expect
            'baseUrl': self.base_url,
            'token': self.settings['token'],
            'notebookPath': 'test.ipynb',
            'fullStaticUrl': ujoin(self.base_url, 'static', self.name),
            'frontendUrl': ujoin(self.base_url, 'example/'),
            'mathjaxUrl': mathjax_url,
            'mathjaxConfig': mathjax_config
        }
        return self.write(
            self.render_template('index.html',
                                 static=self.static_url,
                                 base_url=self.base_url,
                                 token=self.settings['token'],
                                 page_config=config_data))
Exemplo n.º 11
0
    def start(self):
        app = LabApp(config=self.config)
        # TODO(@echarles) Fix this...
#        base_url = app.serverapp.base_url
        base_url = '/'
        directory = app.workspaces_dir
        app_url = app.app_url

        if len(self.extra_args) > 1:
            print('Too many arguments were provided for workspace export.')
            self.exit(1)

        workspaces_url = ujoin(app_url, 'workspaces')
        raw = (app_url if not self.extra_args
               else ujoin(workspaces_url, self.extra_args[0]))
        slug = slugify(raw, base_url)
        workspace_path = pjoin(directory, slug + WORKSPACE_EXTENSION)

        if osp.exists(workspace_path):
            with open(workspace_path) as fid:
                try:  # to load the workspace file.
                    print(fid.read())
                except Exception as e:
                    print(json.dumps(dict(data=dict(), metadata=dict(id=raw))))
        else:
            print(json.dumps(dict(data=dict(), metadata=dict(id=raw))))
Exemplo n.º 12
0
    def _validate(self, data, base_url, app_url, workspaces_url):
        workspace = json.load(data)

        if 'data' not in workspace:
            raise Exception('The `data` field is missing.')

        # If workspace_name is set in config, inject the
        # name into the workspace metadata.
        if self.workspace_name is not None:
            if self.workspace_name == "":
                workspace_id = ujoin(base_url, app_url)
            else:
                workspace_id = ujoin(base_url, workspaces_url, self.workspace_name)
            workspace['metadata'] = {'id': workspace_id}
        # else check that the workspace_id is valid.
        else:
            if 'id' not in workspace['metadata']:
                raise Exception('The `id` field is missing in `metadata`.')
            else:
                id = workspace['metadata']['id']
                if id != ujoin(base_url, app_url) and not id.startswith(ujoin(base_url, workspaces_url)):
                    error = '%s does not match app_url or start with workspaces_url.'
                    raise Exception(error % id)

        return workspace
Exemplo n.º 13
0
    def initialize_templates(self):
        # Determine which model to run JupyterLab
        if self.core_mode or self.app_dir.startswith(HERE):
            self.core_mode = True
            self.log.info('Running JupyterLab in dev mode')

        if self.dev_mode or self.app_dir.startswith(DEV_DIR):
            self.dev_mode = True
            self.log.info('Running JupyterLab in dev mode')

        if self.watch and self.core_mode:
            self.log.warn('Cannot watch in core mode, did you mean --dev-mode?')
            self.watch = False

        if self.core_mode and self.dev_mode:
            self.log.warn('Conflicting modes, choosing dev_mode over core_mode')
            self.core_mode = False

        # Set the paths based on JupyterLab's mode.
        if self.dev_mode:
            dev_static_dir = ujoin(DEV_DIR, 'static')
            self.static_paths = [dev_static_dir]
            self.template_paths = [dev_static_dir]
        elif self.core_mode:
            dev_static_dir = ujoin(HERE, 'static')
            self.static_paths = [dev_static_dir]
            self.template_paths = [dev_static_dir]
        else:
            self.static_paths = [self.static_dir]
            self.template_paths = [self.templates_dir]
async def test_redirects_from_classic_notebook_endpoints(
        jp_fetch, jp_base_url, asset_file):
    old_prefix = ujoin("static", "components", "MathJax")
    new_prefix = ujoin("static", "jupyter_server_mathjax")

    # Verify that the redirect is in place
    with pytest.raises(HTTPClientError) as error_info, pytest.deprecated_call(
            match="Redirecting old Notebook MathJax URL .*"):
        await jp_fetch(old_prefix, asset_file, follow_redirects=False)

    err = error_info.value
    assert err.code == 301
    assert err.response.headers["Location"] == ujoin(jp_base_url, new_prefix,
                                                     asset_file)
Exemplo n.º 15
0
    def get_page_config(self):
        config = LabConfig()
        app = self.extensionapp
        base_url = self.settings.get("base_url")

        page_config = {
            "appVersion": version,
            "baseUrl": self.base_url,
            "terminalsAvailable": self.settings.get('terminals_available',
                                                    False),
            "token": self.settings["token"],
            "fullStaticUrl": ujoin(self.base_url, "static", self.name),
            "frontendUrl": ujoin(self.base_url, "classic/"),
        }

        mathjax_config = self.settings.get("mathjax_config",
                                           "TeX-AMS_HTML-full,Safe")
        # TODO Remove CDN usage.
        mathjax_url = self.settings.get(
            "mathjax_url",
            "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js",
        )
        page_config.setdefault("mathjaxConfig", mathjax_config)
        page_config.setdefault("fullMathjaxUrl", mathjax_url)

        # Put all our config in page_config
        for name in config.trait_names():
            page_config[_camelCase(name)] = getattr(app, name)

        # Add full versions of all the urls
        for name in config.trait_names():
            if not name.endswith("_url"):
                continue
            full_name = _camelCase("full_" + name)
            full_url = getattr(app, name)
            if not is_url(full_url):
                # Relative URL will be prefixed with base_url
                full_url = ujoin(base_url, full_url)
            page_config[full_name] = full_url

        labextensions_path = app.extra_labextensions_path + app.labextensions_path
        recursive_update(
            page_config,
            get_page_config(
                labextensions_path,
                logger=self.log,
            ),
        )
        return page_config
Exemplo n.º 16
0
 def get(self):
     config_data = {
         # Use camelCase here, since that's what the lab components expect
         "appVersion": version,
         'baseUrl': self.base_url,
         'token': self.settings['token'],
         'fullStaticUrl': ujoin(self.base_url, 'static', self.name),
         'frontendUrl': ujoin(self.base_url, 'example/'),
     }
     return self.write(
         self.render_template('index.html',
                              static=self.static_url,
                              base_url=self.base_url,
                              token=self.settings['token'],
                              page_config=config_data))
Exemplo n.º 17
0
 def get(self):
     """Get the main page for the application's interface."""
     config_data = {
         # Use camelCase here, since that's what the lab components expect
         'baseUrl': self.base_url,
         'token': self.settings['token'],
         'fullStaticUrl': ujoin(self.base_url, 'static', self.name),
         'frontendUrl': ujoin(self.base_url, 'example/'),
     }
     return self.write(
         self.render_template('index.html',
                              static=self.static_url,
                              base_url=self.base_url,
                              token=self.settings['token'],
                              page_config=config_data))
Exemplo n.º 18
0
 def get(self):
     config_data = {
         "appVersion": __version__,
         "baseUrl": self.base_url,
         "token": self.settings["token"],
         "fullStaticUrl": ujoin(self.base_url, "static", self.name),
         "frontendUrl": ujoin(self.base_url, "gator/"),
     }
     return self.write(
         self.render_template(
             "index.html",
             static=self.static_url,
             base_url=self.base_url,
             token=self.settings["token"],
             page_config=config_data,
         ))
Exemplo n.º 19
0
    def start(self):
        app = LabApp(config=self.config)
        base_url = app.settings.get('base_url', '/')
        directory = app.workspaces_dir
        app_url = app.app_url
        workspaces_url = ujoin(app.app_url, 'workspaces')

        if len(self.extra_args) != 1:
            print('One argument is required for workspace import.')
            self.exit(1)

        workspace = dict()
        with self._smart_open() as fid:
            try:  # to load, parse, and validate the workspace file.
                workspace = self._validate(fid, base_url, app_url,
                                           workspaces_url)
            except Exception as e:
                print('%s is not a valid workspace:\n%s' % (fid.name, e))
                self.exit(1)

        if not osp.exists(directory):
            try:
                os.makedirs(directory)
            except Exception as e:
                print('Workspaces directory could not be created:\n%s' % e)
                self.exit(1)

        slug = slugify(workspace['metadata']['id'], base_url)
        workspace_path = pjoin(directory, slug + WORKSPACE_EXTENSION)

        # Write the workspace data to a file.
        with open(workspace_path, 'w') as fid:
            fid.write(json.dumps(workspace))

        print('Saved workspace: %s' % workspace_path)
Exemplo n.º 20
0
 def _default_static_url_prefix(self):
     if self.override_static_url:
         return self.override_static_url
     else:
         static_url = "/static/{name}/".format(
         name=self.name)
         return ujoin(self.serverapp.base_url, static_url)
Exemplo n.º 21
0
 def get(self):
     config_data = {
         "appVersion": version,
         'baseUrl': self.base_url,
         'token': self.settings['token'],
         'fullStaticUrl': ujoin(self.base_url, 'static', self.name),
         'frontendUrl': ujoin(self.base_url, 'gridstack/'),
     }
     return self.write(
         self.render_template(
             'index.html',
             static=self.static_url,
             base_url=self.base_url,
             token=self.settings['token'],
             page_config=config_data
             )
         )
Exemplo n.º 22
0
 def get(self):
     config_data = {
         # Use camelCase here, since that's what the lab components expect
         "appVersion": version,
         "baseUrl": self.base_url,
         "token": self.settings["token"],
         "fullStaticUrl": ujoin(self.base_url, "static", self.name),
         "frontendUrl": ujoin(self.base_url, "example/"),
     }
     return self.write(
         self.render_template(
             "index.html",
             static=self.static_url,
             base_url=self.base_url,
             token=self.settings["token"],
             page_config=config_data,
         ))
Exemplo n.º 23
0
def add_handlers(nbapp):
    """Add Language Server routes to the notebook server web application"""
    lsp_url = ujoin(nbapp.base_url, "lsp")
    re_langservers = "(?P<language_server>.*)"

    opts = {"manager": nbapp.language_server_manager}

    nbapp.web_app.add_handlers(
        ".*",
        [
            (ujoin(lsp_url, "status"), LanguageServersHandler, opts),
            (
                ujoin(lsp_url, "ws", re_langservers),
                LanguageServerWebSocketHandler,
                opts,
            ),
        ],
    )
Exemplo n.º 24
0
def load_jupyter_server_extension(nbapp):
    """Load the nbserver extension"""

    nbapp.log.info("Loading IPython parallel extension")
    webapp = nbapp.web_app
    webapp.settings['cluster_manager'] = ClusterManager(parent=nbapp)

    base_url = webapp.settings['base_url']
    webapp.add_handlers(".*$", [(ujoin(base_url, pat), handler)
                                for pat, handler in default_handlers])
Exemplo n.º 25
0
 def init_webapp(self):
     """initialize tornado webapp and httpserver."""
     super().init_webapp()
     default_handlers = [(
         ujoin(self.base_url, r"/listings/(.*)"),
         FileFindHandler,
         {
             "path": os.path.join(HERE, "list")
         },
     )]
     self.web_app.add_handlers(".*$", default_handlers)
Exemplo n.º 26
0
 def init_webapp(self):
     """initialize tornado webapp and httpserver.
     """
     super().init_webapp()
     default_handlers = [
         (
             ujoin(self.base_url, r"/listings/(.*)"), FileFindHandler,
              {'path': os.path.join(HERE, 'list')}
         )
     ]
     self.web_app.add_handlers('.*$', default_handlers)
Exemplo n.º 27
0
def add_handlers(nbapp, manager) -> None:
    """Add starter routes to the notebook server web application"""

    opts = {"manager": manager}

    url = ujoin(nbapp.base_url, NS)
    starter_url = ujoin(url, "(?P<starter>.*?)", "(?P<path>.*?)", "?$")
    nbapp.log.debug("💡 starters will list under %s", url)
    nbapp.log.debug("💡 starters will run under %s", starter_url)

    nbapp.web_app.add_handlers(
        ".*",
        [
            (url, StartersHandler, opts),
            (
                starter_url,
                StarterHandler,
                opts,
            ),
        ],
    )
Exemplo n.º 28
0
 def get(self):
     """Get the main page for the application's interface."""
     available = self.settings["terminals_available"]
     config_data = {
         # Use camelCase here, since that's what the lab components expect
         "appVersion": version,
         "baseUrl": self.base_url,
         "token": self.settings["token"],
         "fullStaticUrl": ujoin(self.base_url, "static", self.name),
         "frontendUrl": ujoin(self.base_url, "example/"),
         "terminalsAvailable": available,
     }
     return self.write(
         self.render_template(
             "index.html",
             static=self.static_url,
             base_url=self.base_url,
             token=self.settings["token"],
             terminals_available=available,
             page_config=config_data,
         ))
Exemplo n.º 29
0
def make_handlers(base_url, server_processes):
    """
    Get tornado handlers for registered server_processes
    """
    handlers = []
    for sp in server_processes:
        handler = _make_serverproxy_handler(
            sp.name,
            sp.command,
            sp.environment,
            sp.timeout,
            sp.absolute_url,
            sp.port,
            sp.mappath,
        )
        handlers.append((
            ujoin(base_url, sp.name, r'(.*)'),
            handler,
            dict(state={}),
        ))
        handlers.append((ujoin(base_url, sp.name), AddSlashHandler))
    return handlers
Exemplo n.º 30
0
def initialize(webapp, root_dir, connection_url, settings):
    if os.name == "nt":
        default_shell = "powershell.exe"
    else:
        default_shell = which("sh")
    shell_override = settings.get("shell_command")
    shell = [os.environ.get("SHELL") or default_shell
             ] if shell_override is None else shell_override
    # When the notebook server is not running in a terminal (e.g. when
    # it's launched by a JupyterHub spawner), it's likely that the user
    # environment hasn't been fully set up. In that case, run a login
    # shell to automatically source /etc/profile and the like, unless
    # the user has specifically set a preferred shell command.
    if os.name != "nt" and shell_override is None and not sys.stdout.isatty():
        shell.append("-l")
    terminal_manager = webapp.settings["terminal_manager"] = TerminalManager(
        shell_command=shell,
        extra_env={
            "JUPYTER_SERVER_ROOT": root_dir,
            "JUPYTER_SERVER_URL": connection_url,
        },
        parent=webapp.settings["serverapp"],
    )
    terminal_manager.log = webapp.settings["serverapp"].log
    base_url = webapp.settings["base_url"]
    handlers = [
        (
            ujoin(base_url, r"/terminals/websocket/(\w+)"),
            TermSocket,
            {
                "term_manager": terminal_manager
            },
        ),
        (ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler),
        (ujoin(base_url,
               r"/api/terminals/(\w+)"), api_handlers.TerminalHandler),
    ]
    webapp.add_handlers(".*$", handlers)