From 5eb7a14a3398f57e98d68aad390c14a0cf5fb1e4 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 13:30:28 +0200 Subject: [PATCH 001/156] [WIP] Add support for Jupyter Server --- .travis.yml | 7 +++++++ jupyterhub/singleuser.py | 27 ++++++++++++++++++--------- jupyterhub/spawner.py | 2 +- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 788bfa4f..3980dd87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,6 +61,13 @@ script: make html popd fi + - | + if [[ "$TEST" == "jupyter_server" ]]; then + pip uninstall notebook + pip install jupyter_server + export USE_JUPYTER_SERVER=True + pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests + fi after_success: - codecov after_failure: diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py index 6fa4f350..0ab557a5 100755 --- a/jupyterhub/singleuser.py +++ b/jupyterhub/singleuser.py @@ -6,6 +6,7 @@ import asyncio import json import os import random +import importlib from datetime import datetime from datetime import timezone from textwrap import dedent @@ -20,10 +21,13 @@ from tornado.httpclient import HTTPRequest from tornado.web import HTTPError from tornado.web import RequestHandler +use_serverapp = os.environ.get('USE_JUPYTER_SERVER', 'False') == 'True' + +required_package = 'jupyter_server' if use_serverapp else 'notebook' try: - import notebook + parent_module = importlib.import_module(required_package) except ImportError: - raise ImportError("JupyterHub single-user server requires notebook >= 4.0") + raise ImportError("JupyterHub single-user server requires {}".format(required_package)) from traitlets import ( Any, @@ -38,14 +42,19 @@ from traitlets import ( TraitError, ) -from notebook.notebookapp import ( - NotebookApp, - aliases as notebook_aliases, - flags as notebook_flags, +app_name = 'jupyter_server.serverapp' if use_serverapp else 'notebook.notebookapp' +app_module = importlib.import_module(app_name) + +NotebookApp = getattr(app_module, 'ServerApp' if use_serverapp else 'NotebookApp') +notebook_aliases = app_module.aliases +notebook_flags = app_module.flags + +LoginHandler = getattr(importlib.import_module(required_package + '.auth.login'), 'LoginHandler') +LogoutHandler = getattr(importlib.import_module(required_package + '.auth.logout'), 'LogoutHandler') +IPythonHandler = getattr( + importlib.import_module(required_package + '.base.handlers'), + 'JupyterHandler' if use_serverapp else 'IPythonHandler' ) -from notebook.auth.login import LoginHandler -from notebook.auth.logout import LogoutHandler -from notebook.base.handlers import IPythonHandler from ._version import __version__, _check_version from .log import log_request diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index af84c122..348eb8d5 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -931,7 +931,7 @@ class Spawner(LoggingConfigurable): args.append('--notebook-dir=%s' % _quote_safe(notebook_dir)) if self.default_url: default_url = self.format_string(self.default_url) - args.append('--NotebookApp.default_url=%s' % _quote_safe(default_url)) + args.append('--SingleUserNotebookApp.default_url=%s' % _quote_safe(default_url)) if self.debug: args.append('--debug') From cab1bca6fbe7c5b87e38072499c41f26619c9f52 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 13:42:52 +0200 Subject: [PATCH 002/156] Use jupyter_server if notebook package isn't available --- jupyterhub/singleuser.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/jupyterhub/singleuser.py b/jupyterhub/singleuser.py index 0ab557a5..deff5b01 100755 --- a/jupyterhub/singleuser.py +++ b/jupyterhub/singleuser.py @@ -21,13 +21,19 @@ from tornado.httpclient import HTTPRequest from tornado.web import HTTPError from tornado.web import RequestHandler -use_serverapp = os.environ.get('USE_JUPYTER_SERVER', 'False') == 'True' - -required_package = 'jupyter_server' if use_serverapp else 'notebook' try: - parent_module = importlib.import_module(required_package) + import notebook + use_serverapp = False + server_package = 'notebook' + app_name = 'notebook.notebookapp' except ImportError: - raise ImportError("JupyterHub single-user server requires {}".format(required_package)) + try: + import jupyter_server + use_serverapp = True + server_package = 'jupyter_server' + app_name = 'jupyter_server.serverapp' + except ImportError: + raise ImportError("JupyterHub single-user server requires notebook or jupyter_server packages") from traitlets import ( Any, @@ -42,17 +48,15 @@ from traitlets import ( TraitError, ) -app_name = 'jupyter_server.serverapp' if use_serverapp else 'notebook.notebookapp' app_module = importlib.import_module(app_name) - NotebookApp = getattr(app_module, 'ServerApp' if use_serverapp else 'NotebookApp') notebook_aliases = app_module.aliases notebook_flags = app_module.flags -LoginHandler = getattr(importlib.import_module(required_package + '.auth.login'), 'LoginHandler') -LogoutHandler = getattr(importlib.import_module(required_package + '.auth.logout'), 'LogoutHandler') +LoginHandler = getattr(importlib.import_module(server_package + '.auth.login'), 'LoginHandler') +LogoutHandler = getattr(importlib.import_module(server_package + '.auth.logout'), 'LogoutHandler') IPythonHandler = getattr( - importlib.import_module(required_package + '.base.handlers'), + importlib.import_module(server_package + '.base.handlers'), 'JupyterHandler' if use_serverapp else 'IPythonHandler' ) From f830b2a41798b1936639f5b98ce5434dc4c7a135 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 13:45:57 +0200 Subject: [PATCH 003/156] Try to test notebook package is still uninstalled --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3980dd87..c2d83296 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,8 +65,10 @@ script: if [[ "$TEST" == "jupyter_server" ]]; then pip uninstall notebook pip install jupyter_server - export USE_JUPYTER_SERVER=True pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests + # Make sure that the notebook package is still not installed + pip list | grep notebook + test $? -eq 1 fi after_success: - codecov From 399def182b639171d1fbf526fb0af43ab47fdff2 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 13:57:26 +0200 Subject: [PATCH 004/156] Actually run jupyter_server test on Travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c2d83296..d279947d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -90,6 +90,8 @@ matrix: include: - python: 3.6 env: TEST=lint + - python: 3.6 + env: TEST=jupyter_server - python: 3.6 env: TEST=docs - python: 3.6 From 8f7e25f9a1897d1db1370338380df89b27e06c09 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 14:24:30 +0200 Subject: [PATCH 005/156] Don't make pip uninstall wait for human input --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d279947d..02c0645c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -63,7 +63,7 @@ script: fi - | if [[ "$TEST" == "jupyter_server" ]]; then - pip uninstall notebook + pip uninstall notebook --yes pip install jupyter_server pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests # Make sure that the notebook package is still not installed From e9bc25cce0616b6cf1d14c4aab61b87b13617711 Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 4 Jun 2019 14:42:49 +0200 Subject: [PATCH 006/156] Run all tests for jupyter_server regardless of failure --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 02c0645c..19f05529 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,7 +65,8 @@ script: if [[ "$TEST" == "jupyter_server" ]]; then pip uninstall notebook --yes pip install jupyter_server - pytest -v --maxfail=2 --cov=jupyterhub jupyterhub/tests + # Run all tests regardless of failure, so we know what's up + pytest -v --cov=jupyterhub jupyterhub/tests # Make sure that the notebook package is still not installed pip list | grep notebook test $? -eq 1 From 3d0da4f25adfb69ba36ef0af94d751c0f59b34a9 Mon Sep 17 00:00:00 2001 From: Juan Cruz-Benito Date: Thu, 13 Feb 2020 18:35:17 +0100 Subject: [PATCH 007/156] Adding python-paginate package and using it to paginate admin panel --- jupyterhub/handlers/pages.py | 22 ++++++++++++++++++++-- requirements.txt | 1 + share/jupyterhub/templates/admin.html | 12 +++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index 3ca03653..c9a15d6a 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -12,7 +12,8 @@ from http.client import responses from jinja2 import TemplateNotFound from tornado import gen from tornado import web -from tornado.httputil import url_concat +from tornado.httputil import url_concat, urlparse +from python_paginate.web.tornado_paginate import Pagination from .. import __version__ from .. import orm @@ -402,6 +403,15 @@ class AdminHandler(BaseHandler): @web.authenticated @admin_only async def get(self): + DEFAULT_PER_PAGE = 100 + + page, per_page, offset = Pagination.get_page_args(self) + _per_page = self.get_arguments("per_page") + # No arg called per_page in the URL, + # avoiding default value from the python_paginate lib + if per_page==10 and len(_per_page)==0: + per_page = DEFAULT_PER_PAGE + available = {'name', 'admin', 'running', 'last_activity'} default_sort = ['admin', 'name'] mapping = {'running': orm.Spawner.server_id} @@ -442,7 +452,7 @@ class AdminHandler(BaseHandler): # get User.col.desc() order objects ordered = [getattr(c, o)() for c, o in zip(cols, orders)] - users = self.db.query(orm.User).outerjoin(orm.Spawner).order_by(*ordered) + users = self.db.query(orm.User).outerjoin(orm.Spawner).order_by(*ordered).limit(per_page).offset(offset) users = [self._user_from_orm(u) for u in users] from itertools import chain @@ -450,9 +460,16 @@ class AdminHandler(BaseHandler): for u in users: running.extend(s for s in u.spawners.values() if s.active) + total = self.db.query(orm.User.id).count() + pagination = Pagination(url=self.request.uri, total=total, record_name='users', + display_msg = 'Displaying {record_name} {start} - {end}. Total {record_name}: {total}', + page=page, per_page=per_page) + + auth_state = await self.current_user.get_auth_state() html = self.render_template( 'admin.html', + request=self.request, current_user=self.current_user, auth_state=auth_state, admin_access=self.settings.get('admin_access', False), @@ -462,6 +479,7 @@ class AdminHandler(BaseHandler): allow_named_servers=self.allow_named_servers, named_server_limit_per_user=self.named_server_limit_per_user, server_version='{} {}'.format(__version__, self.version_hash), + pagination=pagination ) self.finish(html) diff --git a/requirements.txt b/requirements.txt index 825fd409..a6cdb504 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ requests SQLAlchemy>=1.1 tornado>=5.0 traitlets>=4.3.2 +python-paginate \ No newline at end of file diff --git a/share/jupyterhub/templates/admin.html b/share/jupyterhub/templates/admin.html index a0c6fb87..0835d758 100644 --- a/share/jupyterhub/templates/admin.html +++ b/share/jupyterhub/templates/admin.html @@ -18,11 +18,12 @@ {% block main %}
+ {{ pagination.info|safe }} {% block thead %} - {{ th("User (%i)" % users|length, 'name') }} + {{ th("User", 'name') }} {{ th("Admin", 'admin') }} {{ th("Last Activity", 'last_activity') }} {{ th("Running (%i)" % running|length, 'running', colspan=2) }} @@ -102,6 +103,15 @@ {% endfor %}
+ {% if pagination.links %} + + + + + + + + {% endif %}