Merge branch 'master' into issue-2404

pull/2449/head
Grant Nestor 9 years ago committed by GitHub
commit 35a9ff7932

@ -7,7 +7,7 @@
"bootstrap-tour": "0.9.0",
"codemirror": "components/codemirror#~5.22.2",
"es6-promise": "~1.0",
"font-awesome": "components/font-awesome#~4.2.0",
"font-awesome": "components/font-awesome#~4.7.0",
"google-caja": "5669",
"jquery": "components/jquery#~2.0",
"jquery-typeahead": "~2.0.0",

@ -31,7 +31,8 @@ When you start a notebook server with token authentication enabled (default),
a token is generated to use for authentication.
This token is logged to the terminal, so that you can copy/paste the URL into your browser::
[I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01
[I 11:59:16.597 NotebookApp] The Jupyter Notebook is running at:
http://localhost:8888/?token=c8de56fa4deed24899803e93c227592aef6538f93025fe01
If the notebook server is going to open your browser automatically

@ -46,11 +46,15 @@ def pkg_commit_hash(pkg_path):
while cur_path != par_path:
cur_path = par_path
if p.exists(p.join(cur_path, '.git')):
proc = subprocess.Popen('git rev-parse --short HEAD',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=pkg_path, shell=True)
repo_commit, _ = proc.communicate()
try:
proc = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=pkg_path)
repo_commit, _ = proc.communicate()
except OSError:
repo_commit = None
if repo_commit:
return 'repository', repo_commit.strip().decode('ascii')
else:

@ -39,7 +39,12 @@ from notebook.services.security import csp_report_uri
#-----------------------------------------------------------------------------
non_alphanum = re.compile(r'[^A-Za-z0-9]')
sys_info = json.dumps(get_sys_info())
_sys_info_cache = None
def json_sys_info():
global _sys_info_cache
if _sys_info_cache is None:
_sys_info_cache = json.dumps(get_sys_info())
return _sys_info_cache
def log():
if Application.initialized():
@ -357,7 +362,7 @@ class IPythonHandler(AuthenticatedHandler):
login_available=self.login_available,
token_available=bool(self.token or self.one_time_token),
static_url=self.static_url,
sys_info=sys_info,
sys_info=json_sys_info(),
contents_js_source=self.contents_js_source,
version_hash=self.version_hash,
ignore_minified_js=self.ignore_minified_js,

@ -296,5 +296,4 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
self.session = Session(config=self.config)
def get_compression_options(self):
# use deflate compress websocket
return {}
return self.settings.get('websocket_compression_options', None)

