Compare commits

...

24 Commits
0.6.0 ... 0.6.1

Author SHA1 Message Date
Min RK
2985562c2f release 0.6.1 2016-05-04 14:08:39 +02:00
Min RK
754f850e95 changelog for 0.6.1 2016-05-04 14:08:39 +02:00
Min RK
dccb85d225 plural add-users ids 2016-05-04 13:57:16 +02:00
Min RK
a0e401bc87 Merge pull request #551 from minrk/proxy-error
Serve proxy error pages from the Hub
2016-05-04 12:34:10 +02:00
Min RK
c6885a2124 Merge pull request #552 from minrk/poll-and-notify
notice dead servers more often
2016-05-04 12:33:09 +02:00
Min RK
7528fb7d9b notice dead servers more often
call poll_and_notify to ensure triggering of dead-server events in a few places:

- `/hub/home` page view
- user start and stop API endpoints

This should avoid the failure to stop a server that's died uncleanly because the server hasn't noticed yet
2016-05-04 11:07:28 +02:00
Carol Willing
e7df5a299c Merge pull request #556 from minrk/shutdown-all
Add Stop All button to admin page
2016-05-03 05:58:41 -07:00
Min RK
ff997bbce5 Add Stop All button to admin page
for stopping all single-user servers at once
2016-05-03 13:25:12 +02:00
Min RK
1e21e00e1a return status from poll_and_notify
allows calling it directly
2016-04-27 14:28:23 +02:00
Min RK
77d3ee98f9 allow logo_url in template namespace to set the logo link 2016-04-27 14:06:51 +02:00
Min RK
1f861b2c90 server proxy error pages from the Hub 2016-04-27 14:06:29 +02:00
Carol Willing
14a00e67b4 Merge pull request #550 from daradib/typo
Fix docs typo for Spawner.disable_user_config
2016-04-26 15:28:02 -07:00
Dara Adib
14f63c168d Fix docs typo for Spawner.disable_user_config 2016-04-26 11:36:48 -07:00
Kyle Kelley
e70dbb3d32 Merge pull request #549 from minrk/optional-statsd
Make statsd an optional dependency
2016-04-26 07:46:28 -05:00
Min RK
b679275a68 remove unneeded codecov.yml
codecov team config suffices
2016-04-26 13:44:29 +02:00
Min RK
0c1478a67e Make statsd an optional dependency
only import it if it's used
2016-04-26 13:37:39 +02:00
Min RK
d26e2346a2 Merge pull request #548 from minrk/jupyterhub-urls
fix a few more jupyter->jupyterhub URLs
2016-04-26 12:41:19 +02:00
Min RK
9a09c841b9 Merge pull request #547 from minrk/disable-codecov-comments
disable codecov PR comments
2016-04-26 12:41:02 +02:00
Min RK
f1d4f5a733 fix a few more jupyter->jupyterhub URLs
in README
2016-04-26 11:58:27 +02:00
Min RK
d970dd4c89 disable CodeCov PR comments
The've removed web app config, in favor of codecov.yml,
discarding our existing config,
which means coverage reports are showing up in most Jupyter PRs now.
2016-04-26 11:55:52 +02:00
Min RK
f3279bf849 Merge pull request #544 from rafael-ladislau/master
Fix multiple windows logout error
2016-04-26 11:41:53 +02:00
Rafael Ladislau
db0878a495 Fix multiple windows logout error
When you have two JupyterHub windows and log out successfully in one of them. If you try to click the logout button in the other window, you will receive a 500 error.

It happened because there were operations being done in a None user object.
2016-04-25 13:31:39 -04:00
Min RK
c9b1042791 back to dev 2016-04-25 14:34:15 +02:00
Min RK
cd81320d8f push tags on circleci 2016-04-25 14:25:34 +02:00
16 changed files with 123 additions and 44 deletions

View File

