##// END OF EJS Templates
restore websocket_url configurable...
MinRK -
Show More
@@ -1,422 +1,427 b''
1 """Base Tornado handlers for the notebook."""
1 """Base Tornado handlers for the notebook."""
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 functools
6 import functools
7 import json
7 import json
8 import logging
8 import logging
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12 import traceback
12 import traceback
13 try:
13 try:
14 # py3
14 # py3
15 from http.client import responses
15 from http.client import responses
16 except ImportError:
16 except ImportError:
17 from httplib import responses
17 from httplib import responses
18
18
19 from jinja2 import TemplateNotFound
19 from jinja2 import TemplateNotFound
20 from tornado import web
20 from tornado import web
21
21
22 try:
22 try:
23 from tornado.log import app_log
23 from tornado.log import app_log
24 except ImportError:
24 except ImportError:
25 app_log = logging.getLogger()
25 app_log = logging.getLogger()
26
26
27 from IPython.config import Application
27 from IPython.config import Application
28 from IPython.utils.path import filefind
28 from IPython.utils.path import filefind
29 from IPython.utils.py3compat import string_types
29 from IPython.utils.py3compat import string_types
30 from IPython.html.utils import is_hidden
30 from IPython.html.utils import is_hidden
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Top-level handlers
33 # Top-level handlers
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 non_alphanum = re.compile(r'[^A-Za-z0-9]')
35 non_alphanum = re.compile(r'[^A-Za-z0-9]')
36
36
37 class AuthenticatedHandler(web.RequestHandler):
37 class AuthenticatedHandler(web.RequestHandler):
38 """A RequestHandler with an authenticated user."""
38 """A RequestHandler with an authenticated user."""
39
39
40 def set_default_headers(self):
40 def set_default_headers(self):
41 headers = self.settings.get('headers', {})
41 headers = self.settings.get('headers', {})
42
42
43 if "X-Frame-Options" not in headers:
43 if "X-Frame-Options" not in headers:
44 headers["X-Frame-Options"] = "SAMEORIGIN"
44 headers["X-Frame-Options"] = "SAMEORIGIN"
45
45
46 for header_name,value in headers.items() :
46 for header_name,value in headers.items() :
47 try:
47 try:
48 self.set_header(header_name, value)
48 self.set_header(header_name, value)
49 except Exception:
49 except Exception:
50 # tornado raise Exception (not a subclass)
50 # tornado raise Exception (not a subclass)
51 # if method is unsupported (websocket and Access-Control-Allow-Origin
51 # if method is unsupported (websocket and Access-Control-Allow-Origin
52 # for example, so just ignore)
52 # for example, so just ignore)
53 pass
53 pass
54
54
55 def clear_login_cookie(self):
55 def clear_login_cookie(self):
56 self.clear_cookie(self.cookie_name)
56 self.clear_cookie(self.cookie_name)
57
57
58 def get_current_user(self):
58 def get_current_user(self):
59 user_id = self.get_secure_cookie(self.cookie_name)
59 user_id = self.get_secure_cookie(self.cookie_name)
60 # For now the user_id should not return empty, but it could eventually
60 # For now the user_id should not return empty, but it could eventually
61 if user_id == '':
61 if user_id == '':
62 user_id = 'anonymous'
62 user_id = 'anonymous'
63 if user_id is None:
63 if user_id is None:
64 # prevent extra Invalid cookie sig warnings:
64 # prevent extra Invalid cookie sig warnings:
65 self.clear_login_cookie()
65 self.clear_login_cookie()
66 if not self.login_available:
66 if not self.login_available:
67 user_id = 'anonymous'
67 user_id = 'anonymous'
68 return user_id
68 return user_id
69
69
70 @property
70 @property
71 def cookie_name(self):
71 def cookie_name(self):
72 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
72 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
73 self.request.host
73 self.request.host
74 ))
74 ))
75 return self.settings.get('cookie_name', default_cookie_name)
75 return self.settings.get('cookie_name', default_cookie_name)
76
76
77 @property
77 @property
78 def password(self):
78 def password(self):
79 """our password"""
79 """our password"""
80 return self.settings.get('password', '')
80 return self.settings.get('password', '')
81
81
82 @property
82 @property
83 def logged_in(self):
83 def logged_in(self):
84 """Is a user currently logged in?
84 """Is a user currently logged in?
85
85
86 """
86 """
87 user = self.get_current_user()
87 user = self.get_current_user()
88 return (user and not user == 'anonymous')
88 return (user and not user == 'anonymous')
89
89
90 @property
90 @property
91 def login_available(self):
91 def login_available(self):
92 """May a user proceed to log in?
92 """May a user proceed to log in?
93
93
94 This returns True if login capability is available, irrespective of
94 This returns True if login capability is available, irrespective of
95 whether the user is already logged in or not.
95 whether the user is already logged in or not.
96
96
97 """
97 """
98 return bool(self.settings.get('password', ''))
98 return bool(self.settings.get('password', ''))
99
99
100
100
101 class IPythonHandler(AuthenticatedHandler):
101 class IPythonHandler(AuthenticatedHandler):
102 """IPython-specific extensions to authenticated handling
102 """IPython-specific extensions to authenticated handling
103
103
104 Mostly property shortcuts to IPython-specific settings.
104 Mostly property shortcuts to IPython-specific settings.
105 """
105 """
106
106
107 @property
107 @property
108 def config(self):
108 def config(self):
109 return self.settings.get('config', None)
109 return self.settings.get('config', None)
110
110
111 @property
111 @property
112 def log(self):
112 def log(self):
113 """use the IPython log by default, falling back on tornado's logger"""
113 """use the IPython log by default, falling back on tornado's logger"""
114 if Application.initialized():
114 if Application.initialized():
115 return Application.instance().log
115 return Application.instance().log
116 else:
116 else:
117 return app_log
117 return app_log
118
118
119 #---------------------------------------------------------------
119 #---------------------------------------------------------------
120 # URLs
120 # URLs
121 #---------------------------------------------------------------
121 #---------------------------------------------------------------
122
122
123 @property
123 @property
124 def mathjax_url(self):
124 def mathjax_url(self):
125 return self.settings.get('mathjax_url', '')
125 return self.settings.get('mathjax_url', '')
126
126
127 @property
127 @property
128 def base_url(self):
128 def base_url(self):
129 return self.settings.get('base_url', '/')
129 return self.settings.get('base_url', '/')
130
131 @property
132 def ws_url(self):
133 return self.settings.get('websocket_url', '')
130
134
131 #---------------------------------------------------------------
135 #---------------------------------------------------------------
132 # Manager objects
136 # Manager objects
133 #---------------------------------------------------------------
137 #---------------------------------------------------------------
134
138
135 @property
139 @property
136 def kernel_manager(self):
140 def kernel_manager(self):
137 return self.settings['kernel_manager']
141 return self.settings['kernel_manager']
138
142
139 @property
143 @property
140 def notebook_manager(self):
144 def notebook_manager(self):
141 return self.settings['notebook_manager']
145 return self.settings['notebook_manager']
142
146
143 @property
147 @property
144 def cluster_manager(self):
148 def cluster_manager(self):
145 return self.settings['cluster_manager']
149 return self.settings['cluster_manager']
146
150
147 @property
151 @property
148 def session_manager(self):
152 def session_manager(self):
149 return self.settings['session_manager']
153 return self.settings['session_manager']
150
154
151 @property
155 @property
152 def kernel_spec_manager(self):
156 def kernel_spec_manager(self):
153 return self.settings['kernel_spec_manager']
157 return self.settings['kernel_spec_manager']
154
158
155 @property
159 @property
156 def project_dir(self):
160 def project_dir(self):
157 return self.notebook_manager.notebook_dir
161 return self.notebook_manager.notebook_dir
158
162
159 #---------------------------------------------------------------
163 #---------------------------------------------------------------
160 # CORS
164 # CORS
161 #---------------------------------------------------------------
165 #---------------------------------------------------------------
162
166
163 @property
167 @property
164 def allow_origin(self):
168 def allow_origin(self):
165 """Normal Access-Control-Allow-Origin"""
169 """Normal Access-Control-Allow-Origin"""
166 return self.settings.get('allow_origin', '')
170 return self.settings.get('allow_origin', '')
167
171
168 @property
172 @property
169 def allow_origin_pat(self):
173 def allow_origin_pat(self):
170 """Regular expression version of allow_origin"""
174 """Regular expression version of allow_origin"""
171 return self.settings.get('allow_origin_pat', None)
175 return self.settings.get('allow_origin_pat', None)
172
176
173 @property
177 @property
174 def allow_credentials(self):
178 def allow_credentials(self):
175 """Whether to set Access-Control-Allow-Credentials"""
179 """Whether to set Access-Control-Allow-Credentials"""
176 return self.settings.get('allow_credentials', False)
180 return self.settings.get('allow_credentials', False)
177
181
178 def set_default_headers(self):
182 def set_default_headers(self):
179 """Add CORS headers, if defined"""
183 """Add CORS headers, if defined"""
180 super(IPythonHandler, self).set_default_headers()
184 super(IPythonHandler, self).set_default_headers()
181 if self.allow_origin:
185 if self.allow_origin:
182 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
186 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
183 elif self.allow_origin_pat:
187 elif self.allow_origin_pat:
184 origin = self.get_origin()
188 origin = self.get_origin()
185 if origin and self.allow_origin_pat.match(origin):
189 if origin and self.allow_origin_pat.match(origin):
186 self.set_header("Access-Control-Allow-Origin", origin)
190 self.set_header("Access-Control-Allow-Origin", origin)
187 if self.allow_credentials:
191 if self.allow_credentials:
188 self.set_header("Access-Control-Allow-Credentials", 'true')
192 self.set_header("Access-Control-Allow-Credentials", 'true')
189
193
190 def get_origin(self):
194 def get_origin(self):
191 # Handle WebSocket Origin naming convention differences
195 # Handle WebSocket Origin naming convention differences
192 # The difference between version 8 and 13 is that in 8 the
196 # The difference between version 8 and 13 is that in 8 the
193 # client sends a "Sec-Websocket-Origin" header and in 13 it's
197 # client sends a "Sec-Websocket-Origin" header and in 13 it's
194 # simply "Origin".
198 # simply "Origin".
195 if "Origin" in self.request.headers:
199 if "Origin" in self.request.headers:
196 origin = self.request.headers.get("Origin")
200 origin = self.request.headers.get("Origin")
197 else:
201 else:
198 origin = self.request.headers.get("Sec-Websocket-Origin", None)
202 origin = self.request.headers.get("Sec-Websocket-Origin", None)
199 return origin
203 return origin
200
204
201 #---------------------------------------------------------------
205 #---------------------------------------------------------------
202 # template rendering
206 # template rendering
203 #---------------------------------------------------------------
207 #---------------------------------------------------------------
204
208
205 def get_template(self, name):
209 def get_template(self, name):
206 """Return the jinja template object for a given name"""
210 """Return the jinja template object for a given name"""
207 return self.settings['jinja2_env'].get_template(name)
211 return self.settings['jinja2_env'].get_template(name)
208
212
209 def render_template(self, name, **ns):
213 def render_template(self, name, **ns):
210 ns.update(self.template_namespace)
214 ns.update(self.template_namespace)
211 template = self.get_template(name)
215 template = self.get_template(name)
212 return template.render(**ns)
216 return template.render(**ns)
213
217
214 @property
218 @property
215 def template_namespace(self):
219 def template_namespace(self):
216 return dict(
220 return dict(
217 base_url=self.base_url,
221 base_url=self.base_url,
222 ws_url=self.ws_url,
218 logged_in=self.logged_in,
223 logged_in=self.logged_in,
219 login_available=self.login_available,
224 login_available=self.login_available,
220 static_url=self.static_url,
225 static_url=self.static_url,
221 )
226 )
222
227
223 def get_json_body(self):
228 def get_json_body(self):
224 """Return the body of the request as JSON data."""
229 """Return the body of the request as JSON data."""
225 if not self.request.body:
230 if not self.request.body:
226 return None
231 return None
227 # Do we need to call body.decode('utf-8') here?
232 # Do we need to call body.decode('utf-8') here?
228 body = self.request.body.strip().decode(u'utf-8')
233 body = self.request.body.strip().decode(u'utf-8')
229 try:
234 try:
230 model = json.loads(body)
235 model = json.loads(body)
231 except Exception:
236 except Exception:
232 self.log.debug("Bad JSON: %r", body)
237 self.log.debug("Bad JSON: %r", body)
233 self.log.error("Couldn't parse JSON", exc_info=True)
238 self.log.error("Couldn't parse JSON", exc_info=True)
234 raise web.HTTPError(400, u'Invalid JSON in body of request')
239 raise web.HTTPError(400, u'Invalid JSON in body of request')
235 return model
240 return model
236
241
237 def get_error_html(self, status_code, **kwargs):
242 def get_error_html(self, status_code, **kwargs):
238 """render custom error pages"""
243 """render custom error pages"""
239 exception = kwargs.get('exception')
244 exception = kwargs.get('exception')
240 message = ''
245 message = ''
241 status_message = responses.get(status_code, 'Unknown HTTP Error')
246 status_message = responses.get(status_code, 'Unknown HTTP Error')
242 if exception:
247 if exception:
243 # get the custom message, if defined
248 # get the custom message, if defined
244 try:
249 try:
245 message = exception.log_message % exception.args
250 message = exception.log_message % exception.args
246 except Exception:
251 except Exception:
247 pass
252 pass
248
253
249 # construct the custom reason, if defined
254 # construct the custom reason, if defined
250 reason = getattr(exception, 'reason', '')
255 reason = getattr(exception, 'reason', '')
251 if reason:
256 if reason:
252 status_message = reason
257 status_message = reason
253
258
254 # build template namespace
259 # build template namespace
255 ns = dict(
260 ns = dict(
256 status_code=status_code,
261 status_code=status_code,
257 status_message=status_message,
262 status_message=status_message,
258 message=message,
263 message=message,
259 exception=exception,
264 exception=exception,
260 )
265 )
261
266
262 # render the template
267 # render the template
263 try:
268 try:
264 html = self.render_template('%s.html' % status_code, **ns)
269 html = self.render_template('%s.html' % status_code, **ns)
265 except TemplateNotFound:
270 except TemplateNotFound:
266 self.log.debug("No template for %d", status_code)
271 self.log.debug("No template for %d", status_code)
267 html = self.render_template('error.html', **ns)
272 html = self.render_template('error.html', **ns)
268 return html
273 return html
269
274
270
275
271 class Template404(IPythonHandler):
276 class Template404(IPythonHandler):
272 """Render our 404 template"""
277 """Render our 404 template"""
273 def prepare(self):
278 def prepare(self):
274 raise web.HTTPError(404)
279 raise web.HTTPError(404)
275
280
276
281
277 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
282 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
278 """static files should only be accessible when logged in"""
283 """static files should only be accessible when logged in"""
279
284
280 @web.authenticated
285 @web.authenticated
281 def get(self, path):
286 def get(self, path):
282 if os.path.splitext(path)[1] == '.ipynb':
287 if os.path.splitext(path)[1] == '.ipynb':
283 name = os.path.basename(path)
288 name = os.path.basename(path)
284 self.set_header('Content-Type', 'application/json')
289 self.set_header('Content-Type', 'application/json')
285 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
290 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
286
291
287 return web.StaticFileHandler.get(self, path)
292 return web.StaticFileHandler.get(self, path)
288
293
289 def compute_etag(self):
294 def compute_etag(self):
290 return None
295 return None
291
296
292 def validate_absolute_path(self, root, absolute_path):
297 def validate_absolute_path(self, root, absolute_path):
293 """Validate and return the absolute path.
298 """Validate and return the absolute path.
294
299
295 Requires tornado 3.1
300 Requires tornado 3.1
296
301
297 Adding to tornado's own handling, forbids the serving of hidden files.
302 Adding to tornado's own handling, forbids the serving of hidden files.
298 """
303 """
299 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
304 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
300 abs_root = os.path.abspath(root)
305 abs_root = os.path.abspath(root)
301 if is_hidden(abs_path, abs_root):
306 if is_hidden(abs_path, abs_root):
302 self.log.info("Refusing to serve hidden file, via 404 Error")
307 self.log.info("Refusing to serve hidden file, via 404 Error")
303 raise web.HTTPError(404)
308 raise web.HTTPError(404)
304 return abs_path
309 return abs_path
305
310
306
311
307 def json_errors(method):
312 def json_errors(method):
308 """Decorate methods with this to return GitHub style JSON errors.
313 """Decorate methods with this to return GitHub style JSON errors.
309
314
310 This should be used on any JSON API on any handler method that can raise HTTPErrors.
315 This should be used on any JSON API on any handler method that can raise HTTPErrors.
311
316
312 This will grab the latest HTTPError exception using sys.exc_info
317 This will grab the latest HTTPError exception using sys.exc_info
313 and then:
318 and then:
314
319
315 1. Set the HTTP status code based on the HTTPError
320 1. Set the HTTP status code based on the HTTPError
316 2. Create and return a JSON body with a message field describing
321 2. Create and return a JSON body with a message field describing
317 the error in a human readable form.
322 the error in a human readable form.
318 """
323 """
319 @functools.wraps(method)
324 @functools.wraps(method)
320 def wrapper(self, *args, **kwargs):
325 def wrapper(self, *args, **kwargs):
321 try:
326 try:
322 result = method(self, *args, **kwargs)
327 result = method(self, *args, **kwargs)
323 except web.HTTPError as e:
328 except web.HTTPError as e:
324 status = e.status_code
329 status = e.status_code
325 message = e.log_message
330 message = e.log_message
326 self.log.warn(message)
331 self.log.warn(message)
327 self.set_status(e.status_code)
332 self.set_status(e.status_code)
328 self.finish(json.dumps(dict(message=message)))
333 self.finish(json.dumps(dict(message=message)))
329 except Exception:
334 except Exception:
330 self.log.error("Unhandled error in API request", exc_info=True)
335 self.log.error("Unhandled error in API request", exc_info=True)
331 status = 500
336 status = 500
332 message = "Unknown server error"
337 message = "Unknown server error"
333 t, value, tb = sys.exc_info()
338 t, value, tb = sys.exc_info()
334 self.set_status(status)
339 self.set_status(status)
335 tb_text = ''.join(traceback.format_exception(t, value, tb))
340 tb_text = ''.join(traceback.format_exception(t, value, tb))
336 reply = dict(message=message, traceback=tb_text)
341 reply = dict(message=message, traceback=tb_text)
337 self.finish(json.dumps(reply))
342 self.finish(json.dumps(reply))
338 else:
343 else:
339 return result
344 return result
340 return wrapper
345 return wrapper
341
346
342
347
343
348
344 #-----------------------------------------------------------------------------
349 #-----------------------------------------------------------------------------
345 # File handler
350 # File handler
346 #-----------------------------------------------------------------------------
351 #-----------------------------------------------------------------------------
347
352
348 # to minimize subclass changes:
353 # to minimize subclass changes:
349 HTTPError = web.HTTPError
354 HTTPError = web.HTTPError
350
355
351 class FileFindHandler(web.StaticFileHandler):
356 class FileFindHandler(web.StaticFileHandler):
352 """subclass of StaticFileHandler for serving files from a search path"""
357 """subclass of StaticFileHandler for serving files from a search path"""
353
358
354 # cache search results, don't search for files more than once
359 # cache search results, don't search for files more than once
355 _static_paths = {}
360 _static_paths = {}
356
361
357 def initialize(self, path, default_filename=None):
362 def initialize(self, path, default_filename=None):
358 if isinstance(path, string_types):
363 if isinstance(path, string_types):
359 path = [path]
364 path = [path]
360
365
361 self.root = tuple(
366 self.root = tuple(
362 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
367 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
363 )
368 )
364 self.default_filename = default_filename
369 self.default_filename = default_filename
365
370
366 def compute_etag(self):
371 def compute_etag(self):
367 return None
372 return None
368
373
369 @classmethod
374 @classmethod
370 def get_absolute_path(cls, roots, path):
375 def get_absolute_path(cls, roots, path):
371 """locate a file to serve on our static file search path"""
376 """locate a file to serve on our static file search path"""
372 with cls._lock:
377 with cls._lock:
373 if path in cls._static_paths:
378 if path in cls._static_paths:
374 return cls._static_paths[path]
379 return cls._static_paths[path]
375 try:
380 try:
376 abspath = os.path.abspath(filefind(path, roots))
381 abspath = os.path.abspath(filefind(path, roots))
377 except IOError:
382 except IOError:
378 # IOError means not found
383 # IOError means not found
379 return ''
384 return ''
380
385
381 cls._static_paths[path] = abspath
386 cls._static_paths[path] = abspath
382 return abspath
387 return abspath
383
388
384 def validate_absolute_path(self, root, absolute_path):
389 def validate_absolute_path(self, root, absolute_path):
385 """check if the file should be served (raises 404, 403, etc.)"""
390 """check if the file should be served (raises 404, 403, etc.)"""
386 if absolute_path == '':
391 if absolute_path == '':
387 raise web.HTTPError(404)
392 raise web.HTTPError(404)
388
393
389 for root in self.root:
394 for root in self.root:
390 if (absolute_path + os.sep).startswith(root):
395 if (absolute_path + os.sep).startswith(root):
391 break
396 break
392
397
393 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
398 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
394
399
395
400
396 class TrailingSlashHandler(web.RequestHandler):
401 class TrailingSlashHandler(web.RequestHandler):
397 """Simple redirect handler that strips trailing slashes
402 """Simple redirect handler that strips trailing slashes
398
403
399 This should be the first, highest priority handler.
404 This should be the first, highest priority handler.
400 """
405 """
401
406
402 SUPPORTED_METHODS = ['GET']
407 SUPPORTED_METHODS = ['GET']
403
408
404 def get(self):
409 def get(self):
405 self.redirect(self.request.uri.rstrip('/'))
410 self.redirect(self.request.uri.rstrip('/'))
406
411
407 #-----------------------------------------------------------------------------
412 #-----------------------------------------------------------------------------
408 # URL pattern fragments for re-use
413 # URL pattern fragments for re-use
409 #-----------------------------------------------------------------------------
414 #-----------------------------------------------------------------------------
410
415
411 path_regex = r"(?P<path>(?:/.*)*)"
416 path_regex = r"(?P<path>(?:/.*)*)"
412 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
417 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
413 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
418 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
414
419
415 #-----------------------------------------------------------------------------
420 #-----------------------------------------------------------------------------
416 # URL to handler mappings
421 # URL to handler mappings
417 #-----------------------------------------------------------------------------
422 #-----------------------------------------------------------------------------
418
423
419
424
420 default_handlers = [
425 default_handlers = [
421 (r".*/", TrailingSlashHandler)
426 (r".*/", TrailingSlashHandler)
422 ]
427 ]
@@ -1,935 +1,943 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 >= 3.1.0"
38 msg = "The IPython Notebook requires tornado >= 3.1.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 < (3,1,0):
47 if version_info < (3,1,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
52 from tornado.log import LogFormatter
53
53
54 from IPython.html import DEFAULT_STATIC_FILES_PATH
54 from IPython.html import DEFAULT_STATIC_FILES_PATH
55 from .base.handlers import Template404
55 from .base.handlers import Template404
56 from .log import log_request
56 from .log import log_request
57 from .services.kernels.kernelmanager import MappingKernelManager
57 from .services.kernels.kernelmanager import MappingKernelManager
58 from .services.notebooks.nbmanager import NotebookManager
58 from .services.notebooks.nbmanager import NotebookManager
59 from .services.notebooks.filenbmanager import FileNotebookManager
59 from .services.notebooks.filenbmanager import FileNotebookManager
60 from .services.clusters.clustermanager import ClusterManager
60 from .services.clusters.clustermanager import ClusterManager
61 from .services.sessions.sessionmanager import SessionManager
61 from .services.sessions.sessionmanager import SessionManager
62
62
63 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
63 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
64
64
65 from IPython.config import Config
65 from IPython.config import Config
66 from IPython.config.application import catch_config_error, boolean_flag
66 from IPython.config.application import catch_config_error, boolean_flag
67 from IPython.core.application import (
67 from IPython.core.application import (
68 BaseIPythonApplication, base_flags, base_aliases,
68 BaseIPythonApplication, base_flags, base_aliases,
69 )
69 )
70 from IPython.core.profiledir import ProfileDir
70 from IPython.core.profiledir import ProfileDir
71 from IPython.kernel import KernelManager
71 from IPython.kernel import KernelManager
72 from IPython.kernel.kernelspec import KernelSpecManager
72 from IPython.kernel.kernelspec import KernelSpecManager
73 from IPython.kernel.zmq.session import default_secure, Session
73 from IPython.kernel.zmq.session import default_secure, Session
74 from IPython.nbformat.sign import NotebookNotary
74 from IPython.nbformat.sign import NotebookNotary
75 from IPython.utils.importstring import import_item
75 from IPython.utils.importstring import import_item
76 from IPython.utils import submodule
76 from IPython.utils import submodule
77 from IPython.utils.process import check_pid
77 from IPython.utils.process import check_pid
78 from IPython.utils.traitlets import (
78 from IPython.utils.traitlets import (
79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
80 DottedObjectName, TraitError,
80 DottedObjectName, TraitError,
81 )
81 )
82 from IPython.utils import py3compat
82 from IPython.utils import py3compat
83 from IPython.utils.path import filefind, get_ipython_dir
83 from IPython.utils.path import filefind, get_ipython_dir
84
84
85 from .utils import url_path_join
85 from .utils import url_path_join
86
86
87 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
88 # Module globals
88 # Module globals
89 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
90
90
91 _examples = """
91 _examples = """
92 ipython notebook # start the notebook
92 ipython notebook # start the notebook
93 ipython notebook --profile=sympy # use the sympy profile
93 ipython notebook --profile=sympy # use the sympy profile
94 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
94 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
95 """
95 """
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Helper functions
98 # Helper functions
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 def random_ports(port, n):
101 def random_ports(port, n):
102 """Generate a list of n random ports near the given port.
102 """Generate a list of n random ports near the given port.
103
103
104 The first 5 ports will be sequential, and the remaining n-5 will be
104 The first 5 ports will be sequential, and the remaining n-5 will be
105 randomly selected in the range [port-2*n, port+2*n].
105 randomly selected in the range [port-2*n, port+2*n].
106 """
106 """
107 for i in range(min(5, n)):
107 for i in range(min(5, n)):
108 yield port + i
108 yield port + i
109 for i in range(n-5):
109 for i in range(n-5):
110 yield max(1, port + random.randint(-2*n, 2*n))
110 yield max(1, port + random.randint(-2*n, 2*n))
111
111
112 def load_handlers(name):
112 def load_handlers(name):
113 """Load the (URL pattern, handler) tuples for each component."""
113 """Load the (URL pattern, handler) tuples for each component."""
114 name = 'IPython.html.' + name
114 name = 'IPython.html.' + name
115 mod = __import__(name, fromlist=['default_handlers'])
115 mod = __import__(name, fromlist=['default_handlers'])
116 return mod.default_handlers
116 return mod.default_handlers
117
117
118 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
119 # The Tornado web application
119 # The Tornado web application
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121
121
122 class NotebookWebApplication(web.Application):
122 class NotebookWebApplication(web.Application):
123
123
124 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 def __init__(self, ipython_app, kernel_manager, notebook_manager,
125 cluster_manager, session_manager, kernel_spec_manager, log,
125 cluster_manager, session_manager, kernel_spec_manager, log,
126 base_url, settings_overrides, jinja_env_options):
126 base_url, settings_overrides, jinja_env_options):
127
127
128 settings = self.init_settings(
128 settings = self.init_settings(
129 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, notebook_manager, cluster_manager,
130 session_manager, kernel_spec_manager, log, base_url,
130 session_manager, kernel_spec_manager, log, base_url,
131 settings_overrides, jinja_env_options)
131 settings_overrides, jinja_env_options)
132 handlers = self.init_handlers(settings)
132 handlers = self.init_handlers(settings)
133
133
134 super(NotebookWebApplication, self).__init__(handlers, **settings)
134 super(NotebookWebApplication, self).__init__(handlers, **settings)
135
135
136 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
137 cluster_manager, session_manager, kernel_spec_manager,
137 cluster_manager, session_manager, kernel_spec_manager,
138 log, base_url, settings_overrides,
138 log, base_url, settings_overrides,
139 jinja_env_options=None):
139 jinja_env_options=None):
140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
141 # base_url will always be unicode, which will in turn
141 # base_url will always be unicode, which will in turn
142 # make the patterns unicode, and ultimately result in unicode
142 # make the patterns unicode, and ultimately result in unicode
143 # keys in kwargs to handler._execute(**kwargs) in tornado.
143 # keys in kwargs to handler._execute(**kwargs) in tornado.
144 # This enforces that base_url be ascii in that situation.
144 # This enforces that base_url be ascii in that situation.
145 #
145 #
146 # Note that the URLs these patterns check against are escaped,
146 # Note that the URLs these patterns check against are escaped,
147 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
147 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
148 base_url = py3compat.unicode_to_str(base_url, 'ascii')
148 base_url = py3compat.unicode_to_str(base_url, 'ascii')
149 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
149 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
150 jenv_opt = jinja_env_options if jinja_env_options else {}
150 jenv_opt = jinja_env_options if jinja_env_options else {}
151 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
151 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
152 settings = dict(
152 settings = dict(
153 # basics
153 # basics
154 log_function=log_request,
154 log_function=log_request,
155 base_url=base_url,
155 base_url=base_url,
156 template_path=template_path,
156 template_path=template_path,
157 static_path=ipython_app.static_file_path,
157 static_path=ipython_app.static_file_path,
158 static_handler_class = FileFindHandler,
158 static_handler_class = FileFindHandler,
159 static_url_prefix = url_path_join(base_url,'/static/'),
159 static_url_prefix = url_path_join(base_url,'/static/'),
160
160
161 # authentication
161 # authentication
162 cookie_secret=ipython_app.cookie_secret,
162 cookie_secret=ipython_app.cookie_secret,
163 login_url=url_path_join(base_url,'/login'),
163 login_url=url_path_join(base_url,'/login'),
164 password=ipython_app.password,
164 password=ipython_app.password,
165
165
166 # managers
166 # managers
167 kernel_manager=kernel_manager,
167 kernel_manager=kernel_manager,
168 notebook_manager=notebook_manager,
168 notebook_manager=notebook_manager,
169 cluster_manager=cluster_manager,
169 cluster_manager=cluster_manager,
170 session_manager=session_manager,
170 session_manager=session_manager,
171 kernel_spec_manager=kernel_spec_manager,
171 kernel_spec_manager=kernel_spec_manager,
172
172
173 # IPython stuff
173 # IPython stuff
174 nbextensions_path = ipython_app.nbextensions_path,
174 nbextensions_path = ipython_app.nbextensions_path,
175 websocket_url=ipython_app.websocket_url,
175 mathjax_url=ipython_app.mathjax_url,
176 mathjax_url=ipython_app.mathjax_url,
176 config=ipython_app.config,
177 config=ipython_app.config,
177 jinja2_env=env,
178 jinja2_env=env,
178 )
179 )
179
180
180 # allow custom overrides for the tornado web app.
181 # allow custom overrides for the tornado web app.
181 settings.update(settings_overrides)
182 settings.update(settings_overrides)
182 return settings
183 return settings
183
184
184 def init_handlers(self, settings):
185 def init_handlers(self, settings):
185 # Load the (URL pattern, handler) tuples for each component.
186 # Load the (URL pattern, handler) tuples for each component.
186 handlers = []
187 handlers = []
187 handlers.extend(load_handlers('base.handlers'))
188 handlers.extend(load_handlers('base.handlers'))
188 handlers.extend(load_handlers('tree.handlers'))
189 handlers.extend(load_handlers('tree.handlers'))
189 handlers.extend(load_handlers('auth.login'))
190 handlers.extend(load_handlers('auth.login'))
190 handlers.extend(load_handlers('auth.logout'))
191 handlers.extend(load_handlers('auth.logout'))
191 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('kernelspecs.handlers'))
194 handlers.extend(load_handlers('kernelspecs.handlers'))
194 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.clusters.handlers'))
197 handlers.extend(load_handlers('services.sessions.handlers'))
198 handlers.extend(load_handlers('services.sessions.handlers'))
198 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 handlers.extend(load_handlers('services.kernelspecs.handlers'))
200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
200 # FIXME: /files/ should be handled by the Contents service when it exists
201 # FIXME: /files/ should be handled by the Contents service when it exists
201 nbm = settings['notebook_manager']
202 nbm = settings['notebook_manager']
202 if hasattr(nbm, 'notebook_dir'):
203 if hasattr(nbm, 'notebook_dir'):
203 handlers.extend([
204 handlers.extend([
204 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
205 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
206 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
206 ])
207 ])
207 # prepend base_url onto the patterns that we match
208 # prepend base_url onto the patterns that we match
208 new_handlers = []
209 new_handlers = []
209 for handler in handlers:
210 for handler in handlers:
210 pattern = url_path_join(settings['base_url'], handler[0])
211 pattern = url_path_join(settings['base_url'], handler[0])
211 new_handler = tuple([pattern] + list(handler[1:]))
212 new_handler = tuple([pattern] + list(handler[1:]))
212 new_handlers.append(new_handler)
213 new_handlers.append(new_handler)
213 # add 404 on the end, which will catch everything that falls through
214 # add 404 on the end, which will catch everything that falls through
214 new_handlers.append((r'(.*)', Template404))
215 new_handlers.append((r'(.*)', Template404))
215 return new_handlers
216 return new_handlers
216
217
217
218
218 class NbserverListApp(BaseIPythonApplication):
219 class NbserverListApp(BaseIPythonApplication):
219
220
220 description="List currently running notebook servers in this profile."
221 description="List currently running notebook servers in this profile."
221
222
222 flags = dict(
223 flags = dict(
223 json=({'NbserverListApp': {'json': True}},
224 json=({'NbserverListApp': {'json': True}},
224 "Produce machine-readable JSON output."),
225 "Produce machine-readable JSON output."),
225 )
226 )
226
227
227 json = Bool(False, config=True,
228 json = Bool(False, config=True,
228 help="If True, each line of output will be a JSON object with the "
229 help="If True, each line of output will be a JSON object with the "
229 "details from the server info file.")
230 "details from the server info file.")
230
231
231 def start(self):
232 def start(self):
232 if not self.json:
233 if not self.json:
233 print("Currently running servers:")
234 print("Currently running servers:")
234 for serverinfo in list_running_servers(self.profile):
235 for serverinfo in list_running_servers(self.profile):
235 if self.json:
236 if self.json:
236 print(json.dumps(serverinfo))
237 print(json.dumps(serverinfo))
237 else:
238 else:
238 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
239 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
239
240
240 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
241 # Aliases and Flags
242 # Aliases and Flags
242 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
243
244
244 flags = dict(base_flags)
245 flags = dict(base_flags)
245 flags['no-browser']=(
246 flags['no-browser']=(
246 {'NotebookApp' : {'open_browser' : False}},
247 {'NotebookApp' : {'open_browser' : False}},
247 "Don't open the notebook in a browser after startup."
248 "Don't open the notebook in a browser after startup."
248 )
249 )
249 flags['pylab']=(
250 flags['pylab']=(
250 {'NotebookApp' : {'pylab' : 'warn'}},
251 {'NotebookApp' : {'pylab' : 'warn'}},
251 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
252 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
252 )
253 )
253 flags['no-mathjax']=(
254 flags['no-mathjax']=(
254 {'NotebookApp' : {'enable_mathjax' : False}},
255 {'NotebookApp' : {'enable_mathjax' : False}},
255 """Disable MathJax
256 """Disable MathJax
256
257
257 MathJax is the javascript library IPython uses to render math/LaTeX. It is
258 MathJax is the javascript library IPython uses to render math/LaTeX. It is
258 very large, so you may want to disable it if you have a slow internet
259 very large, so you may want to disable it if you have a slow internet
259 connection, or for offline use of the notebook.
260 connection, or for offline use of the notebook.
260
261
261 When disabled, equations etc. will appear as their untransformed TeX source.
262 When disabled, equations etc. will appear as their untransformed TeX source.
262 """
263 """
263 )
264 )
264
265
265 # Add notebook manager flags
266 # Add notebook manager flags
266 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
267 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
267 'Auto-save a .py script everytime the .ipynb notebook is saved',
268 'Auto-save a .py script everytime the .ipynb notebook is saved',
268 'Do not auto-save .py scripts for every notebook'))
269 'Do not auto-save .py scripts for every notebook'))
269
270
270 aliases = dict(base_aliases)
271 aliases = dict(base_aliases)
271
272
272 aliases.update({
273 aliases.update({
273 'ip': 'NotebookApp.ip',
274 'ip': 'NotebookApp.ip',
274 'port': 'NotebookApp.port',
275 'port': 'NotebookApp.port',
275 'port-retries': 'NotebookApp.port_retries',
276 'port-retries': 'NotebookApp.port_retries',
276 'transport': 'KernelManager.transport',
277 'transport': 'KernelManager.transport',
277 'keyfile': 'NotebookApp.keyfile',
278 'keyfile': 'NotebookApp.keyfile',
278 'certfile': 'NotebookApp.certfile',
279 'certfile': 'NotebookApp.certfile',
279 'notebook-dir': 'NotebookApp.notebook_dir',
280 'notebook-dir': 'NotebookApp.notebook_dir',
280 'browser': 'NotebookApp.browser',
281 'browser': 'NotebookApp.browser',
281 'pylab': 'NotebookApp.pylab',
282 'pylab': 'NotebookApp.pylab',
282 })
283 })
283
284
284 #-----------------------------------------------------------------------------
285 #-----------------------------------------------------------------------------
285 # NotebookApp
286 # NotebookApp
286 #-----------------------------------------------------------------------------
287 #-----------------------------------------------------------------------------
287
288
288 class NotebookApp(BaseIPythonApplication):
289 class NotebookApp(BaseIPythonApplication):
289
290
290 name = 'ipython-notebook'
291 name = 'ipython-notebook'
291
292
292 description = """
293 description = """
293 The IPython HTML Notebook.
294 The IPython HTML Notebook.
294
295
295 This launches a Tornado based HTML Notebook Server that serves up an
296 This launches a Tornado based HTML Notebook Server that serves up an
296 HTML5/Javascript Notebook client.
297 HTML5/Javascript Notebook client.
297 """
298 """
298 examples = _examples
299 examples = _examples
299 aliases = aliases
300 aliases = aliases
300 flags = flags
301 flags = flags
301
302
302 classes = [
303 classes = [
303 KernelManager, ProfileDir, Session, MappingKernelManager,
304 KernelManager, ProfileDir, Session, MappingKernelManager,
304 NotebookManager, FileNotebookManager, NotebookNotary,
305 NotebookManager, FileNotebookManager, NotebookNotary,
305 ]
306 ]
306 flags = Dict(flags)
307 flags = Dict(flags)
307 aliases = Dict(aliases)
308 aliases = Dict(aliases)
308
309
309 subcommands = dict(
310 subcommands = dict(
310 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
311 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
311 )
312 )
312
313
313 kernel_argv = List(Unicode)
314 kernel_argv = List(Unicode)
314
315
315 _log_formatter_cls = LogFormatter
316 _log_formatter_cls = LogFormatter
316
317
317 def _log_level_default(self):
318 def _log_level_default(self):
318 return logging.INFO
319 return logging.INFO
319
320
320 def _log_datefmt_default(self):
321 def _log_datefmt_default(self):
321 """Exclude date from default date format"""
322 """Exclude date from default date format"""
322 return "%H:%M:%S"
323 return "%H:%M:%S"
323
324
324 def _log_format_default(self):
325 def _log_format_default(self):
325 """override default log format to include time"""
326 """override default log format to include time"""
326 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
327 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
327
328
328 # create requested profiles by default, if they don't exist:
329 # create requested profiles by default, if they don't exist:
329 auto_create = Bool(True)
330 auto_create = Bool(True)
330
331
331 # file to be opened in the notebook server
332 # file to be opened in the notebook server
332 file_to_run = Unicode('', config=True)
333 file_to_run = Unicode('', config=True)
333 def _file_to_run_changed(self, name, old, new):
334 def _file_to_run_changed(self, name, old, new):
334 path, base = os.path.split(new)
335 path, base = os.path.split(new)
335 if path:
336 if path:
336 self.file_to_run = base
337 self.file_to_run = base
337 self.notebook_dir = path
338 self.notebook_dir = path
338
339
339 # Network related information
340 # Network related information
340
341
341 allow_origin = Unicode('', config=True,
342 allow_origin = Unicode('', config=True,
342 help="""Set the Access-Control-Allow-Origin header
343 help="""Set the Access-Control-Allow-Origin header
343
344
344 Use '*' to allow any origin to access your server.
345 Use '*' to allow any origin to access your server.
345
346
346 Takes precedence over allow_origin_pat.
347 Takes precedence over allow_origin_pat.
347 """
348 """
348 )
349 )
349
350
350 allow_origin_pat = Unicode('', config=True,
351 allow_origin_pat = Unicode('', config=True,
351 help="""Use a regular expression for the Access-Control-Allow-Origin header
352 help="""Use a regular expression for the Access-Control-Allow-Origin header
352
353
353 Requests from an origin matching the expression will get replies with:
354 Requests from an origin matching the expression will get replies with:
354
355
355 Access-Control-Allow-Origin: origin
356 Access-Control-Allow-Origin: origin
356
357
357 where `origin` is the origin of the request.
358 where `origin` is the origin of the request.
358
359
359 Ignored if allow_origin is set.
360 Ignored if allow_origin is set.
360 """
361 """
361 )
362 )
362
363
363 allow_credentials = Bool(False, config=True,
364 allow_credentials = Bool(False, config=True,
364 help="Set the Access-Control-Allow-Credentials: true header"
365 help="Set the Access-Control-Allow-Credentials: true header"
365 )
366 )
366
367
367 ip = Unicode('localhost', config=True,
368 ip = Unicode('localhost', config=True,
368 help="The IP address the notebook server will listen on."
369 help="The IP address the notebook server will listen on."
369 )
370 )
370
371
371 def _ip_changed(self, name, old, new):
372 def _ip_changed(self, name, old, new):
372 if new == u'*': self.ip = u''
373 if new == u'*': self.ip = u''
373
374
374 port = Integer(8888, config=True,
375 port = Integer(8888, config=True,
375 help="The port the notebook server will listen on."
376 help="The port the notebook server will listen on."
376 )
377 )
377 port_retries = Integer(50, config=True,
378 port_retries = Integer(50, config=True,
378 help="The number of additional ports to try if the specified port is not available."
379 help="The number of additional ports to try if the specified port is not available."
379 )
380 )
380
381
381 certfile = Unicode(u'', config=True,
382 certfile = Unicode(u'', config=True,
382 help="""The full path to an SSL/TLS certificate file."""
383 help="""The full path to an SSL/TLS certificate file."""
383 )
384 )
384
385
385 keyfile = Unicode(u'', config=True,
386 keyfile = Unicode(u'', config=True,
386 help="""The full path to a private key file for usage with SSL/TLS."""
387 help="""The full path to a private key file for usage with SSL/TLS."""
387 )
388 )
388
389
389 cookie_secret_file = Unicode(config=True,
390 cookie_secret_file = Unicode(config=True,
390 help="""The file where the cookie secret is stored."""
391 help="""The file where the cookie secret is stored."""
391 )
392 )
392 def _cookie_secret_file_default(self):
393 def _cookie_secret_file_default(self):
393 if self.profile_dir is None:
394 if self.profile_dir is None:
394 return ''
395 return ''
395 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
396 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
396
397
397 cookie_secret = Bytes(b'', config=True,
398 cookie_secret = Bytes(b'', config=True,
398 help="""The random bytes used to secure cookies.
399 help="""The random bytes used to secure cookies.
399 By default this is a new random number every time you start the Notebook.
400 By default this is a new random number every time you start the Notebook.
400 Set it to a value in a config file to enable logins to persist across server sessions.
401 Set it to a value in a config file to enable logins to persist across server sessions.
401
402
402 Note: Cookie secrets should be kept private, do not share config files with
403 Note: Cookie secrets should be kept private, do not share config files with
403 cookie_secret stored in plaintext (you can read the value from a file).
404 cookie_secret stored in plaintext (you can read the value from a file).
404 """
405 """
405 )
406 )
406 def _cookie_secret_default(self):
407 def _cookie_secret_default(self):
407 if os.path.exists(self.cookie_secret_file):
408 if os.path.exists(self.cookie_secret_file):
408 with io.open(self.cookie_secret_file, 'rb') as f:
409 with io.open(self.cookie_secret_file, 'rb') as f:
409 return f.read()
410 return f.read()
410 else:
411 else:
411 secret = base64.encodestring(os.urandom(1024))
412 secret = base64.encodestring(os.urandom(1024))
412 self._write_cookie_secret_file(secret)
413 self._write_cookie_secret_file(secret)
413 return secret
414 return secret
414
415
415 def _write_cookie_secret_file(self, secret):
416 def _write_cookie_secret_file(self, secret):
416 """write my secret to my secret_file"""
417 """write my secret to my secret_file"""
417 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
418 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
418 with io.open(self.cookie_secret_file, 'wb') as f:
419 with io.open(self.cookie_secret_file, 'wb') as f:
419 f.write(secret)
420 f.write(secret)
420 try:
421 try:
421 os.chmod(self.cookie_secret_file, 0o600)
422 os.chmod(self.cookie_secret_file, 0o600)
422 except OSError:
423 except OSError:
423 self.log.warn(
424 self.log.warn(
424 "Could not set permissions on %s",
425 "Could not set permissions on %s",
425 self.cookie_secret_file
426 self.cookie_secret_file
426 )
427 )
427
428
428 password = Unicode(u'', config=True,
429 password = Unicode(u'', config=True,
429 help="""Hashed password to use for web authentication.
430 help="""Hashed password to use for web authentication.
430
431
431 To generate, type in a python/IPython shell:
432 To generate, type in a python/IPython shell:
432
433
433 from IPython.lib import passwd; passwd()
434 from IPython.lib import passwd; passwd()
434
435
435 The string should be of the form type:salt:hashed-password.
436 The string should be of the form type:salt:hashed-password.
436 """
437 """
437 )
438 )
438
439
439 open_browser = Bool(True, config=True,
440 open_browser = Bool(True, config=True,
440 help="""Whether to open in a browser after starting.
441 help="""Whether to open in a browser after starting.
441 The specific browser used is platform dependent and
442 The specific browser used is platform dependent and
442 determined by the python standard library `webbrowser`
443 determined by the python standard library `webbrowser`
443 module, unless it is overridden using the --browser
444 module, unless it is overridden using the --browser
444 (NotebookApp.browser) configuration option.
445 (NotebookApp.browser) configuration option.
445 """)
446 """)
446
447
447 browser = Unicode(u'', config=True,
448 browser = Unicode(u'', config=True,
448 help="""Specify what command to use to invoke a web
449 help="""Specify what command to use to invoke a web
449 browser when opening the notebook. If not specified, the
450 browser when opening the notebook. If not specified, the
450 default browser will be determined by the `webbrowser`
451 default browser will be determined by the `webbrowser`
451 standard library module, which allows setting of the
452 standard library module, which allows setting of the
452 BROWSER environment variable to override it.
453 BROWSER environment variable to override it.
453 """)
454 """)
454
455
455 webapp_settings = Dict(config=True,
456 webapp_settings = Dict(config=True,
456 help="Supply overrides for the tornado.web.Application that the "
457 help="Supply overrides for the tornado.web.Application that the "
457 "IPython notebook uses.")
458 "IPython notebook uses.")
458
459
459 jinja_environment_options = Dict(config=True,
460 jinja_environment_options = Dict(config=True,
460 help="Supply extra arguments that will be passed to Jinja environment.")
461 help="Supply extra arguments that will be passed to Jinja environment.")
461
462
462
463
463 enable_mathjax = Bool(True, config=True,
464 enable_mathjax = Bool(True, config=True,
464 help="""Whether to enable MathJax for typesetting math/TeX
465 help="""Whether to enable MathJax for typesetting math/TeX
465
466
466 MathJax is the javascript library IPython uses to render math/LaTeX. It is
467 MathJax is the javascript library IPython uses to render math/LaTeX. It is
467 very large, so you may want to disable it if you have a slow internet
468 very large, so you may want to disable it if you have a slow internet
468 connection, or for offline use of the notebook.
469 connection, or for offline use of the notebook.
469
470
470 When disabled, equations etc. will appear as their untransformed TeX source.
471 When disabled, equations etc. will appear as their untransformed TeX source.
471 """
472 """
472 )
473 )
473 def _enable_mathjax_changed(self, name, old, new):
474 def _enable_mathjax_changed(self, name, old, new):
474 """set mathjax url to empty if mathjax is disabled"""
475 """set mathjax url to empty if mathjax is disabled"""
475 if not new:
476 if not new:
476 self.mathjax_url = u''
477 self.mathjax_url = u''
477
478
478 base_url = Unicode('/', config=True,
479 base_url = Unicode('/', config=True,
479 help='''The base URL for the notebook server.
480 help='''The base URL for the notebook server.
480
481
481 Leading and trailing slashes can be omitted,
482 Leading and trailing slashes can be omitted,
482 and will automatically be added.
483 and will automatically be added.
483 ''')
484 ''')
484 def _base_url_changed(self, name, old, new):
485 def _base_url_changed(self, name, old, new):
485 if not new.startswith('/'):
486 if not new.startswith('/'):
486 self.base_url = '/'+new
487 self.base_url = '/'+new
487 elif not new.endswith('/'):
488 elif not new.endswith('/'):
488 self.base_url = new+'/'
489 self.base_url = new+'/'
489
490
490 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
491 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
491 def _base_project_url_changed(self, name, old, new):
492 def _base_project_url_changed(self, name, old, new):
492 self.log.warn("base_project_url is deprecated, use base_url")
493 self.log.warn("base_project_url is deprecated, use base_url")
493 self.base_url = new
494 self.base_url = new
494
495
495 extra_static_paths = List(Unicode, config=True,
496 extra_static_paths = List(Unicode, config=True,
496 help="""Extra paths to search for serving static files.
497 help="""Extra paths to search for serving static files.
497
498
498 This allows adding javascript/css to be available from the notebook server machine,
499 This allows adding javascript/css to be available from the notebook server machine,
499 or overriding individual files in the IPython"""
500 or overriding individual files in the IPython"""
500 )
501 )
501 def _extra_static_paths_default(self):
502 def _extra_static_paths_default(self):
502 return [os.path.join(self.profile_dir.location, 'static')]
503 return [os.path.join(self.profile_dir.location, 'static')]
503
504
504 @property
505 @property
505 def static_file_path(self):
506 def static_file_path(self):
506 """return extra paths + the default location"""
507 """return extra paths + the default location"""
507 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
508 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
508
509
509 nbextensions_path = List(Unicode, config=True,
510 nbextensions_path = List(Unicode, config=True,
510 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
511 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
511 )
512 )
512 def _nbextensions_path_default(self):
513 def _nbextensions_path_default(self):
513 return [os.path.join(get_ipython_dir(), 'nbextensions')]
514 return [os.path.join(get_ipython_dir(), 'nbextensions')]
514
515
516 websocket_url = Unicode("", config=True,
517 help="""The base URL for websockets,
518 if it differs from the HTTP server (hint: it almost certainly doesn't).
519
520 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
521 """
522 )
515 mathjax_url = Unicode("", config=True,
523 mathjax_url = Unicode("", config=True,
516 help="""The url for MathJax.js."""
524 help="""The url for MathJax.js."""
517 )
525 )
518 def _mathjax_url_default(self):
526 def _mathjax_url_default(self):
519 if not self.enable_mathjax:
527 if not self.enable_mathjax:
520 return u''
528 return u''
521 static_url_prefix = self.webapp_settings.get("static_url_prefix",
529 static_url_prefix = self.webapp_settings.get("static_url_prefix",
522 url_path_join(self.base_url, "static")
530 url_path_join(self.base_url, "static")
523 )
531 )
524
532
525 # try local mathjax, either in nbextensions/mathjax or static/mathjax
533 # try local mathjax, either in nbextensions/mathjax or static/mathjax
526 for (url_prefix, search_path) in [
534 for (url_prefix, search_path) in [
527 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
535 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
528 (static_url_prefix, self.static_file_path),
536 (static_url_prefix, self.static_file_path),
529 ]:
537 ]:
530 self.log.debug("searching for local mathjax in %s", search_path)
538 self.log.debug("searching for local mathjax in %s", search_path)
531 try:
539 try:
532 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
540 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
533 except IOError:
541 except IOError:
534 continue
542 continue
535 else:
543 else:
536 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
544 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
537 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
545 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
538 return url
546 return url
539
547
540 # no local mathjax, serve from CDN
548 # no local mathjax, serve from CDN
541 url = u"//cdn.mathjax.org/mathjax/latest/MathJax.js"
549 url = u"//cdn.mathjax.org/mathjax/latest/MathJax.js"
542 self.log.info("Using MathJax from CDN: %s", url)
550 self.log.info("Using MathJax from CDN: %s", url)
543 return url
551 return url
544
552
545 def _mathjax_url_changed(self, name, old, new):
553 def _mathjax_url_changed(self, name, old, new):
546 if new and not self.enable_mathjax:
554 if new and not self.enable_mathjax:
547 # enable_mathjax=False overrides mathjax_url
555 # enable_mathjax=False overrides mathjax_url
548 self.mathjax_url = u''
556 self.mathjax_url = u''
549 else:
557 else:
550 self.log.info("Using MathJax: %s", new)
558 self.log.info("Using MathJax: %s", new)
551
559
552 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
560 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
553 config=True,
561 config=True,
554 help='The notebook manager class to use.'
562 help='The notebook manager class to use.'
555 )
563 )
556 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
564 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
557 config=True,
565 config=True,
558 help='The kernel manager class to use.'
566 help='The kernel manager class to use.'
559 )
567 )
560 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
568 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
561 config=True,
569 config=True,
562 help='The session manager class to use.'
570 help='The session manager class to use.'
563 )
571 )
564 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
572 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
565 config=True,
573 config=True,
566 help='The cluster manager class to use.'
574 help='The cluster manager class to use.'
567 )
575 )
568
576
569 kernel_spec_manager = Instance(KernelSpecManager)
577 kernel_spec_manager = Instance(KernelSpecManager)
570
578
571 def _kernel_spec_manager_default(self):
579 def _kernel_spec_manager_default(self):
572 return KernelSpecManager(ipython_dir=self.ipython_dir)
580 return KernelSpecManager(ipython_dir=self.ipython_dir)
573
581
574 trust_xheaders = Bool(False, config=True,
582 trust_xheaders = Bool(False, config=True,
575 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
583 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
576 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
584 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
577 )
585 )
578
586
579 info_file = Unicode()
587 info_file = Unicode()
580
588
581 def _info_file_default(self):
589 def _info_file_default(self):
582 info_file = "nbserver-%s.json"%os.getpid()
590 info_file = "nbserver-%s.json"%os.getpid()
583 return os.path.join(self.profile_dir.security_dir, info_file)
591 return os.path.join(self.profile_dir.security_dir, info_file)
584
592
585 notebook_dir = Unicode(py3compat.getcwd(), config=True,
593 notebook_dir = Unicode(py3compat.getcwd(), config=True,
586 help="The directory to use for notebooks and kernels."
594 help="The directory to use for notebooks and kernels."
587 )
595 )
588
596
589 pylab = Unicode('disabled', config=True,
597 pylab = Unicode('disabled', config=True,
590 help="""
598 help="""
591 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
599 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
592 """
600 """
593 )
601 )
594 def _pylab_changed(self, name, old, new):
602 def _pylab_changed(self, name, old, new):
595 """when --pylab is specified, display a warning and exit"""
603 """when --pylab is specified, display a warning and exit"""
596 if new != 'warn':
604 if new != 'warn':
597 backend = ' %s' % new
605 backend = ' %s' % new
598 else:
606 else:
599 backend = ''
607 backend = ''
600 self.log.error("Support for specifying --pylab on the command line has been removed.")
608 self.log.error("Support for specifying --pylab on the command line has been removed.")
601 self.log.error(
609 self.log.error(
602 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
610 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
603 )
611 )
604 self.exit(1)
612 self.exit(1)
605
613
606 def _notebook_dir_changed(self, name, old, new):
614 def _notebook_dir_changed(self, name, old, new):
607 """Do a bit of validation of the notebook dir."""
615 """Do a bit of validation of the notebook dir."""
608 if not os.path.isabs(new):
616 if not os.path.isabs(new):
609 # If we receive a non-absolute path, make it absolute.
617 # If we receive a non-absolute path, make it absolute.
610 self.notebook_dir = os.path.abspath(new)
618 self.notebook_dir = os.path.abspath(new)
611 return
619 return
612 if not os.path.isdir(new):
620 if not os.path.isdir(new):
613 raise TraitError("No such notebook dir: %r" % new)
621 raise TraitError("No such notebook dir: %r" % new)
614
622
615 # setting App.notebook_dir implies setting notebook and kernel dirs as well
623 # setting App.notebook_dir implies setting notebook and kernel dirs as well
616 self.config.FileNotebookManager.notebook_dir = new
624 self.config.FileNotebookManager.notebook_dir = new
617 self.config.MappingKernelManager.root_dir = new
625 self.config.MappingKernelManager.root_dir = new
618
626
619
627
620 def parse_command_line(self, argv=None):
628 def parse_command_line(self, argv=None):
621 super(NotebookApp, self).parse_command_line(argv)
629 super(NotebookApp, self).parse_command_line(argv)
622
630
623 if self.extra_args:
631 if self.extra_args:
624 arg0 = self.extra_args[0]
632 arg0 = self.extra_args[0]
625 f = os.path.abspath(arg0)
633 f = os.path.abspath(arg0)
626 self.argv.remove(arg0)
634 self.argv.remove(arg0)
627 if not os.path.exists(f):
635 if not os.path.exists(f):
628 self.log.critical("No such file or directory: %s", f)
636 self.log.critical("No such file or directory: %s", f)
629 self.exit(1)
637 self.exit(1)
630
638
631 # Use config here, to ensure that it takes higher priority than
639 # Use config here, to ensure that it takes higher priority than
632 # anything that comes from the profile.
640 # anything that comes from the profile.
633 c = Config()
641 c = Config()
634 if os.path.isdir(f):
642 if os.path.isdir(f):
635 c.NotebookApp.notebook_dir = f
643 c.NotebookApp.notebook_dir = f
636 elif os.path.isfile(f):
644 elif os.path.isfile(f):
637 c.NotebookApp.file_to_run = f
645 c.NotebookApp.file_to_run = f
638 self.update_config(c)
646 self.update_config(c)
639
647
640 def init_kernel_argv(self):
648 def init_kernel_argv(self):
641 """construct the kernel arguments"""
649 """construct the kernel arguments"""
642 # Kernel should get *absolute* path to profile directory
650 # Kernel should get *absolute* path to profile directory
643 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
651 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
644
652
645 def init_configurables(self):
653 def init_configurables(self):
646 # force Session default to be secure
654 # force Session default to be secure
647 default_secure(self.config)
655 default_secure(self.config)
648 kls = import_item(self.kernel_manager_class)
656 kls = import_item(self.kernel_manager_class)
649 self.kernel_manager = kls(
657 self.kernel_manager = kls(
650 parent=self, log=self.log, kernel_argv=self.kernel_argv,
658 parent=self, log=self.log, kernel_argv=self.kernel_argv,
651 connection_dir = self.profile_dir.security_dir,
659 connection_dir = self.profile_dir.security_dir,
652 )
660 )
653 kls = import_item(self.notebook_manager_class)
661 kls = import_item(self.notebook_manager_class)
654 self.notebook_manager = kls(parent=self, log=self.log)
662 self.notebook_manager = kls(parent=self, log=self.log)
655 kls = import_item(self.session_manager_class)
663 kls = import_item(self.session_manager_class)
656 self.session_manager = kls(parent=self, log=self.log,
664 self.session_manager = kls(parent=self, log=self.log,
657 kernel_manager=self.kernel_manager,
665 kernel_manager=self.kernel_manager,
658 notebook_manager=self.notebook_manager)
666 notebook_manager=self.notebook_manager)
659 kls = import_item(self.cluster_manager_class)
667 kls = import_item(self.cluster_manager_class)
660 self.cluster_manager = kls(parent=self, log=self.log)
668 self.cluster_manager = kls(parent=self, log=self.log)
661 self.cluster_manager.update_profiles()
669 self.cluster_manager.update_profiles()
662
670
663 def init_logging(self):
671 def init_logging(self):
664 # This prevents double log messages because tornado use a root logger that
672 # This prevents double log messages because tornado use a root logger that
665 # self.log is a child of. The logging module dipatches log messages to a log
673 # self.log is a child of. The logging module dipatches log messages to a log
666 # and all of its ancenstors until propagate is set to False.
674 # and all of its ancenstors until propagate is set to False.
667 self.log.propagate = False
675 self.log.propagate = False
668
676
669 # hook up tornado 3's loggers to our app handlers
677 # hook up tornado 3's loggers to our app handlers
670 logger = logging.getLogger('tornado')
678 logger = logging.getLogger('tornado')
671 logger.propagate = True
679 logger.propagate = True
672 logger.parent = self.log
680 logger.parent = self.log
673 logger.setLevel(self.log.level)
681 logger.setLevel(self.log.level)
674
682
675 def init_webapp(self):
683 def init_webapp(self):
676 """initialize tornado webapp and httpserver"""
684 """initialize tornado webapp and httpserver"""
677 self.webapp_settings['allow_origin'] = self.allow_origin
685 self.webapp_settings['allow_origin'] = self.allow_origin
678 if self.allow_origin_pat:
686 if self.allow_origin_pat:
679 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
687 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
680 self.webapp_settings['allow_credentials'] = self.allow_credentials
688 self.webapp_settings['allow_credentials'] = self.allow_credentials
681
689
682 self.web_app = NotebookWebApplication(
690 self.web_app = NotebookWebApplication(
683 self, self.kernel_manager, self.notebook_manager,
691 self, self.kernel_manager, self.notebook_manager,
684 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
692 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
685 self.log, self.base_url, self.webapp_settings,
693 self.log, self.base_url, self.webapp_settings,
686 self.jinja_environment_options
694 self.jinja_environment_options
687 )
695 )
688 if self.certfile:
696 if self.certfile:
689 ssl_options = dict(certfile=self.certfile)
697 ssl_options = dict(certfile=self.certfile)
690 if self.keyfile:
698 if self.keyfile:
691 ssl_options['keyfile'] = self.keyfile
699 ssl_options['keyfile'] = self.keyfile
692 else:
700 else:
693 ssl_options = None
701 ssl_options = None
694 self.web_app.password = self.password
702 self.web_app.password = self.password
695 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
703 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
696 xheaders=self.trust_xheaders)
704 xheaders=self.trust_xheaders)
697 if not self.ip:
705 if not self.ip:
698 warning = "WARNING: The notebook server is listening on all IP addresses"
706 warning = "WARNING: The notebook server is listening on all IP addresses"
699 if ssl_options is None:
707 if ssl_options is None:
700 self.log.critical(warning + " and not using encryption. This "
708 self.log.critical(warning + " and not using encryption. This "
701 "is not recommended.")
709 "is not recommended.")
702 if not self.password:
710 if not self.password:
703 self.log.critical(warning + " and not using authentication. "
711 self.log.critical(warning + " and not using authentication. "
704 "This is highly insecure and not recommended.")
712 "This is highly insecure and not recommended.")
705 success = None
713 success = None
706 for port in random_ports(self.port, self.port_retries+1):
714 for port in random_ports(self.port, self.port_retries+1):
707 try:
715 try:
708 self.http_server.listen(port, self.ip)
716 self.http_server.listen(port, self.ip)
709 except socket.error as e:
717 except socket.error as e:
710 if e.errno == errno.EADDRINUSE:
718 if e.errno == errno.EADDRINUSE:
711 self.log.info('The port %i is already in use, trying another random port.' % port)
719 self.log.info('The port %i is already in use, trying another random port.' % port)
712 continue
720 continue
713 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
721 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
714 self.log.warn("Permission to listen on port %i denied" % port)
722 self.log.warn("Permission to listen on port %i denied" % port)
715 continue
723 continue
716 else:
724 else:
717 raise
725 raise
718 else:
726 else:
719 self.port = port
727 self.port = port
720 success = True
728 success = True
721 break
729 break
722 if not success:
730 if not success:
723 self.log.critical('ERROR: the notebook server could not be started because '
731 self.log.critical('ERROR: the notebook server could not be started because '
724 'no available port could be found.')
732 'no available port could be found.')
725 self.exit(1)
733 self.exit(1)
726
734
727 @property
735 @property
728 def display_url(self):
736 def display_url(self):
729 ip = self.ip if self.ip else '[all ip addresses on your system]'
737 ip = self.ip if self.ip else '[all ip addresses on your system]'
730 return self._url(ip)
738 return self._url(ip)
731
739
732 @property
740 @property
733 def connection_url(self):
741 def connection_url(self):
734 ip = self.ip if self.ip else 'localhost'
742 ip = self.ip if self.ip else 'localhost'
735 return self._url(ip)
743 return self._url(ip)
736
744
737 def _url(self, ip):
745 def _url(self, ip):
738 proto = 'https' if self.certfile else 'http'
746 proto = 'https' if self.certfile else 'http'
739 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
747 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
740
748
741 def init_signal(self):
749 def init_signal(self):
742 if not sys.platform.startswith('win'):
750 if not sys.platform.startswith('win'):
743 signal.signal(signal.SIGINT, self._handle_sigint)
751 signal.signal(signal.SIGINT, self._handle_sigint)
744 signal.signal(signal.SIGTERM, self._signal_stop)
752 signal.signal(signal.SIGTERM, self._signal_stop)
745 if hasattr(signal, 'SIGUSR1'):
753 if hasattr(signal, 'SIGUSR1'):
746 # Windows doesn't support SIGUSR1
754 # Windows doesn't support SIGUSR1
747 signal.signal(signal.SIGUSR1, self._signal_info)
755 signal.signal(signal.SIGUSR1, self._signal_info)
748 if hasattr(signal, 'SIGINFO'):
756 if hasattr(signal, 'SIGINFO'):
749 # only on BSD-based systems
757 # only on BSD-based systems
750 signal.signal(signal.SIGINFO, self._signal_info)
758 signal.signal(signal.SIGINFO, self._signal_info)
751
759
752 def _handle_sigint(self, sig, frame):
760 def _handle_sigint(self, sig, frame):
753 """SIGINT handler spawns confirmation dialog"""
761 """SIGINT handler spawns confirmation dialog"""
754 # register more forceful signal handler for ^C^C case
762 # register more forceful signal handler for ^C^C case
755 signal.signal(signal.SIGINT, self._signal_stop)
763 signal.signal(signal.SIGINT, self._signal_stop)
756 # request confirmation dialog in bg thread, to avoid
764 # request confirmation dialog in bg thread, to avoid
757 # blocking the App
765 # blocking the App
758 thread = threading.Thread(target=self._confirm_exit)
766 thread = threading.Thread(target=self._confirm_exit)
759 thread.daemon = True
767 thread.daemon = True
760 thread.start()
768 thread.start()
761
769
762 def _restore_sigint_handler(self):
770 def _restore_sigint_handler(self):
763 """callback for restoring original SIGINT handler"""
771 """callback for restoring original SIGINT handler"""
764 signal.signal(signal.SIGINT, self._handle_sigint)
772 signal.signal(signal.SIGINT, self._handle_sigint)
765
773
766 def _confirm_exit(self):
774 def _confirm_exit(self):
767 """confirm shutdown on ^C
775 """confirm shutdown on ^C
768
776
769 A second ^C, or answering 'y' within 5s will cause shutdown,
777 A second ^C, or answering 'y' within 5s will cause shutdown,
770 otherwise original SIGINT handler will be restored.
778 otherwise original SIGINT handler will be restored.
771
779
772 This doesn't work on Windows.
780 This doesn't work on Windows.
773 """
781 """
774 info = self.log.info
782 info = self.log.info
775 info('interrupted')
783 info('interrupted')
776 print(self.notebook_info())
784 print(self.notebook_info())
777 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
785 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
778 sys.stdout.flush()
786 sys.stdout.flush()
779 r,w,x = select.select([sys.stdin], [], [], 5)
787 r,w,x = select.select([sys.stdin], [], [], 5)
780 if r:
788 if r:
781 line = sys.stdin.readline()
789 line = sys.stdin.readline()
782 if line.lower().startswith('y') and 'n' not in line.lower():
790 if line.lower().startswith('y') and 'n' not in line.lower():
783 self.log.critical("Shutdown confirmed")
791 self.log.critical("Shutdown confirmed")
784 ioloop.IOLoop.instance().stop()
792 ioloop.IOLoop.instance().stop()
785 return
793 return
786 else:
794 else:
787 print("No answer for 5s:", end=' ')
795 print("No answer for 5s:", end=' ')
788 print("resuming operation...")
796 print("resuming operation...")
789 # no answer, or answer is no:
797 # no answer, or answer is no:
790 # set it back to original SIGINT handler
798 # set it back to original SIGINT handler
791 # use IOLoop.add_callback because signal.signal must be called
799 # use IOLoop.add_callback because signal.signal must be called
792 # from main thread
800 # from main thread
793 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
801 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
794
802
795 def _signal_stop(self, sig, frame):
803 def _signal_stop(self, sig, frame):
796 self.log.critical("received signal %s, stopping", sig)
804 self.log.critical("received signal %s, stopping", sig)
797 ioloop.IOLoop.instance().stop()
805 ioloop.IOLoop.instance().stop()
798
806
799 def _signal_info(self, sig, frame):
807 def _signal_info(self, sig, frame):
800 print(self.notebook_info())
808 print(self.notebook_info())
801
809
802 def init_components(self):
810 def init_components(self):
803 """Check the components submodule, and warn if it's unclean"""
811 """Check the components submodule, and warn if it's unclean"""
804 status = submodule.check_submodule_status()
812 status = submodule.check_submodule_status()
805 if status == 'missing':
813 if status == 'missing':
806 self.log.warn("components submodule missing, running `git submodule update`")
814 self.log.warn("components submodule missing, running `git submodule update`")
807 submodule.update_submodules(submodule.ipython_parent())
815 submodule.update_submodules(submodule.ipython_parent())
808 elif status == 'unclean':
816 elif status == 'unclean':
809 self.log.warn("components submodule unclean, you may see 404s on static/components")
817 self.log.warn("components submodule unclean, you may see 404s on static/components")
810 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
818 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
811
819
812 @catch_config_error
820 @catch_config_error
813 def initialize(self, argv=None):
821 def initialize(self, argv=None):
814 super(NotebookApp, self).initialize(argv)
822 super(NotebookApp, self).initialize(argv)
815 self.init_logging()
823 self.init_logging()
816 self.init_kernel_argv()
824 self.init_kernel_argv()
817 self.init_configurables()
825 self.init_configurables()
818 self.init_components()
826 self.init_components()
819 self.init_webapp()
827 self.init_webapp()
820 self.init_signal()
828 self.init_signal()
821
829
822 def cleanup_kernels(self):
830 def cleanup_kernels(self):
823 """Shutdown all kernels.
831 """Shutdown all kernels.
824
832
825 The kernels will shutdown themselves when this process no longer exists,
833 The kernels will shutdown themselves when this process no longer exists,
826 but explicit shutdown allows the KernelManagers to cleanup the connection files.
834 but explicit shutdown allows the KernelManagers to cleanup the connection files.
827 """
835 """
828 self.log.info('Shutting down kernels')
836 self.log.info('Shutting down kernels')
829 self.kernel_manager.shutdown_all()
837 self.kernel_manager.shutdown_all()
830
838
831 def notebook_info(self):
839 def notebook_info(self):
832 "Return the current working directory and the server url information"
840 "Return the current working directory and the server url information"
833 info = self.notebook_manager.info_string() + "\n"
841 info = self.notebook_manager.info_string() + "\n"
834 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
842 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
835 return info + "The IPython Notebook is running at: %s" % self.display_url
843 return info + "The IPython Notebook is running at: %s" % self.display_url
836
844
837 def server_info(self):
845 def server_info(self):
838 """Return a JSONable dict of information about this server."""
846 """Return a JSONable dict of information about this server."""
839 return {'url': self.connection_url,
847 return {'url': self.connection_url,
840 'hostname': self.ip if self.ip else 'localhost',
848 'hostname': self.ip if self.ip else 'localhost',
841 'port': self.port,
849 'port': self.port,
842 'secure': bool(self.certfile),
850 'secure': bool(self.certfile),
843 'base_url': self.base_url,
851 'base_url': self.base_url,
844 'notebook_dir': os.path.abspath(self.notebook_dir),
852 'notebook_dir': os.path.abspath(self.notebook_dir),
845 'pid': os.getpid()
853 'pid': os.getpid()
846 }
854 }
847
855
848 def write_server_info_file(self):
856 def write_server_info_file(self):
849 """Write the result of server_info() to the JSON file info_file."""
857 """Write the result of server_info() to the JSON file info_file."""
850 with open(self.info_file, 'w') as f:
858 with open(self.info_file, 'w') as f:
851 json.dump(self.server_info(), f, indent=2)
859 json.dump(self.server_info(), f, indent=2)
852
860
853 def remove_server_info_file(self):
861 def remove_server_info_file(self):
854 """Remove the nbserver-<pid>.json file created for this server.
862 """Remove the nbserver-<pid>.json file created for this server.
855
863
856 Ignores the error raised when the file has already been removed.
864 Ignores the error raised when the file has already been removed.
857 """
865 """
858 try:
866 try:
859 os.unlink(self.info_file)
867 os.unlink(self.info_file)
860 except OSError as e:
868 except OSError as e:
861 if e.errno != errno.ENOENT:
869 if e.errno != errno.ENOENT:
862 raise
870 raise
863
871
864 def start(self):
872 def start(self):
865 """ Start the IPython Notebook server app, after initialization
873 """ Start the IPython Notebook server app, after initialization
866
874
867 This method takes no arguments so all configuration and initialization
875 This method takes no arguments so all configuration and initialization
868 must be done prior to calling this method."""
876 must be done prior to calling this method."""
869 if self.subapp is not None:
877 if self.subapp is not None:
870 return self.subapp.start()
878 return self.subapp.start()
871
879
872 info = self.log.info
880 info = self.log.info
873 for line in self.notebook_info().split("\n"):
881 for line in self.notebook_info().split("\n"):
874 info(line)
882 info(line)
875 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
883 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
876
884
877 self.write_server_info_file()
885 self.write_server_info_file()
878
886
879 if self.open_browser or self.file_to_run:
887 if self.open_browser or self.file_to_run:
880 try:
888 try:
881 browser = webbrowser.get(self.browser or None)
889 browser = webbrowser.get(self.browser or None)
882 except webbrowser.Error as e:
890 except webbrowser.Error as e:
883 self.log.warn('No web browser found: %s.' % e)
891 self.log.warn('No web browser found: %s.' % e)
884 browser = None
892 browser = None
885
893
886 if self.file_to_run:
894 if self.file_to_run:
887 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
895 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
888 if not os.path.exists(fullpath):
896 if not os.path.exists(fullpath):
889 self.log.critical("%s does not exist" % fullpath)
897 self.log.critical("%s does not exist" % fullpath)
890 self.exit(1)
898 self.exit(1)
891
899
892 uri = url_path_join('notebooks', self.file_to_run)
900 uri = url_path_join('notebooks', self.file_to_run)
893 else:
901 else:
894 uri = 'tree'
902 uri = 'tree'
895 if browser:
903 if browser:
896 b = lambda : browser.open(url_path_join(self.connection_url, uri),
904 b = lambda : browser.open(url_path_join(self.connection_url, uri),
897 new=2)
905 new=2)
898 threading.Thread(target=b).start()
906 threading.Thread(target=b).start()
899 try:
907 try:
900 ioloop.IOLoop.instance().start()
908 ioloop.IOLoop.instance().start()
901 except KeyboardInterrupt:
909 except KeyboardInterrupt:
902 info("Interrupted...")
910 info("Interrupted...")
903 finally:
911 finally:
904 self.cleanup_kernels()
912 self.cleanup_kernels()
905 self.remove_server_info_file()
913 self.remove_server_info_file()
906
914
907
915
908 def list_running_servers(profile='default'):
916 def list_running_servers(profile='default'):
909 """Iterate over the server info files of running notebook servers.
917 """Iterate over the server info files of running notebook servers.
910
918
911 Given a profile name, find nbserver-* files in the security directory of
919 Given a profile name, find nbserver-* files in the security directory of
912 that profile, and yield dicts of their information, each one pertaining to
920 that profile, and yield dicts of their information, each one pertaining to
913 a currently running notebook server instance.
921 a currently running notebook server instance.
914 """
922 """
915 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
923 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
916 for file in os.listdir(pd.security_dir):
924 for file in os.listdir(pd.security_dir):
917 if file.startswith('nbserver-'):
925 if file.startswith('nbserver-'):
918 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
926 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
919 info = json.load(f)
927 info = json.load(f)
920
928
921 # Simple check whether that process is really still running
929 # Simple check whether that process is really still running
922 if check_pid(info['pid']):
930 if check_pid(info['pid']):
923 yield info
931 yield info
924 else:
932 else:
925 # If the process has died, try to delete its info file
933 # If the process has died, try to delete its info file
926 try:
934 try:
927 os.unlink(file)
935 os.unlink(file)
928 except OSError:
936 except OSError:
929 pass # TODO: This should warn or log or something
937 pass # TODO: This should warn or log or something
930 #-----------------------------------------------------------------------------
938 #-----------------------------------------------------------------------------
931 # Main entry point
939 # Main entry point
932 #-----------------------------------------------------------------------------
940 #-----------------------------------------------------------------------------
933
941
934 launch_new_instance = NotebookApp.launch_instance
942 launch_new_instance = NotebookApp.launch_instance
935
943
@@ -1,136 +1,137 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 'base/js/utils',
8 'base/js/utils',
9 'base/js/page',
9 'base/js/page',
10 'notebook/js/layoutmanager',
10 'notebook/js/layoutmanager',
11 'base/js/events',
11 'base/js/events',
12 'auth/js/loginwidget',
12 'auth/js/loginwidget',
13 'notebook/js/maintoolbar',
13 'notebook/js/maintoolbar',
14 'notebook/js/pager',
14 'notebook/js/pager',
15 'notebook/js/quickhelp',
15 'notebook/js/quickhelp',
16 'notebook/js/menubar',
16 'notebook/js/menubar',
17 'notebook/js/notificationarea',
17 'notebook/js/notificationarea',
18 'notebook/js/savewidget',
18 'notebook/js/savewidget',
19 'notebook/js/keyboardmanager',
19 'notebook/js/keyboardmanager',
20 'notebook/js/config',
20 'notebook/js/config',
21 ], function(
21 ], function(
22 IPython,
22 IPython,
23 $,
23 $,
24 notebook,
24 notebook,
25 utils,
25 utils,
26 page,
26 page,
27 layoutmanager,
27 layoutmanager,
28 events,
28 events,
29 loginwidget,
29 loginwidget,
30 maintoolbar,
30 maintoolbar,
31 pager,
31 pager,
32 quickhelp,
32 quickhelp,
33 menubar,
33 menubar,
34 notificationarea,
34 notificationarea,
35 savewidget,
35 savewidget,
36 keyboardmanager,
36 keyboardmanager,
37 config
37 config
38 ) {
38 ) {
39 "use strict";
39 "use strict";
40
40
41 $('#ipython-main-app').addClass('border-box-sizing');
41 $('#ipython-main-app').addClass('border-box-sizing');
42 $('div#notebook_panel').addClass('border-box-sizing');
42 $('div#notebook_panel').addClass('border-box-sizing');
43
43
44 var common_options = {
44 var common_options = {
45 base_url : utils.get_body_data("baseUrl"),
45 base_url : utils.get_body_data("baseUrl"),
46 ws_url : IPython.utils.get_body_data("wsUrl"),
46 notebook_path : utils.get_body_data("notebookPath"),
47 notebook_path : utils.get_body_data("notebookPath"),
47 notebook_name : utils.get_body_data('notebookName')
48 notebook_name : utils.get_body_data('notebookName')
48 };
49 };
49
50
50 var user_config = $.extend({}, config.default_config);
51 var user_config = $.extend({}, config.default_config);
51 var page = new page.Page();
52 var page = new page.Page();
52 var layout_manager = new layoutmanager.LayoutManager();
53 var layout_manager = new layoutmanager.LayoutManager();
53 var events = $([new events.Events()]);
54 var events = $([new events.Events()]);
54 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
55 layout_manager: layout_manager,
56 layout_manager: layout_manager,
56 events: events});
57 events: events});
57 var keyboard_manager = new keyboardmanager.KeyboardManager({
58 var keyboard_manager = new keyboardmanager.KeyboardManager({
58 pager: pager,
59 pager: pager,
59 events: events});
60 events: events});
60 var save_widget = new savewidget.SaveWidget('span#save_widget', {
61 var save_widget = new savewidget.SaveWidget('span#save_widget', {
61 events: events,
62 events: events,
62 keyboard_manager: keyboard_manager});
63 keyboard_manager: keyboard_manager});
63 var notebook = new notebook.Notebook('div#notebook', $.extend({
64 var notebook = new notebook.Notebook('div#notebook', $.extend({
64 events: events,
65 events: events,
65 keyboard_manager: keyboard_manager,
66 keyboard_manager: keyboard_manager,
66 save_widget: save_widget,
67 save_widget: save_widget,
67 config: user_config},
68 config: user_config},
68 common_options));
69 common_options));
69 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
70 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
70 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
71 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
71 notebook: notebook,
72 notebook: notebook,
72 events: events});
73 events: events});
73 var quick_help = new quickhelp.QuickHelp({
74 var quick_help = new quickhelp.QuickHelp({
74 keyboard_manager: keyboard_manager,
75 keyboard_manager: keyboard_manager,
75 events: events,
76 events: events,
76 notebook: notebook});
77 notebook: notebook});
77 var menubar = new menubar.MenuBar('#menubar', $.extend({
78 var menubar = new menubar.MenuBar('#menubar', $.extend({
78 notebook: notebook,
79 notebook: notebook,
79 layout_manager: layout_manager,
80 layout_manager: layout_manager,
80 events: events,
81 events: events,
81 save_widget: save_widget,
82 save_widget: save_widget,
82 quick_help: quick_help},
83 quick_help: quick_help},
83 common_options));
84 common_options));
84 var notification_area = new notificationarea.NotificationArea(
85 var notification_area = new notificationarea.NotificationArea(
85 '#notification_area', {
86 '#notification_area', {
86 events: events,
87 events: events,
87 save_widget: save_widget,
88 save_widget: save_widget,
88 notebook: notebook,
89 notebook: notebook,
89 keyboard_manager: keyboard_manager});
90 keyboard_manager: keyboard_manager});
90 notification_area.init_notification_widgets();
91 notification_area.init_notification_widgets();
91
92
92 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
93 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
93 '<span id="test2" style="font-weight: bold;">x</span>'+
94 '<span id="test2" style="font-weight: bold;">x</span>'+
94 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
95 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
95 var nh = $('#test1').innerHeight();
96 var nh = $('#test1').innerHeight();
96 var bh = $('#test2').innerHeight();
97 var bh = $('#test2').innerHeight();
97 var ih = $('#test3').innerHeight();
98 var ih = $('#test3').innerHeight();
98 if(nh != bh || nh != ih) {
99 if(nh != bh || nh != ih) {
99 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
100 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
100 }
101 }
101 $('#fonttest').remove();
102 $('#fonttest').remove();
102
103
103 page.show();
104 page.show();
104
105
105 layout_manager.do_resize();
106 layout_manager.do_resize();
106 var first_load = function () {
107 var first_load = function () {
107 layout_manager.do_resize();
108 layout_manager.do_resize();
108 var hash = document.location.hash;
109 var hash = document.location.hash;
109 if (hash) {
110 if (hash) {
110 document.location.hash = '';
111 document.location.hash = '';
111 document.location.hash = hash;
112 document.location.hash = hash;
112 }
113 }
113 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
114 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
114 // only do this once
115 // only do this once
115 events.off('notebook_loaded.Notebook', first_load);
116 events.off('notebook_loaded.Notebook', first_load);
116 };
117 };
117
118
118 events.on('notebook_loaded.Notebook', first_load);
119 events.on('notebook_loaded.Notebook', first_load);
119 events.trigger('app_initialized.NotebookApp');
120 events.trigger('app_initialized.NotebookApp');
120 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
121 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
121
122
122 IPython.page = page;
123 IPython.page = page;
123 IPython.layout_manager = layout_manager;
124 IPython.layout_manager = layout_manager;
124 IPython.notebook = notebook;
125 IPython.notebook = notebook;
125 IPython.pager = pager;
126 IPython.pager = pager;
126 IPython.quick_help = quick_help;
127 IPython.quick_help = quick_help;
127 IPython.login_widget = login_widget;
128 IPython.login_widget = login_widget;
128 IPython.menubar = menubar;
129 IPython.menubar = menubar;
129 IPython.toolbar = toolbar;
130 IPython.toolbar = toolbar;
130 IPython.notification_area = notification_area;
131 IPython.notification_area = notification_area;
131 IPython.events = events;
132 IPython.events = events;
132 IPython.keyboard_manager = keyboard_manager;
133 IPython.keyboard_manager = keyboard_manager;
133 IPython.save_widget = save_widget;
134 IPython.save_widget = save_widget;
134 IPython.config = user_config;
135 IPython.config = user_config;
135 IPython.tooltip = notebook.tooltip;
136 IPython.tooltip = notebook.tooltip;
136 });
137 });
@@ -1,613 +1,616 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 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/comm',
8 'services/kernels/js/comm',
9 'widgets/js/init',
9 'widgets/js/init',
10 ], function(IPython, $, utils, comm, widgetmanager) {
10 ], function(IPython, $, utils, comm, widgetmanager) {
11 "use strict";
11 "use strict";
12
12
13 // Initialization and connection.
13 // Initialization and connection.
14 /**
14 /**
15 * A Kernel Class to communicate with the Python kernel
15 * A Kernel Class to communicate with the Python kernel
16 * @Class Kernel
16 * @Class Kernel
17 */
17 */
18 var Kernel = function (kernel_service_url, notebook, name) {
18 var Kernel = function (kernel_service_url, notebook, name) {
19 this.events = notebook.events;
19 this.events = notebook.events;
20 this.kernel_id = null;
20 this.kernel_id = null;
21 this.shell_channel = null;
21 this.shell_channel = null;
22 this.iopub_channel = null;
22 this.iopub_channel = null;
23 this.stdin_channel = null;
23 this.stdin_channel = null;
24 this.kernel_service_url = kernel_service_url;
24 this.kernel_service_url = kernel_service_url;
25 this.name = name;
25 this.name = name;
26 this.ws_url = IPython.utils.get_body_data("wsUrl");
27 if (!this.ws_url) {
28 // trailing 's' in https will become wss for secure web sockets
29 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
30 }
26 this.running = false;
31 this.running = false;
27 this.username = "username";
32 this.username = "username";
28 this.session_id = utils.uuid();
33 this.session_id = utils.uuid();
29 this._msg_callbacks = {};
34 this._msg_callbacks = {};
30 this.post = $.post;
35 this.post = $.post;
31
36
32 if (typeof(WebSocket) !== 'undefined') {
37 if (typeof(WebSocket) !== 'undefined') {
33 this.WebSocket = WebSocket;
38 this.WebSocket = WebSocket;
34 } else if (typeof(MozWebSocket) !== 'undefined') {
39 } else if (typeof(MozWebSocket) !== 'undefined') {
35 this.WebSocket = MozWebSocket;
40 this.WebSocket = MozWebSocket;
36 } else {
41 } else {
37 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
42 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
38 }
43 }
39
44
40 this.bind_events();
45 this.bind_events();
41 this.init_iopub_handlers();
46 this.init_iopub_handlers();
42 this.comm_manager = new comm.CommManager(this);
47 this.comm_manager = new comm.CommManager(this);
43 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
48 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
44
49
45 this.last_msg_id = null;
50 this.last_msg_id = null;
46 this.last_msg_callbacks = {};
51 this.last_msg_callbacks = {};
47 };
52 };
48
53
49
54
50 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
55 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
51 var msg = {
56 var msg = {
52 header : {
57 header : {
53 msg_id : utils.uuid(),
58 msg_id : utils.uuid(),
54 username : this.username,
59 username : this.username,
55 session : this.session_id,
60 session : this.session_id,
56 msg_type : msg_type,
61 msg_type : msg_type,
57 version : "5.0"
62 version : "5.0"
58 },
63 },
59 metadata : metadata || {},
64 metadata : metadata || {},
60 content : content,
65 content : content,
61 parent_header : {}
66 parent_header : {}
62 };
67 };
63 return msg;
68 return msg;
64 };
69 };
65
70
66 Kernel.prototype.bind_events = function () {
71 Kernel.prototype.bind_events = function () {
67 var that = this;
72 var that = this;
68 this.events.on('send_input_reply.Kernel', function(evt, data) {
73 this.events.on('send_input_reply.Kernel', function(evt, data) {
69 that.send_input_reply(data);
74 that.send_input_reply(data);
70 });
75 });
71 };
76 };
72
77
73 // Initialize the iopub handlers
78 // Initialize the iopub handlers
74
79
75 Kernel.prototype.init_iopub_handlers = function () {
80 Kernel.prototype.init_iopub_handlers = function () {
76 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
81 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
77 this._iopub_handlers = {};
82 this._iopub_handlers = {};
78 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
83 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
79 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
84 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
80
85
81 for (var i=0; i < output_msg_types.length; i++) {
86 for (var i=0; i < output_msg_types.length; i++) {
82 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
87 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
83 }
88 }
84 };
89 };
85
90
86 /**
91 /**
87 * Start the Python kernel
92 * Start the Python kernel
88 * @method start
93 * @method start
89 */
94 */
90 Kernel.prototype.start = function (params) {
95 Kernel.prototype.start = function (params) {
91 params = params || {};
96 params = params || {};
92 if (!this.running) {
97 if (!this.running) {
93 var qs = $.param(params);
98 var qs = $.param(params);
94 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
99 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
95 $.proxy(this._kernel_started, this),
100 $.proxy(this._kernel_started, this),
96 'json'
101 'json'
97 );
102 );
98 }
103 }
99 };
104 };
100
105
101 /**
106 /**
102 * Restart the python kernel.
107 * Restart the python kernel.
103 *
108 *
104 * Emit a 'status_restarting.Kernel' event with
109 * Emit a 'status_restarting.Kernel' event with
105 * the current object as parameter
110 * the current object as parameter
106 *
111 *
107 * @method restart
112 * @method restart
108 */
113 */
109 Kernel.prototype.restart = function () {
114 Kernel.prototype.restart = function () {
110 this.events.trigger('status_restarting.Kernel', {kernel: this});
115 this.events.trigger('status_restarting.Kernel', {kernel: this});
111 if (this.running) {
116 if (this.running) {
112 this.stop_channels();
117 this.stop_channels();
113 this.post(utils.url_join_encode(this.kernel_url, "restart"),
118 this.post(utils.url_join_encode(this.kernel_url, "restart"),
114 $.proxy(this._kernel_started, this),
119 $.proxy(this._kernel_started, this),
115 'json'
120 'json'
116 );
121 );
117 }
122 }
118 };
123 };
119
124
120
125
121 Kernel.prototype._kernel_started = function (json) {
126 Kernel.prototype._kernel_started = function (json) {
122 console.log("Kernel started: ", json.id);
127 console.log("Kernel started: ", json.id);
123 this.running = true;
128 this.running = true;
124 this.kernel_id = json.id;
129 this.kernel_id = json.id;
125 // trailing 's' in https will become wss for secure web sockets
126 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
127 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
130 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
128 this.start_channels();
131 this.start_channels();
129 };
132 };
130
133
131
134
132 Kernel.prototype._websocket_closed = function(ws_url, early) {
135 Kernel.prototype._websocket_closed = function(ws_url, early) {
133 this.stop_channels();
136 this.stop_channels();
134 this.events.trigger('websocket_closed.Kernel',
137 this.events.trigger('websocket_closed.Kernel',
135 {ws_url: ws_url, kernel: this, early: early}
138 {ws_url: ws_url, kernel: this, early: early}
136 );
139 );
137 };
140 };
138
141
139 /**
142 /**
140 * Start the `shell`and `iopub` channels.
143 * Start the `shell`and `iopub` channels.
141 * Will stop and restart them if they already exist.
144 * Will stop and restart them if they already exist.
142 *
145 *
143 * @method start_channels
146 * @method start_channels
144 */
147 */
145 Kernel.prototype.start_channels = function () {
148 Kernel.prototype.start_channels = function () {
146 var that = this;
149 var that = this;
147 this.stop_channels();
150 this.stop_channels();
148 var ws_host_url = this.ws_host + this.kernel_url;
151 var ws_host_url = this.ws_url + this.kernel_url;
149 console.log("Starting WebSockets:", ws_host_url);
152 console.log("Starting WebSockets:", ws_host_url);
150 this.shell_channel = new this.WebSocket(
153 this.shell_channel = new this.WebSocket(
151 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
154 this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
152 );
155 );
153 this.stdin_channel = new this.WebSocket(
156 this.stdin_channel = new this.WebSocket(
154 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
157 this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
155 );
158 );
156 this.iopub_channel = new this.WebSocket(
159 this.iopub_channel = new this.WebSocket(
157 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
160 this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
158 );
161 );
159
162
160 var already_called_onclose = false; // only alert once
163 var already_called_onclose = false; // only alert once
161 var ws_closed_early = function(evt){
164 var ws_closed_early = function(evt){
162 if (already_called_onclose){
165 if (already_called_onclose){
163 return;
166 return;
164 }
167 }
165 already_called_onclose = true;
168 already_called_onclose = true;
166 if ( ! evt.wasClean ){
169 if ( ! evt.wasClean ){
167 that._websocket_closed(ws_host_url, true);
170 that._websocket_closed(ws_host_url, true);
168 }
171 }
169 };
172 };
170 var ws_closed_late = function(evt){
173 var ws_closed_late = function(evt){
171 if (already_called_onclose){
174 if (already_called_onclose){
172 return;
175 return;
173 }
176 }
174 already_called_onclose = true;
177 already_called_onclose = true;
175 if ( ! evt.wasClean ){
178 if ( ! evt.wasClean ){
176 that._websocket_closed(ws_host_url, false);
179 that._websocket_closed(ws_host_url, false);
177 }
180 }
178 };
181 };
179 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
182 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
180 for (var i=0; i < channels.length; i++) {
183 for (var i=0; i < channels.length; i++) {
181 channels[i].onopen = $.proxy(this._ws_opened, this);
184 channels[i].onopen = $.proxy(this._ws_opened, this);
182 channels[i].onclose = ws_closed_early;
185 channels[i].onclose = ws_closed_early;
183 }
186 }
184 // switch from early-close to late-close message after 1s
187 // switch from early-close to late-close message after 1s
185 setTimeout(function() {
188 setTimeout(function() {
186 for (var i=0; i < channels.length; i++) {
189 for (var i=0; i < channels.length; i++) {
187 if (channels[i] !== null) {
190 if (channels[i] !== null) {
188 channels[i].onclose = ws_closed_late;
191 channels[i].onclose = ws_closed_late;
189 }
192 }
190 }
193 }
191 }, 1000);
194 }, 1000);
192 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
195 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
193 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
196 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
194 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
197 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
195 };
198 };
196
199
197 /**
200 /**
198 * Handle a websocket entering the open state
201 * Handle a websocket entering the open state
199 * sends session and cookie authentication info as first message.
202 * sends session and cookie authentication info as first message.
200 * Once all sockets are open, signal the Kernel.status_started event.
203 * Once all sockets are open, signal the Kernel.status_started event.
201 * @method _ws_opened
204 * @method _ws_opened
202 */
205 */
203 Kernel.prototype._ws_opened = function (evt) {
206 Kernel.prototype._ws_opened = function (evt) {
204 // send the session id so the Session object Python-side
207 // send the session id so the Session object Python-side
205 // has the same identity
208 // has the same identity
206 evt.target.send(this.session_id + ':' + document.cookie);
209 evt.target.send(this.session_id + ':' + document.cookie);
207
210
208 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
211 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
209 for (var i=0; i < channels.length; i++) {
212 for (var i=0; i < channels.length; i++) {
210 // if any channel is not ready, don't trigger event.
213 // if any channel is not ready, don't trigger event.
211 if ( !channels[i].readyState ) return;
214 if ( !channels[i].readyState ) return;
212 }
215 }
213 // all events ready, trigger started event.
216 // all events ready, trigger started event.
214 this.events.trigger('status_started.Kernel', {kernel: this});
217 this.events.trigger('status_started.Kernel', {kernel: this});
215 };
218 };
216
219
217 /**
220 /**
218 * Stop the websocket channels.
221 * Stop the websocket channels.
219 * @method stop_channels
222 * @method stop_channels
220 */
223 */
221 Kernel.prototype.stop_channels = function () {
224 Kernel.prototype.stop_channels = function () {
222 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
225 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
223 for (var i=0; i < channels.length; i++) {
226 for (var i=0; i < channels.length; i++) {
224 if ( channels[i] !== null ) {
227 if ( channels[i] !== null ) {
225 channels[i].onclose = null;
228 channels[i].onclose = null;
226 channels[i].close();
229 channels[i].close();
227 }
230 }
228 }
231 }
229 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
232 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
230 };
233 };
231
234
232 // Main public methods.
235 // Main public methods.
233
236
234 // send a message on the Kernel's shell channel
237 // send a message on the Kernel's shell channel
235 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
238 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
236 var msg = this._get_msg(msg_type, content, metadata);
239 var msg = this._get_msg(msg_type, content, metadata);
237 this.shell_channel.send(JSON.stringify(msg));
240 this.shell_channel.send(JSON.stringify(msg));
238 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
241 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
239 return msg.header.msg_id;
242 return msg.header.msg_id;
240 };
243 };
241
244
242 /**
245 /**
243 * Get kernel info
246 * Get kernel info
244 *
247 *
245 * @param callback {function}
248 * @param callback {function}
246 * @method kernel_info
249 * @method kernel_info
247 *
250 *
248 * When calling this method, pass a callback function that expects one argument.
251 * When calling this method, pass a callback function that expects one argument.
249 * The callback will be passed the complete `kernel_info_reply` message documented
252 * The callback will be passed the complete `kernel_info_reply` message documented
250 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
253 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
251 */
254 */
252 Kernel.prototype.kernel_info = function (callback) {
255 Kernel.prototype.kernel_info = function (callback) {
253 var callbacks;
256 var callbacks;
254 if (callback) {
257 if (callback) {
255 callbacks = { shell : { reply : callback } };
258 callbacks = { shell : { reply : callback } };
256 }
259 }
257 return this.send_shell_message("kernel_info_request", {}, callbacks);
260 return this.send_shell_message("kernel_info_request", {}, callbacks);
258 };
261 };
259
262
260 /**
263 /**
261 * Get info on an object
264 * Get info on an object
262 *
265 *
263 * @param code {string}
266 * @param code {string}
264 * @param cursor_pos {integer}
267 * @param cursor_pos {integer}
265 * @param callback {function}
268 * @param callback {function}
266 * @method inspect
269 * @method inspect
267 *
270 *
268 * When calling this method, pass a callback function that expects one argument.
271 * When calling this method, pass a callback function that expects one argument.
269 * The callback will be passed the complete `inspect_reply` message documented
272 * The callback will be passed the complete `inspect_reply` message documented
270 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
271 */
274 */
272 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
275 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
273 var callbacks;
276 var callbacks;
274 if (callback) {
277 if (callback) {
275 callbacks = { shell : { reply : callback } };
278 callbacks = { shell : { reply : callback } };
276 }
279 }
277
280
278 var content = {
281 var content = {
279 code : code,
282 code : code,
280 cursor_pos : cursor_pos,
283 cursor_pos : cursor_pos,
281 detail_level : 0,
284 detail_level : 0,
282 };
285 };
283 return this.send_shell_message("inspect_request", content, callbacks);
286 return this.send_shell_message("inspect_request", content, callbacks);
284 };
287 };
285
288
286 /**
289 /**
287 * Execute given code into kernel, and pass result to callback.
290 * Execute given code into kernel, and pass result to callback.
288 *
291 *
289 * @async
292 * @async
290 * @method execute
293 * @method execute
291 * @param {string} code
294 * @param {string} code
292 * @param [callbacks] {Object} With the following keys (all optional)
295 * @param [callbacks] {Object} With the following keys (all optional)
293 * @param callbacks.shell.reply {function}
296 * @param callbacks.shell.reply {function}
294 * @param callbacks.shell.payload.[payload_name] {function}
297 * @param callbacks.shell.payload.[payload_name] {function}
295 * @param callbacks.iopub.output {function}
298 * @param callbacks.iopub.output {function}
296 * @param callbacks.iopub.clear_output {function}
299 * @param callbacks.iopub.clear_output {function}
297 * @param callbacks.input {function}
300 * @param callbacks.input {function}
298 * @param {object} [options]
301 * @param {object} [options]
299 * @param [options.silent=false] {Boolean}
302 * @param [options.silent=false] {Boolean}
300 * @param [options.user_expressions=empty_dict] {Dict}
303 * @param [options.user_expressions=empty_dict] {Dict}
301 * @param [options.allow_stdin=false] {Boolean} true|false
304 * @param [options.allow_stdin=false] {Boolean} true|false
302 *
305 *
303 * @example
306 * @example
304 *
307 *
305 * The options object should contain the options for the execute call. Its default
308 * The options object should contain the options for the execute call. Its default
306 * values are:
309 * values are:
307 *
310 *
308 * options = {
311 * options = {
309 * silent : true,
312 * silent : true,
310 * user_expressions : {},
313 * user_expressions : {},
311 * allow_stdin : false
314 * allow_stdin : false
312 * }
315 * }
313 *
316 *
314 * When calling this method pass a callbacks structure of the form:
317 * When calling this method pass a callbacks structure of the form:
315 *
318 *
316 * callbacks = {
319 * callbacks = {
317 * shell : {
320 * shell : {
318 * reply : execute_reply_callback,
321 * reply : execute_reply_callback,
319 * payload : {
322 * payload : {
320 * set_next_input : set_next_input_callback,
323 * set_next_input : set_next_input_callback,
321 * }
324 * }
322 * },
325 * },
323 * iopub : {
326 * iopub : {
324 * output : output_callback,
327 * output : output_callback,
325 * clear_output : clear_output_callback,
328 * clear_output : clear_output_callback,
326 * },
329 * },
327 * input : raw_input_callback
330 * input : raw_input_callback
328 * }
331 * }
329 *
332 *
330 * Each callback will be passed the entire message as a single arugment.
333 * Each callback will be passed the entire message as a single arugment.
331 * Payload handlers will be passed the corresponding payload and the execute_reply message.
334 * Payload handlers will be passed the corresponding payload and the execute_reply message.
332 */
335 */
333 Kernel.prototype.execute = function (code, callbacks, options) {
336 Kernel.prototype.execute = function (code, callbacks, options) {
334
337
335 var content = {
338 var content = {
336 code : code,
339 code : code,
337 silent : true,
340 silent : true,
338 store_history : false,
341 store_history : false,
339 user_expressions : {},
342 user_expressions : {},
340 allow_stdin : false
343 allow_stdin : false
341 };
344 };
342 callbacks = callbacks || {};
345 callbacks = callbacks || {};
343 if (callbacks.input !== undefined) {
346 if (callbacks.input !== undefined) {
344 content.allow_stdin = true;
347 content.allow_stdin = true;
345 }
348 }
346 $.extend(true, content, options);
349 $.extend(true, content, options);
347 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
350 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
348 return this.send_shell_message("execute_request", content, callbacks);
351 return this.send_shell_message("execute_request", content, callbacks);
349 };
352 };
350
353
351 /**
354 /**
352 * When calling this method, pass a function to be called with the `complete_reply` message
355 * When calling this method, pass a function to be called with the `complete_reply` message
353 * as its only argument when it arrives.
356 * as its only argument when it arrives.
354 *
357 *
355 * `complete_reply` is documented
358 * `complete_reply` is documented
356 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
359 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
357 *
360 *
358 * @method complete
361 * @method complete
359 * @param code {string}
362 * @param code {string}
360 * @param cursor_pos {integer}
363 * @param cursor_pos {integer}
361 * @param callback {function}
364 * @param callback {function}
362 *
365 *
363 */
366 */
364 Kernel.prototype.complete = function (code, cursor_pos, callback) {
367 Kernel.prototype.complete = function (code, cursor_pos, callback) {
365 var callbacks;
368 var callbacks;
366 if (callback) {
369 if (callback) {
367 callbacks = { shell : { reply : callback } };
370 callbacks = { shell : { reply : callback } };
368 }
371 }
369 var content = {
372 var content = {
370 code : code,
373 code : code,
371 cursor_pos : cursor_pos,
374 cursor_pos : cursor_pos,
372 };
375 };
373 return this.send_shell_message("complete_request", content, callbacks);
376 return this.send_shell_message("complete_request", content, callbacks);
374 };
377 };
375
378
376
379
377 Kernel.prototype.interrupt = function () {
380 Kernel.prototype.interrupt = function () {
378 if (this.running) {
381 if (this.running) {
379 this.events.trigger('status_interrupting.Kernel', {kernel: this});
382 this.events.trigger('status_interrupting.Kernel', {kernel: this});
380 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
383 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
381 }
384 }
382 };
385 };
383
386
384
387
385 Kernel.prototype.kill = function () {
388 Kernel.prototype.kill = function () {
386 if (this.running) {
389 if (this.running) {
387 this.running = false;
390 this.running = false;
388 var settings = {
391 var settings = {
389 cache : false,
392 cache : false,
390 type : "DELETE",
393 type : "DELETE",
391 error : utils.log_ajax_error,
394 error : utils.log_ajax_error,
392 };
395 };
393 $.ajax(utils.url_join_encode(this.kernel_url), settings);
396 $.ajax(utils.url_join_encode(this.kernel_url), settings);
394 }
397 }
395 };
398 };
396
399
397 Kernel.prototype.send_input_reply = function (input) {
400 Kernel.prototype.send_input_reply = function (input) {
398 var content = {
401 var content = {
399 value : input,
402 value : input,
400 };
403 };
401 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
404 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
402 var msg = this._get_msg("input_reply", content);
405 var msg = this._get_msg("input_reply", content);
403 this.stdin_channel.send(JSON.stringify(msg));
406 this.stdin_channel.send(JSON.stringify(msg));
404 return msg.header.msg_id;
407 return msg.header.msg_id;
405 };
408 };
406
409
407
410
408 // Reply handlers
411 // Reply handlers
409
412
410 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
413 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
411 this._iopub_handlers[msg_type] = callback;
414 this._iopub_handlers[msg_type] = callback;
412 };
415 };
413
416
414 Kernel.prototype.get_iopub_handler = function (msg_type) {
417 Kernel.prototype.get_iopub_handler = function (msg_type) {
415 // get iopub handler for a specific message type
418 // get iopub handler for a specific message type
416 return this._iopub_handlers[msg_type];
419 return this._iopub_handlers[msg_type];
417 };
420 };
418
421
419
422
420 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
423 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
421 // get callbacks for a specific message
424 // get callbacks for a specific message
422 if (msg_id == this.last_msg_id) {
425 if (msg_id == this.last_msg_id) {
423 return this.last_msg_callbacks;
426 return this.last_msg_callbacks;
424 } else {
427 } else {
425 return this._msg_callbacks[msg_id];
428 return this._msg_callbacks[msg_id];
426 }
429 }
427 };
430 };
428
431
429
432
430 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
433 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
431 if (this._msg_callbacks[msg_id] !== undefined ) {
434 if (this._msg_callbacks[msg_id] !== undefined ) {
432 delete this._msg_callbacks[msg_id];
435 delete this._msg_callbacks[msg_id];
433 }
436 }
434 };
437 };
435
438
436 Kernel.prototype._finish_shell = function (msg_id) {
439 Kernel.prototype._finish_shell = function (msg_id) {
437 var callbacks = this._msg_callbacks[msg_id];
440 var callbacks = this._msg_callbacks[msg_id];
438 if (callbacks !== undefined) {
441 if (callbacks !== undefined) {
439 callbacks.shell_done = true;
442 callbacks.shell_done = true;
440 if (callbacks.iopub_done) {
443 if (callbacks.iopub_done) {
441 this.clear_callbacks_for_msg(msg_id);
444 this.clear_callbacks_for_msg(msg_id);
442 }
445 }
443 }
446 }
444 };
447 };
445
448
446 Kernel.prototype._finish_iopub = function (msg_id) {
449 Kernel.prototype._finish_iopub = function (msg_id) {
447 var callbacks = this._msg_callbacks[msg_id];
450 var callbacks = this._msg_callbacks[msg_id];
448 if (callbacks !== undefined) {
451 if (callbacks !== undefined) {
449 callbacks.iopub_done = true;
452 callbacks.iopub_done = true;
450 if (callbacks.shell_done) {
453 if (callbacks.shell_done) {
451 this.clear_callbacks_for_msg(msg_id);
454 this.clear_callbacks_for_msg(msg_id);
452 }
455 }
453 }
456 }
454 };
457 };
455
458
456 /* Set callbacks for a particular message.
459 /* Set callbacks for a particular message.
457 * Callbacks should be a struct of the following form:
460 * Callbacks should be a struct of the following form:
458 * shell : {
461 * shell : {
459 *
462 *
460 * }
463 * }
461
464
462 */
465 */
463 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
466 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
464 this.last_msg_id = msg_id;
467 this.last_msg_id = msg_id;
465 if (callbacks) {
468 if (callbacks) {
466 // shallow-copy mapping, because we will modify it at the top level
469 // shallow-copy mapping, because we will modify it at the top level
467 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
470 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
468 cbcopy.shell = callbacks.shell;
471 cbcopy.shell = callbacks.shell;
469 cbcopy.iopub = callbacks.iopub;
472 cbcopy.iopub = callbacks.iopub;
470 cbcopy.input = callbacks.input;
473 cbcopy.input = callbacks.input;
471 cbcopy.shell_done = (!callbacks.shell);
474 cbcopy.shell_done = (!callbacks.shell);
472 cbcopy.iopub_done = (!callbacks.iopub);
475 cbcopy.iopub_done = (!callbacks.iopub);
473 } else {
476 } else {
474 this.last_msg_callbacks = {};
477 this.last_msg_callbacks = {};
475 }
478 }
476 };
479 };
477
480
478
481
479 Kernel.prototype._handle_shell_reply = function (e) {
482 Kernel.prototype._handle_shell_reply = function (e) {
480 var reply = $.parseJSON(e.data);
483 var reply = $.parseJSON(e.data);
481 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
484 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
482 var content = reply.content;
485 var content = reply.content;
483 var metadata = reply.metadata;
486 var metadata = reply.metadata;
484 var parent_id = reply.parent_header.msg_id;
487 var parent_id = reply.parent_header.msg_id;
485 var callbacks = this.get_callbacks_for_msg(parent_id);
488 var callbacks = this.get_callbacks_for_msg(parent_id);
486 if (!callbacks || !callbacks.shell) {
489 if (!callbacks || !callbacks.shell) {
487 return;
490 return;
488 }
491 }
489 var shell_callbacks = callbacks.shell;
492 var shell_callbacks = callbacks.shell;
490
493
491 // signal that shell callbacks are done
494 // signal that shell callbacks are done
492 this._finish_shell(parent_id);
495 this._finish_shell(parent_id);
493
496
494 if (shell_callbacks.reply !== undefined) {
497 if (shell_callbacks.reply !== undefined) {
495 shell_callbacks.reply(reply);
498 shell_callbacks.reply(reply);
496 }
499 }
497 if (content.payload && shell_callbacks.payload) {
500 if (content.payload && shell_callbacks.payload) {
498 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
501 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
499 }
502 }
500 };
503 };
501
504
502
505
503 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
506 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
504 var l = payloads.length;
507 var l = payloads.length;
505 // Payloads are handled by triggering events because we don't want the Kernel
508 // Payloads are handled by triggering events because we don't want the Kernel
506 // to depend on the Notebook or Pager classes.
509 // to depend on the Notebook or Pager classes.
507 for (var i=0; i<l; i++) {
510 for (var i=0; i<l; i++) {
508 var payload = payloads[i];
511 var payload = payloads[i];
509 var callback = payload_callbacks[payload.source];
512 var callback = payload_callbacks[payload.source];
510 if (callback) {
513 if (callback) {
511 callback(payload, msg);
514 callback(payload, msg);
512 }
515 }
513 }
516 }
514 };
517 };
515
518
516 Kernel.prototype._handle_status_message = function (msg) {
519 Kernel.prototype._handle_status_message = function (msg) {
517 var execution_state = msg.content.execution_state;
520 var execution_state = msg.content.execution_state;
518 var parent_id = msg.parent_header.msg_id;
521 var parent_id = msg.parent_header.msg_id;
519
522
520 // dispatch status msg callbacks, if any
523 // dispatch status msg callbacks, if any
521 var callbacks = this.get_callbacks_for_msg(parent_id);
524 var callbacks = this.get_callbacks_for_msg(parent_id);
522 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
525 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
523 try {
526 try {
524 callbacks.iopub.status(msg);
527 callbacks.iopub.status(msg);
525 } catch (e) {
528 } catch (e) {
526 console.log("Exception in status msg handler", e, e.stack);
529 console.log("Exception in status msg handler", e, e.stack);
527 }
530 }
528 }
531 }
529
532
530 if (execution_state === 'busy') {
533 if (execution_state === 'busy') {
531 this.events.trigger('status_busy.Kernel', {kernel: this});
534 this.events.trigger('status_busy.Kernel', {kernel: this});
532 } else if (execution_state === 'idle') {
535 } else if (execution_state === 'idle') {
533 // signal that iopub callbacks are (probably) done
536 // signal that iopub callbacks are (probably) done
534 // async output may still arrive,
537 // async output may still arrive,
535 // but only for the most recent request
538 // but only for the most recent request
536 this._finish_iopub(parent_id);
539 this._finish_iopub(parent_id);
537
540
538 // trigger status_idle event
541 // trigger status_idle event
539 this.events.trigger('status_idle.Kernel', {kernel: this});
542 this.events.trigger('status_idle.Kernel', {kernel: this});
540 } else if (execution_state === 'restarting') {
543 } else if (execution_state === 'restarting') {
541 // autorestarting is distinct from restarting,
544 // autorestarting is distinct from restarting,
542 // in that it means the kernel died and the server is restarting it.
545 // in that it means the kernel died and the server is restarting it.
543 // status_restarting sets the notification widget,
546 // status_restarting sets the notification widget,
544 // autorestart shows the more prominent dialog.
547 // autorestart shows the more prominent dialog.
545 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
548 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
546 this.events.trigger('status_restarting.Kernel', {kernel: this});
549 this.events.trigger('status_restarting.Kernel', {kernel: this});
547 } else if (execution_state === 'dead') {
550 } else if (execution_state === 'dead') {
548 this.stop_channels();
551 this.stop_channels();
549 this.events.trigger('status_dead.Kernel', {kernel: this});
552 this.events.trigger('status_dead.Kernel', {kernel: this});
550 }
553 }
551 };
554 };
552
555
553
556
554 // handle clear_output message
557 // handle clear_output message
555 Kernel.prototype._handle_clear_output = function (msg) {
558 Kernel.prototype._handle_clear_output = function (msg) {
556 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
559 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
557 if (!callbacks || !callbacks.iopub) {
560 if (!callbacks || !callbacks.iopub) {
558 return;
561 return;
559 }
562 }
560 var callback = callbacks.iopub.clear_output;
563 var callback = callbacks.iopub.clear_output;
561 if (callback) {
564 if (callback) {
562 callback(msg);
565 callback(msg);
563 }
566 }
564 };
567 };
565
568
566
569
567 // handle an output message (execute_result, display_data, etc.)
570 // handle an output message (execute_result, display_data, etc.)
568 Kernel.prototype._handle_output_message = function (msg) {
571 Kernel.prototype._handle_output_message = function (msg) {
569 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
572 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
570 if (!callbacks || !callbacks.iopub) {
573 if (!callbacks || !callbacks.iopub) {
571 return;
574 return;
572 }
575 }
573 var callback = callbacks.iopub.output;
576 var callback = callbacks.iopub.output;
574 if (callback) {
577 if (callback) {
575 callback(msg);
578 callback(msg);
576 }
579 }
577 };
580 };
578
581
579 // dispatch IOPub messages to respective handlers.
582 // dispatch IOPub messages to respective handlers.
580 // each message type should have a handler.
583 // each message type should have a handler.
581 Kernel.prototype._handle_iopub_message = function (e) {
584 Kernel.prototype._handle_iopub_message = function (e) {
582 var msg = $.parseJSON(e.data);
585 var msg = $.parseJSON(e.data);
583
586
584 var handler = this.get_iopub_handler(msg.header.msg_type);
587 var handler = this.get_iopub_handler(msg.header.msg_type);
585 if (handler !== undefined) {
588 if (handler !== undefined) {
586 handler(msg);
589 handler(msg);
587 }
590 }
588 };
591 };
589
592
590
593
591 Kernel.prototype._handle_input_request = function (e) {
594 Kernel.prototype._handle_input_request = function (e) {
592 var request = $.parseJSON(e.data);
595 var request = $.parseJSON(e.data);
593 var header = request.header;
596 var header = request.header;
594 var content = request.content;
597 var content = request.content;
595 var metadata = request.metadata;
598 var metadata = request.metadata;
596 var msg_type = header.msg_type;
599 var msg_type = header.msg_type;
597 if (msg_type !== 'input_request') {
600 if (msg_type !== 'input_request') {
598 console.log("Invalid input request!", request);
601 console.log("Invalid input request!", request);
599 return;
602 return;
600 }
603 }
601 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
604 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
602 if (callbacks) {
605 if (callbacks) {
603 if (callbacks.input) {
606 if (callbacks.input) {
604 callbacks.input(request);
607 callbacks.input(request);
605 }
608 }
606 }
609 }
607 };
610 };
608
611
609 // Backwards compatability.
612 // Backwards compatability.
610 IPython.Kernel = Kernel;
613 IPython.Kernel = Kernel;
611
614
612 return {'Kernel': Kernel};
615 return {'Kernel': Kernel};
613 });
616 });
@@ -1,320 +1,321 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16
16
17 {{super()}}
17 {{super()}}
18
18
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20
20
21 {% endblock %}
21 {% endblock %}
22
22
23 {% block params %}
23 {% block params %}
24
24
25 data-project="{{project}}"
25 data-project="{{project}}"
26 data-base-url="{{base_url}}"
26 data-base-url="{{base_url}}"
27 data-ws-url="{{ws_url}}"
27 data-notebook-name="{{notebook_name}}"
28 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
29 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
30 class="notebook_app"
30
31
31 {% endblock %}
32 {% endblock %}
32
33
33
34
34 {% block header %}
35 {% block header %}
35
36
36 <span id="save_widget" class="nav pull-left">
37 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
38 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
39 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
40 <span id="autosave_status"></span>
40 </span>
41 </span>
41
42
42 {% endblock %}
43 {% endblock %}
43
44
44
45
45 {% block site %}
46 {% block site %}
46
47
47 <div id="menubar-container" class="container">
48 <div id="menubar-container" class="container">
48 <div id="menubar">
49 <div id="menubar">
49 <div id="menus" class="navbar navbar-default" role="navigation">
50 <div id="menus" class="navbar navbar-default" role="navigation">
50 <div class="container-fluid">
51 <div class="container-fluid">
51 <ul class="nav navbar-nav">
52 <ul class="nav navbar-nav">
52 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 <ul id="file_menu" class="dropdown-menu">
54 <ul id="file_menu" class="dropdown-menu">
54 <li id="new_notebook"
55 <li id="new_notebook"
55 title="Make a new notebook (Opens a new window)">
56 title="Make a new notebook (Opens a new window)">
56 <a href="#">New</a></li>
57 <a href="#">New</a></li>
57 <li id="open_notebook"
58 <li id="open_notebook"
58 title="Opens a new window with the Dashboard view">
59 title="Opens a new window with the Dashboard view">
59 <a href="#">Open...</a></li>
60 <a href="#">Open...</a></li>
60 <!-- <hr/> -->
61 <!-- <hr/> -->
61 <li class="divider"></li>
62 <li class="divider"></li>
62 <li id="copy_notebook"
63 <li id="copy_notebook"
63 title="Open a copy of this notebook's contents and start a new kernel">
64 title="Open a copy of this notebook's contents and start a new kernel">
64 <a href="#">Make a Copy...</a></li>
65 <a href="#">Make a Copy...</a></li>
65 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 <!-- <hr/> -->
68 <!-- <hr/> -->
68 <li class="divider"></li>
69 <li class="divider"></li>
69 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 <ul class="dropdown-menu">
71 <ul class="dropdown-menu">
71 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
76 </ul>
77 </ul>
77 </li>
78 </li>
78 <li class="divider"></li>
79 <li class="divider"></li>
79 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 <ul class="dropdown-menu">
82 <ul class="dropdown-menu">
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
84 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
87 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
87 </ul>
88 </ul>
88 </li>
89 </li>
89 <li class="divider"></li>
90 <li class="divider"></li>
90 <li id="trust_notebook"
91 <li id="trust_notebook"
91 title="Trust the output of this notebook">
92 title="Trust the output of this notebook">
92 <a href="#" >Trust Notebook</a></li>
93 <a href="#" >Trust Notebook</a></li>
93 <li class="divider"></li>
94 <li class="divider"></li>
94 <li id="kill_and_exit"
95 <li id="kill_and_exit"
95 title="Shutdown this notebook's kernel, and close this window">
96 title="Shutdown this notebook's kernel, and close this window">
96 <a href="#" >Close and halt</a></li>
97 <a href="#" >Close and halt</a></li>
97 </ul>
98 </ul>
98 </li>
99 </li>
99 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
100 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
100 <ul id="edit_menu" class="dropdown-menu">
101 <ul id="edit_menu" class="dropdown-menu">
101 <li id="cut_cell"><a href="#">Cut Cell</a></li>
102 <li id="cut_cell"><a href="#">Cut Cell</a></li>
102 <li id="copy_cell"><a href="#">Copy Cell</a></li>
103 <li id="copy_cell"><a href="#">Copy Cell</a></li>
103 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
104 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
104 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
105 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
105 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
106 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
106 <li id="delete_cell"><a href="#">Delete Cell</a></li>
107 <li id="delete_cell"><a href="#">Delete Cell</a></li>
107 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
108 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
108 <li class="divider"></li>
109 <li class="divider"></li>
109 <li id="split_cell"><a href="#">Split Cell</a></li>
110 <li id="split_cell"><a href="#">Split Cell</a></li>
110 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
111 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
111 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
112 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
112 <li class="divider"></li>
113 <li class="divider"></li>
113 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
114 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
114 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
115 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
115 <li class="divider"></li>
116 <li class="divider"></li>
116 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 </ul>
118 </ul>
118 </li>
119 </li>
119 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 <ul id="view_menu" class="dropdown-menu">
121 <ul id="view_menu" class="dropdown-menu">
121 <li id="toggle_header"
122 <li id="toggle_header"
122 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 <a href="#">Toggle Header</a></li>
124 <a href="#">Toggle Header</a></li>
124 <li id="toggle_toolbar"
125 <li id="toggle_toolbar"
125 title="Show/Hide the action icons (below menu bar)">
126 title="Show/Hide the action icons (below menu bar)">
126 <a href="#">Toggle Toolbar</a></li>
127 <a href="#">Toggle Toolbar</a></li>
127 </ul>
128 </ul>
128 </li>
129 </li>
129 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 <ul id="insert_menu" class="dropdown-menu">
131 <ul id="insert_menu" class="dropdown-menu">
131 <li id="insert_cell_above"
132 <li id="insert_cell_above"
132 title="Insert an empty Code cell above the currently active cell">
133 title="Insert an empty Code cell above the currently active cell">
133 <a href="#">Insert Cell Above</a></li>
134 <a href="#">Insert Cell Above</a></li>
134 <li id="insert_cell_below"
135 <li id="insert_cell_below"
135 title="Insert an empty Code cell below the currently active cell">
136 title="Insert an empty Code cell below the currently active cell">
136 <a href="#">Insert Cell Below</a></li>
137 <a href="#">Insert Cell Below</a></li>
137 </ul>
138 </ul>
138 </li>
139 </li>
139 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 <ul id="cell_menu" class="dropdown-menu">
141 <ul id="cell_menu" class="dropdown-menu">
141 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 <a href="#">Run</a></li>
143 <a href="#">Run</a></li>
143 <li id="run_cell_select_below" title="Run this cell, select below">
144 <li id="run_cell_select_below" title="Run this cell, select below">
144 <a href="#">Run and Select Below</a></li>
145 <a href="#">Run and Select Below</a></li>
145 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 <a href="#">Run and Insert Below</a></li>
147 <a href="#">Run and Insert Below</a></li>
147 <li id="run_all_cells" title="Run all cells in the notebook">
148 <li id="run_all_cells" title="Run all cells in the notebook">
148 <a href="#">Run All</a></li>
149 <a href="#">Run All</a></li>
149 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 <a href="#">Run All Above</a></li>
151 <a href="#">Run All Above</a></li>
151 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 <a href="#">Run All Below</a></li>
153 <a href="#">Run All Below</a></li>
153 <li class="divider"></li>
154 <li class="divider"></li>
154 <li id="change_cell_type" class="dropdown-submenu"
155 <li id="change_cell_type" class="dropdown-submenu"
155 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 <a href="#">Cell Type</a>
157 <a href="#">Cell Type</a>
157 <ul class="dropdown-menu">
158 <ul class="dropdown-menu">
158 <li id="to_code"
159 <li id="to_code"
159 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
160 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
160 <a href="#">Code</a></li>
161 <a href="#">Code</a></li>
161 <li id="to_markdown"
162 <li id="to_markdown"
162 title="Contents will be rendered as HTML and serve as explanatory text">
163 title="Contents will be rendered as HTML and serve as explanatory text">
163 <a href="#">Markdown</a></li>
164 <a href="#">Markdown</a></li>
164 <li id="to_raw"
165 <li id="to_raw"
165 title="Contents will pass through nbconvert unmodified">
166 title="Contents will pass through nbconvert unmodified">
166 <a href="#">Raw NBConvert</a></li>
167 <a href="#">Raw NBConvert</a></li>
167 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 </ul>
174 </ul>
174 </li>
175 </li>
175 <li class="divider"></li>
176 <li class="divider"></li>
176 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
177 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
177 <ul class="dropdown-menu">
178 <ul class="dropdown-menu">
178 <li id="toggle_current_output"
179 <li id="toggle_current_output"
179 title="Hide/Show the output of the current cell">
180 title="Hide/Show the output of the current cell">
180 <a href="#">Toggle</a>
181 <a href="#">Toggle</a>
181 </li>
182 </li>
182 <li id="toggle_current_output_scroll"
183 <li id="toggle_current_output_scroll"
183 title="Scroll the output of the current cell">
184 title="Scroll the output of the current cell">
184 <a href="#">Toggle Scrolling</a>
185 <a href="#">Toggle Scrolling</a>
185 </li>
186 </li>
186 <li id="clear_current_output"
187 <li id="clear_current_output"
187 title="Clear the output of the current cell">
188 title="Clear the output of the current cell">
188 <a href="#">Clear</a>
189 <a href="#">Clear</a>
189 </li>
190 </li>
190 </ul>
191 </ul>
191 </li>
192 </li>
192 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
193 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
193 <ul class="dropdown-menu">
194 <ul class="dropdown-menu">
194 <li id="toggle_all_output"
195 <li id="toggle_all_output"
195 title="Hide/Show the output of all cells">
196 title="Hide/Show the output of all cells">
196 <a href="#">Toggle</a>
197 <a href="#">Toggle</a>
197 </li>
198 </li>
198 <li id="toggle_all_output_scroll"
199 <li id="toggle_all_output_scroll"
199 title="Scroll the output of all cells">
200 title="Scroll the output of all cells">
200 <a href="#">Toggle Scrolling</a>
201 <a href="#">Toggle Scrolling</a>
201 </li>
202 </li>
202 <li id="clear_all_output"
203 <li id="clear_all_output"
203 title="Clear the output of all cells">
204 title="Clear the output of all cells">
204 <a href="#">Clear</a>
205 <a href="#">Clear</a>
205 </li>
206 </li>
206 </ul>
207 </ul>
207 </li>
208 </li>
208 </ul>
209 </ul>
209 </li>
210 </li>
210 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
211 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
211 <ul id="kernel_menu" class="dropdown-menu">
212 <ul id="kernel_menu" class="dropdown-menu">
212 <li id="int_kernel"
213 <li id="int_kernel"
213 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
214 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
214 <a href="#">Interrupt</a></li>
215 <a href="#">Interrupt</a></li>
215 <li id="restart_kernel"
216 <li id="restart_kernel"
216 title="Restart the Kernel">
217 title="Restart the Kernel">
217 <a href="#">Restart</a></li>
218 <a href="#">Restart</a></li>
218 </ul>
219 </ul>
219 </li>
220 </li>
220 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
221 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
221 <ul id="help_menu" class="dropdown-menu">
222 <ul id="help_menu" class="dropdown-menu">
222 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
223 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
223 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
224 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
224 <li class="divider"></li>
225 <li class="divider"></li>
225 {% set
226 {% set
226 sections = (
227 sections = (
227 (
228 (
228 ("http://ipython.org/documentation.html","IPython Help",True),
229 ("http://ipython.org/documentation.html","IPython Help",True),
229 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
230 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
230 ),(
231 ),(
231 ("http://docs.python.org","Python",True),
232 ("http://docs.python.org","Python",True),
232 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
233 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
233 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 ("http://docs.sympy.org/latest/index.html","SymPy",True),
237 ("http://docs.sympy.org/latest/index.html","SymPy",True),
237 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 )
239 )
239 )
240 )
240 %}
241 %}
241
242
242 {% for helplinks in sections %}
243 {% for helplinks in sections %}
243 {% for link in helplinks %}
244 {% for link in helplinks %}
244 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
245 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
245 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
246 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
246 {{link[1]}}
247 {{link[1]}}
247 </a></li>
248 </a></li>
248 {% endfor %}
249 {% endfor %}
249 {% if not loop.last %}
250 {% if not loop.last %}
250 <li class="divider"></li>
251 <li class="divider"></li>
251 {% endif %}
252 {% endif %}
252 {% endfor %}
253 {% endfor %}
253 </li>
254 </li>
254 </ul>
255 </ul>
255 </li>
256 </li>
256 </ul>
257 </ul>
257 <ul class="nav navbar-nav navbar-right">
258 <ul class="nav navbar-nav navbar-right">
258 <div id="kernel_indicator">
259 <div id="kernel_indicator">
259 <i id="kernel_indicator_icon"></i>
260 <i id="kernel_indicator_icon"></i>
260 </div>
261 </div>
261 <div id="modal_indicator">
262 <div id="modal_indicator">
262 <i id="modal_indicator_icon"></i>
263 <i id="modal_indicator_icon"></i>
263 </div>
264 </div>
264 <div id="notification_area"></div>
265 <div id="notification_area"></div>
265 </ul>
266 </ul>
266 </div>
267 </div>
267 </div>
268 </div>
268 </div>
269 </div>
269 <div id="maintoolbar" class="navbar">
270 <div id="maintoolbar" class="navbar">
270 <div class="toolbar-inner navbar-inner navbar-nobg">
271 <div class="toolbar-inner navbar-inner navbar-nobg">
271 <div id="maintoolbar-container" class="container"></div>
272 <div id="maintoolbar-container" class="container"></div>
272 </div>
273 </div>
273 </div>
274 </div>
274 </div>
275 </div>
275
276
276 <div id="ipython-main-app">
277 <div id="ipython-main-app">
277
278
278 <div id="notebook_panel">
279 <div id="notebook_panel">
279 <div id="notebook"></div>
280 <div id="notebook"></div>
280 <div id="pager_splitter"></div>
281 <div id="pager_splitter"></div>
281 <div id="pager">
282 <div id="pager">
282 <div id='pager_button_area'>
283 <div id='pager_button_area'>
283 </div>
284 </div>
284 <div id="pager-container" class="container"></div>
285 <div id="pager-container" class="container"></div>
285 </div>
286 </div>
286 </div>
287 </div>
287
288
288 </div>
289 </div>
289 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
290 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
290
291
291
292
292 {% endblock %}
293 {% endblock %}
293
294
294
295
295 {% block script %}
296 {% block script %}
296 {{super()}}
297 {{super()}}
297
298
298 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
299 <script type="text/javascript">
300 <script type="text/javascript">
300 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
301 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
301 </script>
302 </script>
302 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
313 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
313 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
314 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
315 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
315 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
316 <script src="{{ static_url("notebook/js/codemirror-ipythongfm.js") }}" charset="utf-8"></script>
317 <script src="{{ static_url("notebook/js/codemirror-ipythongfm.js") }}" charset="utf-8"></script>
317
318
318 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
319 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
319
320
320 {% endblock %}
321 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now