mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 10:34:10 +00:00
Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
20b3229249 | ||
![]() |
f0862f1d10 | ||
![]() |
3c5f9b255e | ||
![]() |
b6d9d5c120 | ||
![]() |
bccd0e2ff1 | ||
![]() |
a2d39c693d | ||
![]() |
76e65da9ff | ||
![]() |
eb9bb71655 | ||
![]() |
a39ef8f163 | ||
![]() |
f4727cba47 | ||
![]() |
14dfa65c75 | ||
![]() |
9f23bc2959 | ||
![]() |
24e8362401 | ||
![]() |
c4c662843c | ||
![]() |
6d5b13962c | ||
![]() |
fe64595d75 |
@@ -6,7 +6,7 @@ info:
|
|||||||
description: The REST API for JupyterHub
|
description: The REST API for JupyterHub
|
||||||
license:
|
license:
|
||||||
name: BSD-3-Clause
|
name: BSD-3-Clause
|
||||||
version: 2.3.0
|
version: 2.3.1
|
||||||
servers:
|
servers:
|
||||||
- url: /hub/api
|
- url: /hub/api
|
||||||
security:
|
security:
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -200,6 +200,25 @@ const ServerDashboard = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ServerRowTable = ({ data }) => {
|
||||||
|
return (
|
||||||
|
<ReactObjectTableViewer
|
||||||
|
className="table-striped table-bordered"
|
||||||
|
style={{
|
||||||
|
padding: "3px 6px",
|
||||||
|
margin: "auto",
|
||||||
|
}}
|
||||||
|
keyStyle={{
|
||||||
|
padding: "4px",
|
||||||
|
}}
|
||||||
|
valueStyle={{
|
||||||
|
padding: "4px",
|
||||||
|
}}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const serverRow = (user, server) => {
|
const serverRow = (user, server) => {
|
||||||
const { servers, ...userNoServers } = user;
|
const { servers, ...userNoServers } = user;
|
||||||
const serverNameDash = server.name ? `-${server.name}` : "";
|
const serverNameDash = server.name ? `-${server.name}` : "";
|
||||||
@@ -286,37 +305,11 @@ const ServerDashboard = (props) => {
|
|||||||
>
|
>
|
||||||
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
||||||
<Card.Title>User</Card.Title>
|
<Card.Title>User</Card.Title>
|
||||||
<ReactObjectTableViewer
|
<ServerRowTable data={userNoServers} />
|
||||||
className="table-striped table-bordered admin-table-head"
|
|
||||||
style={{
|
|
||||||
padding: "3px 6px",
|
|
||||||
margin: "auto",
|
|
||||||
}}
|
|
||||||
keyStyle={{
|
|
||||||
padding: "4px",
|
|
||||||
}}
|
|
||||||
valueStyle={{
|
|
||||||
padding: "4px",
|
|
||||||
}}
|
|
||||||
data={userNoServers}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
||||||
<Card.Title>Server</Card.Title>
|
<Card.Title>Server</Card.Title>
|
||||||
<ReactObjectTableViewer
|
<ServerRowTable data={server} />
|
||||||
className="table-striped table-bordered admin-table-head"
|
|
||||||
style={{
|
|
||||||
padding: "3px 6px",
|
|
||||||
margin: "auto",
|
|
||||||
}}
|
|
||||||
keyStyle={{
|
|
||||||
padding: "4px",
|
|
||||||
}}
|
|
||||||
valueStyle={{
|
|
||||||
padding: "4px",
|
|
||||||
}}
|
|
||||||
data={server}
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</CardGroup>
|
</CardGroup>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
# version_info updated by running `tbump`
|
# version_info updated by running `tbump`
|
||||||
version_info = (2, 3, 0, "", "")
|
version_info = (2, 3, 1, "", "")
|
||||||
|
|
||||||
# pep 440 version: no dot before beta/rc, but before .dev
|
# pep 440 version: no dot before beta/rc, but before .dev
|
||||||
# 0.1.0rc1
|
# 0.1.0rc1
|
||||||
|
@@ -1689,7 +1689,9 @@ class JupyterHub(Application):
|
|||||||
for authority, files in self.internal_ssl_authorities.items():
|
for authority, files in self.internal_ssl_authorities.items():
|
||||||
if files:
|
if files:
|
||||||
self.log.info("Adding CA for %s", authority)
|
self.log.info("Adding CA for %s", authority)
|
||||||
certipy.store.add_record(authority, is_ca=True, files=files)
|
certipy.store.add_record(
|
||||||
|
authority, is_ca=True, files=files, overwrite=True
|
||||||
|
)
|
||||||
|
|
||||||
self.internal_trust_bundles = certipy.trust_from_graph(
|
self.internal_trust_bundles = certipy.trust_from_graph(
|
||||||
self.internal_ssl_components_trust
|
self.internal_ssl_components_trust
|
||||||
|
@@ -536,9 +536,7 @@ class Hashed(Expiring):
|
|||||||
prefix = token[: cls.prefix_length]
|
prefix = token[: cls.prefix_length]
|
||||||
# since we can't filter on hashed values, filter on prefix
|
# since we can't filter on hashed values, filter on prefix
|
||||||
# so we aren't comparing with all tokens
|
# so we aren't comparing with all tokens
|
||||||
prefix_match = db.query(cls).filter(
|
prefix_match = db.query(cls).filter_by(prefix=prefix)
|
||||||
bindparam('prefix', prefix).startswith(cls.prefix)
|
|
||||||
)
|
|
||||||
prefix_match = prefix_match.filter(
|
prefix_match = prefix_match.filter(
|
||||||
or_(cls.expires_at == None, cls.expires_at >= cls.now())
|
or_(cls.expires_at == None, cls.expires_at >= cls.now())
|
||||||
)
|
)
|
||||||
|
@@ -29,9 +29,9 @@ else:
|
|||||||
try:
|
try:
|
||||||
App = import_item(JUPYTERHUB_SINGLEUSER_APP)
|
App = import_item(JUPYTERHUB_SINGLEUSER_APP)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
continue
|
|
||||||
if _import_error is None:
|
if _import_error is None:
|
||||||
_import_error = e
|
_import_error = e
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if App is None:
|
if App is None:
|
||||||
|
@@ -182,6 +182,7 @@ page_template = """
|
|||||||
|
|
||||||
<span>
|
<span>
|
||||||
<a href='{{hub_control_panel_url}}'
|
<a href='{{hub_control_panel_url}}'
|
||||||
|
id='jupyterhub-control-panel-link'
|
||||||
class='btn btn-default btn-sm navbar-btn pull-right'
|
class='btn btn-default btn-sm navbar-btn pull-right'
|
||||||
style='margin-right: 4px; margin-left: 2px;'>
|
style='margin-right: 4px; margin-left: 2px;'>
|
||||||
Control Panel
|
Control Panel
|
||||||
@@ -633,8 +634,15 @@ class SingleUserNotebookAppMixin(Configurable):
|
|||||||
# disable trash by default
|
# disable trash by default
|
||||||
# this can be re-enabled by config
|
# this can be re-enabled by config
|
||||||
self.config.FileContentsManager.delete_to_trash = False
|
self.config.FileContentsManager.delete_to_trash = False
|
||||||
|
# load default-url env at higher priority than `@default`,
|
||||||
|
# which may have their own _defaults_ which should not override explicit default_url config
|
||||||
|
# via e.g. c.Spawner.default_url. Seen in jupyterlab's SingleUserLabApp.
|
||||||
|
default_url = os.environ.get("JUPYTERHUB_DEFAULT_URL")
|
||||||
|
if default_url:
|
||||||
|
self.config[self.__class__.__name__].default_url = default_url
|
||||||
self._log_app_versions()
|
self._log_app_versions()
|
||||||
return super().initialize(argv)
|
super().initialize(argv)
|
||||||
|
self.patch_templates()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.log.info("Starting jupyterhub-singleuser server version %s", __version__)
|
self.log.info("Starting jupyterhub-singleuser server version %s", __version__)
|
||||||
@@ -705,7 +713,6 @@ class SingleUserNotebookAppMixin(Configurable):
|
|||||||
|
|
||||||
# apply X-JupyterHub-Version to *all* request handlers (even redirects)
|
# apply X-JupyterHub-Version to *all* request handlers (even redirects)
|
||||||
self.patch_default_headers()
|
self.patch_default_headers()
|
||||||
self.patch_templates()
|
|
||||||
|
|
||||||
def page_config_hook(self, handler, page_config):
|
def page_config_hook(self, handler, page_config):
|
||||||
"""JupyterLab page config hook
|
"""JupyterLab page config hook
|
||||||
@@ -738,19 +745,30 @@ class SingleUserNotebookAppMixin(Configurable):
|
|||||||
)
|
)
|
||||||
self.jinja_template_vars['hub_host'] = self.hub_host
|
self.jinja_template_vars['hub_host'] = self.hub_host
|
||||||
self.jinja_template_vars['hub_prefix'] = self.hub_prefix
|
self.jinja_template_vars['hub_prefix'] = self.hub_prefix
|
||||||
env = self.web_app.settings['jinja2_env']
|
self.jinja_template_vars[
|
||||||
|
'hub_control_panel_url'
|
||||||
|
] = self.hub_host + url_path_join(self.hub_prefix, 'home')
|
||||||
|
|
||||||
env.globals['hub_control_panel_url'] = self.hub_host + url_path_join(
|
settings = self.web_app.settings
|
||||||
self.hub_prefix, 'home'
|
# patch classic notebook jinja env
|
||||||
)
|
jinja_envs = []
|
||||||
|
if 'jinja2_env' in settings:
|
||||||
|
# default jinja env (should we do this on jupyter-server, or only notebook?)
|
||||||
|
jinja_envs.append(settings['jinja2_env'])
|
||||||
|
if 'notebook_jinja2_env' in settings:
|
||||||
|
# when running with jupyter-server, classic notebook (nbclassic server extension)
|
||||||
|
# gets its own jinja env, which needs the same patch
|
||||||
|
jinja_envs.append(settings['notebook_jinja2_env'])
|
||||||
|
|
||||||
# patch jinja env loading to modify page template
|
# patch jinja env loading to get modified template, only for base page.html
|
||||||
def get_page(name):
|
def get_page(name):
|
||||||
if name == 'page.html':
|
if name == 'page.html':
|
||||||
return page_template
|
return page_template
|
||||||
|
|
||||||
orig_loader = env.loader
|
for jinja_env in jinja_envs:
|
||||||
env.loader = ChoiceLoader([FunctionLoader(get_page), orig_loader])
|
jinja_env.loader = ChoiceLoader(
|
||||||
|
[FunctionLoader(get_page), jinja_env.loader]
|
||||||
|
)
|
||||||
|
|
||||||
def load_server_extensions(self):
|
def load_server_extensions(self):
|
||||||
# Loading LabApp sets $JUPYTERHUB_API_TOKEN on load, which is incorrect
|
# Loading LabApp sets $JUPYTERHUB_API_TOKEN on load, which is incorrect
|
||||||
|
@@ -5,9 +5,11 @@ from contextlib import contextmanager
|
|||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
from urllib.parse import urlencode
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
import jupyterhub
|
import jupyterhub
|
||||||
from .. import orm
|
from .. import orm
|
||||||
@@ -16,6 +18,7 @@ from .mocking import public_url
|
|||||||
from .mocking import StubSingleUserSpawner
|
from .mocking import StubSingleUserSpawner
|
||||||
from .utils import async_requests
|
from .utils import async_requests
|
||||||
from .utils import AsyncSession
|
from .utils import AsyncSession
|
||||||
|
from .utils import get_page
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@@ -225,3 +228,22 @@ def test_singleuser_app_class(JUPYTERHUB_SINGLEUSER_APP):
|
|||||||
else:
|
else:
|
||||||
assert '--ServerApp.' in out
|
assert '--ServerApp.' in out
|
||||||
assert '--NotebookApp.' not in out
|
assert '--NotebookApp.' not in out
|
||||||
|
|
||||||
|
|
||||||
|
async def test_nbclassic_control_panel(app, user):
|
||||||
|
# use StubSingleUserSpawner to launch a single-user app in a thread
|
||||||
|
app.spawner_class = StubSingleUserSpawner
|
||||||
|
app.tornado_settings['spawner_class'] = StubSingleUserSpawner
|
||||||
|
|
||||||
|
# login, start the server
|
||||||
|
await user.spawn()
|
||||||
|
cookies = await app.login_user(user.name)
|
||||||
|
next_url = url_path_join(user.url, "tree/")
|
||||||
|
url = '/?' + urlencode({'next': next_url})
|
||||||
|
r = await get_page(url, app, cookies=cookies)
|
||||||
|
r.raise_for_status()
|
||||||
|
assert urlparse(r.url).path == urlparse(next_url).path
|
||||||
|
page = BeautifulSoup(r.text, "html.parser")
|
||||||
|
link = page.find("a", id="jupyterhub-control-panel-link")
|
||||||
|
assert link, f"Missing jupyterhub-control-panel-link in {page}"
|
||||||
|
assert link["href"] == url_path_join(app.base_url, "hub/home")
|
||||||
|
@@ -15,7 +15,7 @@ target_version = [
|
|||||||
github_url = "https://github.com/jupyterhub/jupyterhub"
|
github_url = "https://github.com/jupyterhub/jupyterhub"
|
||||||
|
|
||||||
[tool.tbump.version]
|
[tool.tbump.version]
|
||||||
current = "2.3.0"
|
current = "2.3.1"
|
||||||
|
|
||||||
# Example of a semver regexp.
|
# Example of a semver regexp.
|
||||||
# Make sure this matches current_version before
|
# Make sure this matches current_version before
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user