@@ -6,7 +6,7 @@ Questions, comments? Visit our Google Group:
[![Build Status](https://travis-ci.org/jupyterhub/jupyterhub.svg?branch=master)](https://travis-ci.org/jupyterhub/jupyterhub)
[![Circle CI](https://circleci.com/gh/jupyterhub/jupyterhub.svg?style=shield&circle-token=b5b65862eb2617b9a8d39e79340b0a6b816da8cc)](https://circleci.com/gh/jupyterhub/jupyterhub)
[![Documentation Status](https://readthedocs.org/projects/jupyterhub/badge/?version=latest)](http://jupyterhub.readthedocs.org/en/latest/?badge=latest)
[![codecov.io](https://codecov.io/github/jupyter/jupyterhub/coverage.svg?branch=master)](https://codecov.io/github/jupyter/jupyterhub?branch=master)
[![codecov.io](https://codecov.io/github/jupyterhub/jupyterhub/coverage.svg?branch=master)](https://codecov.io/github/jupyterhub/jupyterhub?branch=master)
JupyterHub, a multi-user server, manages and proxies multiple instances of the single-user <del>IPython</del> Jupyter notebook server.
@@ -67,7 +67,7 @@ Jupyter ~~IPython~~ notebook:
For a development install, clone the repository and then install from source:
git clone https://github.com/jupyter/jupyterhub
git clone https://github.com/jupyterhub/jupyterhub
cd jupyterhub
pip3 install -r dev-requirements.txt -e .
@@ -93,7 +93,7 @@ and then visit `http://localhost:8000`, and sign in with your unix credentials.
To allow multiple users to sign into the server, you will need to
run the `jupyterhub` command as a *privileged user*, such as root.
The [wiki](https://github.com/jupyter/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
The [wiki](https://github.com/jupyterhub/jupyterhub/wiki/Using-sudo-to-run-JupyterHub-without-root-privileges)
describes how to run the server as a *less privileged user*, which requires more
configuration of the system.
@@ -116,13 +116,13 @@ The authentication and process spawning mechanisms can be replaced,
which should allow plugging into a variety of authentication or process control environments.
Some examples, meant as illustration and testing of this concept:
- Using GitHub OAuth instead of PAM with [OAuthenticator](https://github.com/jupyter/oauthenticator)
- Spawning single-user servers with Docker, using the [DockerSpawner](https://github.com/jupyter/dockerspawner)
- Using GitHub OAuth instead of PAM with [OAuthenticator](https://github.com/jupyterhub/oauthenticator)
- Spawning single-user servers with Docker, using the [DockerSpawner](https://github.com/jupyterhub/dockerspawner)
### Docker
There is a ready to go [docker image for JupyterHub](https://hub.docker.com/r/jupyter/jupyterhub/).
[Note: This `jupyter/jupyterhub` docker image is only an image for running the Hub service itself.
There is a ready to go [docker image for JupyterHub](https://hub.docker.com/r/jupyterhub/jupyterhub/).
[Note: This `jupyterhub/jupyterhub` docker image is only an image for running the Hub service itself.
It does not require the other Jupyter components, which are needed by the single-user servers.
To run the single-user servers, which may be on the same system as the Hub or not, installation of Jupyter Notebook ≥ 4 is required.]
@@ -148,7 +148,7 @@ We encourage you to ask questions on the mailing list:
and you may participate in development discussions or get live help on Gitter:
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jupyter/jupyterhub?utm_source=badge&utm_medium=badge)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jupyterhub/jupyterhub?utm_source=badge&utm_medium=badge)
## Resources
- [Project Jupyter website](https://jupyter.org)

View File

@@ -16,4 +16,9 @@ deployment:
branch: master
commands:
- docker login -u $DOCKER_USER -p $DOCKER_PASS -e unused@example.com
- docker push jupyterhub/jupyterhub-onbuild:${CIRCLE_TAG:-latest}
- docker push jupyterhub/jupyterhub-onbuild
release:
tag: /.*/
commands:
- docker login -u $DOCKER_USER -p $DOCKER_PASS -e unused@example.com
- docker push jupyterhub/jupyterhub-onbuild:$CIRCLE_TAG

View File

@@ -4,6 +4,17 @@ See `git log` for a more detailed summary.
## 0.6
### 0.6.1
Bugfixes on 0.6:
- statsd is an optional dependency, only needed if in use
- Notice more quickly when servers have crashed
- Better error pages for proxy errors
- Add Stop All button to admin panel for stopping all servers at once
### 0.6.0
- JupyterHub has moved to a new `jupyterhub` namespace on GitHub and Docker. What was `juptyer/jupyterhub` is now `jupyterhub/jupyterhub`, etc.
- `jupyterhub/jupyterhub` image on DockerHub no longer loads the jupyterhub_config.py in an ONBUILD step. A new `jupyterhub/jupyterhub-onbuild` image does this
- Add statsd support, via `c.JupyterHub.statsd_{host,port,prefix}`

View File

@@ -45,7 +45,7 @@ as it resolves all of the cross-site issues.
### Disabling user config
If subdomains are not available or not desirable,
0.5 also adds an option `Spawner.disable_use_config`,
0.5 also adds an option `Spawner.disable_user_config`,
which you can set to prevent the user-owned configuration files from being loaded.
This leaves only package installation and PATHs as things the admin must enforce.

View File

@@ -161,8 +161,9 @@ class UserServerAPIHandler(APIHandler):
@admin_or_self
def post(self, name):
user = self.find_user(name)
if user.spawner:
state = yield user.spawner.poll()
if user.running:
# include notify, so that a server that died is noticed immediately
state = yield user.spawner.poll_and_notify()
if state is None:
raise web.HTTPError(400, "%s's server is already running" % name)
@@ -180,7 +181,8 @@ class UserServerAPIHandler(APIHandler):
return
if not user.running:
raise web.HTTPError(400, "%s's server is not running" % name)
status = yield user.spawner.poll()
# include notify, so that a server that died is noticed immediately
status = yield user.spawner.poll_and_notify()
if status is not None:
raise web.HTTPError(400, "%s's server is not running" % name)
yield self.stop_single_user(user)

View File

@@ -12,7 +12,6 @@ import signal
import socket
import sys
import threading
import statsd
from datetime import datetime
from distutils.version import LooseVersion as V
from getpass import getuser
@@ -508,21 +507,20 @@ class JupyterHub(Application):
help="Extra log handlers to set on JupyterHub logger",
).tag(config=True)
@property
def statsd(self):
if hasattr(self, '_statsd'):
return self._statsd
statsd = Any(allow_none=False, help="The statsd client, if any. A mock will be used if we aren't using statsd")
@default('statsd')
def _statsd(self):
if self.statsd_host:
self._statsd = statsd.StatsClient(
import statsd
client = statsd.StatsClient(
self.statsd_host,
self.statsd_port,
self.statsd_prefix
)
return self._statsd
return client
else:
# return an empty mock object!
self._statsd = EmptyClass()
return self._statsd
return EmptyClass()
def init_logging(self):
# This prevents double log messages because tornado use a root logger that
@@ -939,6 +937,7 @@ class JupyterHub(Application):
'--api-ip', self.proxy.api_server.ip,
'--api-port', str(self.proxy.api_server.port),
'--default-target', self.hub.server.host,
'--error-target', url_path_join(self.hub.server.url, 'error'),
]
if self.subdomain_host:
cmd.append('--host-routing')

View File

@@ -410,6 +410,7 @@ class BaseHandler(RequestHandler):
"""render custom error pages"""
exc_info = kwargs.get('exc_info')
message = ''
exception = None
status_message = responses.get(status_code, 'Unknown HTTP Error')
if exc_info:
exception = exc_info[1]

View File

@@ -19,8 +19,8 @@ class LogoutHandler(BaseHandler):
for name in user.other_user_cookies:
self.clear_login_cookie(name)
user.other_user_cookies = set([])
self.redirect(self.hub.server.base_url, permanent=False)
self.statsd.incr('logout')
self.redirect(self.hub.server.base_url, permanent=False)
class LoginHandler(BaseHandler):

View File

@@ -3,12 +3,14 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from http.client import responses
from jinja2 import TemplateNotFound
from tornado import web, gen
from .. import orm
from ..utils import admin_only, url_path_join
from .base import BaseHandler
from urllib.parse import quote
class RootHandler(BaseHandler):
@@ -41,9 +43,14 @@ class HomeHandler(BaseHandler):
"""Render the user's home page."""
@web.authenticated
@gen.coroutine
def get(self):
user = self.get_current_user()
if user.running:
# trigger poll_and_notify event in case of a server that died
yield user.spawner.poll_and_notify()
html = self.render_template('home.html',
user=self.get_current_user(),
user=user,
)
self.finish(html)
@@ -160,9 +167,43 @@ class AdminHandler(BaseHandler):
self.finish(html)
class ProxyErrorHandler(BaseHandler):
"""Handler for rendering proxy error pages"""
def get(self, status_code_s):
status_code = int(status_code_s)
status_message = responses.get(status_code, 'Unknown HTTP Error')
# build template namespace
hub_home = url_path_join(self.hub.server.base_url, 'home')
message_html = ''
if status_code == 503:
message_html = ' '.join([
"Your server appears to be down.",
"Try restarting it <a href='%s'>from the hub</a>" % hub_home
])
ns = dict(
status_code=status_code,
status_message=status_message,
message_html=message_html,
logo_url=hub_home,
)
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)
default_handlers = [
(r'/', RootHandler),
(r'/home', HomeHandler),
(r'/admin', AdminHandler),
(r'/spawn', SpawnHandler),
(r'/error/(\d+)', ProxyErrorHandler),
]

View File

@@ -15,7 +15,7 @@ from subprocess import Popen
from tempfile import mkdtemp
from tornado import gen
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.ioloop import PeriodicCallback
from traitlets.config import LoggingConfigurable
from traitlets import (
@@ -335,15 +335,17 @@ class Spawner(LoggingConfigurable):
self.stop_polling()
add_callback = IOLoop.current().add_callback
for callback in self._callbacks:
add_callback(callback)
try:
yield gen.maybe_future(callback())
except Exception:
self.log.exception("Unhandled error in poll callback for %s", self)
return status
death_interval = Float(0.1)
@gen.coroutine
def wait_for_death(self, timeout=10):
"""wait for the process to die, up to timeout seconds"""
loop = IOLoop.current()
for i in range(int(timeout / self.death_interval)):
status = yield self.poll()
if status is not None:

View File

@@ -6,7 +6,7 @@
version_info = (
0,
6,
0,
1,
# 'dev',
)

View File

@@ -2,6 +2,5 @@ traitlets>=4.1
tornado>=4.1
jinja2
pamela
statsd
sqlalchemy>=1.0
requests

View File

@@ -152,15 +152,15 @@ require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, mo
});
});
$("#add-user").click(function () {
var dialog = $("#add-user-dialog");
$("#add-users").click(function () {
var dialog = $("#add-users-dialog");
dialog.find(".username-input").val('');
dialog.find(".admin-checkbox").prop("checked", false);
dialog.modal();
});
$("#add-user-dialog").find(".save-button").click(function () {
var dialog = $("#add-user-dialog");
$("#add-users-dialog").find(".save-button").click(function () {
var dialog = $("#add-users-dialog");
var lines = dialog.find(".username-input").val().split('\n');
var admin = dialog.find(".admin-checkbox").prop("checked");
var usernames = [];
@@ -178,6 +178,15 @@ require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, mo
});
});
$("#stop-all-servers").click(function () {
$("#stop-all-servers-dialog").modal();
});
$("#stop-all-servers-dialog").find(".stop-all-button").click(function () {
// stop all clicks all the active stop buttons
$('.stop-server').not('.hidden').click();
});
$("#shutdown-hub").click(function () {
var dialog = $("#shutdown-hub-dialog");
dialog.find("input[type=checkbox]").prop("checked", true);

View File

@@ -32,8 +32,9 @@
<tbody>
<tr class="user-row add-user-row">
<td colspan="12">
<a id="add-user" class="col-xs-5 btn btn-default">Add User</a>
<a id="shutdown-hub" class="col-xs-5 col-xs-offset-2 btn btn-danger">Shutdown Hub</a>
<a id="add-users" class="col-xs-2 btn btn-default">Add Users</a>
<a id="stop-all-servers" class="col-xs-2 col-xs-offset-5 btn btn-danger">Stop All</a>
<a id="shutdown-hub" class="col-xs-2 col-xs-offset-1 btn btn-danger">Shutdown Hub</a>
</td>
</tr>
{% for u in users %}
@@ -71,6 +72,10 @@
This operation cannot be undone.
{% endcall %}
{% call modal('Stop All Servers', btn_label='Stop All', btn_class='btn-danger stop-all-button') %}
Are you sure you want to stop all your users' servers? Kernels will be shutdown and unsaved data may be lost.
{% endcall %}
{% call modal('Shutdown Hub', btn_label='Shutdown', btn_class='btn-danger shutdown-button') %}
Are you sure you want to shutdown the Hub?
You can choose to leave the proxy and/or single-user servers running by unchecking the boxes below:
@@ -108,7 +113,7 @@
{{ user_modal('Edit User') }}
{{ user_modal('Add User', multi=True) }}
{{ user_modal('Add Users', multi=True) }}
{% endblock %}

View File

@@ -17,6 +17,11 @@
{{message}}
</p>
{% endif %}
{% if message_html %}
<p>
{{message_html | safe}}
</p>
{% endif %}
{% endblock error_detail %}
</div>

View File

@@ -82,7 +82,7 @@
<div id="header" class="navbar navbar-static-top">
<div class="container">
<span id="jupyterhub-logo" class="pull-left"><a href="{{base_url}}"><img src='{{base_url}}logo' alt='JupyterHub' class='jpy-logo' title='Home'/></a></span>
<span id="jupyterhub-logo" class="pull-left"><a href="{{logo_url or base_url}}"><img src='{{base_url}}logo' alt='JupyterHub' class='jpy-logo' title='Home'/></a></span>
{% block login_widget %}