mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 10:34:10 +00:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,5 +5,7 @@ node_modules
|
|||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
share/jupyter/static/components
|
share/jupyter/static/components
|
||||||
|
share/jupyter/static/css/style.min.css
|
||||||
|
share/jupyter/static/css/style.min.css.map
|
||||||
*.egg-info
|
*.egg-info
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
@@ -24,6 +24,10 @@ Basic principles:
|
|||||||
# install the Python pargs (-e for editable/development install)
|
# install the Python pargs (-e for editable/development install)
|
||||||
pip install [-e] .
|
pip install [-e] .
|
||||||
|
|
||||||
|
You will also need `bower` to fetch frontend-javascript and `less` to compile CSS:
|
||||||
|
|
||||||
|
npm install -g bower less
|
||||||
|
|
||||||
Note on debian/ubuntu machines, you may need to install the `nodejs-legacy` package
|
Note on debian/ubuntu machines, you may need to install the `nodejs-legacy` package
|
||||||
to get node executables to work:
|
to get node executables to work:
|
||||||
|
|
||||||
|
@@ -9,10 +9,12 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
|
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
import tornado.httpserver
|
import tornado.httpserver
|
||||||
import tornado.options
|
import tornado.options
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.log import LogFormatter
|
from tornado.log import LogFormatter, app_log
|
||||||
from tornado import gen, web
|
from tornado import gen, web
|
||||||
|
|
||||||
from IPython.utils.traitlets import (
|
from IPython.utils.traitlets import (
|
||||||
@@ -25,6 +27,7 @@ from IPython.utils.importstring import import_item
|
|||||||
here = os.path.dirname(__file__)
|
here = os.path.dirname(__file__)
|
||||||
|
|
||||||
from .handlers import (
|
from .handlers import (
|
||||||
|
Template404,
|
||||||
RootHandler,
|
RootHandler,
|
||||||
LoginHandler,
|
LoginHandler,
|
||||||
LogoutHandler,
|
LogoutHandler,
|
||||||
@@ -36,6 +39,13 @@ from . import orm
|
|||||||
from ._data import DATA_FILES_PATH
|
from ._data import DATA_FILES_PATH
|
||||||
from .utils import url_path_join
|
from .utils import url_path_join
|
||||||
|
|
||||||
|
|
||||||
|
class RedirectHandler(web.RedirectHandler):
|
||||||
|
def get(self, *a, **kw):
|
||||||
|
self.set_header("mu-redirect", True)
|
||||||
|
app_log.warn("mu redirect: %s -> %s", self.request.path, self._url)
|
||||||
|
return super(RedirectHandler, self).get(*a, **kw)
|
||||||
|
|
||||||
class JupyterHubApp(Application):
|
class JupyterHubApp(Application):
|
||||||
"""An Application for starting a Multi-User Notebook server."""
|
"""An Application for starting a Multi-User Notebook server."""
|
||||||
data_files_path = Unicode(DATA_FILES_PATH, config=True,
|
data_files_path = Unicode(DATA_FILES_PATH, config=True,
|
||||||
@@ -64,6 +74,10 @@ class JupyterHubApp(Application):
|
|||||||
help="The base URL of the entire application"
|
help="The base URL of the entire application"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
jinja_environment_options = Dict(config=True,
|
||||||
|
help="Supply extra arguments that will be passed to Jinja environment."
|
||||||
|
)
|
||||||
|
|
||||||
proxy_cmd = Unicode('configurable-http-proxy', config=True,
|
proxy_cmd = Unicode('configurable-http-proxy', config=True,
|
||||||
help="""The command to start the http proxy.
|
help="""The command to start the http proxy.
|
||||||
|
|
||||||
@@ -157,7 +171,7 @@ class JupyterHubApp(Application):
|
|||||||
|
|
||||||
def _log_format_default(self):
|
def _log_format_default(self):
|
||||||
"""override default log format to include time"""
|
"""override default log format to include time"""
|
||||||
return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
|
return u"H %(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
|
||||||
|
|
||||||
def init_logging(self):
|
def init_logging(self):
|
||||||
# This prevents double log messages because tornado use a root logger that
|
# This prevents double log messages because tornado use a root logger that
|
||||||
@@ -191,8 +205,11 @@ class JupyterHubApp(Application):
|
|||||||
self.handlers = self.add_url_prefix(self.hub_prefix, handlers)
|
self.handlers = self.add_url_prefix(self.hub_prefix, handlers)
|
||||||
self.handlers.extend([
|
self.handlers.extend([
|
||||||
(r"/user/([^/]+)/?.*", UserHandler),
|
(r"/user/([^/]+)/?.*", UserHandler),
|
||||||
(r"/", web.RedirectHandler, {"url" : self.hub_prefix}),
|
(r"/?", RedirectHandler, {"url" : self.hub_prefix}),
|
||||||
])
|
])
|
||||||
|
self.handlers.append(
|
||||||
|
(r'(.*)', Template404)
|
||||||
|
)
|
||||||
|
|
||||||
def init_db(self):
|
def init_db(self):
|
||||||
# TODO: load state from db for resume
|
# TODO: load state from db for resume
|
||||||
@@ -240,20 +257,28 @@ class JupyterHubApp(Application):
|
|||||||
'--api-ip', self.proxy.api_server.ip,
|
'--api-ip', self.proxy.api_server.ip,
|
||||||
'--api-port', str(self.proxy.api_server.port),
|
'--api-port', str(self.proxy.api_server.port),
|
||||||
'--default-target', self.hub.server.host,
|
'--default-target', self.hub.server.host,
|
||||||
|
'--log-level=debug',
|
||||||
]
|
]
|
||||||
if self.ssl_key:
|
if self.ssl_key:
|
||||||
cmd.extend(['--ssl-key', self.ssl_key])
|
cmd.extend(['--ssl-key', self.ssl_key])
|
||||||
if self.ssl_cert:
|
if self.ssl_cert:
|
||||||
cmd.extend(['--ssl-cert', self.ssl_cert])
|
cmd.extend(['--ssl-cert', self.ssl_cert])
|
||||||
|
|
||||||
|
|
||||||
self.proxy = Popen(cmd, env=env)
|
self.proxy = Popen(cmd, env=env)
|
||||||
|
|
||||||
def init_tornado_settings(self):
|
def init_tornado_settings(self):
|
||||||
"""Set up the tornado settings dict."""
|
"""Set up the tornado settings dict."""
|
||||||
base_url = self.base_url
|
base_url = self.base_url
|
||||||
|
template_path = os.path.join(self.data_files_path, 'templates'),
|
||||||
|
jinja_env = Environment(
|
||||||
|
loader=FileSystemLoader(template_path),
|
||||||
|
**self.jinja_environment_options
|
||||||
|
)
|
||||||
|
|
||||||
settings = dict(
|
settings = dict(
|
||||||
config=self.config,
|
config=self.config,
|
||||||
# log=self.log,
|
log=self.log,
|
||||||
db=self.db,
|
db=self.db,
|
||||||
hub=self.hub,
|
hub=self.hub,
|
||||||
authenticator=import_item(self.authenticator)(config=self.config),
|
authenticator=import_item(self.authenticator)(config=self.config),
|
||||||
@@ -261,8 +286,10 @@ class JupyterHubApp(Application):
|
|||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
cookie_secret=self.cookie_secret,
|
cookie_secret=self.cookie_secret,
|
||||||
login_url=url_path_join(self.hub.server.base_url, 'login'),
|
login_url=url_path_join(self.hub.server.base_url, 'login'),
|
||||||
template_path=os.path.join(self.data_files_path, 'templates'),
|
static_path=os.path.join(self.data_files_path, 'static'),
|
||||||
static_files_path=os.path.join(self.data_files_path, 'static'),
|
static_url_prefix=url_path_join(self.hub.server.base_url, 'static/'),
|
||||||
|
template_path=template_path,
|
||||||
|
jinja2_env=jinja_env,
|
||||||
)
|
)
|
||||||
# allow configured settings to have priority
|
# allow configured settings to have priority
|
||||||
settings.update(self.tornado_settings)
|
settings.update(self.tornado_settings)
|
||||||
|
@@ -5,9 +5,16 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
try:
|
||||||
|
# py3
|
||||||
|
from http.client import responses
|
||||||
|
except ImportError:
|
||||||
|
from httplib import responses
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from jinja2 import TemplateNotFound
|
||||||
|
|
||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
from tornado.escape import url_escape
|
from tornado.escape import url_escape
|
||||||
from tornado.httputil import url_concat
|
from tornado.httputil import url_concat
|
||||||
@@ -54,26 +61,97 @@ class BaseHandler(RequestHandler):
|
|||||||
return cookie_token.user.name
|
return cookie_token.user.name
|
||||||
else:
|
else:
|
||||||
# have cookie, but it's not valid. Clear it and start over.
|
# have cookie, but it's not valid. Clear it and start over.
|
||||||
self.clear_cookie(self.hub.server.cookie_name)
|
self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url)
|
||||||
|
|
||||||
def clear_login_cookie(self):
|
def clear_login_cookie(self):
|
||||||
username = self.get_current_user()
|
username = self.get_current_user()
|
||||||
if username is not None:
|
if username is not None:
|
||||||
user = self.db.query(orm.User).filter(name=username).first()
|
user = self.db.query(orm.User).filter(name=username).first()
|
||||||
if user is not None:
|
if user is not None and user.server is not None:
|
||||||
self.clear_cookie(user.server.cookie_name, path=user.server.base_url)
|
self.clear_cookie(user.server.cookie_name, path=user.server.base_url)
|
||||||
self.clear_cookie(self.cookie_name, path=self.hub.base_url)
|
self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def spawner_class(self):
|
def spawner_class(self):
|
||||||
return self.settings.get('spawner_class', LocalProcessSpawner)
|
return self.settings.get('spawner_class', LocalProcessSpawner)
|
||||||
|
|
||||||
|
#---------------------------------------------------------------
|
||||||
|
# template rendering
|
||||||
|
#---------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_template(self, name):
|
||||||
|
"""Return the jinja template object for a given name"""
|
||||||
|
return self.settings['jinja2_env'].get_template(name)
|
||||||
|
|
||||||
|
def render_template(self, name, **ns):
|
||||||
|
ns.update(self.template_namespace)
|
||||||
|
template = self.get_template(name)
|
||||||
|
return template.render(**ns)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logged_in(self):
|
||||||
|
"""Is a user currently logged in?"""
|
||||||
|
user = self.get_current_user()
|
||||||
|
return (user and not user == 'anonymous')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_namespace(self):
|
||||||
|
return dict(
|
||||||
|
base_url=self.base_url,
|
||||||
|
logged_in=self.logged_in,
|
||||||
|
login_url=self.settings['login_url'],
|
||||||
|
static_url=self.static_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
def write_error(self, status_code, **kwargs):
|
||||||
|
"""render custom error pages"""
|
||||||
|
exc_info = kwargs.get('exc_info')
|
||||||
|
message = ''
|
||||||
|
status_message = responses.get(status_code, 'Unknown HTTP Error')
|
||||||
|
if exc_info:
|
||||||
|
exception = exc_info[1]
|
||||||
|
# get the custom message, if defined
|
||||||
|
try:
|
||||||
|
message = exception.log_message % exception.args
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# construct the custom reason, if defined
|
||||||
|
reason = getattr(exception, 'reason', '')
|
||||||
|
if reason:
|
||||||
|
status_message = reason
|
||||||
|
|
||||||
|
# build template namespace
|
||||||
|
ns = dict(
|
||||||
|
status_code=status_code,
|
||||||
|
status_message=status_message,
|
||||||
|
message=message,
|
||||||
|
exception=exception,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.set_header('Content-Type', 'text/html')
|
||||||
|
# render the template
|
||||||
|
try:
|
||||||
|
html = self.render_template('%s.html' % status_code, **ns)
|
||||||
|
except TemplateNotFound:
|
||||||
|
self.log.debug("No template for %d", status_code)
|
||||||
|
html = self.render_template('error.html', **ns)
|
||||||
|
|
||||||
|
self.write(html)
|
||||||
|
|
||||||
|
|
||||||
|
class Template404(BaseHandler):
|
||||||
|
"""Render our 404 template"""
|
||||||
|
def prepare(self):
|
||||||
|
raise web.HTTPError(404)
|
||||||
|
|
||||||
|
|
||||||
class RootHandler(BaseHandler):
|
class RootHandler(BaseHandler):
|
||||||
"""Redirect from / to /user/foo/ after logging in."""
|
"""Redirect from / to /user/foo/ after logging in."""
|
||||||
@web.authenticated
|
@web.authenticated
|
||||||
def get(self):
|
def get(self):
|
||||||
self.redirect("/user/%s/" % self.get_current_user())
|
uri = "/user/%s/" % self.get_current_user()
|
||||||
|
self.redirect(uri, permanent=False)
|
||||||
|
|
||||||
|
|
||||||
class UserHandler(BaseHandler):
|
class UserHandler(BaseHandler):
|
||||||
@@ -98,24 +176,25 @@ class LogoutHandler(BaseHandler):
|
|||||||
"""Log a user out by clearing their login cookie."""
|
"""Log a user out by clearing their login cookie."""
|
||||||
def get(self):
|
def get(self):
|
||||||
self.clear_login_cookie()
|
self.clear_login_cookie()
|
||||||
self.write("logged out")
|
html = self.render_template('logout.html')
|
||||||
|
self.finish(html)
|
||||||
|
|
||||||
class LoginHandler(BaseHandler):
|
class LoginHandler(BaseHandler):
|
||||||
"""Render the login page."""
|
"""Render the login page."""
|
||||||
|
|
||||||
def _render(self, message=None, username=None):
|
def _render(self, message=None, username=None):
|
||||||
self.render('login.html',
|
return self.render_template('login.html',
|
||||||
next=url_escape(self.get_argument('next', default='')),
|
next=url_escape(self.get_argument('next', default='')),
|
||||||
username=username,
|
username=username,
|
||||||
message=message,
|
message=message,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
if False and self.get_current_user():
|
if self.get_argument('next', False) and self.get_current_user():
|
||||||
self.redirect(self.get_argument('next', default='/'))
|
self.redirect(self.get_argument('next'))
|
||||||
else:
|
else:
|
||||||
username = self.get_argument('username', default='')
|
username = self.get_argument('username', default='')
|
||||||
self._render(username=username)
|
self.finish(self._render(username=username))
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def notify_proxy(self, user):
|
def notify_proxy(self, user):
|
||||||
@@ -211,13 +290,15 @@ class LoginHandler(BaseHandler):
|
|||||||
user = yield self.spawn_single_user(username)
|
user = yield self.spawn_single_user(username)
|
||||||
self.set_login_cookies(user)
|
self.set_login_cookies(user)
|
||||||
next_url = self.get_argument('next', default='') or '/user/%s/' % username
|
next_url = self.get_argument('next', default='') or '/user/%s/' % username
|
||||||
|
print('next', next_url)
|
||||||
self.redirect(next_url)
|
self.redirect(next_url)
|
||||||
else:
|
else:
|
||||||
self.log.debug("Failed login for %s", username)
|
self.log.debug("Failed login for %s", username)
|
||||||
self._render(
|
html = self._render(
|
||||||
message={'error': 'Invalid username or password'},
|
message={'error': 'Invalid username or password'},
|
||||||
username=username,
|
username=username,
|
||||||
)
|
)
|
||||||
|
self.finish(html)
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------------------------
|
#------------------------------------------------------------------------------
|
||||||
|
36
setup.py
36
setup.py
@@ -42,9 +42,12 @@ except NameError:
|
|||||||
locs = locs or globs
|
locs = locs or globs
|
||||||
exec(compile(open(fname).read(), fname, "exec"), globs, locs)
|
exec(compile(open(fname).read(), fname, "exec"), globs, locs)
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
pjoin = os.path.join
|
pjoin = os.path.join
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
share_jupyter = pjoin(here, 'share', 'jupyter')
|
||||||
|
static = pjoin(share_jupyter, 'static')
|
||||||
|
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
# Build basic package data, etc.
|
# Build basic package data, etc.
|
||||||
#---------------------------------------------------------------------------
|
#---------------------------------------------------------------------------
|
||||||
@@ -53,7 +56,6 @@ def get_data_files():
|
|||||||
"""Get data files in share/jupyter"""
|
"""Get data files in share/jupyter"""
|
||||||
|
|
||||||
data_files = []
|
data_files = []
|
||||||
share_jupyter = pjoin(here, 'share', 'jupyter')
|
|
||||||
ntrim = len(here) + 1
|
ntrim = len(here) + 1
|
||||||
|
|
||||||
for (d, dirs, filenames) in os.walk(share_jupyter):
|
for (d, dirs, filenames) in os.walk(share_jupyter):
|
||||||
@@ -117,19 +119,41 @@ class Bower(Command):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
check_call(['bower', 'install', '--allow-root'])
|
check_call(['bower', 'install', '--allow-root'])
|
||||||
|
# update data-files in case this created new files
|
||||||
self.distribution.data_files = get_data_files()
|
self.distribution.data_files = get_data_files()
|
||||||
|
|
||||||
def get_outputs(self):
|
class CSS(Command):
|
||||||
return []
|
description = "compile CSS from LESS"
|
||||||
|
|
||||||
def get_inputs(self):
|
user_options = []
|
||||||
return []
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
style_less = pjoin(static, 'less', 'style.less')
|
||||||
|
style_css = pjoin(static, 'css', 'style.min.css')
|
||||||
|
sourcemap = style_css + '.map'
|
||||||
|
check_call([
|
||||||
|
'lessc', '-x', '--verbose',
|
||||||
|
'--source-map-basepath={}'.format(static),
|
||||||
|
'--source-map={}'.format(sourcemap),
|
||||||
|
'--source-map-rootpath=../',
|
||||||
|
style_less, style_css,
|
||||||
|
])
|
||||||
|
# update data-files in case this created new files
|
||||||
|
self.distribution.data_files = get_data_files()
|
||||||
|
|
||||||
# ensure bower is run as part of install
|
# ensure bower is run as part of install
|
||||||
install.sub_commands.insert(0, ('bower', None))
|
install.sub_commands.insert(0, ('bower', None))
|
||||||
|
install.sub_commands.insert(1, ('css', None))
|
||||||
|
|
||||||
setup_args['cmdclass'] = {
|
setup_args['cmdclass'] = {
|
||||||
'bower': Bower,
|
'bower': Bower,
|
||||||
|
'css': CSS,
|
||||||
}
|
}
|
||||||
|
|
||||||
# setuptools requirements
|
# setuptools requirements
|
||||||
|
BIN
share/jupyter/static/images/jupyterlogo.png
Normal file
BIN
share/jupyter/static/images/jupyterlogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
20
share/jupyter/static/less/error.less
Normal file
20
share/jupyter/static/less/error.less
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
div.error {
|
||||||
|
margin: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.error > h1 {
|
||||||
|
font-size: 500%;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.error > p {
|
||||||
|
font-size: 200%;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.traceback-wrapper {
|
||||||
|
text-align: left;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
14
share/jupyter/static/less/logout.less
Normal file
14
share/jupyter/static/less/logout.less
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
div.logout-main {
|
||||||
|
margin: 2em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.logout-main > h1 {
|
||||||
|
font-size: 400%;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.logout-main > p {
|
||||||
|
font-size: 200%;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
3
share/jupyter/static/less/page.less
Normal file
3
share/jupyter/static/less/page.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.jpy-logo {
|
||||||
|
height: 48px;
|
||||||
|
}
|
26
share/jupyter/static/less/style.less
Normal file
26
share/jupyter/static/less/style.less
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*!
|
||||||
|
*
|
||||||
|
* Twitter Bootstrap
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@import "../components/bootstrap/less/bootstrap.less";
|
||||||
|
@import "../components/bootstrap/less/responsive-utilities.less";
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* Font Awesome
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@import "../components/font-awesome/less/font-awesome.less";
|
||||||
|
@fa-font-path: "../components/font-awesome/fonts";
|
||||||
|
|
||||||
|
/*!
|
||||||
|
*
|
||||||
|
* Jupyter
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import "./variables.less";
|
||||||
|
@import "./page.less";
|
||||||
|
@import "./error.less";
|
||||||
|
@import "./logout.less";
|
0
share/jupyter/static/less/variables.less
Normal file
0
share/jupyter/static/less/variables.less
Normal file
6
share/jupyter/templates/404.html
Normal file
6
share/jupyter/templates/404.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends "error.html" %}
|
||||||
|
|
||||||
|
{% block error_detail %}
|
||||||
|
<p>Jupyter has lots of moons, but this is not one...</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
22
share/jupyter/templates/error.html
Normal file
22
share/jupyter/templates/error.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% block login_widget %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="error">
|
||||||
|
{% block h1_error %}
|
||||||
|
<h1>{{status_code}} : {{status_message}}</h1>
|
||||||
|
{% endblock h1_error %}
|
||||||
|
{% block error_detail %}
|
||||||
|
{% if message %}
|
||||||
|
<p>The error was:</p>
|
||||||
|
<div class="traceback-wrapper">
|
||||||
|
<pre class="traceback">{{message}}</pre>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{% endblock %}
|
@@ -1,18 +1,44 @@
|
|||||||
<html>
|
{% extends "page.html" %}
|
||||||
<head>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
{% block login_widget %}
|
||||||
{% if message %}
|
{% endblock %}
|
||||||
<div id="message">{{message}}</div>
|
|
||||||
{% end if %}
|
{% block site %}
|
||||||
<form action="?next={{next}}" method="post">
|
|
||||||
<input type="text" name="username" id="user_input" value="{{username}}">
|
<div id="login-main" class="container">
|
||||||
<input type="password" name="password" id="password_input">
|
<div class="row">
|
||||||
<button type="submit" id="login_submit">Log in</button>
|
<form action="{{login_url}}?next={{next}}" method="post" role="form">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon">Username:</span>
|
||||||
|
<input type="username" class="form-control" name="username" id="username_input" val="{{username}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon">Password:</span>
|
||||||
|
<input type="password" class="form-control" name="password" id="password_input">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" id="login_submit" class="btn btn-default">Log in</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
{% if message %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="message">
|
||||||
|
{{message}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div/>
|
||||||
|
|
||||||
<script type="text/javascript">
|
{% endblock %}
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
{% block script %}
|
||||||
|
{{super()}}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
13
share/jupyter/templates/logout.html
Normal file
13
share/jupyter/templates/logout.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "page.html" %}
|
||||||
|
|
||||||
|
{% block login_widget %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
|
||||||
|
<div class="container logout-main">
|
||||||
|
<h1>You have been logged out</h1>
|
||||||
|
<p><a href="{base_url}login">Log in again...</a></p>
|
||||||
|
<div/>
|
||||||
|
|
||||||
|
{% endblock %}
|
76
share/jupyter/templates/page.html
Normal file
76
share/jupyter/templates/page.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>{% block title %}Jupyter Hub{% endblock %}</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="{{static_url("images/favicon.ico") }}">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
{% block stylesheet %}
|
||||||
|
<link rel="stylesheet" href="{{ static_url("css/style.min.css") }}" type="text/css"/>
|
||||||
|
{% endblock %}
|
||||||
|
<script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script>
|
||||||
|
require.config({
|
||||||
|
baseUrl: '{{static_url("", include_version=False)}}',
|
||||||
|
paths: {
|
||||||
|
jquery: 'components/jquery/jquery.min',
|
||||||
|
bootstrap: 'components/bootstrap/js/bootstrap.min',
|
||||||
|
moment: "components/moment/moment",
|
||||||
|
},
|
||||||
|
shim: {
|
||||||
|
bootstrap: {
|
||||||
|
deps: ["jquery"],
|
||||||
|
exports: "bootstrap"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% block meta %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body {% block params %}{% endblock %}>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<div id='noscript'>
|
||||||
|
Jupyter Hub requires JavaScript.<br>
|
||||||
|
Please enable it to proceed.
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<div id="header" class="navbar navbar-static-top">
|
||||||
|
<div class="container">
|
||||||
|
<span id="jupyterlogo" class="pull-left"><a href="{{base_url}}home/" alt='dashboard'><img src='{{static_url("images/jupyterlogo.png") }}' alt='Jupyter Hub' class='jpy-logo'/></a></span>
|
||||||
|
|
||||||
|
{% block login_widget %}
|
||||||
|
|
||||||
|
<span id="login_widget">
|
||||||
|
{% if logged_in %}
|
||||||
|
<button id="logout">Logout</button>
|
||||||
|
{% else %}
|
||||||
|
<button id="login">Login</button>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Reference in New Issue
Block a user