@ -93,7 +93,7 @@ from jupyter_client.kernelspec import KernelSpecManager, NoSuchKernel, NATIVE_KE
from jupyter_client.session import Session
from nbformat.sign import NotebookNotary
from traitlets import (
Dict, Unicode, Integer, List, Bool, Bytes, Instance,
Any, Dict, Unicode, Integer, List, Bool, Bytes, Instance,
TraitError, Type, Float, observe, default, validate
)
from ipython_genutils import py3compat
@ -294,6 +294,7 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
handlers.extend(load_handlers('services.security.handlers'))
handlers.extend(load_handlers('services.shutdown'))
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {
@ -740,7 +741,18 @@ class NotebookApp(JupyterApp):
tornado_settings = Dict(config=True,
help="Supply overrides for the tornado.web.Application that the "
"Jupyter notebook uses.")
websocket_compression_options = Any(None, config=True,
help="""
Set the tornado compression options for websocket connections.
This value will be returned from :meth:`WebSocketHandler.get_compression_options`.
None (default) will disable compression.
A dict (even an empty one) will enable compression.
See the tornado docs for WebSocketHandler.get_compression_options for details.
"""
)
terminado_settings = Dict(config=True,
help='Supply overrides for terminado. Currently only supports "shell_command".')
@ -1107,6 +1119,7 @@ class NotebookApp(JupyterApp):
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
self.tornado_settings['allow_origin'] = self.allow_origin
self.tornado_settings['websocket_compression_options'] = self.websocket_compression_options
if self.allow_origin_pat:
self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
self.tornado_settings['allow_credentials'] = self.allow_credentials
@ -1206,7 +1219,7 @@ class NotebookApp(JupyterApp):
log("Terminals not available (error was %s)", e)
def init_signal(self):
if not sys.platform.startswith('win') and sys.stdin.isatty():
if not sys.platform.startswith('win') and sys.stdin and sys.stdin.isatty():
signal.signal(signal.SIGINT, self._handle_sigint)
signal.signal(signal.SIGTERM, self._signal_stop)
if hasattr(signal, 'SIGUSR1'):
@ -1353,7 +1366,8 @@ class NotebookApp(JupyterApp):
"Return the current working directory and the server url information"
info = self.contents_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The Jupyter Notebook is running at: %s" % self.display_url
# Format the info so that the URL fits on a single line in 80 char display
return info + "The Jupyter Notebook is running at:\n\r%s" % self.display_url
def server_info(self):
"""Return a JSONable dict of information about this server."""

@ -281,7 +281,7 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
if e.errno == errno.ENOENT:
self.log.warning("%s doesn't exist", os_path)
else:
self.log.warning("Error stat-ing %s: %s", (os_path, e))
self.log.warning("Error stat-ing %s: %s", os_path, e)
continue
if not stat.S_ISREG(st.st_mode) and not stat.S_ISDIR(st.st_mode):

@ -249,8 +249,8 @@ class APITest(NotebookTestBase):
self.assertEqual(nbnames, expected)
nbs = notebooks_only(self.api.list('ordering').json())
nbnames = [n['name'] for n in nbs]
expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
nbnames = {n['name'] for n in nbs}
expected = {'A.ipynb', 'b.ipynb', 'C.ipynb'}
self.assertEqual(nbnames, expected)
def test_list_dirs(self):

@ -15,7 +15,7 @@ from tornado.ioloop import IOLoop, PeriodicCallback
from jupyter_client.session import Session
from jupyter_client.multikernelmanager import MultiKernelManager
from traitlets import Dict, List, Unicode, TraitError, Integer, default, validate
from traitlets import Bool, Dict, List, Unicode, TraitError, Integer, default, validate
from notebook.utils import to_os_path
from notebook._tz import utcnow, isoformat
@ -71,6 +71,16 @@ class MappingKernelManager(MultiKernelManager):
help="""The interval (in seconds) on which to check for idle kernels exceeding the cull timeout value."""
)
cull_connected = Bool(False, config=True,
help="""Whether to consider culling kernels which have one or more connections.
Only effective if cull_idle_timeout is not 0."""
)
cull_busy = Bool(False, config=True,
help="""Whether to consider culling kernels which are busy.
Only effective if cull_idle_timeout is not 0."""
)
#-------------------------------------------------------------------------
# Methods for managing kernels and sessions
#-------------------------------------------------------------------------
@ -273,6 +283,10 @@ class MappingKernelManager(MultiKernelManager):
self.cull_kernels, 1000*self.cull_interval, loop)
self.log.info("Culling kernels with idle durations > %s seconds at %s second intervals ...",
self.cull_idle_timeout, self.cull_interval)
if self.cull_busy:
self.log.info("Culling kernels even if busy")
if self.cull_connected:
self.log.info("Culling kernels even with connected clients")
self._culler_callback.start()
self._initialized_culler = True
@ -294,8 +308,15 @@ class MappingKernelManager(MultiKernelManager):
if kernel.last_activity is not None:
dt_now = utcnow()
dt_idle = dt_now - kernel.last_activity
if dt_idle > timedelta(seconds=self.cull_idle_timeout): # exceeds timeout, can be culled
# Compute idle properties
is_idle_time = dt_idle > timedelta(seconds=self.cull_idle_timeout)
is_idle_execute = self.cull_busy or (kernel.execution_state != 'busy')
connections = self._kernel_connections.get(kernel_id, 0)
is_idle_connected = self.cull_connected or not connections
# Cull the kernel if all three criteria are met
if (is_idle_time and is_idle_execute and is_idle_connected):
idle_duration = int(dt_idle.total_seconds())
self.log.warning("Culling kernel '%s' (%s) due to %s seconds of inactivity.", kernel.kernel_name, kernel_id, idle_duration)
self.log.warning("Culling '%s' kernel '%s' (%s) with %d connections due to %s seconds of inactivity.",
kernel.execution_state, kernel.kernel_name, kernel_id, connections, idle_duration)
self.shutdown_kernel(kernel_id)

