##// END OF EJS Templates
Merge pull request #6866 from takluyver/nb-texteditor...
Min RK -
r19078:231dfe88 merge
parent child Browse files
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,29 b''
1 #encoding: utf-8
2 """Tornado handlers for the terminal emulator."""
3
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7 from tornado import web
8 from ..base.handlers import IPythonHandler, path_regex
9 from ..utils import url_escape
10
11 class EditorHandler(IPythonHandler):
12 """Render the text editor interface."""
13 @web.authenticated
14 def get(self, path):
15 path = path.strip('/')
16 if not self.contents_manager.file_exists(path):
17 raise web.HTTPError(404, u'File does not exist: %s' % path)
18
19 basename = path.rsplit('/', 1)[-1]
20 self.write(self.render_template('edit.html',
21 file_path=url_escape(path),
22 basename=basename,
23 page_title=basename + " (editing)",
24 )
25 )
26
27 default_handlers = [
28 (r"/edit%s" % path_regex, EditorHandler),
29 ] No newline at end of file
@@ -0,0 +1,83 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'jquery',
6 'base/js/notificationwidget',
7 ], function($, notificationwidget) {
8 "use strict";
9
10 // store reference to the NotificationWidget class
11 var NotificationWidget = notificationwidget.NotificationWidget;
12
13 /**
14 * Construct the NotificationArea object. Options are:
15 * events: $(Events) instance
16 * save_widget: SaveWidget instance
17 * notebook: Notebook instance
18 * keyboard_manager: KeyboardManager instance
19 *
20 * @constructor
21 * @param {string} selector - a jQuery selector string for the
22 * notification area element
23 * @param {Object} [options] - a dictionary of keyword arguments.
24 */
25 var NotificationArea = function (selector, options) {
26 this.selector = selector;
27 this.events = options.events;
28 if (this.selector !== undefined) {
29 this.element = $(selector);
30 }
31 this.widget_dict = {};
32 };
33
34 /**
35 * Get a widget by name, creating it if it doesn't exist.
36 *
37 * @method widget
38 * @param {string} name - the widget name
39 */
40 NotificationArea.prototype.widget = function (name) {
41 if (this.widget_dict[name] === undefined) {
42 return this.new_notification_widget(name);
43 }
44 return this.get_widget(name);
45 };
46
47 /**
48 * Get a widget by name, throwing an error if it doesn't exist.
49 *
50 * @method get_widget
51 * @param {string} name - the widget name
52 */
53 NotificationArea.prototype.get_widget = function (name) {
54 if(this.widget_dict[name] === undefined) {
55 throw('no widgets with this name');
56 }
57 return this.widget_dict[name];
58 };
59
60 /**
61 * Create a new notification widget with the given name. The
62 * widget must not already exist.
63 *
64 * @method new_notification_widget
65 * @param {string} name - the widget name
66 */
67 NotificationArea.prototype.new_notification_widget = function (name) {
68 if (this.widget_dict[name] !== undefined) {
69 throw('widget with that name already exists!');
70 }
71
72 // create the element for the notification widget and add it
73 // to the notification aread element
74 var div = $('<div/>').attr('id', 'notification_' + name);
75 $(this.selector).append(div);
76
77 // create the widget object and return it
78 this.widget_dict[name] = new NotificationWidget('#notification_' + name);
79 return this.widget_dict[name];
80 };
81
82 return {'NotificationArea': NotificationArea};
83 });
@@ -0,0 +1,74 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'jquery',
6 'base/js/utils',
7 'codemirror/lib/codemirror',
8 'codemirror/mode/meta',
9 'codemirror/addon/search/search'
10 ],
11 function($,
12 utils,
13 CodeMirror
14 ) {
15 var Editor = function(selector, options) {
16 this.selector = selector;
17 this.contents = options.contents;
18 this.events = options.events;
19 this.base_url = options.base_url;
20 this.file_path = options.file_path;
21
22 this.codemirror = CodeMirror($(this.selector)[0]);
23
24 // It appears we have to set commands on the CodeMirror class, not the
25 // instance. I'd like to be wrong, but since there should only be one CM
26 // instance on the page, this is good enough for now.
27 CodeMirror.commands.save = $.proxy(this.save, this);
28
29 this.save_enabled = false;
30 };
31
32 Editor.prototype.load = function() {
33 var that = this;
34 var cm = this.codemirror;
35 this.contents.get(this.file_path, {type: 'file', format: 'text'})
36 .then(function(model) {
37 cm.setValue(model.content);
38
39 // Find and load the highlighting mode
40 var modeinfo = CodeMirror.findModeByMIME(model.mimetype);
41 if (modeinfo) {
42 utils.requireCodeMirrorMode(modeinfo.mode, function() {
43 cm.setOption('mode', modeinfo.mode);
44 });
45 }
46 that.save_enabled = true;
47 },
48 function(error) {
49 cm.setValue("Error! " + error.message +
50 "\nSaving disabled.");
51 that.save_enabled = false;
52 }
53 );
54 };
55
56 Editor.prototype.save = function() {
57 if (!this.save_enabled) {
58 console.log("Not saving, save disabled");
59 return;
60 }
61 var model = {
62 path: this.file_path,
63 type: 'file',
64 format: 'text',
65 content: this.codemirror.getValue(),
66 };
67 var that = this;
68 this.contents.save(this.file_path, model).then(function() {
69 that.events.trigger("save_succeeded.TextEditor");
70 });
71 };
72
73 return {Editor: Editor};
74 });
@@ -0,0 +1,53 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 require([
5 'base/js/namespace',
6 'base/js/utils',
7 'base/js/page',
8 'base/js/events',
9 'contents',
10 'edit/js/editor',
11 'edit/js/menubar',
12 'edit/js/notificationarea',
13 'custom/custom',
14 ], function(
15 IPython,
16 utils,
17 page,
18 events,
19 contents,
20 editor,
21 menubar,
22 notificationarea
23 ){
24 page = new page.Page();
25
26 var base_url = utils.get_body_data('baseUrl');
27 var file_path = utils.get_body_data('filePath');
28 contents = new contents.Contents({base_url: base_url});
29
30 var editor = new editor.Editor('#texteditor-container', {
31 base_url: base_url,
32 events: events,
33 contents: contents,
34 file_path: file_path,
35 });
36
37 // Make it available for debugging
38 IPython.editor = editor;
39
40 var menus = new menubar.MenuBar('#menubar', {
41 base_url: base_url,
42 editor: editor,
43 });
44
45 var notification_area = new notificationarea.EditorNotificationArea(
46 '#notification_area', {
47 events: events,
48 });
49 notification_area.init_notification_widgets();
50
51 editor.load();
52 page.show();
53 });
@@ -0,0 +1,46 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
5 'base/js/namespace',
6 'jquery',
7 'base/js/utils',
8 'bootstrap',
9 ], function(IPython, $, utils, bootstrap) {
10 "use strict";
11
12 var MenuBar = function (selector, options) {
13 // Constructor
14 //
15 // A MenuBar Class to generate the menubar of IPython notebook
16 //
17 // Parameters:
18 // selector: string
19 // options: dictionary
20 // Dictionary of keyword arguments.
21 // codemirror: CodeMirror instance
22 // contents: ContentManager instance
23 // events: $(Events) instance
24 // base_url : string
25 // file_path : string
26 options = options || {};
27 this.base_url = options.base_url || utils.get_body_data("baseUrl");
28 this.selector = selector;
29 this.editor = options.editor;
30
31 if (this.selector !== undefined) {
32 this.element = $(selector);
33 this.bind_events();
34 }
35 };
36
37 MenuBar.prototype.bind_events = function () {
38 // File
39 var that = this;
40 this.element.find('#save_file').click(function () {
41 that.editor.save();
42 });
43 };
44
45 return {'MenuBar': MenuBar};
46 });
@@ -0,0 +1,29 b''
1 define([
2 'base/js/notificationarea'
3 ], function(notificationarea) {
4 "use strict";
5 var NotificationArea = notificationarea.NotificationArea;
6
7 var EditorNotificationArea = function(selector, options) {
8 NotificationArea.apply(this, [selector, options]);
9 }
10
11 EditorNotificationArea.prototype = Object.create(NotificationArea.prototype);
12
13 /**
14 * Initialize the default set of notification widgets.
15 *
16 * @method init_notification_widgets
17 */
18 EditorNotificationArea.prototype.init_notification_widgets = function () {
19 var that = this;
20 var enw = this.new_notification_widget('editor');
21
22 this.events.on("save_succeeded.TextEditor", function() {
23 enw.set_message("File saved", 2000);
24 });
25 };
26
27
28 return {EditorNotificationArea: EditorNotificationArea};
29 });
@@ -0,0 +1,72 b''
1 {% extends "page.html" %}
2
3 {% block title %}{{page_title}}{% endblock %}
4
5 {% block stylesheet %}
6 <link rel="stylesheet" href="{{ static_url('components/codemirror/lib/codemirror.css') }}">
7 <link rel="stylesheet" href="{{ static_url('components/codemirror/addon/dialog/dialog.css') }}">
8 <style>
9 #texteditor-container {
10 border-bottom: 1px solid #ccc;
11 }
12
13 #filename {
14 font-size: 16pt;
15 display: table;
16 padding: 0px 5px;
17 }
18 </style>
19
20 {{super()}}
21 {% endblock %}
22
23 {% block params %}
24
25 data-base-url="{{base_url}}"
26 data-file-path="{{file_path}}"
27
28 {% endblock %}
29
30 {% block header %}
31
32 <span id="filename">{{ basename }}</span>
33
34 {% endblock %}
35
36 {% block site %}
37
38 <div id="menubar-container" class="container">
39 <div id="menubar">
40 <div id="menus" class="navbar navbar-default" role="navigation">
41 <div class="container-fluid">
42 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
43 <i class="fa fa-bars"></i>
44 <span class="navbar-text">Menu</span>
45 </button>
46 <ul class="nav navbar-nav navbar-right">
47 <li id="notification_area"></li>
48 </ul>
49 <div class="navbar-collapse collapse">
50 <ul class="nav navbar-nav">
51 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
52 <ul id="file_menu" class="dropdown-menu">
53 <li id="save_file"><a href="#">Save</a></li>
54 </ul>
55 </li>
56 </ul>
57 </div>
58 </div>
59 </div>
60 </div>
61 </div>
62
63 <div id="texteditor-container" class="container"></div>
64
65 {% endblock %}
66
67 {% block script %}
68
69 {{super()}}
70
71 <script src="{{ static_url("edit/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
72 {% endblock %}
@@ -1,1008 +1,1009 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server."""
2 """A tornado based IPython notebook server."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import errno
10 import errno
11 import io
11 import io
12 import json
12 import json
13 import logging
13 import logging
14 import os
14 import os
15 import random
15 import random
16 import re
16 import re
17 import select
17 import select
18 import signal
18 import signal
19 import socket
19 import socket
20 import sys
20 import sys
21 import threading
21 import threading
22 import time
22 import time
23 import webbrowser
23 import webbrowser
24
24
25
25
26 # check for pyzmq 2.1.11
26 # check for pyzmq 2.1.11
27 from IPython.utils.zmqrelated import check_for_zmq
27 from IPython.utils.zmqrelated import check_for_zmq
28 check_for_zmq('2.1.11', 'IPython.html')
28 check_for_zmq('2.1.11', 'IPython.html')
29
29
30 from jinja2 import Environment, FileSystemLoader
30 from jinja2 import Environment, FileSystemLoader
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 ioloop.install()
35 ioloop.install()
36
36
37 # check for tornado 3.1.0
37 # check for tornado 3.1.0
38 msg = "The IPython Notebook requires tornado >= 4.0"
38 msg = "The IPython Notebook requires tornado >= 4.0"
39 try:
39 try:
40 import tornado
40 import tornado
41 except ImportError:
41 except ImportError:
42 raise ImportError(msg)
42 raise ImportError(msg)
43 try:
43 try:
44 version_info = tornado.version_info
44 version_info = tornado.version_info
45 except AttributeError:
45 except AttributeError:
46 raise ImportError(msg + ", but you have < 1.1.0")
46 raise ImportError(msg + ", but you have < 1.1.0")
47 if version_info < (4,0):
47 if version_info < (4,0):
48 raise ImportError(msg + ", but you have %s" % tornado.version)
48 raise ImportError(msg + ", but you have %s" % tornado.version)
49
49
50 from tornado import httpserver
50 from tornado import httpserver
51 from tornado import web
51 from tornado import web
52 from tornado.log import LogFormatter, app_log, access_log, gen_log
52 from tornado.log import LogFormatter, app_log, access_log, gen_log
53
53
54 from IPython.html import (
54 from IPython.html import (
55 DEFAULT_STATIC_FILES_PATH,
55 DEFAULT_STATIC_FILES_PATH,
56 DEFAULT_TEMPLATE_PATH_LIST,
56 DEFAULT_TEMPLATE_PATH_LIST,
57 )
57 )
58 from .base.handlers import Template404
58 from .base.handlers import Template404
59 from .log import log_request
59 from .log import log_request
60 from .services.kernels.kernelmanager import MappingKernelManager
60 from .services.kernels.kernelmanager import MappingKernelManager
61 from .services.contents.manager import ContentsManager
61 from .services.contents.manager import ContentsManager
62 from .services.contents.filemanager import FileContentsManager
62 from .services.contents.filemanager import FileContentsManager
63 from .services.clusters.clustermanager import ClusterManager
63 from .services.clusters.clustermanager import ClusterManager
64 from .services.sessions.sessionmanager import SessionManager
64 from .services.sessions.sessionmanager import SessionManager
65
65
66 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
66 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
67
67
68 from IPython.config import Config
68 from IPython.config import Config
69 from IPython.config.application import catch_config_error, boolean_flag
69 from IPython.config.application import catch_config_error, boolean_flag
70 from IPython.core.application import (
70 from IPython.core.application import (
71 BaseIPythonApplication, base_flags, base_aliases,
71 BaseIPythonApplication, base_flags, base_aliases,
72 )
72 )
73 from IPython.core.profiledir import ProfileDir
73 from IPython.core.profiledir import ProfileDir
74 from IPython.kernel import KernelManager
74 from IPython.kernel import KernelManager
75 from IPython.kernel.kernelspec import KernelSpecManager
75 from IPython.kernel.kernelspec import KernelSpecManager
76 from IPython.kernel.zmq.session import default_secure, Session
76 from IPython.kernel.zmq.session import default_secure, Session
77 from IPython.nbformat.sign import NotebookNotary
77 from IPython.nbformat.sign import NotebookNotary
78 from IPython.utils.importstring import import_item
78 from IPython.utils.importstring import import_item
79 from IPython.utils import submodule
79 from IPython.utils import submodule
80 from IPython.utils.process import check_pid
80 from IPython.utils.process import check_pid
81 from IPython.utils.traitlets import (
81 from IPython.utils.traitlets import (
82 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
82 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
83 DottedObjectName, TraitError,
83 DottedObjectName, TraitError,
84 )
84 )
85 from IPython.utils import py3compat
85 from IPython.utils import py3compat
86 from IPython.utils.path import filefind, get_ipython_dir
86 from IPython.utils.path import filefind, get_ipython_dir
87
87
88 from .utils import url_path_join
88 from .utils import url_path_join
89
89
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91 # Module globals
91 # Module globals
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93
93
94 _examples = """
94 _examples = """
95 ipython notebook # start the notebook
95 ipython notebook # start the notebook
96 ipython notebook --profile=sympy # use the sympy profile
96 ipython notebook --profile=sympy # use the sympy profile
97 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
97 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
98 """
98 """
99
99
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101 # Helper functions
101 # Helper functions
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103
103
104 def random_ports(port, n):
104 def random_ports(port, n):
105 """Generate a list of n random ports near the given port.
105 """Generate a list of n random ports near the given port.
106
106
107 The first 5 ports will be sequential, and the remaining n-5 will be
107 The first 5 ports will be sequential, and the remaining n-5 will be
108 randomly selected in the range [port-2*n, port+2*n].
108 randomly selected in the range [port-2*n, port+2*n].
109 """
109 """
110 for i in range(min(5, n)):
110 for i in range(min(5, n)):
111 yield port + i
111 yield port + i
112 for i in range(n-5):
112 for i in range(n-5):
113 yield max(1, port + random.randint(-2*n, 2*n))
113 yield max(1, port + random.randint(-2*n, 2*n))
114
114
115 def load_handlers(name):
115 def load_handlers(name):
116 """Load the (URL pattern, handler) tuples for each component."""
116 """Load the (URL pattern, handler) tuples for each component."""
117 name = 'IPython.html.' + name
117 name = 'IPython.html.' + name
118 mod = __import__(name, fromlist=['default_handlers'])
118 mod = __import__(name, fromlist=['default_handlers'])
119 return mod.default_handlers
119 return mod.default_handlers
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # The Tornado web application
122 # The Tornado web application
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124
124
125 class NotebookWebApplication(web.Application):
125 class NotebookWebApplication(web.Application):
126
126
127 def __init__(self, ipython_app, kernel_manager, contents_manager,
127 def __init__(self, ipython_app, kernel_manager, contents_manager,
128 cluster_manager, session_manager, kernel_spec_manager, log,
128 cluster_manager, session_manager, kernel_spec_manager, log,
129 base_url, default_url, settings_overrides, jinja_env_options):
129 base_url, default_url, settings_overrides, jinja_env_options):
130
130
131 settings = self.init_settings(
131 settings = self.init_settings(
132 ipython_app, kernel_manager, contents_manager, cluster_manager,
132 ipython_app, kernel_manager, contents_manager, cluster_manager,
133 session_manager, kernel_spec_manager, log, base_url, default_url,
133 session_manager, kernel_spec_manager, log, base_url, default_url,
134 settings_overrides, jinja_env_options)
134 settings_overrides, jinja_env_options)
135 handlers = self.init_handlers(settings)
135 handlers = self.init_handlers(settings)
136
136
137 super(NotebookWebApplication, self).__init__(handlers, **settings)
137 super(NotebookWebApplication, self).__init__(handlers, **settings)
138
138
139 def init_settings(self, ipython_app, kernel_manager, contents_manager,
139 def init_settings(self, ipython_app, kernel_manager, contents_manager,
140 cluster_manager, session_manager, kernel_spec_manager,
140 cluster_manager, session_manager, kernel_spec_manager,
141 log, base_url, default_url, settings_overrides,
141 log, base_url, default_url, settings_overrides,
142 jinja_env_options=None):
142 jinja_env_options=None):
143
143
144 _template_path = settings_overrides.get(
144 _template_path = settings_overrides.get(
145 "template_path",
145 "template_path",
146 ipython_app.template_file_path,
146 ipython_app.template_file_path,
147 )
147 )
148 if isinstance(_template_path, str):
148 if isinstance(_template_path, str):
149 _template_path = (_template_path,)
149 _template_path = (_template_path,)
150 template_path = [os.path.expanduser(path) for path in _template_path]
150 template_path = [os.path.expanduser(path) for path in _template_path]
151
151
152 jenv_opt = jinja_env_options if jinja_env_options else {}
152 jenv_opt = jinja_env_options if jinja_env_options else {}
153 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
153 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
154 settings = dict(
154 settings = dict(
155 # basics
155 # basics
156 log_function=log_request,
156 log_function=log_request,
157 base_url=base_url,
157 base_url=base_url,
158 default_url=default_url,
158 default_url=default_url,
159 template_path=template_path,
159 template_path=template_path,
160 static_path=ipython_app.static_file_path,
160 static_path=ipython_app.static_file_path,
161 static_handler_class = FileFindHandler,
161 static_handler_class = FileFindHandler,
162 static_url_prefix = url_path_join(base_url,'/static/'),
162 static_url_prefix = url_path_join(base_url,'/static/'),
163
163
164 # authentication
164 # authentication
165 cookie_secret=ipython_app.cookie_secret,
165 cookie_secret=ipython_app.cookie_secret,
166 login_url=url_path_join(base_url,'/login'),
166 login_url=url_path_join(base_url,'/login'),
167 password=ipython_app.password,
167 password=ipython_app.password,
168
168
169 # managers
169 # managers
170 kernel_manager=kernel_manager,
170 kernel_manager=kernel_manager,
171 contents_manager=contents_manager,
171 contents_manager=contents_manager,
172 cluster_manager=cluster_manager,
172 cluster_manager=cluster_manager,
173 session_manager=session_manager,
173 session_manager=session_manager,
174 kernel_spec_manager=kernel_spec_manager,
174 kernel_spec_manager=kernel_spec_manager,
175
175
176 # IPython stuff
176 # IPython stuff
177 nbextensions_path = ipython_app.nbextensions_path,
177 nbextensions_path = ipython_app.nbextensions_path,
178 websocket_url=ipython_app.websocket_url,
178 websocket_url=ipython_app.websocket_url,
179 mathjax_url=ipython_app.mathjax_url,
179 mathjax_url=ipython_app.mathjax_url,
180 config=ipython_app.config,
180 config=ipython_app.config,
181 jinja2_env=env,
181 jinja2_env=env,
182 terminals_available=False, # Set later if terminals are available
182 terminals_available=False, # Set later if terminals are available
183 profile_dir = ipython_app.profile_dir.location,
183 profile_dir = ipython_app.profile_dir.location,
184 )
184 )
185
185
186 # allow custom overrides for the tornado web app.
186 # allow custom overrides for the tornado web app.
187 settings.update(settings_overrides)
187 settings.update(settings_overrides)
188 return settings
188 return settings
189
189
190 def init_handlers(self, settings):
190 def init_handlers(self, settings):
191 """Load the (URL pattern, handler) tuples for each component."""
191 """Load the (URL pattern, handler) tuples for each component."""
192
192
193 # Order matters. The first handler to match the URL will handle the request.
193 # Order matters. The first handler to match the URL will handle the request.
194 handlers = []
194 handlers = []
195 handlers.extend(load_handlers('tree.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('auth.login'))
196 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.logout'))
197 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('files.handlers'))
198 handlers.extend(load_handlers('files.handlers'))
199 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('notebook.handlers'))
200 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('nbconvert.handlers'))
201 handlers.extend(load_handlers('kernelspecs.handlers'))
201 handlers.extend(load_handlers('kernelspecs.handlers'))
202 handlers.extend(load_handlers('edit.handlers'))
202 handlers.extend(load_handlers('services.config.handlers'))
203 handlers.extend(load_handlers('services.config.handlers'))
203 handlers.extend(load_handlers('services.kernels.handlers'))
204 handlers.extend(load_handlers('services.kernels.handlers'))
204 handlers.extend(load_handlers('services.contents.handlers'))
205 handlers.extend(load_handlers('services.contents.handlers'))
205 handlers.extend(load_handlers('services.clusters.handlers'))
206 handlers.extend(load_handlers('services.clusters.handlers'))
206 handlers.extend(load_handlers('services.sessions.handlers'))
207 handlers.extend(load_handlers('services.sessions.handlers'))
207 handlers.extend(load_handlers('services.nbconvert.handlers'))
208 handlers.extend(load_handlers('services.nbconvert.handlers'))
208 handlers.extend(load_handlers('services.kernelspecs.handlers'))
209 handlers.extend(load_handlers('services.kernelspecs.handlers'))
209 handlers.append(
210 handlers.append(
210 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
211 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
211 )
212 )
212 # register base handlers last
213 # register base handlers last
213 handlers.extend(load_handlers('base.handlers'))
214 handlers.extend(load_handlers('base.handlers'))
214 # set the URL that will be redirected from `/`
215 # set the URL that will be redirected from `/`
215 handlers.append(
216 handlers.append(
216 (r'/?', web.RedirectHandler, {
217 (r'/?', web.RedirectHandler, {
217 'url' : url_path_join(settings['base_url'], settings['default_url']),
218 'url' : url_path_join(settings['base_url'], settings['default_url']),
218 'permanent': False, # want 302, not 301
219 'permanent': False, # want 302, not 301
219 })
220 })
220 )
221 )
221 # prepend base_url onto the patterns that we match
222 # prepend base_url onto the patterns that we match
222 new_handlers = []
223 new_handlers = []
223 for handler in handlers:
224 for handler in handlers:
224 pattern = url_path_join(settings['base_url'], handler[0])
225 pattern = url_path_join(settings['base_url'], handler[0])
225 new_handler = tuple([pattern] + list(handler[1:]))
226 new_handler = tuple([pattern] + list(handler[1:]))
226 new_handlers.append(new_handler)
227 new_handlers.append(new_handler)
227 # add 404 on the end, which will catch everything that falls through
228 # add 404 on the end, which will catch everything that falls through
228 new_handlers.append((r'(.*)', Template404))
229 new_handlers.append((r'(.*)', Template404))
229 return new_handlers
230 return new_handlers
230
231
231
232
232 class NbserverListApp(BaseIPythonApplication):
233 class NbserverListApp(BaseIPythonApplication):
233
234
234 description="List currently running notebook servers in this profile."
235 description="List currently running notebook servers in this profile."
235
236
236 flags = dict(
237 flags = dict(
237 json=({'NbserverListApp': {'json': True}},
238 json=({'NbserverListApp': {'json': True}},
238 "Produce machine-readable JSON output."),
239 "Produce machine-readable JSON output."),
239 )
240 )
240
241
241 json = Bool(False, config=True,
242 json = Bool(False, config=True,
242 help="If True, each line of output will be a JSON object with the "
243 help="If True, each line of output will be a JSON object with the "
243 "details from the server info file.")
244 "details from the server info file.")
244
245
245 def start(self):
246 def start(self):
246 if not self.json:
247 if not self.json:
247 print("Currently running servers:")
248 print("Currently running servers:")
248 for serverinfo in list_running_servers(self.profile):
249 for serverinfo in list_running_servers(self.profile):
249 if self.json:
250 if self.json:
250 print(json.dumps(serverinfo))
251 print(json.dumps(serverinfo))
251 else:
252 else:
252 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
253 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
253
254
254 #-----------------------------------------------------------------------------
255 #-----------------------------------------------------------------------------
255 # Aliases and Flags
256 # Aliases and Flags
256 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
257
258
258 flags = dict(base_flags)
259 flags = dict(base_flags)
259 flags['no-browser']=(
260 flags['no-browser']=(
260 {'NotebookApp' : {'open_browser' : False}},
261 {'NotebookApp' : {'open_browser' : False}},
261 "Don't open the notebook in a browser after startup."
262 "Don't open the notebook in a browser after startup."
262 )
263 )
263 flags['pylab']=(
264 flags['pylab']=(
264 {'NotebookApp' : {'pylab' : 'warn'}},
265 {'NotebookApp' : {'pylab' : 'warn'}},
265 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
266 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
266 )
267 )
267 flags['no-mathjax']=(
268 flags['no-mathjax']=(
268 {'NotebookApp' : {'enable_mathjax' : False}},
269 {'NotebookApp' : {'enable_mathjax' : False}},
269 """Disable MathJax
270 """Disable MathJax
270
271
271 MathJax is the javascript library IPython uses to render math/LaTeX. It is
272 MathJax is the javascript library IPython uses to render math/LaTeX. It is
272 very large, so you may want to disable it if you have a slow internet
273 very large, so you may want to disable it if you have a slow internet
273 connection, or for offline use of the notebook.
274 connection, or for offline use of the notebook.
274
275
275 When disabled, equations etc. will appear as their untransformed TeX source.
276 When disabled, equations etc. will appear as their untransformed TeX source.
276 """
277 """
277 )
278 )
278
279
279 # Add notebook manager flags
280 # Add notebook manager flags
280 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
281 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
281 'DEPRECATED, IGNORED',
282 'DEPRECATED, IGNORED',
282 'DEPRECATED, IGNORED'))
283 'DEPRECATED, IGNORED'))
283
284
284 aliases = dict(base_aliases)
285 aliases = dict(base_aliases)
285
286
286 aliases.update({
287 aliases.update({
287 'ip': 'NotebookApp.ip',
288 'ip': 'NotebookApp.ip',
288 'port': 'NotebookApp.port',
289 'port': 'NotebookApp.port',
289 'port-retries': 'NotebookApp.port_retries',
290 'port-retries': 'NotebookApp.port_retries',
290 'transport': 'KernelManager.transport',
291 'transport': 'KernelManager.transport',
291 'keyfile': 'NotebookApp.keyfile',
292 'keyfile': 'NotebookApp.keyfile',
292 'certfile': 'NotebookApp.certfile',
293 'certfile': 'NotebookApp.certfile',
293 'notebook-dir': 'NotebookApp.notebook_dir',
294 'notebook-dir': 'NotebookApp.notebook_dir',
294 'browser': 'NotebookApp.browser',
295 'browser': 'NotebookApp.browser',
295 'pylab': 'NotebookApp.pylab',
296 'pylab': 'NotebookApp.pylab',
296 })
297 })
297
298
298 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
299 # NotebookApp
300 # NotebookApp
300 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
301
302
302 class NotebookApp(BaseIPythonApplication):
303 class NotebookApp(BaseIPythonApplication):
303
304
304 name = 'ipython-notebook'
305 name = 'ipython-notebook'
305
306
306 description = """
307 description = """
307 The IPython HTML Notebook.
308 The IPython HTML Notebook.
308
309
309 This launches a Tornado based HTML Notebook Server that serves up an
310 This launches a Tornado based HTML Notebook Server that serves up an
310 HTML5/Javascript Notebook client.
311 HTML5/Javascript Notebook client.
311 """
312 """
312 examples = _examples
313 examples = _examples
313 aliases = aliases
314 aliases = aliases
314 flags = flags
315 flags = flags
315
316
316 classes = [
317 classes = [
317 KernelManager, ProfileDir, Session, MappingKernelManager,
318 KernelManager, ProfileDir, Session, MappingKernelManager,
318 ContentsManager, FileContentsManager, NotebookNotary,
319 ContentsManager, FileContentsManager, NotebookNotary,
319 ]
320 ]
320 flags = Dict(flags)
321 flags = Dict(flags)
321 aliases = Dict(aliases)
322 aliases = Dict(aliases)
322
323
323 subcommands = dict(
324 subcommands = dict(
324 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
325 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
325 )
326 )
326
327
327 ipython_kernel_argv = List(Unicode)
328 ipython_kernel_argv = List(Unicode)
328
329
329 _log_formatter_cls = LogFormatter
330 _log_formatter_cls = LogFormatter
330
331
331 def _log_level_default(self):
332 def _log_level_default(self):
332 return logging.INFO
333 return logging.INFO
333
334
334 def _log_datefmt_default(self):
335 def _log_datefmt_default(self):
335 """Exclude date from default date format"""
336 """Exclude date from default date format"""
336 return "%H:%M:%S"
337 return "%H:%M:%S"
337
338
338 def _log_format_default(self):
339 def _log_format_default(self):
339 """override default log format to include time"""
340 """override default log format to include time"""
340 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
341 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
341
342
342 # create requested profiles by default, if they don't exist:
343 # create requested profiles by default, if they don't exist:
343 auto_create = Bool(True)
344 auto_create = Bool(True)
344
345
345 # file to be opened in the notebook server
346 # file to be opened in the notebook server
346 file_to_run = Unicode('', config=True)
347 file_to_run = Unicode('', config=True)
347
348
348 # Network related information
349 # Network related information
349
350
350 allow_origin = Unicode('', config=True,
351 allow_origin = Unicode('', config=True,
351 help="""Set the Access-Control-Allow-Origin header
352 help="""Set the Access-Control-Allow-Origin header
352
353
353 Use '*' to allow any origin to access your server.
354 Use '*' to allow any origin to access your server.
354
355
355 Takes precedence over allow_origin_pat.
356 Takes precedence over allow_origin_pat.
356 """
357 """
357 )
358 )
358
359
359 allow_origin_pat = Unicode('', config=True,
360 allow_origin_pat = Unicode('', config=True,
360 help="""Use a regular expression for the Access-Control-Allow-Origin header
361 help="""Use a regular expression for the Access-Control-Allow-Origin header
361
362
362 Requests from an origin matching the expression will get replies with:
363 Requests from an origin matching the expression will get replies with:
363
364
364 Access-Control-Allow-Origin: origin
365 Access-Control-Allow-Origin: origin
365
366
366 where `origin` is the origin of the request.
367 where `origin` is the origin of the request.
367
368
368 Ignored if allow_origin is set.
369 Ignored if allow_origin is set.
369 """
370 """
370 )
371 )
371
372
372 allow_credentials = Bool(False, config=True,
373 allow_credentials = Bool(False, config=True,
373 help="Set the Access-Control-Allow-Credentials: true header"
374 help="Set the Access-Control-Allow-Credentials: true header"
374 )
375 )
375
376
376 default_url = Unicode('/tree', config=True,
377 default_url = Unicode('/tree', config=True,
377 help="The default URL to redirect to from `/`"
378 help="The default URL to redirect to from `/`"
378 )
379 )
379
380
380 ip = Unicode('localhost', config=True,
381 ip = Unicode('localhost', config=True,
381 help="The IP address the notebook server will listen on."
382 help="The IP address the notebook server will listen on."
382 )
383 )
383
384
384 def _ip_changed(self, name, old, new):
385 def _ip_changed(self, name, old, new):
385 if new == u'*': self.ip = u''
386 if new == u'*': self.ip = u''
386
387
387 port = Integer(8888, config=True,
388 port = Integer(8888, config=True,
388 help="The port the notebook server will listen on."
389 help="The port the notebook server will listen on."
389 )
390 )
390 port_retries = Integer(50, config=True,
391 port_retries = Integer(50, config=True,
391 help="The number of additional ports to try if the specified port is not available."
392 help="The number of additional ports to try if the specified port is not available."
392 )
393 )
393
394
394 certfile = Unicode(u'', config=True,
395 certfile = Unicode(u'', config=True,
395 help="""The full path to an SSL/TLS certificate file."""
396 help="""The full path to an SSL/TLS certificate file."""
396 )
397 )
397
398
398 keyfile = Unicode(u'', config=True,
399 keyfile = Unicode(u'', config=True,
399 help="""The full path to a private key file for usage with SSL/TLS."""
400 help="""The full path to a private key file for usage with SSL/TLS."""
400 )
401 )
401
402
402 cookie_secret_file = Unicode(config=True,
403 cookie_secret_file = Unicode(config=True,
403 help="""The file where the cookie secret is stored."""
404 help="""The file where the cookie secret is stored."""
404 )
405 )
405 def _cookie_secret_file_default(self):
406 def _cookie_secret_file_default(self):
406 if self.profile_dir is None:
407 if self.profile_dir is None:
407 return ''
408 return ''
408 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
409 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
409
410
410 cookie_secret = Bytes(b'', config=True,
411 cookie_secret = Bytes(b'', config=True,
411 help="""The random bytes used to secure cookies.
412 help="""The random bytes used to secure cookies.
412 By default this is a new random number every time you start the Notebook.
413 By default this is a new random number every time you start the Notebook.
413 Set it to a value in a config file to enable logins to persist across server sessions.
414 Set it to a value in a config file to enable logins to persist across server sessions.
414
415
415 Note: Cookie secrets should be kept private, do not share config files with
416 Note: Cookie secrets should be kept private, do not share config files with
416 cookie_secret stored in plaintext (you can read the value from a file).
417 cookie_secret stored in plaintext (you can read the value from a file).
417 """
418 """
418 )
419 )
419 def _cookie_secret_default(self):
420 def _cookie_secret_default(self):
420 if os.path.exists(self.cookie_secret_file):
421 if os.path.exists(self.cookie_secret_file):
421 with io.open(self.cookie_secret_file, 'rb') as f:
422 with io.open(self.cookie_secret_file, 'rb') as f:
422 return f.read()
423 return f.read()
423 else:
424 else:
424 secret = base64.encodestring(os.urandom(1024))
425 secret = base64.encodestring(os.urandom(1024))
425 self._write_cookie_secret_file(secret)
426 self._write_cookie_secret_file(secret)
426 return secret
427 return secret
427
428
428 def _write_cookie_secret_file(self, secret):
429 def _write_cookie_secret_file(self, secret):
429 """write my secret to my secret_file"""
430 """write my secret to my secret_file"""
430 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
431 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
431 with io.open(self.cookie_secret_file, 'wb') as f:
432 with io.open(self.cookie_secret_file, 'wb') as f:
432 f.write(secret)
433 f.write(secret)
433 try:
434 try:
434 os.chmod(self.cookie_secret_file, 0o600)
435 os.chmod(self.cookie_secret_file, 0o600)
435 except OSError:
436 except OSError:
436 self.log.warn(
437 self.log.warn(
437 "Could not set permissions on %s",
438 "Could not set permissions on %s",
438 self.cookie_secret_file
439 self.cookie_secret_file
439 )
440 )
440
441
441 password = Unicode(u'', config=True,
442 password = Unicode(u'', config=True,
442 help="""Hashed password to use for web authentication.
443 help="""Hashed password to use for web authentication.
443
444
444 To generate, type in a python/IPython shell:
445 To generate, type in a python/IPython shell:
445
446
446 from IPython.lib import passwd; passwd()
447 from IPython.lib import passwd; passwd()
447
448
448 The string should be of the form type:salt:hashed-password.
449 The string should be of the form type:salt:hashed-password.
449 """
450 """
450 )
451 )
451
452
452 open_browser = Bool(True, config=True,
453 open_browser = Bool(True, config=True,
453 help="""Whether to open in a browser after starting.
454 help="""Whether to open in a browser after starting.
454 The specific browser used is platform dependent and
455 The specific browser used is platform dependent and
455 determined by the python standard library `webbrowser`
456 determined by the python standard library `webbrowser`
456 module, unless it is overridden using the --browser
457 module, unless it is overridden using the --browser
457 (NotebookApp.browser) configuration option.
458 (NotebookApp.browser) configuration option.
458 """)
459 """)
459
460
460 browser = Unicode(u'', config=True,
461 browser = Unicode(u'', config=True,
461 help="""Specify what command to use to invoke a web
462 help="""Specify what command to use to invoke a web
462 browser when opening the notebook. If not specified, the
463 browser when opening the notebook. If not specified, the
463 default browser will be determined by the `webbrowser`
464 default browser will be determined by the `webbrowser`
464 standard library module, which allows setting of the
465 standard library module, which allows setting of the
465 BROWSER environment variable to override it.
466 BROWSER environment variable to override it.
466 """)
467 """)
467
468
468 webapp_settings = Dict(config=True,
469 webapp_settings = Dict(config=True,
469 help="DEPRECATED, use tornado_settings"
470 help="DEPRECATED, use tornado_settings"
470 )
471 )
471 def _webapp_settings_changed(self, name, old, new):
472 def _webapp_settings_changed(self, name, old, new):
472 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
473 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
473 self.tornado_settings = new
474 self.tornado_settings = new
474
475
475 tornado_settings = Dict(config=True,
476 tornado_settings = Dict(config=True,
476 help="Supply overrides for the tornado.web.Application that the "
477 help="Supply overrides for the tornado.web.Application that the "
477 "IPython notebook uses.")
478 "IPython notebook uses.")
478
479
479 jinja_environment_options = Dict(config=True,
480 jinja_environment_options = Dict(config=True,
480 help="Supply extra arguments that will be passed to Jinja environment.")
481 help="Supply extra arguments that will be passed to Jinja environment.")
481
482
482
483
483 enable_mathjax = Bool(True, config=True,
484 enable_mathjax = Bool(True, config=True,
484 help="""Whether to enable MathJax for typesetting math/TeX
485 help="""Whether to enable MathJax for typesetting math/TeX
485
486
486 MathJax is the javascript library IPython uses to render math/LaTeX. It is
487 MathJax is the javascript library IPython uses to render math/LaTeX. It is
487 very large, so you may want to disable it if you have a slow internet
488 very large, so you may want to disable it if you have a slow internet
488 connection, or for offline use of the notebook.
489 connection, or for offline use of the notebook.
489
490
490 When disabled, equations etc. will appear as their untransformed TeX source.
491 When disabled, equations etc. will appear as their untransformed TeX source.
491 """
492 """
492 )
493 )
493 def _enable_mathjax_changed(self, name, old, new):
494 def _enable_mathjax_changed(self, name, old, new):
494 """set mathjax url to empty if mathjax is disabled"""
495 """set mathjax url to empty if mathjax is disabled"""
495 if not new:
496 if not new:
496 self.mathjax_url = u''
497 self.mathjax_url = u''
497
498
498 base_url = Unicode('/', config=True,
499 base_url = Unicode('/', config=True,
499 help='''The base URL for the notebook server.
500 help='''The base URL for the notebook server.
500
501
501 Leading and trailing slashes can be omitted,
502 Leading and trailing slashes can be omitted,
502 and will automatically be added.
503 and will automatically be added.
503 ''')
504 ''')
504 def _base_url_changed(self, name, old, new):
505 def _base_url_changed(self, name, old, new):
505 if not new.startswith('/'):
506 if not new.startswith('/'):
506 self.base_url = '/'+new
507 self.base_url = '/'+new
507 elif not new.endswith('/'):
508 elif not new.endswith('/'):
508 self.base_url = new+'/'
509 self.base_url = new+'/'
509
510
510 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
511 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
511 def _base_project_url_changed(self, name, old, new):
512 def _base_project_url_changed(self, name, old, new):
512 self.log.warn("base_project_url is deprecated, use base_url")
513 self.log.warn("base_project_url is deprecated, use base_url")
513 self.base_url = new
514 self.base_url = new
514
515
515 extra_static_paths = List(Unicode, config=True,
516 extra_static_paths = List(Unicode, config=True,
516 help="""Extra paths to search for serving static files.
517 help="""Extra paths to search for serving static files.
517
518
518 This allows adding javascript/css to be available from the notebook server machine,
519 This allows adding javascript/css to be available from the notebook server machine,
519 or overriding individual files in the IPython"""
520 or overriding individual files in the IPython"""
520 )
521 )
521 def _extra_static_paths_default(self):
522 def _extra_static_paths_default(self):
522 return [os.path.join(self.profile_dir.location, 'static')]
523 return [os.path.join(self.profile_dir.location, 'static')]
523
524
524 @property
525 @property
525 def static_file_path(self):
526 def static_file_path(self):
526 """return extra paths + the default location"""
527 """return extra paths + the default location"""
527 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
528 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
528
529
529 extra_template_paths = List(Unicode, config=True,
530 extra_template_paths = List(Unicode, config=True,
530 help="""Extra paths to search for serving jinja templates.
531 help="""Extra paths to search for serving jinja templates.
531
532
532 Can be used to override templates from IPython.html.templates."""
533 Can be used to override templates from IPython.html.templates."""
533 )
534 )
534 def _extra_template_paths_default(self):
535 def _extra_template_paths_default(self):
535 return []
536 return []
536
537
537 @property
538 @property
538 def template_file_path(self):
539 def template_file_path(self):
539 """return extra paths + the default locations"""
540 """return extra paths + the default locations"""
540 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
541 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
541
542
542 nbextensions_path = List(Unicode, config=True,
543 nbextensions_path = List(Unicode, config=True,
543 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
544 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
544 )
545 )
545 def _nbextensions_path_default(self):
546 def _nbextensions_path_default(self):
546 return [os.path.join(get_ipython_dir(), 'nbextensions')]
547 return [os.path.join(get_ipython_dir(), 'nbextensions')]
547
548
548 websocket_url = Unicode("", config=True,
549 websocket_url = Unicode("", config=True,
549 help="""The base URL for websockets,
550 help="""The base URL for websockets,
550 if it differs from the HTTP server (hint: it almost certainly doesn't).
551 if it differs from the HTTP server (hint: it almost certainly doesn't).
551
552
552 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
553 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
553 """
554 """
554 )
555 )
555 mathjax_url = Unicode("", config=True,
556 mathjax_url = Unicode("", config=True,
556 help="""The url for MathJax.js."""
557 help="""The url for MathJax.js."""
557 )
558 )
558 def _mathjax_url_default(self):
559 def _mathjax_url_default(self):
559 if not self.enable_mathjax:
560 if not self.enable_mathjax:
560 return u''
561 return u''
561 static_url_prefix = self.tornado_settings.get("static_url_prefix",
562 static_url_prefix = self.tornado_settings.get("static_url_prefix",
562 url_path_join(self.base_url, "static")
563 url_path_join(self.base_url, "static")
563 )
564 )
564
565
565 # try local mathjax, either in nbextensions/mathjax or static/mathjax
566 # try local mathjax, either in nbextensions/mathjax or static/mathjax
566 for (url_prefix, search_path) in [
567 for (url_prefix, search_path) in [
567 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
568 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
568 (static_url_prefix, self.static_file_path),
569 (static_url_prefix, self.static_file_path),
569 ]:
570 ]:
570 self.log.debug("searching for local mathjax in %s", search_path)
571 self.log.debug("searching for local mathjax in %s", search_path)
571 try:
572 try:
572 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
573 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
573 except IOError:
574 except IOError:
574 continue
575 continue
575 else:
576 else:
576 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
577 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
577 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
578 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
578 return url
579 return url
579
580
580 # no local mathjax, serve from CDN
581 # no local mathjax, serve from CDN
581 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
582 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
582 self.log.info("Using MathJax from CDN: %s", url)
583 self.log.info("Using MathJax from CDN: %s", url)
583 return url
584 return url
584
585
585 def _mathjax_url_changed(self, name, old, new):
586 def _mathjax_url_changed(self, name, old, new):
586 if new and not self.enable_mathjax:
587 if new and not self.enable_mathjax:
587 # enable_mathjax=False overrides mathjax_url
588 # enable_mathjax=False overrides mathjax_url
588 self.mathjax_url = u''
589 self.mathjax_url = u''
589 else:
590 else:
590 self.log.info("Using MathJax: %s", new)
591 self.log.info("Using MathJax: %s", new)
591
592
592 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
593 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
593 config=True,
594 config=True,
594 help='The notebook manager class to use.'
595 help='The notebook manager class to use.'
595 )
596 )
596 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
597 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
597 config=True,
598 config=True,
598 help='The kernel manager class to use.'
599 help='The kernel manager class to use.'
599 )
600 )
600 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
601 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
601 config=True,
602 config=True,
602 help='The session manager class to use.'
603 help='The session manager class to use.'
603 )
604 )
604 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
605 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
605 config=True,
606 config=True,
606 help='The cluster manager class to use.'
607 help='The cluster manager class to use.'
607 )
608 )
608
609
609 kernel_spec_manager = Instance(KernelSpecManager)
610 kernel_spec_manager = Instance(KernelSpecManager)
610
611
611 def _kernel_spec_manager_default(self):
612 def _kernel_spec_manager_default(self):
612 return KernelSpecManager(ipython_dir=self.ipython_dir)
613 return KernelSpecManager(ipython_dir=self.ipython_dir)
613
614
614
615
615 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
616 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
616 config=True,
617 config=True,
617 help="""
618 help="""
618 The kernel spec manager class to use. Should be a subclass
619 The kernel spec manager class to use. Should be a subclass
619 of `IPython.kernel.kernelspec.KernelSpecManager`.
620 of `IPython.kernel.kernelspec.KernelSpecManager`.
620
621
621 The Api of KernelSpecManager is provisional and might change
622 The Api of KernelSpecManager is provisional and might change
622 without warning between this version of IPython and the next stable one.
623 without warning between this version of IPython and the next stable one.
623 """)
624 """)
624
625
625 trust_xheaders = Bool(False, config=True,
626 trust_xheaders = Bool(False, config=True,
626 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
627 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
627 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
628 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
628 )
629 )
629
630
630 info_file = Unicode()
631 info_file = Unicode()
631
632
632 def _info_file_default(self):
633 def _info_file_default(self):
633 info_file = "nbserver-%s.json"%os.getpid()
634 info_file = "nbserver-%s.json"%os.getpid()
634 return os.path.join(self.profile_dir.security_dir, info_file)
635 return os.path.join(self.profile_dir.security_dir, info_file)
635
636
636 pylab = Unicode('disabled', config=True,
637 pylab = Unicode('disabled', config=True,
637 help="""
638 help="""
638 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
639 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
639 """
640 """
640 )
641 )
641 def _pylab_changed(self, name, old, new):
642 def _pylab_changed(self, name, old, new):
642 """when --pylab is specified, display a warning and exit"""
643 """when --pylab is specified, display a warning and exit"""
643 if new != 'warn':
644 if new != 'warn':
644 backend = ' %s' % new
645 backend = ' %s' % new
645 else:
646 else:
646 backend = ''
647 backend = ''
647 self.log.error("Support for specifying --pylab on the command line has been removed.")
648 self.log.error("Support for specifying --pylab on the command line has been removed.")
648 self.log.error(
649 self.log.error(
649 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
650 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
650 )
651 )
651 self.exit(1)
652 self.exit(1)
652
653
653 notebook_dir = Unicode(config=True,
654 notebook_dir = Unicode(config=True,
654 help="The directory to use for notebooks and kernels."
655 help="The directory to use for notebooks and kernels."
655 )
656 )
656
657
657 def _notebook_dir_default(self):
658 def _notebook_dir_default(self):
658 if self.file_to_run:
659 if self.file_to_run:
659 return os.path.dirname(os.path.abspath(self.file_to_run))
660 return os.path.dirname(os.path.abspath(self.file_to_run))
660 else:
661 else:
661 return py3compat.getcwd()
662 return py3compat.getcwd()
662
663
663 def _notebook_dir_changed(self, name, old, new):
664 def _notebook_dir_changed(self, name, old, new):
664 """Do a bit of validation of the notebook dir."""
665 """Do a bit of validation of the notebook dir."""
665 if not os.path.isabs(new):
666 if not os.path.isabs(new):
666 # If we receive a non-absolute path, make it absolute.
667 # If we receive a non-absolute path, make it absolute.
667 self.notebook_dir = os.path.abspath(new)
668 self.notebook_dir = os.path.abspath(new)
668 return
669 return
669 if not os.path.isdir(new):
670 if not os.path.isdir(new):
670 raise TraitError("No such notebook dir: %r" % new)
671 raise TraitError("No such notebook dir: %r" % new)
671
672
672 # setting App.notebook_dir implies setting notebook and kernel dirs as well
673 # setting App.notebook_dir implies setting notebook and kernel dirs as well
673 self.config.FileContentsManager.root_dir = new
674 self.config.FileContentsManager.root_dir = new
674 self.config.MappingKernelManager.root_dir = new
675 self.config.MappingKernelManager.root_dir = new
675
676
676
677
677 def parse_command_line(self, argv=None):
678 def parse_command_line(self, argv=None):
678 super(NotebookApp, self).parse_command_line(argv)
679 super(NotebookApp, self).parse_command_line(argv)
679
680
680 if self.extra_args:
681 if self.extra_args:
681 arg0 = self.extra_args[0]
682 arg0 = self.extra_args[0]
682 f = os.path.abspath(arg0)
683 f = os.path.abspath(arg0)
683 self.argv.remove(arg0)
684 self.argv.remove(arg0)
684 if not os.path.exists(f):
685 if not os.path.exists(f):
685 self.log.critical("No such file or directory: %s", f)
686 self.log.critical("No such file or directory: %s", f)
686 self.exit(1)
687 self.exit(1)
687
688
688 # Use config here, to ensure that it takes higher priority than
689 # Use config here, to ensure that it takes higher priority than
689 # anything that comes from the profile.
690 # anything that comes from the profile.
690 c = Config()
691 c = Config()
691 if os.path.isdir(f):
692 if os.path.isdir(f):
692 c.NotebookApp.notebook_dir = f
693 c.NotebookApp.notebook_dir = f
693 elif os.path.isfile(f):
694 elif os.path.isfile(f):
694 c.NotebookApp.file_to_run = f
695 c.NotebookApp.file_to_run = f
695 self.update_config(c)
696 self.update_config(c)
696
697
697 def init_kernel_argv(self):
698 def init_kernel_argv(self):
698 """add the profile-dir to arguments to be passed to IPython kernels"""
699 """add the profile-dir to arguments to be passed to IPython kernels"""
699 # FIXME: remove special treatment of IPython kernels
700 # FIXME: remove special treatment of IPython kernels
700 # Kernel should get *absolute* path to profile directory
701 # Kernel should get *absolute* path to profile directory
701 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
702 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
702
703
703 def init_configurables(self):
704 def init_configurables(self):
704 # force Session default to be secure
705 # force Session default to be secure
705 default_secure(self.config)
706 default_secure(self.config)
706 kls = import_item(self.kernel_spec_manager_class)
707 kls = import_item(self.kernel_spec_manager_class)
707 self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir)
708 self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir)
708
709
709 kls = import_item(self.kernel_manager_class)
710 kls = import_item(self.kernel_manager_class)
710 self.kernel_manager = kls(
711 self.kernel_manager = kls(
711 parent=self, log=self.log, ipython_kernel_argv=self.ipython_kernel_argv,
712 parent=self, log=self.log, ipython_kernel_argv=self.ipython_kernel_argv,
712 connection_dir = self.profile_dir.security_dir,
713 connection_dir = self.profile_dir.security_dir,
713 )
714 )
714 kls = import_item(self.contents_manager_class)
715 kls = import_item(self.contents_manager_class)
715 self.contents_manager = kls(parent=self, log=self.log)
716 self.contents_manager = kls(parent=self, log=self.log)
716 kls = import_item(self.session_manager_class)
717 kls = import_item(self.session_manager_class)
717 self.session_manager = kls(parent=self, log=self.log,
718 self.session_manager = kls(parent=self, log=self.log,
718 kernel_manager=self.kernel_manager,
719 kernel_manager=self.kernel_manager,
719 contents_manager=self.contents_manager)
720 contents_manager=self.contents_manager)
720 kls = import_item(self.cluster_manager_class)
721 kls = import_item(self.cluster_manager_class)
721 self.cluster_manager = kls(parent=self, log=self.log)
722 self.cluster_manager = kls(parent=self, log=self.log)
722 self.cluster_manager.update_profiles()
723 self.cluster_manager.update_profiles()
723
724
724 def init_logging(self):
725 def init_logging(self):
725 # This prevents double log messages because tornado use a root logger that
726 # This prevents double log messages because tornado use a root logger that
726 # self.log is a child of. The logging module dipatches log messages to a log
727 # self.log is a child of. The logging module dipatches log messages to a log
727 # and all of its ancenstors until propagate is set to False.
728 # and all of its ancenstors until propagate is set to False.
728 self.log.propagate = False
729 self.log.propagate = False
729
730
730 for log in app_log, access_log, gen_log:
731 for log in app_log, access_log, gen_log:
731 # consistent log output name (NotebookApp instead of tornado.access, etc.)
732 # consistent log output name (NotebookApp instead of tornado.access, etc.)
732 log.name = self.log.name
733 log.name = self.log.name
733 # hook up tornado 3's loggers to our app handlers
734 # hook up tornado 3's loggers to our app handlers
734 logger = logging.getLogger('tornado')
735 logger = logging.getLogger('tornado')
735 logger.propagate = True
736 logger.propagate = True
736 logger.parent = self.log
737 logger.parent = self.log
737 logger.setLevel(self.log.level)
738 logger.setLevel(self.log.level)
738
739
739 def init_webapp(self):
740 def init_webapp(self):
740 """initialize tornado webapp and httpserver"""
741 """initialize tornado webapp and httpserver"""
741 self.tornado_settings['allow_origin'] = self.allow_origin
742 self.tornado_settings['allow_origin'] = self.allow_origin
742 if self.allow_origin_pat:
743 if self.allow_origin_pat:
743 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
744 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
744 self.tornado_settings['allow_credentials'] = self.allow_credentials
745 self.tornado_settings['allow_credentials'] = self.allow_credentials
745
746
746 self.web_app = NotebookWebApplication(
747 self.web_app = NotebookWebApplication(
747 self, self.kernel_manager, self.contents_manager,
748 self, self.kernel_manager, self.contents_manager,
748 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
749 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
749 self.log, self.base_url, self.default_url, self.tornado_settings,
750 self.log, self.base_url, self.default_url, self.tornado_settings,
750 self.jinja_environment_options
751 self.jinja_environment_options
751 )
752 )
752 if self.certfile:
753 if self.certfile:
753 ssl_options = dict(certfile=self.certfile)
754 ssl_options = dict(certfile=self.certfile)
754 if self.keyfile:
755 if self.keyfile:
755 ssl_options['keyfile'] = self.keyfile
756 ssl_options['keyfile'] = self.keyfile
756 else:
757 else:
757 ssl_options = None
758 ssl_options = None
758 self.web_app.password = self.password
759 self.web_app.password = self.password
759 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
760 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
760 xheaders=self.trust_xheaders)
761 xheaders=self.trust_xheaders)
761 if not self.ip:
762 if not self.ip:
762 warning = "WARNING: The notebook server is listening on all IP addresses"
763 warning = "WARNING: The notebook server is listening on all IP addresses"
763 if ssl_options is None:
764 if ssl_options is None:
764 self.log.critical(warning + " and not using encryption. This "
765 self.log.critical(warning + " and not using encryption. This "
765 "is not recommended.")
766 "is not recommended.")
766 if not self.password:
767 if not self.password:
767 self.log.critical(warning + " and not using authentication. "
768 self.log.critical(warning + " and not using authentication. "
768 "This is highly insecure and not recommended.")
769 "This is highly insecure and not recommended.")
769 success = None
770 success = None
770 for port in random_ports(self.port, self.port_retries+1):
771 for port in random_ports(self.port, self.port_retries+1):
771 try:
772 try:
772 self.http_server.listen(port, self.ip)
773 self.http_server.listen(port, self.ip)
773 except socket.error as e:
774 except socket.error as e:
774 if e.errno == errno.EADDRINUSE:
775 if e.errno == errno.EADDRINUSE:
775 self.log.info('The port %i is already in use, trying another random port.' % port)
776 self.log.info('The port %i is already in use, trying another random port.' % port)
776 continue
777 continue
777 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
778 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
778 self.log.warn("Permission to listen on port %i denied" % port)
779 self.log.warn("Permission to listen on port %i denied" % port)
779 continue
780 continue
780 else:
781 else:
781 raise
782 raise
782 else:
783 else:
783 self.port = port
784 self.port = port
784 success = True
785 success = True
785 break
786 break
786 if not success:
787 if not success:
787 self.log.critical('ERROR: the notebook server could not be started because '
788 self.log.critical('ERROR: the notebook server could not be started because '
788 'no available port could be found.')
789 'no available port could be found.')
789 self.exit(1)
790 self.exit(1)
790
791
791 @property
792 @property
792 def display_url(self):
793 def display_url(self):
793 ip = self.ip if self.ip else '[all ip addresses on your system]'
794 ip = self.ip if self.ip else '[all ip addresses on your system]'
794 return self._url(ip)
795 return self._url(ip)
795
796
796 @property
797 @property
797 def connection_url(self):
798 def connection_url(self):
798 ip = self.ip if self.ip else 'localhost'
799 ip = self.ip if self.ip else 'localhost'
799 return self._url(ip)
800 return self._url(ip)
800
801
801 def _url(self, ip):
802 def _url(self, ip):
802 proto = 'https' if self.certfile else 'http'
803 proto = 'https' if self.certfile else 'http'
803 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
804 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
804
805
805 def init_terminals(self):
806 def init_terminals(self):
806 try:
807 try:
807 from .terminal import initialize
808 from .terminal import initialize
808 initialize(self.web_app)
809 initialize(self.web_app)
809 self.web_app.settings['terminals_available'] = True
810 self.web_app.settings['terminals_available'] = True
810 except ImportError as e:
811 except ImportError as e:
811 self.log.info("Terminals not available (error was %s)", e)
812 self.log.info("Terminals not available (error was %s)", e)
812
813
813 def init_signal(self):
814 def init_signal(self):
814 if not sys.platform.startswith('win'):
815 if not sys.platform.startswith('win'):
815 signal.signal(signal.SIGINT, self._handle_sigint)
816 signal.signal(signal.SIGINT, self._handle_sigint)
816 signal.signal(signal.SIGTERM, self._signal_stop)
817 signal.signal(signal.SIGTERM, self._signal_stop)
817 if hasattr(signal, 'SIGUSR1'):
818 if hasattr(signal, 'SIGUSR1'):
818 # Windows doesn't support SIGUSR1
819 # Windows doesn't support SIGUSR1
819 signal.signal(signal.SIGUSR1, self._signal_info)
820 signal.signal(signal.SIGUSR1, self._signal_info)
820 if hasattr(signal, 'SIGINFO'):
821 if hasattr(signal, 'SIGINFO'):
821 # only on BSD-based systems
822 # only on BSD-based systems
822 signal.signal(signal.SIGINFO, self._signal_info)
823 signal.signal(signal.SIGINFO, self._signal_info)
823
824
824 def _handle_sigint(self, sig, frame):
825 def _handle_sigint(self, sig, frame):
825 """SIGINT handler spawns confirmation dialog"""
826 """SIGINT handler spawns confirmation dialog"""
826 # register more forceful signal handler for ^C^C case
827 # register more forceful signal handler for ^C^C case
827 signal.signal(signal.SIGINT, self._signal_stop)
828 signal.signal(signal.SIGINT, self._signal_stop)
828 # request confirmation dialog in bg thread, to avoid
829 # request confirmation dialog in bg thread, to avoid
829 # blocking the App
830 # blocking the App
830 thread = threading.Thread(target=self._confirm_exit)
831 thread = threading.Thread(target=self._confirm_exit)
831 thread.daemon = True
832 thread.daemon = True
832 thread.start()
833 thread.start()
833
834
834 def _restore_sigint_handler(self):
835 def _restore_sigint_handler(self):
835 """callback for restoring original SIGINT handler"""
836 """callback for restoring original SIGINT handler"""
836 signal.signal(signal.SIGINT, self._handle_sigint)
837 signal.signal(signal.SIGINT, self._handle_sigint)
837
838
838 def _confirm_exit(self):
839 def _confirm_exit(self):
839 """confirm shutdown on ^C
840 """confirm shutdown on ^C
840
841
841 A second ^C, or answering 'y' within 5s will cause shutdown,
842 A second ^C, or answering 'y' within 5s will cause shutdown,
842 otherwise original SIGINT handler will be restored.
843 otherwise original SIGINT handler will be restored.
843
844
844 This doesn't work on Windows.
845 This doesn't work on Windows.
845 """
846 """
846 info = self.log.info
847 info = self.log.info
847 info('interrupted')
848 info('interrupted')
848 print(self.notebook_info())
849 print(self.notebook_info())
849 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
850 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
850 sys.stdout.flush()
851 sys.stdout.flush()
851 r,w,x = select.select([sys.stdin], [], [], 5)
852 r,w,x = select.select([sys.stdin], [], [], 5)
852 if r:
853 if r:
853 line = sys.stdin.readline()
854 line = sys.stdin.readline()
854 if line.lower().startswith('y') and 'n' not in line.lower():
855 if line.lower().startswith('y') and 'n' not in line.lower():
855 self.log.critical("Shutdown confirmed")
856 self.log.critical("Shutdown confirmed")
856 ioloop.IOLoop.instance().stop()
857 ioloop.IOLoop.instance().stop()
857 return
858 return
858 else:
859 else:
859 print("No answer for 5s:", end=' ')
860 print("No answer for 5s:", end=' ')
860 print("resuming operation...")
861 print("resuming operation...")
861 # no answer, or answer is no:
862 # no answer, or answer is no:
862 # set it back to original SIGINT handler
863 # set it back to original SIGINT handler
863 # use IOLoop.add_callback because signal.signal must be called
864 # use IOLoop.add_callback because signal.signal must be called
864 # from main thread
865 # from main thread
865 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
866 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
866
867
867 def _signal_stop(self, sig, frame):
868 def _signal_stop(self, sig, frame):
868 self.log.critical("received signal %s, stopping", sig)
869 self.log.critical("received signal %s, stopping", sig)
869 ioloop.IOLoop.instance().stop()
870 ioloop.IOLoop.instance().stop()
870
871
871 def _signal_info(self, sig, frame):
872 def _signal_info(self, sig, frame):
872 print(self.notebook_info())
873 print(self.notebook_info())
873
874
874 def init_components(self):
875 def init_components(self):
875 """Check the components submodule, and warn if it's unclean"""
876 """Check the components submodule, and warn if it's unclean"""
876 status = submodule.check_submodule_status()
877 status = submodule.check_submodule_status()
877 if status == 'missing':
878 if status == 'missing':
878 self.log.warn("components submodule missing, running `git submodule update`")
879 self.log.warn("components submodule missing, running `git submodule update`")
879 submodule.update_submodules(submodule.ipython_parent())
880 submodule.update_submodules(submodule.ipython_parent())
880 elif status == 'unclean':
881 elif status == 'unclean':
881 self.log.warn("components submodule unclean, you may see 404s on static/components")
882 self.log.warn("components submodule unclean, you may see 404s on static/components")
882 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
883 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
883
884
884 @catch_config_error
885 @catch_config_error
885 def initialize(self, argv=None):
886 def initialize(self, argv=None):
886 super(NotebookApp, self).initialize(argv)
887 super(NotebookApp, self).initialize(argv)
887 self.init_logging()
888 self.init_logging()
888 self.init_kernel_argv()
889 self.init_kernel_argv()
889 self.init_configurables()
890 self.init_configurables()
890 self.init_components()
891 self.init_components()
891 self.init_webapp()
892 self.init_webapp()
892 self.init_terminals()
893 self.init_terminals()
893 self.init_signal()
894 self.init_signal()
894
895
895 def cleanup_kernels(self):
896 def cleanup_kernels(self):
896 """Shutdown all kernels.
897 """Shutdown all kernels.
897
898
898 The kernels will shutdown themselves when this process no longer exists,
899 The kernels will shutdown themselves when this process no longer exists,
899 but explicit shutdown allows the KernelManagers to cleanup the connection files.
900 but explicit shutdown allows the KernelManagers to cleanup the connection files.
900 """
901 """
901 self.log.info('Shutting down kernels')
902 self.log.info('Shutting down kernels')
902 self.kernel_manager.shutdown_all()
903 self.kernel_manager.shutdown_all()
903
904
904 def notebook_info(self):
905 def notebook_info(self):
905 "Return the current working directory and the server url information"
906 "Return the current working directory and the server url information"
906 info = self.contents_manager.info_string() + "\n"
907 info = self.contents_manager.info_string() + "\n"
907 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
908 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
908 return info + "The IPython Notebook is running at: %s" % self.display_url
909 return info + "The IPython Notebook is running at: %s" % self.display_url
909
910
910 def server_info(self):
911 def server_info(self):
911 """Return a JSONable dict of information about this server."""
912 """Return a JSONable dict of information about this server."""
912 return {'url': self.connection_url,
913 return {'url': self.connection_url,
913 'hostname': self.ip if self.ip else 'localhost',
914 'hostname': self.ip if self.ip else 'localhost',
914 'port': self.port,
915 'port': self.port,
915 'secure': bool(self.certfile),
916 'secure': bool(self.certfile),
916 'base_url': self.base_url,
917 'base_url': self.base_url,
917 'notebook_dir': os.path.abspath(self.notebook_dir),
918 'notebook_dir': os.path.abspath(self.notebook_dir),
918 'pid': os.getpid()
919 'pid': os.getpid()
919 }
920 }
920
921
921 def write_server_info_file(self):
922 def write_server_info_file(self):
922 """Write the result of server_info() to the JSON file info_file."""
923 """Write the result of server_info() to the JSON file info_file."""
923 with open(self.info_file, 'w') as f:
924 with open(self.info_file, 'w') as f:
924 json.dump(self.server_info(), f, indent=2)
925 json.dump(self.server_info(), f, indent=2)
925
926
926 def remove_server_info_file(self):
927 def remove_server_info_file(self):
927 """Remove the nbserver-<pid>.json file created for this server.
928 """Remove the nbserver-<pid>.json file created for this server.
928
929
929 Ignores the error raised when the file has already been removed.
930 Ignores the error raised when the file has already been removed.
930 """
931 """
931 try:
932 try:
932 os.unlink(self.info_file)
933 os.unlink(self.info_file)
933 except OSError as e:
934 except OSError as e:
934 if e.errno != errno.ENOENT:
935 if e.errno != errno.ENOENT:
935 raise
936 raise
936
937
937 def start(self):
938 def start(self):
938 """ Start the IPython Notebook server app, after initialization
939 """ Start the IPython Notebook server app, after initialization
939
940
940 This method takes no arguments so all configuration and initialization
941 This method takes no arguments so all configuration and initialization
941 must be done prior to calling this method."""
942 must be done prior to calling this method."""
942 if self.subapp is not None:
943 if self.subapp is not None:
943 return self.subapp.start()
944 return self.subapp.start()
944
945
945 info = self.log.info
946 info = self.log.info
946 for line in self.notebook_info().split("\n"):
947 for line in self.notebook_info().split("\n"):
947 info(line)
948 info(line)
948 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
949 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
949
950
950 self.write_server_info_file()
951 self.write_server_info_file()
951
952
952 if self.open_browser or self.file_to_run:
953 if self.open_browser or self.file_to_run:
953 try:
954 try:
954 browser = webbrowser.get(self.browser or None)
955 browser = webbrowser.get(self.browser or None)
955 except webbrowser.Error as e:
956 except webbrowser.Error as e:
956 self.log.warn('No web browser found: %s.' % e)
957 self.log.warn('No web browser found: %s.' % e)
957 browser = None
958 browser = None
958
959
959 if self.file_to_run:
960 if self.file_to_run:
960 if not os.path.exists(self.file_to_run):
961 if not os.path.exists(self.file_to_run):
961 self.log.critical("%s does not exist" % self.file_to_run)
962 self.log.critical("%s does not exist" % self.file_to_run)
962 self.exit(1)
963 self.exit(1)
963
964
964 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
965 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
965 uri = url_path_join('notebooks', *relpath.split(os.sep))
966 uri = url_path_join('notebooks', *relpath.split(os.sep))
966 else:
967 else:
967 uri = 'tree'
968 uri = 'tree'
968 if browser:
969 if browser:
969 b = lambda : browser.open(url_path_join(self.connection_url, uri),
970 b = lambda : browser.open(url_path_join(self.connection_url, uri),
970 new=2)
971 new=2)
971 threading.Thread(target=b).start()
972 threading.Thread(target=b).start()
972 try:
973 try:
973 ioloop.IOLoop.instance().start()
974 ioloop.IOLoop.instance().start()
974 except KeyboardInterrupt:
975 except KeyboardInterrupt:
975 info("Interrupted...")
976 info("Interrupted...")
976 finally:
977 finally:
977 self.cleanup_kernels()
978 self.cleanup_kernels()
978 self.remove_server_info_file()
979 self.remove_server_info_file()
979
980
980
981
981 def list_running_servers(profile='default'):
982 def list_running_servers(profile='default'):
982 """Iterate over the server info files of running notebook servers.
983 """Iterate over the server info files of running notebook servers.
983
984
984 Given a profile name, find nbserver-* files in the security directory of
985 Given a profile name, find nbserver-* files in the security directory of
985 that profile, and yield dicts of their information, each one pertaining to
986 that profile, and yield dicts of their information, each one pertaining to
986 a currently running notebook server instance.
987 a currently running notebook server instance.
987 """
988 """
988 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
989 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
989 for file in os.listdir(pd.security_dir):
990 for file in os.listdir(pd.security_dir):
990 if file.startswith('nbserver-'):
991 if file.startswith('nbserver-'):
991 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
992 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
992 info = json.load(f)
993 info = json.load(f)
993
994
994 # Simple check whether that process is really still running
995 # Simple check whether that process is really still running
995 if check_pid(info['pid']):
996 if check_pid(info['pid']):
996 yield info
997 yield info
997 else:
998 else:
998 # If the process has died, try to delete its info file
999 # If the process has died, try to delete its info file
999 try:
1000 try:
1000 os.unlink(file)
1001 os.unlink(file)
1001 except OSError:
1002 except OSError:
1002 pass # TODO: This should warn or log or something
1003 pass # TODO: This should warn or log or something
1003 #-----------------------------------------------------------------------------
1004 #-----------------------------------------------------------------------------
1004 # Main entry point
1005 # Main entry point
1005 #-----------------------------------------------------------------------------
1006 #-----------------------------------------------------------------------------
1006
1007
1007 launch_new_instance = NotebookApp.launch_instance
1008 launch_new_instance = NotebookApp.launch_instance
1008
1009
@@ -1,614 +1,619 b''
1 """A contents manager that uses the local file system for storage."""
1 """A contents manager that uses the local file system for storage."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import base64
6 import base64
7 import errno
7 import errno
8 import io
8 import io
9 import os
9 import os
10 import shutil
10 import shutil
11 from contextlib import contextmanager
11 from contextlib import contextmanager
12 import mimetypes
12
13
13 from tornado import web
14 from tornado import web
14
15
15 from .manager import ContentsManager
16 from .manager import ContentsManager
16 from IPython import nbformat
17 from IPython import nbformat
17 from IPython.utils.io import atomic_writing
18 from IPython.utils.io import atomic_writing
18 from IPython.utils.path import ensure_dir_exists
19 from IPython.utils.path import ensure_dir_exists
19 from IPython.utils.traitlets import Unicode, Bool, TraitError
20 from IPython.utils.traitlets import Unicode, Bool, TraitError
20 from IPython.utils.py3compat import getcwd, str_to_unicode
21 from IPython.utils.py3compat import getcwd, str_to_unicode
21 from IPython.utils import tz
22 from IPython.utils import tz
22 from IPython.html.utils import is_hidden, to_os_path, to_api_path
23 from IPython.html.utils import is_hidden, to_os_path, to_api_path
23
24
24
25
25 class FileContentsManager(ContentsManager):
26 class FileContentsManager(ContentsManager):
26
27
27 root_dir = Unicode(config=True)
28 root_dir = Unicode(config=True)
28
29
29 def _root_dir_default(self):
30 def _root_dir_default(self):
30 try:
31 try:
31 return self.parent.notebook_dir
32 return self.parent.notebook_dir
32 except AttributeError:
33 except AttributeError:
33 return getcwd()
34 return getcwd()
34
35
35 @contextmanager
36 @contextmanager
36 def perm_to_403(self, os_path=''):
37 def perm_to_403(self, os_path=''):
37 """context manager for turning permission errors into 403"""
38 """context manager for turning permission errors into 403"""
38 try:
39 try:
39 yield
40 yield
40 except OSError as e:
41 except OSError as e:
41 if e.errno in {errno.EPERM, errno.EACCES}:
42 if e.errno in {errno.EPERM, errno.EACCES}:
42 # make 403 error message without root prefix
43 # make 403 error message without root prefix
43 # this may not work perfectly on unicode paths on Python 2,
44 # this may not work perfectly on unicode paths on Python 2,
44 # but nobody should be doing that anyway.
45 # but nobody should be doing that anyway.
45 if not os_path:
46 if not os_path:
46 os_path = str_to_unicode(e.filename or 'unknown file')
47 os_path = str_to_unicode(e.filename or 'unknown file')
47 path = to_api_path(os_path, self.root_dir)
48 path = to_api_path(os_path, self.root_dir)
48 raise web.HTTPError(403, u'Permission denied: %s' % path)
49 raise web.HTTPError(403, u'Permission denied: %s' % path)
49 else:
50 else:
50 raise
51 raise
51
52
52 @contextmanager
53 @contextmanager
53 def open(self, os_path, *args, **kwargs):
54 def open(self, os_path, *args, **kwargs):
54 """wrapper around io.open that turns permission errors into 403"""
55 """wrapper around io.open that turns permission errors into 403"""
55 with self.perm_to_403(os_path):
56 with self.perm_to_403(os_path):
56 with io.open(os_path, *args, **kwargs) as f:
57 with io.open(os_path, *args, **kwargs) as f:
57 yield f
58 yield f
58
59
59 @contextmanager
60 @contextmanager
60 def atomic_writing(self, os_path, *args, **kwargs):
61 def atomic_writing(self, os_path, *args, **kwargs):
61 """wrapper around atomic_writing that turns permission errors into 403"""
62 """wrapper around atomic_writing that turns permission errors into 403"""
62 with self.perm_to_403(os_path):
63 with self.perm_to_403(os_path):
63 with atomic_writing(os_path, *args, **kwargs) as f:
64 with atomic_writing(os_path, *args, **kwargs) as f:
64 yield f
65 yield f
65
66
66 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
67 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
67 def _save_script_changed(self):
68 def _save_script_changed(self):
68 self.log.warn("""
69 self.log.warn("""
69 Automatically saving notebooks as scripts has been removed.
70 Automatically saving notebooks as scripts has been removed.
70 Use `ipython nbconvert --to python [notebook]` instead.
71 Use `ipython nbconvert --to python [notebook]` instead.
71 """)
72 """)
72
73
73 def _root_dir_changed(self, name, old, new):
74 def _root_dir_changed(self, name, old, new):
74 """Do a bit of validation of the root_dir."""
75 """Do a bit of validation of the root_dir."""
75 if not os.path.isabs(new):
76 if not os.path.isabs(new):
76 # If we receive a non-absolute path, make it absolute.
77 # If we receive a non-absolute path, make it absolute.
77 self.root_dir = os.path.abspath(new)
78 self.root_dir = os.path.abspath(new)
78 return
79 return
79 if not os.path.isdir(new):
80 if not os.path.isdir(new):
80 raise TraitError("%r is not a directory" % new)
81 raise TraitError("%r is not a directory" % new)
81
82
82 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
83 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
83 help="""The directory name in which to keep file checkpoints
84 help="""The directory name in which to keep file checkpoints
84
85
85 This is a path relative to the file's own directory.
86 This is a path relative to the file's own directory.
86
87
87 By default, it is .ipynb_checkpoints
88 By default, it is .ipynb_checkpoints
88 """
89 """
89 )
90 )
90
91
91 def _copy(self, src, dest):
92 def _copy(self, src, dest):
92 """copy src to dest
93 """copy src to dest
93
94
94 like shutil.copy2, but log errors in copystat
95 like shutil.copy2, but log errors in copystat
95 """
96 """
96 shutil.copyfile(src, dest)
97 shutil.copyfile(src, dest)
97 try:
98 try:
98 shutil.copystat(src, dest)
99 shutil.copystat(src, dest)
99 except OSError as e:
100 except OSError as e:
100 self.log.debug("copystat on %s failed", dest, exc_info=True)
101 self.log.debug("copystat on %s failed", dest, exc_info=True)
101
102
102 def _get_os_path(self, path):
103 def _get_os_path(self, path):
103 """Given an API path, return its file system path.
104 """Given an API path, return its file system path.
104
105
105 Parameters
106 Parameters
106 ----------
107 ----------
107 path : string
108 path : string
108 The relative API path to the named file.
109 The relative API path to the named file.
109
110
110 Returns
111 Returns
111 -------
112 -------
112 path : string
113 path : string
113 Native, absolute OS path to for a file.
114 Native, absolute OS path to for a file.
114 """
115 """
115 return to_os_path(path, self.root_dir)
116 return to_os_path(path, self.root_dir)
116
117
117 def dir_exists(self, path):
118 def dir_exists(self, path):
118 """Does the API-style path refer to an extant directory?
119 """Does the API-style path refer to an extant directory?
119
120
120 API-style wrapper for os.path.isdir
121 API-style wrapper for os.path.isdir
121
122
122 Parameters
123 Parameters
123 ----------
124 ----------
124 path : string
125 path : string
125 The path to check. This is an API path (`/` separated,
126 The path to check. This is an API path (`/` separated,
126 relative to root_dir).
127 relative to root_dir).
127
128
128 Returns
129 Returns
129 -------
130 -------
130 exists : bool
131 exists : bool
131 Whether the path is indeed a directory.
132 Whether the path is indeed a directory.
132 """
133 """
133 path = path.strip('/')
134 path = path.strip('/')
134 os_path = self._get_os_path(path=path)
135 os_path = self._get_os_path(path=path)
135 return os.path.isdir(os_path)
136 return os.path.isdir(os_path)
136
137
137 def is_hidden(self, path):
138 def is_hidden(self, path):
138 """Does the API style path correspond to a hidden directory or file?
139 """Does the API style path correspond to a hidden directory or file?
139
140
140 Parameters
141 Parameters
141 ----------
142 ----------
142 path : string
143 path : string
143 The path to check. This is an API path (`/` separated,
144 The path to check. This is an API path (`/` separated,
144 relative to root_dir).
145 relative to root_dir).
145
146
146 Returns
147 Returns
147 -------
148 -------
148 hidden : bool
149 hidden : bool
149 Whether the path exists and is hidden.
150 Whether the path exists and is hidden.
150 """
151 """
151 path = path.strip('/')
152 path = path.strip('/')
152 os_path = self._get_os_path(path=path)
153 os_path = self._get_os_path(path=path)
153 return is_hidden(os_path, self.root_dir)
154 return is_hidden(os_path, self.root_dir)
154
155
155 def file_exists(self, path):
156 def file_exists(self, path):
156 """Returns True if the file exists, else returns False.
157 """Returns True if the file exists, else returns False.
157
158
158 API-style wrapper for os.path.isfile
159 API-style wrapper for os.path.isfile
159
160
160 Parameters
161 Parameters
161 ----------
162 ----------
162 path : string
163 path : string
163 The relative path to the file (with '/' as separator)
164 The relative path to the file (with '/' as separator)
164
165
165 Returns
166 Returns
166 -------
167 -------
167 exists : bool
168 exists : bool
168 Whether the file exists.
169 Whether the file exists.
169 """
170 """
170 path = path.strip('/')
171 path = path.strip('/')
171 os_path = self._get_os_path(path)
172 os_path = self._get_os_path(path)
172 return os.path.isfile(os_path)
173 return os.path.isfile(os_path)
173
174
174 def exists(self, path):
175 def exists(self, path):
175 """Returns True if the path exists, else returns False.
176 """Returns True if the path exists, else returns False.
176
177
177 API-style wrapper for os.path.exists
178 API-style wrapper for os.path.exists
178
179
179 Parameters
180 Parameters
180 ----------
181 ----------
181 path : string
182 path : string
182 The API path to the file (with '/' as separator)
183 The API path to the file (with '/' as separator)
183
184
184 Returns
185 Returns
185 -------
186 -------
186 exists : bool
187 exists : bool
187 Whether the target exists.
188 Whether the target exists.
188 """
189 """
189 path = path.strip('/')
190 path = path.strip('/')
190 os_path = self._get_os_path(path=path)
191 os_path = self._get_os_path(path=path)
191 return os.path.exists(os_path)
192 return os.path.exists(os_path)
192
193
193 def _base_model(self, path):
194 def _base_model(self, path):
194 """Build the common base of a contents model"""
195 """Build the common base of a contents model"""
195 os_path = self._get_os_path(path)
196 os_path = self._get_os_path(path)
196 info = os.stat(os_path)
197 info = os.stat(os_path)
197 last_modified = tz.utcfromtimestamp(info.st_mtime)
198 last_modified = tz.utcfromtimestamp(info.st_mtime)
198 created = tz.utcfromtimestamp(info.st_ctime)
199 created = tz.utcfromtimestamp(info.st_ctime)
199 # Create the base model.
200 # Create the base model.
200 model = {}
201 model = {}
201 model['name'] = path.rsplit('/', 1)[-1]
202 model['name'] = path.rsplit('/', 1)[-1]
202 model['path'] = path
203 model['path'] = path
203 model['last_modified'] = last_modified
204 model['last_modified'] = last_modified
204 model['created'] = created
205 model['created'] = created
205 model['content'] = None
206 model['content'] = None
206 model['format'] = None
207 model['format'] = None
208 model['mimetype'] = None
207 try:
209 try:
208 model['writable'] = os.access(os_path, os.W_OK)
210 model['writable'] = os.access(os_path, os.W_OK)
209 except OSError:
211 except OSError:
210 self.log.error("Failed to check write permissions on %s", os_path)
212 self.log.error("Failed to check write permissions on %s", os_path)
211 model['writable'] = False
213 model['writable'] = False
212 return model
214 return model
213
215
214 def _dir_model(self, path, content=True):
216 def _dir_model(self, path, content=True):
215 """Build a model for a directory
217 """Build a model for a directory
216
218
217 if content is requested, will include a listing of the directory
219 if content is requested, will include a listing of the directory
218 """
220 """
219 os_path = self._get_os_path(path)
221 os_path = self._get_os_path(path)
220
222
221 four_o_four = u'directory does not exist: %r' % path
223 four_o_four = u'directory does not exist: %r' % path
222
224
223 if not os.path.isdir(os_path):
225 if not os.path.isdir(os_path):
224 raise web.HTTPError(404, four_o_four)
226 raise web.HTTPError(404, four_o_four)
225 elif is_hidden(os_path, self.root_dir):
227 elif is_hidden(os_path, self.root_dir):
226 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
228 self.log.info("Refusing to serve hidden directory %r, via 404 Error",
227 os_path
229 os_path
228 )
230 )
229 raise web.HTTPError(404, four_o_four)
231 raise web.HTTPError(404, four_o_four)
230
232
231 model = self._base_model(path)
233 model = self._base_model(path)
232 model['type'] = 'directory'
234 model['type'] = 'directory'
233 if content:
235 if content:
234 model['content'] = contents = []
236 model['content'] = contents = []
235 os_dir = self._get_os_path(path)
237 os_dir = self._get_os_path(path)
236 for name in os.listdir(os_dir):
238 for name in os.listdir(os_dir):
237 os_path = os.path.join(os_dir, name)
239 os_path = os.path.join(os_dir, name)
238 # skip over broken symlinks in listing
240 # skip over broken symlinks in listing
239 if not os.path.exists(os_path):
241 if not os.path.exists(os_path):
240 self.log.warn("%s doesn't exist", os_path)
242 self.log.warn("%s doesn't exist", os_path)
241 continue
243 continue
242 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
244 elif not os.path.isfile(os_path) and not os.path.isdir(os_path):
243 self.log.debug("%s not a regular file", os_path)
245 self.log.debug("%s not a regular file", os_path)
244 continue
246 continue
245 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
247 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
246 contents.append(self.get(
248 contents.append(self.get(
247 path='%s/%s' % (path, name),
249 path='%s/%s' % (path, name),
248 content=False)
250 content=False)
249 )
251 )
250
252
251 model['format'] = 'json'
253 model['format'] = 'json'
252
254
253 return model
255 return model
254
256
255 def _file_model(self, path, content=True, format=None):
257 def _file_model(self, path, content=True, format=None):
256 """Build a model for a file
258 """Build a model for a file
257
259
258 if content is requested, include the file contents.
260 if content is requested, include the file contents.
259
261
260 format:
262 format:
261 If 'text', the contents will be decoded as UTF-8.
263 If 'text', the contents will be decoded as UTF-8.
262 If 'base64', the raw bytes contents will be encoded as base64.
264 If 'base64', the raw bytes contents will be encoded as base64.
263 If not specified, try to decode as UTF-8, and fall back to base64
265 If not specified, try to decode as UTF-8, and fall back to base64
264 """
266 """
265 model = self._base_model(path)
267 model = self._base_model(path)
266 model['type'] = 'file'
268 model['type'] = 'file'
269
270 os_path = self._get_os_path(path)
271 model['mimetype'] = mimetypes.guess_type(os_path)[0] or 'text/plain'
272
267 if content:
273 if content:
268 os_path = self._get_os_path(path)
269 if not os.path.isfile(os_path):
274 if not os.path.isfile(os_path):
270 # could be FIFO
275 # could be FIFO
271 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
276 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
272 with self.open(os_path, 'rb') as f:
277 with self.open(os_path, 'rb') as f:
273 bcontent = f.read()
278 bcontent = f.read()
274
279
275 if format != 'base64':
280 if format != 'base64':
276 try:
281 try:
277 model['content'] = bcontent.decode('utf8')
282 model['content'] = bcontent.decode('utf8')
278 except UnicodeError as e:
283 except UnicodeError as e:
279 if format == 'text':
284 if format == 'text':
280 raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
285 raise web.HTTPError(400, "%s is not UTF-8 encoded" % path)
281 else:
286 else:
282 model['format'] = 'text'
287 model['format'] = 'text'
283
288
284 if model['content'] is None:
289 if model['content'] is None:
285 model['content'] = base64.encodestring(bcontent).decode('ascii')
290 model['content'] = base64.encodestring(bcontent).decode('ascii')
286 model['format'] = 'base64'
291 model['format'] = 'base64'
287
292
288 return model
293 return model
289
294
290
295
291 def _notebook_model(self, path, content=True):
296 def _notebook_model(self, path, content=True):
292 """Build a notebook model
297 """Build a notebook model
293
298
294 if content is requested, the notebook content will be populated
299 if content is requested, the notebook content will be populated
295 as a JSON structure (not double-serialized)
300 as a JSON structure (not double-serialized)
296 """
301 """
297 model = self._base_model(path)
302 model = self._base_model(path)
298 model['type'] = 'notebook'
303 model['type'] = 'notebook'
299 if content:
304 if content:
300 os_path = self._get_os_path(path)
305 os_path = self._get_os_path(path)
301 with self.open(os_path, 'r', encoding='utf-8') as f:
306 with self.open(os_path, 'r', encoding='utf-8') as f:
302 try:
307 try:
303 nb = nbformat.read(f, as_version=4)
308 nb = nbformat.read(f, as_version=4)
304 except Exception as e:
309 except Exception as e:
305 raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
310 raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e))
306 self.mark_trusted_cells(nb, path)
311 self.mark_trusted_cells(nb, path)
307 model['content'] = nb
312 model['content'] = nb
308 model['format'] = 'json'
313 model['format'] = 'json'
309 self.validate_notebook_model(model)
314 self.validate_notebook_model(model)
310 return model
315 return model
311
316
312 def get(self, path, content=True, type_=None, format=None):
317 def get(self, path, content=True, type_=None, format=None):
313 """ Takes a path for an entity and returns its model
318 """ Takes a path for an entity and returns its model
314
319
315 Parameters
320 Parameters
316 ----------
321 ----------
317 path : str
322 path : str
318 the API path that describes the relative path for the target
323 the API path that describes the relative path for the target
319 content : bool
324 content : bool
320 Whether to include the contents in the reply
325 Whether to include the contents in the reply
321 type_ : str, optional
326 type_ : str, optional
322 The requested type - 'file', 'notebook', or 'directory'.
327 The requested type - 'file', 'notebook', or 'directory'.
323 Will raise HTTPError 400 if the content doesn't match.
328 Will raise HTTPError 400 if the content doesn't match.
324 format : str, optional
329 format : str, optional
325 The requested format for file contents. 'text' or 'base64'.
330 The requested format for file contents. 'text' or 'base64'.
326 Ignored if this returns a notebook or directory model.
331 Ignored if this returns a notebook or directory model.
327
332
328 Returns
333 Returns
329 -------
334 -------
330 model : dict
335 model : dict
331 the contents model. If content=True, returns the contents
336 the contents model. If content=True, returns the contents
332 of the file or directory as well.
337 of the file or directory as well.
333 """
338 """
334 path = path.strip('/')
339 path = path.strip('/')
335
340
336 if not self.exists(path):
341 if not self.exists(path):
337 raise web.HTTPError(404, u'No such file or directory: %s' % path)
342 raise web.HTTPError(404, u'No such file or directory: %s' % path)
338
343
339 os_path = self._get_os_path(path)
344 os_path = self._get_os_path(path)
340 if os.path.isdir(os_path):
345 if os.path.isdir(os_path):
341 if type_ not in (None, 'directory'):
346 if type_ not in (None, 'directory'):
342 raise web.HTTPError(400,
347 raise web.HTTPError(400,
343 u'%s is a directory, not a %s' % (path, type_))
348 u'%s is a directory, not a %s' % (path, type_))
344 model = self._dir_model(path, content=content)
349 model = self._dir_model(path, content=content)
345 elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
350 elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
346 model = self._notebook_model(path, content=content)
351 model = self._notebook_model(path, content=content)
347 else:
352 else:
348 if type_ == 'directory':
353 if type_ == 'directory':
349 raise web.HTTPError(400,
354 raise web.HTTPError(400,
350 u'%s is not a directory')
355 u'%s is not a directory')
351 model = self._file_model(path, content=content, format=format)
356 model = self._file_model(path, content=content, format=format)
352 return model
357 return model
353
358
354 def _save_notebook(self, os_path, model, path=''):
359 def _save_notebook(self, os_path, model, path=''):
355 """save a notebook file"""
360 """save a notebook file"""
356 # Save the notebook file
361 # Save the notebook file
357 nb = nbformat.from_dict(model['content'])
362 nb = nbformat.from_dict(model['content'])
358
363
359 self.check_and_sign(nb, path)
364 self.check_and_sign(nb, path)
360
365
361 with self.atomic_writing(os_path, encoding='utf-8') as f:
366 with self.atomic_writing(os_path, encoding='utf-8') as f:
362 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
367 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
363
368
364 def _save_file(self, os_path, model, path=''):
369 def _save_file(self, os_path, model, path=''):
365 """save a non-notebook file"""
370 """save a non-notebook file"""
366 fmt = model.get('format', None)
371 fmt = model.get('format', None)
367 if fmt not in {'text', 'base64'}:
372 if fmt not in {'text', 'base64'}:
368 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
373 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
369 try:
374 try:
370 content = model['content']
375 content = model['content']
371 if fmt == 'text':
376 if fmt == 'text':
372 bcontent = content.encode('utf8')
377 bcontent = content.encode('utf8')
373 else:
378 else:
374 b64_bytes = content.encode('ascii')
379 b64_bytes = content.encode('ascii')
375 bcontent = base64.decodestring(b64_bytes)
380 bcontent = base64.decodestring(b64_bytes)
376 except Exception as e:
381 except Exception as e:
377 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
382 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
378 with self.atomic_writing(os_path, text=False) as f:
383 with self.atomic_writing(os_path, text=False) as f:
379 f.write(bcontent)
384 f.write(bcontent)
380
385
381 def _save_directory(self, os_path, model, path=''):
386 def _save_directory(self, os_path, model, path=''):
382 """create a directory"""
387 """create a directory"""
383 if is_hidden(os_path, self.root_dir):
388 if is_hidden(os_path, self.root_dir):
384 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
389 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
385 if not os.path.exists(os_path):
390 if not os.path.exists(os_path):
386 with self.perm_to_403():
391 with self.perm_to_403():
387 os.mkdir(os_path)
392 os.mkdir(os_path)
388 elif not os.path.isdir(os_path):
393 elif not os.path.isdir(os_path):
389 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
394 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
390 else:
395 else:
391 self.log.debug("Directory %r already exists", os_path)
396 self.log.debug("Directory %r already exists", os_path)
392
397
393 def save(self, model, path=''):
398 def save(self, model, path=''):
394 """Save the file model and return the model with no content."""
399 """Save the file model and return the model with no content."""
395 path = path.strip('/')
400 path = path.strip('/')
396
401
397 if 'type' not in model:
402 if 'type' not in model:
398 raise web.HTTPError(400, u'No file type provided')
403 raise web.HTTPError(400, u'No file type provided')
399 if 'content' not in model and model['type'] != 'directory':
404 if 'content' not in model and model['type'] != 'directory':
400 raise web.HTTPError(400, u'No file content provided')
405 raise web.HTTPError(400, u'No file content provided')
401
406
402 # One checkpoint should always exist
407 # One checkpoint should always exist
403 if self.file_exists(path) and not self.list_checkpoints(path):
408 if self.file_exists(path) and not self.list_checkpoints(path):
404 self.create_checkpoint(path)
409 self.create_checkpoint(path)
405
410
406 os_path = self._get_os_path(path)
411 os_path = self._get_os_path(path)
407 self.log.debug("Saving %s", os_path)
412 self.log.debug("Saving %s", os_path)
408 try:
413 try:
409 if model['type'] == 'notebook':
414 if model['type'] == 'notebook':
410 self._save_notebook(os_path, model, path)
415 self._save_notebook(os_path, model, path)
411 elif model['type'] == 'file':
416 elif model['type'] == 'file':
412 self._save_file(os_path, model, path)
417 self._save_file(os_path, model, path)
413 elif model['type'] == 'directory':
418 elif model['type'] == 'directory':
414 self._save_directory(os_path, model, path)
419 self._save_directory(os_path, model, path)
415 else:
420 else:
416 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
421 raise web.HTTPError(400, "Unhandled contents type: %s" % model['type'])
417 except web.HTTPError:
422 except web.HTTPError:
418 raise
423 raise
419 except Exception as e:
424 except Exception as e:
420 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
425 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
421 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
426 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
422
427
423 validation_message = None
428 validation_message = None
424 if model['type'] == 'notebook':
429 if model['type'] == 'notebook':
425 self.validate_notebook_model(model)
430 self.validate_notebook_model(model)
426 validation_message = model.get('message', None)
431 validation_message = model.get('message', None)
427
432
428 model = self.get(path, content=False)
433 model = self.get(path, content=False)
429 if validation_message:
434 if validation_message:
430 model['message'] = validation_message
435 model['message'] = validation_message
431 return model
436 return model
432
437
433 def update(self, model, path):
438 def update(self, model, path):
434 """Update the file's path
439 """Update the file's path
435
440
436 For use in PATCH requests, to enable renaming a file without
441 For use in PATCH requests, to enable renaming a file without
437 re-uploading its contents. Only used for renaming at the moment.
442 re-uploading its contents. Only used for renaming at the moment.
438 """
443 """
439 path = path.strip('/')
444 path = path.strip('/')
440 new_path = model.get('path', path).strip('/')
445 new_path = model.get('path', path).strip('/')
441 if path != new_path:
446 if path != new_path:
442 self.rename(path, new_path)
447 self.rename(path, new_path)
443 model = self.get(new_path, content=False)
448 model = self.get(new_path, content=False)
444 return model
449 return model
445
450
446 def delete(self, path):
451 def delete(self, path):
447 """Delete file at path."""
452 """Delete file at path."""
448 path = path.strip('/')
453 path = path.strip('/')
449 os_path = self._get_os_path(path)
454 os_path = self._get_os_path(path)
450 rm = os.unlink
455 rm = os.unlink
451 if os.path.isdir(os_path):
456 if os.path.isdir(os_path):
452 listing = os.listdir(os_path)
457 listing = os.listdir(os_path)
453 # don't delete non-empty directories (checkpoints dir doesn't count)
458 # don't delete non-empty directories (checkpoints dir doesn't count)
454 if listing and listing != [self.checkpoint_dir]:
459 if listing and listing != [self.checkpoint_dir]:
455 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
460 raise web.HTTPError(400, u'Directory %s not empty' % os_path)
456 elif not os.path.isfile(os_path):
461 elif not os.path.isfile(os_path):
457 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
462 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
458
463
459 # clear checkpoints
464 # clear checkpoints
460 for checkpoint in self.list_checkpoints(path):
465 for checkpoint in self.list_checkpoints(path):
461 checkpoint_id = checkpoint['id']
466 checkpoint_id = checkpoint['id']
462 cp_path = self.get_checkpoint_path(checkpoint_id, path)
467 cp_path = self.get_checkpoint_path(checkpoint_id, path)
463 if os.path.isfile(cp_path):
468 if os.path.isfile(cp_path):
464 self.log.debug("Unlinking checkpoint %s", cp_path)
469 self.log.debug("Unlinking checkpoint %s", cp_path)
465 with self.perm_to_403():
470 with self.perm_to_403():
466 rm(cp_path)
471 rm(cp_path)
467
472
468 if os.path.isdir(os_path):
473 if os.path.isdir(os_path):
469 self.log.debug("Removing directory %s", os_path)
474 self.log.debug("Removing directory %s", os_path)
470 with self.perm_to_403():
475 with self.perm_to_403():
471 shutil.rmtree(os_path)
476 shutil.rmtree(os_path)
472 else:
477 else:
473 self.log.debug("Unlinking file %s", os_path)
478 self.log.debug("Unlinking file %s", os_path)
474 with self.perm_to_403():
479 with self.perm_to_403():
475 rm(os_path)
480 rm(os_path)
476
481
477 def rename(self, old_path, new_path):
482 def rename(self, old_path, new_path):
478 """Rename a file."""
483 """Rename a file."""
479 old_path = old_path.strip('/')
484 old_path = old_path.strip('/')
480 new_path = new_path.strip('/')
485 new_path = new_path.strip('/')
481 if new_path == old_path:
486 if new_path == old_path:
482 return
487 return
483
488
484 new_os_path = self._get_os_path(new_path)
489 new_os_path = self._get_os_path(new_path)
485 old_os_path = self._get_os_path(old_path)
490 old_os_path = self._get_os_path(old_path)
486
491
487 # Should we proceed with the move?
492 # Should we proceed with the move?
488 if os.path.exists(new_os_path):
493 if os.path.exists(new_os_path):
489 raise web.HTTPError(409, u'File already exists: %s' % new_path)
494 raise web.HTTPError(409, u'File already exists: %s' % new_path)
490
495
491 # Move the file
496 # Move the file
492 try:
497 try:
493 with self.perm_to_403():
498 with self.perm_to_403():
494 shutil.move(old_os_path, new_os_path)
499 shutil.move(old_os_path, new_os_path)
495 except web.HTTPError:
500 except web.HTTPError:
496 raise
501 raise
497 except Exception as e:
502 except Exception as e:
498 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
503 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
499
504
500 # Move the checkpoints
505 # Move the checkpoints
501 old_checkpoints = self.list_checkpoints(old_path)
506 old_checkpoints = self.list_checkpoints(old_path)
502 for cp in old_checkpoints:
507 for cp in old_checkpoints:
503 checkpoint_id = cp['id']
508 checkpoint_id = cp['id']
504 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path)
509 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path)
505 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path)
510 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path)
506 if os.path.isfile(old_cp_path):
511 if os.path.isfile(old_cp_path):
507 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
512 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
508 with self.perm_to_403():
513 with self.perm_to_403():
509 shutil.move(old_cp_path, new_cp_path)
514 shutil.move(old_cp_path, new_cp_path)
510
515
511 # Checkpoint-related utilities
516 # Checkpoint-related utilities
512
517
513 def get_checkpoint_path(self, checkpoint_id, path):
518 def get_checkpoint_path(self, checkpoint_id, path):
514 """find the path to a checkpoint"""
519 """find the path to a checkpoint"""
515 path = path.strip('/')
520 path = path.strip('/')
516 parent, name = ('/' + path).rsplit('/', 1)
521 parent, name = ('/' + path).rsplit('/', 1)
517 parent = parent.strip('/')
522 parent = parent.strip('/')
518 basename, ext = os.path.splitext(name)
523 basename, ext = os.path.splitext(name)
519 filename = u"{name}-{checkpoint_id}{ext}".format(
524 filename = u"{name}-{checkpoint_id}{ext}".format(
520 name=basename,
525 name=basename,
521 checkpoint_id=checkpoint_id,
526 checkpoint_id=checkpoint_id,
522 ext=ext,
527 ext=ext,
523 )
528 )
524 os_path = self._get_os_path(path=parent)
529 os_path = self._get_os_path(path=parent)
525 cp_dir = os.path.join(os_path, self.checkpoint_dir)
530 cp_dir = os.path.join(os_path, self.checkpoint_dir)
526 with self.perm_to_403():
531 with self.perm_to_403():
527 ensure_dir_exists(cp_dir)
532 ensure_dir_exists(cp_dir)
528 cp_path = os.path.join(cp_dir, filename)
533 cp_path = os.path.join(cp_dir, filename)
529 return cp_path
534 return cp_path
530
535
531 def get_checkpoint_model(self, checkpoint_id, path):
536 def get_checkpoint_model(self, checkpoint_id, path):
532 """construct the info dict for a given checkpoint"""
537 """construct the info dict for a given checkpoint"""
533 path = path.strip('/')
538 path = path.strip('/')
534 cp_path = self.get_checkpoint_path(checkpoint_id, path)
539 cp_path = self.get_checkpoint_path(checkpoint_id, path)
535 stats = os.stat(cp_path)
540 stats = os.stat(cp_path)
536 last_modified = tz.utcfromtimestamp(stats.st_mtime)
541 last_modified = tz.utcfromtimestamp(stats.st_mtime)
537 info = dict(
542 info = dict(
538 id = checkpoint_id,
543 id = checkpoint_id,
539 last_modified = last_modified,
544 last_modified = last_modified,
540 )
545 )
541 return info
546 return info
542
547
543 # public checkpoint API
548 # public checkpoint API
544
549
545 def create_checkpoint(self, path):
550 def create_checkpoint(self, path):
546 """Create a checkpoint from the current state of a file"""
551 """Create a checkpoint from the current state of a file"""
547 path = path.strip('/')
552 path = path.strip('/')
548 if not self.file_exists(path):
553 if not self.file_exists(path):
549 raise web.HTTPError(404)
554 raise web.HTTPError(404)
550 src_path = self._get_os_path(path)
555 src_path = self._get_os_path(path)
551 # only the one checkpoint ID:
556 # only the one checkpoint ID:
552 checkpoint_id = u"checkpoint"
557 checkpoint_id = u"checkpoint"
553 cp_path = self.get_checkpoint_path(checkpoint_id, path)
558 cp_path = self.get_checkpoint_path(checkpoint_id, path)
554 self.log.debug("creating checkpoint for %s", path)
559 self.log.debug("creating checkpoint for %s", path)
555 with self.perm_to_403():
560 with self.perm_to_403():
556 self._copy(src_path, cp_path)
561 self._copy(src_path, cp_path)
557
562
558 # return the checkpoint info
563 # return the checkpoint info
559 return self.get_checkpoint_model(checkpoint_id, path)
564 return self.get_checkpoint_model(checkpoint_id, path)
560
565
561 def list_checkpoints(self, path):
566 def list_checkpoints(self, path):
562 """list the checkpoints for a given file
567 """list the checkpoints for a given file
563
568
564 This contents manager currently only supports one checkpoint per file.
569 This contents manager currently only supports one checkpoint per file.
565 """
570 """
566 path = path.strip('/')
571 path = path.strip('/')
567 checkpoint_id = "checkpoint"
572 checkpoint_id = "checkpoint"
568 os_path = self.get_checkpoint_path(checkpoint_id, path)
573 os_path = self.get_checkpoint_path(checkpoint_id, path)
569 if not os.path.exists(os_path):
574 if not os.path.exists(os_path):
570 return []
575 return []
571 else:
576 else:
572 return [self.get_checkpoint_model(checkpoint_id, path)]
577 return [self.get_checkpoint_model(checkpoint_id, path)]
573
578
574
579
575 def restore_checkpoint(self, checkpoint_id, path):
580 def restore_checkpoint(self, checkpoint_id, path):
576 """restore a file to a checkpointed state"""
581 """restore a file to a checkpointed state"""
577 path = path.strip('/')
582 path = path.strip('/')
578 self.log.info("restoring %s from checkpoint %s", path, checkpoint_id)
583 self.log.info("restoring %s from checkpoint %s", path, checkpoint_id)
579 nb_path = self._get_os_path(path)
584 nb_path = self._get_os_path(path)
580 cp_path = self.get_checkpoint_path(checkpoint_id, path)
585 cp_path = self.get_checkpoint_path(checkpoint_id, path)
581 if not os.path.isfile(cp_path):
586 if not os.path.isfile(cp_path):
582 self.log.debug("checkpoint file does not exist: %s", cp_path)
587 self.log.debug("checkpoint file does not exist: %s", cp_path)
583 raise web.HTTPError(404,
588 raise web.HTTPError(404,
584 u'checkpoint does not exist: %s@%s' % (path, checkpoint_id)
589 u'checkpoint does not exist: %s@%s' % (path, checkpoint_id)
585 )
590 )
586 # ensure notebook is readable (never restore from an unreadable notebook)
591 # ensure notebook is readable (never restore from an unreadable notebook)
587 if cp_path.endswith('.ipynb'):
592 if cp_path.endswith('.ipynb'):
588 with self.open(cp_path, 'r', encoding='utf-8') as f:
593 with self.open(cp_path, 'r', encoding='utf-8') as f:
589 nbformat.read(f, as_version=4)
594 nbformat.read(f, as_version=4)
590 self.log.debug("copying %s -> %s", cp_path, nb_path)
595 self.log.debug("copying %s -> %s", cp_path, nb_path)
591 with self.perm_to_403():
596 with self.perm_to_403():
592 self._copy(cp_path, nb_path)
597 self._copy(cp_path, nb_path)
593
598
594 def delete_checkpoint(self, checkpoint_id, path):
599 def delete_checkpoint(self, checkpoint_id, path):
595 """delete a file's checkpoint"""
600 """delete a file's checkpoint"""
596 path = path.strip('/')
601 path = path.strip('/')
597 cp_path = self.get_checkpoint_path(checkpoint_id, path)
602 cp_path = self.get_checkpoint_path(checkpoint_id, path)
598 if not os.path.isfile(cp_path):
603 if not os.path.isfile(cp_path):
599 raise web.HTTPError(404,
604 raise web.HTTPError(404,
600 u'Checkpoint does not exist: %s@%s' % (path, checkpoint_id)
605 u'Checkpoint does not exist: %s@%s' % (path, checkpoint_id)
601 )
606 )
602 self.log.debug("unlinking %s", cp_path)
607 self.log.debug("unlinking %s", cp_path)
603 os.unlink(cp_path)
608 os.unlink(cp_path)
604
609
605 def info_string(self):
610 def info_string(self):
606 return "Serving notebooks from local directory: %s" % self.root_dir
611 return "Serving notebooks from local directory: %s" % self.root_dir
607
612
608 def get_kernel_path(self, path, model=None):
613 def get_kernel_path(self, path, model=None):
609 """Return the initial working dir a kernel associated with a given notebook"""
614 """Return the initial working dir a kernel associated with a given notebook"""
610 if '/' in path:
615 if '/' in path:
611 parent_dir = path.rsplit('/', 1)[0]
616 parent_dir = path.rsplit('/', 1)[0]
612 else:
617 else:
613 parent_dir = ''
618 parent_dir = ''
614 return self._get_os_path(parent_dir)
619 return self._get_os_path(parent_dir)
1 NO CONTENT: file renamed from IPython/html/static/notebook/js/notificationwidget.js to IPython/html/static/base/js/notificationwidget.js
NO CONTENT: file renamed from IPython/html/static/notebook/js/notificationwidget.js to IPython/html/static/base/js/notificationwidget.js
@@ -1,163 +1,163 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/notebook',
7 'notebook/js/notebook',
8 'contents',
8 'contents',
9 'base/js/utils',
9 'base/js/utils',
10 'base/js/page',
10 'base/js/page',
11 'notebook/js/layoutmanager',
11 'notebook/js/layoutmanager',
12 'base/js/events',
12 'base/js/events',
13 'auth/js/loginwidget',
13 'auth/js/loginwidget',
14 'notebook/js/maintoolbar',
14 'notebook/js/maintoolbar',
15 'notebook/js/pager',
15 'notebook/js/pager',
16 'notebook/js/quickhelp',
16 'notebook/js/quickhelp',
17 'notebook/js/menubar',
17 'notebook/js/menubar',
18 'notebook/js/notificationarea',
18 'notebook/js/notificationarea',
19 'notebook/js/savewidget',
19 'notebook/js/savewidget',
20 'notebook/js/actions',
20 'notebook/js/actions',
21 'notebook/js/keyboardmanager',
21 'notebook/js/keyboardmanager',
22 'notebook/js/config',
22 'notebook/js/config',
23 'notebook/js/kernelselector',
23 'notebook/js/kernelselector',
24 'codemirror/lib/codemirror',
24 'codemirror/lib/codemirror',
25 'notebook/js/about',
25 'notebook/js/about',
26 // only loaded, not used, please keep sure this is loaded last
26 // only loaded, not used, please keep sure this is loaded last
27 'custom/custom'
27 'custom/custom'
28 ], function(
28 ], function(
29 IPython,
29 IPython,
30 $,
30 $,
31 notebook,
31 notebook,
32 contents,
32 contents,
33 utils,
33 utils,
34 page,
34 page,
35 layoutmanager,
35 layoutmanager,
36 events,
36 events,
37 loginwidget,
37 loginwidget,
38 maintoolbar,
38 maintoolbar,
39 pager,
39 pager,
40 quickhelp,
40 quickhelp,
41 menubar,
41 menubar,
42 notificationarea,
42 notificationarea,
43 savewidget,
43 savewidget,
44 actions,
44 actions,
45 keyboardmanager,
45 keyboardmanager,
46 config,
46 config,
47 kernelselector,
47 kernelselector,
48 CodeMirror,
48 CodeMirror,
49 about,
49 about,
50 // please keep sure that even if not used, this is loaded last
50 // please keep sure that even if not used, this is loaded last
51 custom
51 custom
52 ) {
52 ) {
53 "use strict";
53 "use strict";
54
54
55 // compat with old IPython, remove for IPython > 3.0
55 // compat with old IPython, remove for IPython > 3.0
56 window.CodeMirror = CodeMirror;
56 window.CodeMirror = CodeMirror;
57
57
58 var common_options = {
58 var common_options = {
59 ws_url : utils.get_body_data("wsUrl"),
59 ws_url : utils.get_body_data("wsUrl"),
60 base_url : utils.get_body_data("baseUrl"),
60 base_url : utils.get_body_data("baseUrl"),
61 notebook_path : utils.get_body_data("notebookPath"),
61 notebook_path : utils.get_body_data("notebookPath"),
62 notebook_name : utils.get_body_data('notebookName')
62 notebook_name : utils.get_body_data('notebookName')
63 };
63 };
64
64
65 var user_config = $.extend({}, config.default_config);
65 var user_config = $.extend({}, config.default_config);
66 var page = new page.Page();
66 var page = new page.Page();
67 var layout_manager = new layoutmanager.LayoutManager();
67 var layout_manager = new layoutmanager.LayoutManager();
68 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
68 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
69 layout_manager: layout_manager,
69 layout_manager: layout_manager,
70 events: events});
70 events: events});
71 var acts = new actions.init();
71 var acts = new actions.init();
72 var keyboard_manager = new keyboardmanager.KeyboardManager({
72 var keyboard_manager = new keyboardmanager.KeyboardManager({
73 pager: pager,
73 pager: pager,
74 events: events,
74 events: events,
75 actions: acts });
75 actions: acts });
76 var save_widget = new savewidget.SaveWidget('span#save_widget', {
76 var save_widget = new savewidget.SaveWidget('span#save_widget', {
77 events: events,
77 events: events,
78 keyboard_manager: keyboard_manager});
78 keyboard_manager: keyboard_manager});
79 var contents = new contents.Contents($.extend({
79 var contents = new contents.Contents($.extend({
80 events: events},
80 events: events},
81 common_options));
81 common_options));
82 var notebook = new notebook.Notebook('div#notebook', $.extend({
82 var notebook = new notebook.Notebook('div#notebook', $.extend({
83 events: events,
83 events: events,
84 keyboard_manager: keyboard_manager,
84 keyboard_manager: keyboard_manager,
85 save_widget: save_widget,
85 save_widget: save_widget,
86 contents: contents,
86 contents: contents,
87 config: user_config},
87 config: user_config},
88 common_options));
88 common_options));
89 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
89 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
90 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
90 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
91 notebook: notebook,
91 notebook: notebook,
92 events: events,
92 events: events,
93 actions: acts});
93 actions: acts});
94 var quick_help = new quickhelp.QuickHelp({
94 var quick_help = new quickhelp.QuickHelp({
95 keyboard_manager: keyboard_manager,
95 keyboard_manager: keyboard_manager,
96 events: events,
96 events: events,
97 notebook: notebook});
97 notebook: notebook});
98 keyboard_manager.set_notebook(notebook);
98 keyboard_manager.set_notebook(notebook);
99 keyboard_manager.set_quickhelp(quick_help);
99 keyboard_manager.set_quickhelp(quick_help);
100 var menubar = new menubar.MenuBar('#menubar', $.extend({
100 var menubar = new menubar.MenuBar('#menubar', $.extend({
101 notebook: notebook,
101 notebook: notebook,
102 contents: contents,
102 contents: contents,
103 layout_manager: layout_manager,
103 layout_manager: layout_manager,
104 events: events,
104 events: events,
105 save_widget: save_widget,
105 save_widget: save_widget,
106 quick_help: quick_help},
106 quick_help: quick_help},
107 common_options));
107 common_options));
108 var notification_area = new notificationarea.NotificationArea(
108 var notification_area = new notificationarea.NotebookNotificationArea(
109 '#notification_area', {
109 '#notification_area', {
110 events: events,
110 events: events,
111 save_widget: save_widget,
111 save_widget: save_widget,
112 notebook: notebook,
112 notebook: notebook,
113 keyboard_manager: keyboard_manager});
113 keyboard_manager: keyboard_manager});
114 notification_area.init_notification_widgets();
114 notification_area.init_notification_widgets();
115 var kernel_selector = new kernelselector.KernelSelector(
115 var kernel_selector = new kernelselector.KernelSelector(
116 '#kernel_selector_widget', notebook);
116 '#kernel_selector_widget', notebook);
117
117
118 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
118 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
119 '<span id="test2" style="font-weight: bold;">x</span>'+
119 '<span id="test2" style="font-weight: bold;">x</span>'+
120 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
120 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
121 var nh = $('#test1').innerHeight();
121 var nh = $('#test1').innerHeight();
122 var bh = $('#test2').innerHeight();
122 var bh = $('#test2').innerHeight();
123 var ih = $('#test3').innerHeight();
123 var ih = $('#test3').innerHeight();
124 if(nh != bh || nh != ih) {
124 if(nh != bh || nh != ih) {
125 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
125 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
126 }
126 }
127 $('#fonttest').remove();
127 $('#fonttest').remove();
128
128
129 page.show();
129 page.show();
130
130
131 layout_manager.do_resize();
131 layout_manager.do_resize();
132 var first_load = function () {
132 var first_load = function () {
133 layout_manager.do_resize();
133 layout_manager.do_resize();
134 var hash = document.location.hash;
134 var hash = document.location.hash;
135 if (hash) {
135 if (hash) {
136 document.location.hash = '';
136 document.location.hash = '';
137 document.location.hash = hash;
137 document.location.hash = hash;
138 }
138 }
139 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
139 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
140 // only do this once
140 // only do this once
141 events.off('notebook_loaded.Notebook', first_load);
141 events.off('notebook_loaded.Notebook', first_load);
142 };
142 };
143 events.on('notebook_loaded.Notebook', first_load);
143 events.on('notebook_loaded.Notebook', first_load);
144
144
145 IPython.page = page;
145 IPython.page = page;
146 IPython.layout_manager = layout_manager;
146 IPython.layout_manager = layout_manager;
147 IPython.notebook = notebook;
147 IPython.notebook = notebook;
148 IPython.contents = contents;
148 IPython.contents = contents;
149 IPython.pager = pager;
149 IPython.pager = pager;
150 IPython.quick_help = quick_help;
150 IPython.quick_help = quick_help;
151 IPython.login_widget = login_widget;
151 IPython.login_widget = login_widget;
152 IPython.menubar = menubar;
152 IPython.menubar = menubar;
153 IPython.toolbar = toolbar;
153 IPython.toolbar = toolbar;
154 IPython.notification_area = notification_area;
154 IPython.notification_area = notification_area;
155 IPython.keyboard_manager = keyboard_manager;
155 IPython.keyboard_manager = keyboard_manager;
156 IPython.save_widget = save_widget;
156 IPython.save_widget = save_widget;
157 IPython.config = user_config;
157 IPython.config = user_config;
158 IPython.tooltip = notebook.tooltip;
158 IPython.tooltip = notebook.tooltip;
159
159
160 events.trigger('app_initialized.NotebookApp');
160 events.trigger('app_initialized.NotebookApp');
161 notebook.load_notebook(common_options.notebook_path);
161 notebook.load_notebook(common_options.notebook_path);
162
162
163 });
163 });
@@ -1,387 +1,320 b''
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
3
4 define([
1 define([
5 'base/js/namespace',
2 'base/js/namespace',
6 'jquery',
3 'jquery',
7 'base/js/utils',
4 'base/js/utils',
8 'base/js/dialog',
5 'base/js/dialog',
9 'notebook/js/notificationwidget',
6 'base/js/notificationarea',
10 'moment'
7 'moment'
11 ], function(IPython, $, utils, dialog, notificationwidget, moment) {
8 ], function(IPython, $, utils, dialog, notificationarea, moment) {
12 "use strict";
9 "use strict";
13
10 var NotificationArea = notificationarea.NotificationArea;
14 // store reference to the NotificationWidget class
11
15 var NotificationWidget = notificationwidget.NotificationWidget;
12 var NotebookNotificationArea = function(selector, options) {
16
13 NotificationArea.apply(this, [selector, options]);
17 /**
18 * Construct the NotificationArea object. Options are:
19 * events: $(Events) instance
20 * save_widget: SaveWidget instance
21 * notebook: Notebook instance
22 * keyboard_manager: KeyboardManager instance
23 *
24 * @constructor
25 * @param {string} selector - a jQuery selector string for the
26 * notification area element
27 * @param {Object} [options] - a dictionary of keyword arguments.
28 */
29 var NotificationArea = function (selector, options) {
30 this.selector = selector;
31 this.events = options.events;
32 this.save_widget = options.save_widget;
14 this.save_widget = options.save_widget;
33 this.notebook = options.notebook;
15 this.notebook = options.notebook;
34 this.keyboard_manager = options.keyboard_manager;
16 this.keyboard_manager = options.keyboard_manager;
35 if (this.selector !== undefined) {
17 }
36 this.element = $(selector);
18
37 }
19 NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype);
38 this.widget_dict = {};
20
39 };
40
41 /**
42 * Get a widget by name, creating it if it doesn't exist.
43 *
44 * @method widget
45 * @param {string} name - the widget name
46 */
47 NotificationArea.prototype.widget = function (name) {
48 if (this.widget_dict[name] === undefined) {
49 return this.new_notification_widget(name);
50 }
51 return this.get_widget(name);
52 };
53
54 /**
55 * Get a widget by name, throwing an error if it doesn't exist.
56 *
57 * @method get_widget
58 * @param {string} name - the widget name
59 */
60 NotificationArea.prototype.get_widget = function (name) {
61 if(this.widget_dict[name] === undefined) {
62 throw('no widgets with this name');
63 }
64 return this.widget_dict[name];
65 };
66
67 /**
68 * Create a new notification widget with the given name. The
69 * widget must not already exist.
70 *
71 * @method new_notification_widget
72 * @param {string} name - the widget name
73 */
74 NotificationArea.prototype.new_notification_widget = function (name) {
75 if (this.widget_dict[name] !== undefined) {
76 throw('widget with that name already exists!');
77 }
78
79 // create the element for the notification widget and add it
80 // to the notification aread element
81 var div = $('<div/>').attr('id', 'notification_' + name);
82 $(this.selector).append(div);
83
84 // create the widget object and return it
85 this.widget_dict[name] = new NotificationWidget('#notification_' + name);
86 return this.widget_dict[name];
87 };
88
89 /**
21 /**
90 * Initialize the default set of notification widgets.
22 * Initialize the default set of notification widgets.
91 *
23 *
92 * @method init_notification_widgets
24 * @method init_notification_widgets
93 */
25 */
94 NotificationArea.prototype.init_notification_widgets = function () {
26 NotebookNotificationArea.prototype.init_notification_widgets = function () {
95 this.init_kernel_notification_widget();
27 this.init_kernel_notification_widget();
96 this.init_notebook_notification_widget();
28 this.init_notebook_notification_widget();
97 };
29 };
98
30
99 /**
31 /**
100 * Initialize the notification widget for kernel status messages.
32 * Initialize the notification widget for kernel status messages.
101 *
33 *
102 * @method init_kernel_notification_widget
34 * @method init_kernel_notification_widget
103 */
35 */
104 NotificationArea.prototype.init_kernel_notification_widget = function () {
36 NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
105 var that = this;
37 var that = this;
106 var knw = this.new_notification_widget('kernel');
38 var knw = this.new_notification_widget('kernel');
107 var $kernel_ind_icon = $("#kernel_indicator_icon");
39 var $kernel_ind_icon = $("#kernel_indicator_icon");
108 var $modal_ind_icon = $("#modal_indicator_icon");
40 var $modal_ind_icon = $("#modal_indicator_icon");
109
41
110 // Command/Edit mode
42 // Command/Edit mode
111 this.events.on('edit_mode.Notebook', function () {
43 this.events.on('edit_mode.Notebook', function () {
112 that.save_widget.update_document_title();
44 that.save_widget.update_document_title();
113 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
45 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
114 });
46 });
115
47
116 this.events.on('command_mode.Notebook', function () {
48 this.events.on('command_mode.Notebook', function () {
117 that.save_widget.update_document_title();
49 that.save_widget.update_document_title();
118 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
50 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
119 });
51 });
120
52
121 // Implicitly start off in Command mode, switching to Edit mode will trigger event
53 // Implicitly start off in Command mode, switching to Edit mode will trigger event
122 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
54 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
123
55
124 // Kernel events
56 // Kernel events
125
57
126 // this can be either kernel_created.Kernel or kernel_created.Session
58 // this can be either kernel_created.Kernel or kernel_created.Session
127 this.events.on('kernel_created.Kernel kernel_created.Session', function () {
59 this.events.on('kernel_created.Kernel kernel_created.Session', function () {
128 knw.info("Kernel Created", 500);
60 knw.info("Kernel Created", 500);
129 });
61 });
130
62
131 this.events.on('kernel_reconnecting.Kernel', function () {
63 this.events.on('kernel_reconnecting.Kernel', function () {
132 knw.warning("Connecting to kernel");
64 knw.warning("Connecting to kernel");
133 });
65 });
134
66
135 this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
67 this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
136 knw.danger("Not Connected", undefined, function () {
68 knw.danger("Not Connected", undefined, function () {
137 // schedule reconnect a short time in the future, don't reconnect immediately
69 // schedule reconnect a short time in the future, don't reconnect immediately
138 setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
70 setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
139 }, {title: 'click to reconnect'});
71 }, {title: 'click to reconnect'});
140 });
72 });
141
73
142 this.events.on('kernel_connected.Kernel', function () {
74 this.events.on('kernel_connected.Kernel', function () {
143 knw.info("Connected", 500);
75 knw.info("Connected", 500);
144 });
76 });
145
77
146 this.events.on('kernel_restarting.Kernel', function () {
78 this.events.on('kernel_restarting.Kernel', function () {
147 that.save_widget.update_document_title();
79 that.save_widget.update_document_title();
148 knw.set_message("Restarting kernel", 2000);
80 knw.set_message("Restarting kernel", 2000);
149 });
81 });
150
82
151 this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
83 this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
152 // Only show the dialog on the first restart attempt. This
84 // Only show the dialog on the first restart attempt. This
153 // number gets tracked by the `Kernel` object and passed
85 // number gets tracked by the `Kernel` object and passed
154 // along here, because we don't want to show the user 5
86 // along here, because we don't want to show the user 5
155 // dialogs saying the same thing (which is the number of
87 // dialogs saying the same thing (which is the number of
156 // times it tries restarting).
88 // times it tries restarting).
157 if (info.attempt === 1) {
89 if (info.attempt === 1) {
158
90
159 dialog.kernel_modal({
91 dialog.kernel_modal({
160 notebook: that.notebook,
92 notebook: that.notebook,
161 keyboard_manager: that.keyboard_manager,
93 keyboard_manager: that.keyboard_manager,
162 title: "Kernel Restarting",
94 title: "Kernel Restarting",
163 body: "The kernel appears to have died. It will restart automatically.",
95 body: "The kernel appears to have died. It will restart automatically.",
164 buttons: {
96 buttons: {
165 OK : {
97 OK : {
166 class : "btn-primary"
98 class : "btn-primary"
167 }
99 }
168 }
100 }
169 });
101 });
170 };
102 };
171
103
172 that.save_widget.update_document_title();
104 that.save_widget.update_document_title();
173 knw.danger("Dead kernel");
105 knw.danger("Dead kernel");
174 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
106 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
175 });
107 });
176
108
177 this.events.on('kernel_interrupting.Kernel', function () {
109 this.events.on('kernel_interrupting.Kernel', function () {
178 knw.set_message("Interrupting kernel", 2000);
110 knw.set_message("Interrupting kernel", 2000);
179 });
111 });
180
112
181 this.events.on('kernel_disconnected.Kernel', function () {
113 this.events.on('kernel_disconnected.Kernel', function () {
182 $kernel_ind_icon
114 $kernel_ind_icon
183 .attr('class', 'kernel_disconnected_icon')
115 .attr('class', 'kernel_disconnected_icon')
184 .attr('title', 'No Connection to Kernel');
116 .attr('title', 'No Connection to Kernel');
185 });
117 });
186
118
187 this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
119 this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
188 // only show the dialog if this is the first failed
120 // only show the dialog if this is the first failed
189 // connect attempt, because the kernel will continue
121 // connect attempt, because the kernel will continue
190 // trying to reconnect and we don't want to spam the user
122 // trying to reconnect and we don't want to spam the user
191 // with messages
123 // with messages
192 if (info.attempt === 1) {
124 if (info.attempt === 1) {
193
125
194 var msg = "A connection to the notebook server could not be established." +
126 var msg = "A connection to the notebook server could not be established." +
195 " The notebook will continue trying to reconnect, but" +
127 " The notebook will continue trying to reconnect, but" +
196 " until it does, you will NOT be able to run code. Check your" +
128 " until it does, you will NOT be able to run code. Check your" +
197 " network connection or notebook server configuration.";
129 " network connection or notebook server configuration.";
198
130
199 dialog.kernel_modal({
131 dialog.kernel_modal({
200 title: "Connection failed",
132 title: "Connection failed",
201 body: msg,
133 body: msg,
202 keyboard_manager: that.keyboard_manager,
134 keyboard_manager: that.keyboard_manager,
203 notebook: that.notebook,
135 notebook: that.notebook,
204 buttons : {
136 buttons : {
205 "OK": {}
137 "OK": {}
206 }
138 }
207 });
139 });
208 }
140 }
209 });
141 });
210
142
211 this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
143 this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
212 that.save_widget.update_document_title();
144 that.save_widget.update_document_title();
213 knw.danger("Dead kernel");
145 knw.danger("Dead kernel");
214 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
146 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
215 });
147 });
216
148
217 this.events.on('kernel_dead.Kernel', function () {
149 this.events.on('kernel_dead.Kernel', function () {
218
150
219 var showMsg = function () {
151 var showMsg = function () {
220
152
221 var msg = 'The kernel has died, and the automatic restart has failed.' +
153 var msg = 'The kernel has died, and the automatic restart has failed.' +
222 ' It is possible the kernel cannot be restarted.' +
154 ' It is possible the kernel cannot be restarted.' +
223 ' If you are not able to restart the kernel, you will still be able to save' +
155 ' If you are not able to restart the kernel, you will still be able to save' +
224 ' the notebook, but running code will no longer work until the notebook' +
156 ' the notebook, but running code will no longer work until the notebook' +
225 ' is reopened.';
157 ' is reopened.';
226
158
227 dialog.kernel_modal({
159 dialog.kernel_modal({
228 title: "Dead kernel",
160 title: "Dead kernel",
229 body : msg,
161 body : msg,
230 keyboard_manager: that.keyboard_manager,
162 keyboard_manager: that.keyboard_manager,
231 notebook: that.notebook,
163 notebook: that.notebook,
232 buttons : {
164 buttons : {
233 "Manual Restart": {
165 "Manual Restart": {
234 class: "btn-danger",
166 class: "btn-danger",
235 click: function () {
167 click: function () {
236 that.notebook.start_session();
168 that.notebook.start_session();
237 }
169 }
238 },
170 },
239 "Don't restart": {}
171 "Don't restart": {}
240 }
172 }
241 });
173 });
242
174
243 return false;
175 return false;
244 };
176 };
245
177
246 that.save_widget.update_document_title();
178 that.save_widget.update_document_title();
247 knw.danger("Dead kernel", undefined, showMsg);
179 knw.danger("Dead kernel", undefined, showMsg);
248 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
180 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
249
181
250 showMsg();
182 showMsg();
251 });
183 });
252
184
253 this.events.on('kernel_dead.Session', function (evt, info) {
185 this.events.on('kernel_dead.Session', function (evt, info) {
254 var full = info.xhr.responseJSON.message;
186 var full = info.xhr.responseJSON.message;
255 var short = info.xhr.responseJSON.short_message || 'Kernel error';
187 var short = info.xhr.responseJSON.short_message || 'Kernel error';
256 var traceback = info.xhr.responseJSON.traceback;
188 var traceback = info.xhr.responseJSON.traceback;
257
189
258 var showMsg = function () {
190 var showMsg = function () {
259 var msg = $('<div/>').append($('<p/>').text(full));
191 var msg = $('<div/>').append($('<p/>').text(full));
260 var cm, cm_elem, cm_open;
192 var cm, cm_elem, cm_open;
261
193
262 if (traceback) {
194 if (traceback) {
263 cm_elem = $('<div/>')
195 cm_elem = $('<div/>')
264 .css('margin-top', '1em')
196 .css('margin-top', '1em')
265 .css('padding', '1em')
197 .css('padding', '1em')
266 .addClass('output_scroll');
198 .addClass('output_scroll');
267 msg.append(cm_elem);
199 msg.append(cm_elem);
268 cm = CodeMirror(cm_elem.get(0), {
200 cm = CodeMirror(cm_elem.get(0), {
269 mode: "python",
201 mode: "python",
270 readOnly : true
202 readOnly : true
271 });
203 });
272 cm.setValue(traceback);
204 cm.setValue(traceback);
273 cm_open = $.proxy(cm.refresh, cm);
205 cm_open = $.proxy(cm.refresh, cm);
274 }
206 }
275
207
276 dialog.kernel_modal({
208 dialog.kernel_modal({
277 title: "Failed to start the kernel",
209 title: "Failed to start the kernel",
278 body : msg,
210 body : msg,
279 keyboard_manager: that.keyboard_manager,
211 keyboard_manager: that.keyboard_manager,
280 notebook: that.notebook,
212 notebook: that.notebook,
281 open: cm_open,
213 open: cm_open,
282 buttons : {
214 buttons : {
283 "Ok": { class: 'btn-primary' }
215 "Ok": { class: 'btn-primary' }
284 }
216 }
285 });
217 });
286
218
287 return false;
219 return false;
288 };
220 };
289
221
290 that.save_widget.update_document_title();
222 that.save_widget.update_document_title();
291 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
223 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
292 knw.danger(short, undefined, showMsg);
224 knw.danger(short, undefined, showMsg);
293 });
225 });
294
226
295 this.events.on('kernel_starting.Kernel', function () {
227 this.events.on('kernel_starting.Kernel', function () {
296 window.document.title='(Starting) '+window.document.title;
228 window.document.title='(Starting) '+window.document.title;
297 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
229 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
298 knw.set_message("Kernel starting, please wait...");
230 knw.set_message("Kernel starting, please wait...");
299 });
231 });
300
232
301 this.events.on('kernel_ready.Kernel', function () {
233 this.events.on('kernel_ready.Kernel', function () {
302 that.save_widget.update_document_title();
234 that.save_widget.update_document_title();
303 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
235 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
304 knw.info("Kernel ready", 500);
236 knw.info("Kernel ready", 500);
305 });
237 });
306
238
307 this.events.on('kernel_idle.Kernel', function () {
239 this.events.on('kernel_idle.Kernel', function () {
308 that.save_widget.update_document_title();
240 that.save_widget.update_document_title();
309 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
241 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
310 });
242 });
311
243
312 this.events.on('kernel_busy.Kernel', function () {
244 this.events.on('kernel_busy.Kernel', function () {
313 window.document.title='(Busy) '+window.document.title;
245 window.document.title='(Busy) '+window.document.title;
314 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
246 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
315 });
247 });
316
248
317 // Start the kernel indicator in the busy state, and send a kernel_info request.
249 // Start the kernel indicator in the busy state, and send a kernel_info request.
318 // When the kernel_info reply arrives, the kernel is idle.
250 // When the kernel_info reply arrives, the kernel is idle.
319 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
251 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
320 };
252 };
321
253
322 /**
254 /**
323 * Initialize the notification widget for notebook status messages.
255 * Initialize the notification widget for notebook status messages.
324 *
256 *
325 * @method init_notebook_notification_widget
257 * @method init_notebook_notification_widget
326 */
258 */
327 NotificationArea.prototype.init_notebook_notification_widget = function () {
259 NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
328 var nnw = this.new_notification_widget('notebook');
260 var nnw = this.new_notification_widget('notebook');
329
261
330 // Notebook events
262 // Notebook events
331 this.events.on('notebook_loading.Notebook', function () {
263 this.events.on('notebook_loading.Notebook', function () {
332 nnw.set_message("Loading notebook",500);
264 nnw.set_message("Loading notebook",500);
333 });
265 });
334 this.events.on('notebook_loaded.Notebook', function () {
266 this.events.on('notebook_loaded.Notebook', function () {
335 nnw.set_message("Notebook loaded",500);
267 nnw.set_message("Notebook loaded",500);
336 });
268 });
337 this.events.on('notebook_saving.Notebook', function () {
269 this.events.on('notebook_saving.Notebook', function () {
338 nnw.set_message("Saving notebook",500);
270 nnw.set_message("Saving notebook",500);
339 });
271 });
340 this.events.on('notebook_saved.Notebook', function () {
272 this.events.on('notebook_saved.Notebook', function () {
341 nnw.set_message("Notebook saved",2000);
273 nnw.set_message("Notebook saved",2000);
342 });
274 });
343 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
275 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
344 nnw.warning(error.message || "Notebook save failed");
276 nnw.warning(error.message || "Notebook save failed");
345 });
277 });
346 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
278 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
347 nnw.warning(error.message || "Notebook copy failed");
279 nnw.warning(error.message || "Notebook copy failed");
348 });
280 });
349
281
350 // Checkpoint events
282 // Checkpoint events
351 this.events.on('checkpoint_created.Notebook', function (evt, data) {
283 this.events.on('checkpoint_created.Notebook', function (evt, data) {
352 var msg = "Checkpoint created";
284 var msg = "Checkpoint created";
353 if (data.last_modified) {
285 if (data.last_modified) {
354 var d = new Date(data.last_modified);
286 var d = new Date(data.last_modified);
355 msg = msg + ": " + moment(d).format("HH:mm:ss");
287 msg = msg + ": " + moment(d).format("HH:mm:ss");
356 }
288 }
357 nnw.set_message(msg, 2000);
289 nnw.set_message(msg, 2000);
358 });
290 });
359 this.events.on('checkpoint_failed.Notebook', function () {
291 this.events.on('checkpoint_failed.Notebook', function () {
360 nnw.warning("Checkpoint failed");
292 nnw.warning("Checkpoint failed");
361 });
293 });
362 this.events.on('checkpoint_deleted.Notebook', function () {
294 this.events.on('checkpoint_deleted.Notebook', function () {
363 nnw.set_message("Checkpoint deleted", 500);
295 nnw.set_message("Checkpoint deleted", 500);
364 });
296 });
365 this.events.on('checkpoint_delete_failed.Notebook', function () {
297 this.events.on('checkpoint_delete_failed.Notebook', function () {
366 nnw.warning("Checkpoint delete failed");
298 nnw.warning("Checkpoint delete failed");
367 });
299 });
368 this.events.on('checkpoint_restoring.Notebook', function () {
300 this.events.on('checkpoint_restoring.Notebook', function () {
369 nnw.set_message("Restoring to checkpoint...", 500);
301 nnw.set_message("Restoring to checkpoint...", 500);
370 });
302 });
371 this.events.on('checkpoint_restore_failed.Notebook', function () {
303 this.events.on('checkpoint_restore_failed.Notebook', function () {
372 nnw.warning("Checkpoint restore failed");
304 nnw.warning("Checkpoint restore failed");
373 });
305 });
374
306
375 // Autosave events
307 // Autosave events
376 this.events.on('autosave_disabled.Notebook', function () {
308 this.events.on('autosave_disabled.Notebook', function () {
377 nnw.set_message("Autosave disabled", 2000);
309 nnw.set_message("Autosave disabled", 2000);
378 });
310 });
379 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
311 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
380 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
312 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
381 });
313 });
382 };
314 };
383
315
384 IPython.NotificationArea = NotificationArea;
316 // Backwards compatibility.
385
317 IPython.NotificationArea = NotebookNotificationArea;
386 return {'NotificationArea': NotificationArea};
318
319 return {'NotebookNotificationArea': NotebookNotificationArea};
387 });
320 });
General Comments 0
You need to be logged in to leave comments. Login now