@ -0,0 +1,15 @@
"""HTTP handler to shut down the notebook server.
"""
from tornado import web, ioloop
from notebook.base.handlers import IPythonHandler
class ShutdownHandler(IPythonHandler):
@web.authenticated
def post(self):
self.log.info("Shutting down on /api/shutdown request.")
ioloop.IOLoop.current().stop()
default_handlers = [
(r"/api/shutdown", ShutdownHandler),
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -1017,6 +1017,36 @@ define([
}
};
// javascript stores text as utf16 and string indices use "code units",
// which stores high-codepoint characters as "surrogate pairs",
// which occupy two indices in the javascript string.
// We need to translate cursor_pos in the protocol (in characters)
// to js offset (with surrogate pairs taking two spots).
function js_idx_to_char_idx (js_idx, text) {
var char_idx = js_idx;
for (var i = 0; i < text.length && i < js_idx; i++) {
var char_code = text.charCodeAt(i);
// check for the first half of a surrogate pair
if (char_code >= 0xD800 && char_code < 0xDC00) {
char_idx -= 1;
}
}
return char_idx;
}
function char_idx_to_js_idx (char_idx, text) {
var js_idx = char_idx;
for (var i = 0; i < text.length && i < js_idx; i++) {
var char_code = text.charCodeAt(i);
// check for the first half of a surrogate pair
if (char_code >= 0xD800 && char_code < 0xDC00) {
js_idx += 1;
}
}
return js_idx;
}
// Test if a drag'n'drop event contains a file (as opposed to an HTML
// element/text from the document)
var dnd_contain_file = function(event) {
@ -1051,6 +1081,17 @@ define([
fn();
}
}
var change_favicon = function (src) {
var link = document.createElement('link'),
oldLink = document.getElementById('favicon');
link.id = 'favicon';
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = utils.url_path_join(utils.get_body_data('baseUrl'), src);
if (oldLink) document.head.removeChild(oldLink);
document.head.appendChild(link);
};
var utils = {
throttle: throttle,
@ -1101,7 +1142,10 @@ define([
format_datetime: format_datetime,
datetime_sort_helper: datetime_sort_helper,
dnd_contain_file: dnd_contain_file,
_ansispan:_ansispan
js_idx_to_char_idx: js_idx_to_char_idx,
char_idx_to_js_idx: char_idx_to_js_idx,
_ansispan:_ansispan,
change_favicon: change_favicon
};
return utils;

@ -27,6 +27,10 @@ body > #header {
z-index: 100;
#header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 5px;
padding-bottom: 5px;
padding-top: 5px;
.border-box-sizing();
@ -57,9 +61,6 @@ body > #header {
padding-left: 0px;
padding-top: (@navbar-height - @logo_height) / 2;
padding-bottom: (@navbar-height - @logo_height) / 2;
@media (max-width: @screen-sm-max){
margin-left: 10px;
}
}
@ -99,8 +100,12 @@ input.ui-button {
padding: 0.3em 0.9em;
}
span#kernel_logo_widget {
margin: 0 10px;
}
span#login_widget {
float: right;
}
span#login_widget > .button,

@ -79,6 +79,11 @@ define([
class: "btn-primary",
click: function () {
var new_name = d.find('input').val();
if (!new_name) {
// Reset the message
d.find('.rename-message').text("Enter a new filename:");
return false;
}
d.find('.rename-message').text("Renaming...");
d.find('input[type="text"]').prop('disabled', true);
that.editor.rename(new_name).then(

@ -164,7 +164,11 @@ define(function(require){
// now src is the right structure for typeahead
input.typeahead({
emptyTemplate: "No results found for <pre>{{query}}</pre>",
emptyTemplate: function(query) {
return $('<div>').text("No results found for ").append(
$('<code>').text(query)
);
},
maxItem: 1e3,
minLength: 0,
hint: true,

@ -153,6 +153,8 @@ define([
// one kernel completion came back, finish_completing will be called with the results
// we fork here and directly call finish completing if kernel is busy
var cursor_pos = this.editor.indexFromPos(cur);
var text = this.editor.getValue();
cursor_pos = utils.js_idx_to_char_idx(cursor_pos, text);
if (this.skip_kernel_completion) {
this.finish_completing({ content: {
matches: [],
@ -160,7 +162,7 @@ define([
cursor_end: cursor_pos,
}});
} else {
this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
this.cell.kernel.complete(text, cursor_pos,
$.proxy(this.finish_completing, this)
);
}
@ -175,6 +177,7 @@ define([
var start = content.cursor_start;
var end = content.cursor_end;
var matches = content.matches;
console.log(content);
var cur = this.editor.getCursor();
if (end === null) {
@ -187,7 +190,13 @@ define([
} else if (start < 0) {
start = end + start;
}
} else {
// handle surrogate pairs
var text = this.editor.getValue();
end = utils.char_idx_to_js_idx(end, text);
start = utils.char_idx_to_js_idx(start, text);
}
var results = CodeMirror.contextHint(this.editor);
var filtered_results = [];
//remove results from context completion

@ -40,6 +40,23 @@ define([
var $modal_ind_icon = $("#modal_indicator");
var $readonly_ind_icon = $('#readonly-indicator');
var $body = $('body');
var interval = 0;
var set_busy_favicon = function(on) {
if (on && !interval) {
var i = 0;
var icons = ['favicon-busy-1.ico', 'favicon-busy-3.ico', 'favicon-busy-3.ico'];
interval = setInterval(function() {
var icon = icons[i % 3];
utils.change_favicon('/static/base/images/' + icon);
i += 1;
}, 300);
} else {
clearInterval(interval);
utils.change_favicon('/static/base/images/favicon-notebook.ico');
interval = 0;
}
};
// Listen for the notebook loaded event. Set readonly indicator.
this.events.on('notebook_loaded.Notebook', function() {
@ -244,41 +261,30 @@ define([
knw.danger(short, undefined, showMsg);
});
var change_favicon = function (src) {
var link = document.createElement('link'),
oldLink = document.getElementById('favicon');
link.id = 'favicon';
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = utils.url_path_join(utils.get_body_data('baseUrl'), src);
if (oldLink) document.head.removeChild(oldLink);
document.head.appendChild(link);
};
this.events.on('kernel_starting.Kernel kernel_created.Session', function () {
// window.document.title='(Starting) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
knw.set_message("Kernel starting, please wait...");
change_favicon('/static/base/images/favicon-busy.ico');
set_busy_favicon(true);
});
this.events.on('kernel_ready.Kernel', function () {
// that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
knw.info("Kernel ready", 500);
change_favicon('/static/base/images/favicon.ico');
set_busy_favicon(false);
});
this.events.on('kernel_idle.Kernel', function () {
// that.save_widget.update_document_title();
$kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
change_favicon('/static/base/images/favicon.ico');
set_busy_favicon(false);
});
this.events.on('kernel_busy.Kernel', function () {
// window.document.title='(Busy) '+window.document.title;
$kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
change_favicon('/static/base/images/favicon-busy.ico');
set_busy_favicon(true);
});
this.events.on('spec_match_found.Kernel', function (evt, data) {

@ -77,7 +77,7 @@ define([
this.prompt_overlay.addClass('out_prompt_overlay prompt');
this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
this.collapse();
this.expand();
};
/**
@ -976,6 +976,7 @@ define([
this._display_id_targets = {};
this.trusted = true;
this.unscroll_area();
this.expand();
return;
}
};

@ -201,8 +201,8 @@ define([
this.cancel_pending();
var editor = cell.code_mirror;
var cursor = editor.getCursor();
var cursor_pos = editor.indexFromPos(cursor);
var text = cell.get_text();
var cursor_pos = utils.js_idx_to_char_idx(editor.indexFromPos(cursor), text);
this._hide_if_no_docstring = hide_if_no_docstring;

@ -1,6 +1,4 @@
#kernel_logo_widget {
.pull-right();
.current_kernel_logo {
display: none;
.navbar-vertical-align(32px);

@ -1,12 +1,15 @@
span.save_widget {
margin-top: 6px;
max-width: 100%;
height: 30px;
margin-top: 4px;
display: flex;
justify-content: flex-start;
align-items: baseline;
width: 50%;
flex: 1;
span.filename {
height: 1em;
height: 100%;
line-height: 1em;
padding: 3px;
margin-left: @padding-large-horizontal;
border: none;
font-size: 146.5%;

@ -2,6 +2,8 @@
{% block title %}{{page_title}}{% endblock %}
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon-file.ico") }}">{% endblock %}
{% block stylesheet %}
<link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
<link rel="stylesheet" href="{{ static_url('components/codemirror/addon/dialog/dialog.css') }}">

@ -1,5 +1,7 @@
{% extends "page.html" %}
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon-notebook.ico") }}">{% endblock %}
{% block stylesheet %}
{% if mathjax_url %}
@ -36,7 +38,7 @@ data-notebook-path="{{notebook_path | urlencode}}"
{% block headercontainer %}
<span id="save_widget" class="pull-left save_widget">
<span id="save_widget" class="save_widget">
<span id="notebook_name" class="filename"></span>
<span class="checkpoint_status"></span>
<span class="autosave_status"></span>

@ -120,6 +120,9 @@ dir="ltr">
<div id="header-container" class="container">
<div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{default_url}}" title='dashboard'>{% block logo %}<img src='{{static_url("base/images/logo.png") }}' alt='Jupyter Notebook'/>{% endblock %}</a></div>
{% block headercontainer %}
{% endblock %}
{% block header_buttons %}
{% block login_widget %}
@ -135,9 +138,7 @@ dir="ltr">
{% endblock %}
{% endblock header_buttons %}
{% block headercontainer %}
{% endblock %}
</div>
<div class="header-bar"></div>

@ -2,6 +2,8 @@
{% block title %}{{page_title}}{% endblock %}
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon-terminal.ico") }}">{% endblock %}
{% block bodyclasses %}terminal-app {{super()}}{% endblock %}
{% block params %}

Loading…
Cancel
Save