##// END OF EJS Templates
Merge pull request #8196 from quantopian/jinja-vars-from-config...
Min RK -
r21162:e4bf8e7e merge
parent child Browse files
Show More
@@ -1,522 +1,528 b''
1 """Base Tornado handlers for the notebook server."""
1 """Base Tornado handlers for the notebook server."""
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 from tornado import gen
22 from tornado import gen
23 from tornado.log import app_log
23 from tornado.log import app_log
24
24
25
25
26 import IPython
26 import IPython
27 from IPython.utils.sysinfo import get_sys_info
27 from IPython.utils.sysinfo import get_sys_info
28
28
29 from IPython.config import Application
29 from IPython.config import Application
30 from IPython.utils.path import filefind
30 from IPython.utils.path import filefind
31 from IPython.utils.py3compat import string_types
31 from IPython.utils.py3compat import string_types
32 from IPython.html.utils import is_hidden, url_path_join, url_escape
32 from IPython.html.utils import is_hidden, url_path_join, url_escape
33
33
34 from IPython.html.services.security import csp_report_uri
34 from IPython.html.services.security import csp_report_uri
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Top-level handlers
37 # Top-level handlers
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 non_alphanum = re.compile(r'[^A-Za-z0-9]')
39 non_alphanum = re.compile(r'[^A-Za-z0-9]')
40
40
41 sys_info = json.dumps(get_sys_info())
41 sys_info = json.dumps(get_sys_info())
42
42
43 class AuthenticatedHandler(web.RequestHandler):
43 class AuthenticatedHandler(web.RequestHandler):
44 """A RequestHandler with an authenticated user."""
44 """A RequestHandler with an authenticated user."""
45
45
46 def set_default_headers(self):
46 def set_default_headers(self):
47 headers = self.settings.get('headers', {})
47 headers = self.settings.get('headers', {})
48
48
49 if "Content-Security-Policy" not in headers:
49 if "Content-Security-Policy" not in headers:
50 headers["Content-Security-Policy"] = (
50 headers["Content-Security-Policy"] = (
51 "frame-ancestors 'self'; "
51 "frame-ancestors 'self'; "
52 # Make sure the report-uri is relative to the base_url
52 # Make sure the report-uri is relative to the base_url
53 "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";"
53 "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";"
54 )
54 )
55
55
56 # Allow for overriding headers
56 # Allow for overriding headers
57 for header_name,value in headers.items() :
57 for header_name,value in headers.items() :
58 try:
58 try:
59 self.set_header(header_name, value)
59 self.set_header(header_name, value)
60 except Exception as e:
60 except Exception as e:
61 # tornado raise Exception (not a subclass)
61 # tornado raise Exception (not a subclass)
62 # if method is unsupported (websocket and Access-Control-Allow-Origin
62 # if method is unsupported (websocket and Access-Control-Allow-Origin
63 # for example, so just ignore)
63 # for example, so just ignore)
64 self.log.debug(e)
64 self.log.debug(e)
65
65
66 def clear_login_cookie(self):
66 def clear_login_cookie(self):
67 self.clear_cookie(self.cookie_name)
67 self.clear_cookie(self.cookie_name)
68
68
69 def get_current_user(self):
69 def get_current_user(self):
70 if self.login_handler is None:
70 if self.login_handler is None:
71 return 'anonymous'
71 return 'anonymous'
72 return self.login_handler.get_user(self)
72 return self.login_handler.get_user(self)
73
73
74 @property
74 @property
75 def cookie_name(self):
75 def cookie_name(self):
76 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
76 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
77 self.request.host
77 self.request.host
78 ))
78 ))
79 return self.settings.get('cookie_name', default_cookie_name)
79 return self.settings.get('cookie_name', default_cookie_name)
80
80
81 @property
81 @property
82 def logged_in(self):
82 def logged_in(self):
83 """Is a user currently logged in?"""
83 """Is a user currently logged in?"""
84 user = self.get_current_user()
84 user = self.get_current_user()
85 return (user and not user == 'anonymous')
85 return (user and not user == 'anonymous')
86
86
87 @property
87 @property
88 def login_handler(self):
88 def login_handler(self):
89 """Return the login handler for this application, if any."""
89 """Return the login handler for this application, if any."""
90 return self.settings.get('login_handler_class', None)
90 return self.settings.get('login_handler_class', None)
91
91
92 @property
92 @property
93 def login_available(self):
93 def login_available(self):
94 """May a user proceed to log in?
94 """May a user proceed to log in?
95
95
96 This returns True if login capability is available, irrespective of
96 This returns True if login capability is available, irrespective of
97 whether the user is already logged in or not.
97 whether the user is already logged in or not.
98
98
99 """
99 """
100 if self.login_handler is None:
100 if self.login_handler is None:
101 return False
101 return False
102 return bool(self.login_handler.login_available(self.settings))
102 return bool(self.login_handler.login_available(self.settings))
103
103
104
104
105 class IPythonHandler(AuthenticatedHandler):
105 class IPythonHandler(AuthenticatedHandler):
106 """IPython-specific extensions to authenticated handling
106 """IPython-specific extensions to authenticated handling
107
107
108 Mostly property shortcuts to IPython-specific settings.
108 Mostly property shortcuts to IPython-specific settings.
109 """
109 """
110
110
111 @property
111 @property
112 def config(self):
112 def config(self):
113 return self.settings.get('config', None)
113 return self.settings.get('config', None)
114
114
115 @property
115 @property
116 def log(self):
116 def log(self):
117 """use the IPython log by default, falling back on tornado's logger"""
117 """use the IPython log by default, falling back on tornado's logger"""
118 if Application.initialized():
118 if Application.initialized():
119 return Application.instance().log
119 return Application.instance().log
120 else:
120 else:
121 return app_log
121 return app_log
122
123 @property
124 def jinja_template_vars(self):
125 """User-supplied values to supply to jinja templates."""
126 return self.settings.get('jinja_template_vars', {})
122
127
123 #---------------------------------------------------------------
128 #---------------------------------------------------------------
124 # URLs
129 # URLs
125 #---------------------------------------------------------------
130 #---------------------------------------------------------------
126
131
127 @property
132 @property
128 def version_hash(self):
133 def version_hash(self):
129 """The version hash to use for cache hints for static files"""
134 """The version hash to use for cache hints for static files"""
130 return self.settings.get('version_hash', '')
135 return self.settings.get('version_hash', '')
131
136
132 @property
137 @property
133 def mathjax_url(self):
138 def mathjax_url(self):
134 return self.settings.get('mathjax_url', '')
139 return self.settings.get('mathjax_url', '')
135
140
136 @property
141 @property
137 def base_url(self):
142 def base_url(self):
138 return self.settings.get('base_url', '/')
143 return self.settings.get('base_url', '/')
139
144
140 @property
145 @property
141 def default_url(self):
146 def default_url(self):
142 return self.settings.get('default_url', '')
147 return self.settings.get('default_url', '')
143
148
144 @property
149 @property
145 def ws_url(self):
150 def ws_url(self):
146 return self.settings.get('websocket_url', '')
151 return self.settings.get('websocket_url', '')
147
152
148 @property
153 @property
149 def contents_js_source(self):
154 def contents_js_source(self):
150 self.log.debug("Using contents: %s", self.settings.get('contents_js_source',
155 self.log.debug("Using contents: %s", self.settings.get('contents_js_source',
151 'services/contents'))
156 'services/contents'))
152 return self.settings.get('contents_js_source', 'services/contents')
157 return self.settings.get('contents_js_source', 'services/contents')
153
158
154 #---------------------------------------------------------------
159 #---------------------------------------------------------------
155 # Manager objects
160 # Manager objects
156 #---------------------------------------------------------------
161 #---------------------------------------------------------------
157
162
158 @property
163 @property
159 def kernel_manager(self):
164 def kernel_manager(self):
160 return self.settings['kernel_manager']
165 return self.settings['kernel_manager']
161
166
162 @property
167 @property
163 def contents_manager(self):
168 def contents_manager(self):
164 return self.settings['contents_manager']
169 return self.settings['contents_manager']
165
170
166 @property
171 @property
167 def cluster_manager(self):
172 def cluster_manager(self):
168 return self.settings['cluster_manager']
173 return self.settings['cluster_manager']
169
174
170 @property
175 @property
171 def session_manager(self):
176 def session_manager(self):
172 return self.settings['session_manager']
177 return self.settings['session_manager']
173
178
174 @property
179 @property
175 def terminal_manager(self):
180 def terminal_manager(self):
176 return self.settings['terminal_manager']
181 return self.settings['terminal_manager']
177
182
178 @property
183 @property
179 def kernel_spec_manager(self):
184 def kernel_spec_manager(self):
180 return self.settings['kernel_spec_manager']
185 return self.settings['kernel_spec_manager']
181
186
182 @property
187 @property
183 def config_manager(self):
188 def config_manager(self):
184 return self.settings['config_manager']
189 return self.settings['config_manager']
185
190
186 #---------------------------------------------------------------
191 #---------------------------------------------------------------
187 # CORS
192 # CORS
188 #---------------------------------------------------------------
193 #---------------------------------------------------------------
189
194
190 @property
195 @property
191 def allow_origin(self):
196 def allow_origin(self):
192 """Normal Access-Control-Allow-Origin"""
197 """Normal Access-Control-Allow-Origin"""
193 return self.settings.get('allow_origin', '')
198 return self.settings.get('allow_origin', '')
194
199
195 @property
200 @property
196 def allow_origin_pat(self):
201 def allow_origin_pat(self):
197 """Regular expression version of allow_origin"""
202 """Regular expression version of allow_origin"""
198 return self.settings.get('allow_origin_pat', None)
203 return self.settings.get('allow_origin_pat', None)
199
204
200 @property
205 @property
201 def allow_credentials(self):
206 def allow_credentials(self):
202 """Whether to set Access-Control-Allow-Credentials"""
207 """Whether to set Access-Control-Allow-Credentials"""
203 return self.settings.get('allow_credentials', False)
208 return self.settings.get('allow_credentials', False)
204
209
205 def set_default_headers(self):
210 def set_default_headers(self):
206 """Add CORS headers, if defined"""
211 """Add CORS headers, if defined"""
207 super(IPythonHandler, self).set_default_headers()
212 super(IPythonHandler, self).set_default_headers()
208 if self.allow_origin:
213 if self.allow_origin:
209 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
214 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
210 elif self.allow_origin_pat:
215 elif self.allow_origin_pat:
211 origin = self.get_origin()
216 origin = self.get_origin()
212 if origin and self.allow_origin_pat.match(origin):
217 if origin and self.allow_origin_pat.match(origin):
213 self.set_header("Access-Control-Allow-Origin", origin)
218 self.set_header("Access-Control-Allow-Origin", origin)
214 if self.allow_credentials:
219 if self.allow_credentials:
215 self.set_header("Access-Control-Allow-Credentials", 'true')
220 self.set_header("Access-Control-Allow-Credentials", 'true')
216
221
217 def get_origin(self):
222 def get_origin(self):
218 # Handle WebSocket Origin naming convention differences
223 # Handle WebSocket Origin naming convention differences
219 # The difference between version 8 and 13 is that in 8 the
224 # The difference between version 8 and 13 is that in 8 the
220 # client sends a "Sec-Websocket-Origin" header and in 13 it's
225 # client sends a "Sec-Websocket-Origin" header and in 13 it's
221 # simply "Origin".
226 # simply "Origin".
222 if "Origin" in self.request.headers:
227 if "Origin" in self.request.headers:
223 origin = self.request.headers.get("Origin")
228 origin = self.request.headers.get("Origin")
224 else:
229 else:
225 origin = self.request.headers.get("Sec-Websocket-Origin", None)
230 origin = self.request.headers.get("Sec-Websocket-Origin", None)
226 return origin
231 return origin
227
232
228 #---------------------------------------------------------------
233 #---------------------------------------------------------------
229 # template rendering
234 # template rendering
230 #---------------------------------------------------------------
235 #---------------------------------------------------------------
231
236
232 def get_template(self, name):
237 def get_template(self, name):
233 """Return the jinja template object for a given name"""
238 """Return the jinja template object for a given name"""
234 return self.settings['jinja2_env'].get_template(name)
239 return self.settings['jinja2_env'].get_template(name)
235
240
236 def render_template(self, name, **ns):
241 def render_template(self, name, **ns):
237 ns.update(self.template_namespace)
242 ns.update(self.template_namespace)
238 template = self.get_template(name)
243 template = self.get_template(name)
239 return template.render(**ns)
244 return template.render(**ns)
240
245
241 @property
246 @property
242 def template_namespace(self):
247 def template_namespace(self):
243 return dict(
248 return dict(
244 base_url=self.base_url,
249 base_url=self.base_url,
245 default_url=self.default_url,
250 default_url=self.default_url,
246 ws_url=self.ws_url,
251 ws_url=self.ws_url,
247 logged_in=self.logged_in,
252 logged_in=self.logged_in,
248 login_available=self.login_available,
253 login_available=self.login_available,
249 static_url=self.static_url,
254 static_url=self.static_url,
250 sys_info=sys_info,
255 sys_info=sys_info,
251 contents_js_source=self.contents_js_source,
256 contents_js_source=self.contents_js_source,
252 version_hash=self.version_hash,
257 version_hash=self.version_hash,
258 **self.jinja_template_vars
253 )
259 )
254
260
255 def get_json_body(self):
261 def get_json_body(self):
256 """Return the body of the request as JSON data."""
262 """Return the body of the request as JSON data."""
257 if not self.request.body:
263 if not self.request.body:
258 return None
264 return None
259 # Do we need to call body.decode('utf-8') here?
265 # Do we need to call body.decode('utf-8') here?
260 body = self.request.body.strip().decode(u'utf-8')
266 body = self.request.body.strip().decode(u'utf-8')
261 try:
267 try:
262 model = json.loads(body)
268 model = json.loads(body)
263 except Exception:
269 except Exception:
264 self.log.debug("Bad JSON: %r", body)
270 self.log.debug("Bad JSON: %r", body)
265 self.log.error("Couldn't parse JSON", exc_info=True)
271 self.log.error("Couldn't parse JSON", exc_info=True)
266 raise web.HTTPError(400, u'Invalid JSON in body of request')
272 raise web.HTTPError(400, u'Invalid JSON in body of request')
267 return model
273 return model
268
274
269 def write_error(self, status_code, **kwargs):
275 def write_error(self, status_code, **kwargs):
270 """render custom error pages"""
276 """render custom error pages"""
271 exc_info = kwargs.get('exc_info')
277 exc_info = kwargs.get('exc_info')
272 message = ''
278 message = ''
273 status_message = responses.get(status_code, 'Unknown HTTP Error')
279 status_message = responses.get(status_code, 'Unknown HTTP Error')
274 if exc_info:
280 if exc_info:
275 exception = exc_info[1]
281 exception = exc_info[1]
276 # get the custom message, if defined
282 # get the custom message, if defined
277 try:
283 try:
278 message = exception.log_message % exception.args
284 message = exception.log_message % exception.args
279 except Exception:
285 except Exception:
280 pass
286 pass
281
287
282 # construct the custom reason, if defined
288 # construct the custom reason, if defined
283 reason = getattr(exception, 'reason', '')
289 reason = getattr(exception, 'reason', '')
284 if reason:
290 if reason:
285 status_message = reason
291 status_message = reason
286
292
287 # build template namespace
293 # build template namespace
288 ns = dict(
294 ns = dict(
289 status_code=status_code,
295 status_code=status_code,
290 status_message=status_message,
296 status_message=status_message,
291 message=message,
297 message=message,
292 exception=exception,
298 exception=exception,
293 )
299 )
294
300
295 self.set_header('Content-Type', 'text/html')
301 self.set_header('Content-Type', 'text/html')
296 # render the template
302 # render the template
297 try:
303 try:
298 html = self.render_template('%s.html' % status_code, **ns)
304 html = self.render_template('%s.html' % status_code, **ns)
299 except TemplateNotFound:
305 except TemplateNotFound:
300 self.log.debug("No template for %d", status_code)
306 self.log.debug("No template for %d", status_code)
301 html = self.render_template('error.html', **ns)
307 html = self.render_template('error.html', **ns)
302
308
303 self.write(html)
309 self.write(html)
304
310
305
311
306
312
307 class Template404(IPythonHandler):
313 class Template404(IPythonHandler):
308 """Render our 404 template"""
314 """Render our 404 template"""
309 def prepare(self):
315 def prepare(self):
310 raise web.HTTPError(404)
316 raise web.HTTPError(404)
311
317
312
318
313 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
319 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
314 """static files should only be accessible when logged in"""
320 """static files should only be accessible when logged in"""
315
321
316 @web.authenticated
322 @web.authenticated
317 def get(self, path):
323 def get(self, path):
318 if os.path.splitext(path)[1] == '.ipynb':
324 if os.path.splitext(path)[1] == '.ipynb':
319 name = path.rsplit('/', 1)[-1]
325 name = path.rsplit('/', 1)[-1]
320 self.set_header('Content-Type', 'application/json')
326 self.set_header('Content-Type', 'application/json')
321 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
327 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
322
328
323 return web.StaticFileHandler.get(self, path)
329 return web.StaticFileHandler.get(self, path)
324
330
325 def set_headers(self):
331 def set_headers(self):
326 super(AuthenticatedFileHandler, self).set_headers()
332 super(AuthenticatedFileHandler, self).set_headers()
327 # disable browser caching, rely on 304 replies for savings
333 # disable browser caching, rely on 304 replies for savings
328 if "v" not in self.request.arguments:
334 if "v" not in self.request.arguments:
329 self.add_header("Cache-Control", "no-cache")
335 self.add_header("Cache-Control", "no-cache")
330
336
331 def compute_etag(self):
337 def compute_etag(self):
332 return None
338 return None
333
339
334 def validate_absolute_path(self, root, absolute_path):
340 def validate_absolute_path(self, root, absolute_path):
335 """Validate and return the absolute path.
341 """Validate and return the absolute path.
336
342
337 Requires tornado 3.1
343 Requires tornado 3.1
338
344
339 Adding to tornado's own handling, forbids the serving of hidden files.
345 Adding to tornado's own handling, forbids the serving of hidden files.
340 """
346 """
341 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
347 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
342 abs_root = os.path.abspath(root)
348 abs_root = os.path.abspath(root)
343 if is_hidden(abs_path, abs_root):
349 if is_hidden(abs_path, abs_root):
344 self.log.info("Refusing to serve hidden file, via 404 Error")
350 self.log.info("Refusing to serve hidden file, via 404 Error")
345 raise web.HTTPError(404)
351 raise web.HTTPError(404)
346 return abs_path
352 return abs_path
347
353
348
354
349 def json_errors(method):
355 def json_errors(method):
350 """Decorate methods with this to return GitHub style JSON errors.
356 """Decorate methods with this to return GitHub style JSON errors.
351
357
352 This should be used on any JSON API on any handler method that can raise HTTPErrors.
358 This should be used on any JSON API on any handler method that can raise HTTPErrors.
353
359
354 This will grab the latest HTTPError exception using sys.exc_info
360 This will grab the latest HTTPError exception using sys.exc_info
355 and then:
361 and then:
356
362
357 1. Set the HTTP status code based on the HTTPError
363 1. Set the HTTP status code based on the HTTPError
358 2. Create and return a JSON body with a message field describing
364 2. Create and return a JSON body with a message field describing
359 the error in a human readable form.
365 the error in a human readable form.
360 """
366 """
361 @functools.wraps(method)
367 @functools.wraps(method)
362 @gen.coroutine
368 @gen.coroutine
363 def wrapper(self, *args, **kwargs):
369 def wrapper(self, *args, **kwargs):
364 try:
370 try:
365 result = yield gen.maybe_future(method(self, *args, **kwargs))
371 result = yield gen.maybe_future(method(self, *args, **kwargs))
366 except web.HTTPError as e:
372 except web.HTTPError as e:
367 status = e.status_code
373 status = e.status_code
368 message = e.log_message
374 message = e.log_message
369 self.log.warn(message)
375 self.log.warn(message)
370 self.set_status(e.status_code)
376 self.set_status(e.status_code)
371 reply = dict(message=message, reason=e.reason)
377 reply = dict(message=message, reason=e.reason)
372 self.finish(json.dumps(reply))
378 self.finish(json.dumps(reply))
373 except Exception:
379 except Exception:
374 self.log.error("Unhandled error in API request", exc_info=True)
380 self.log.error("Unhandled error in API request", exc_info=True)
375 status = 500
381 status = 500
376 message = "Unknown server error"
382 message = "Unknown server error"
377 t, value, tb = sys.exc_info()
383 t, value, tb = sys.exc_info()
378 self.set_status(status)
384 self.set_status(status)
379 tb_text = ''.join(traceback.format_exception(t, value, tb))
385 tb_text = ''.join(traceback.format_exception(t, value, tb))
380 reply = dict(message=message, reason=None, traceback=tb_text)
386 reply = dict(message=message, reason=None, traceback=tb_text)
381 self.finish(json.dumps(reply))
387 self.finish(json.dumps(reply))
382 else:
388 else:
383 # FIXME: can use regular return in generators in py3
389 # FIXME: can use regular return in generators in py3
384 raise gen.Return(result)
390 raise gen.Return(result)
385 return wrapper
391 return wrapper
386
392
387
393
388
394
389 #-----------------------------------------------------------------------------
395 #-----------------------------------------------------------------------------
390 # File handler
396 # File handler
391 #-----------------------------------------------------------------------------
397 #-----------------------------------------------------------------------------
392
398
393 # to minimize subclass changes:
399 # to minimize subclass changes:
394 HTTPError = web.HTTPError
400 HTTPError = web.HTTPError
395
401
396 class FileFindHandler(web.StaticFileHandler):
402 class FileFindHandler(web.StaticFileHandler):
397 """subclass of StaticFileHandler for serving files from a search path"""
403 """subclass of StaticFileHandler for serving files from a search path"""
398
404
399 # cache search results, don't search for files more than once
405 # cache search results, don't search for files more than once
400 _static_paths = {}
406 _static_paths = {}
401
407
402 def set_headers(self):
408 def set_headers(self):
403 super(FileFindHandler, self).set_headers()
409 super(FileFindHandler, self).set_headers()
404 # disable browser caching, rely on 304 replies for savings
410 # disable browser caching, rely on 304 replies for savings
405 if "v" not in self.request.arguments or \
411 if "v" not in self.request.arguments or \
406 any(self.request.path.startswith(path) for path in self.no_cache_paths):
412 any(self.request.path.startswith(path) for path in self.no_cache_paths):
407 self.set_header("Cache-Control", "no-cache")
413 self.set_header("Cache-Control", "no-cache")
408
414
409 def initialize(self, path, default_filename=None, no_cache_paths=None):
415 def initialize(self, path, default_filename=None, no_cache_paths=None):
410 self.no_cache_paths = no_cache_paths or []
416 self.no_cache_paths = no_cache_paths or []
411
417
412 if isinstance(path, string_types):
418 if isinstance(path, string_types):
413 path = [path]
419 path = [path]
414
420
415 self.root = tuple(
421 self.root = tuple(
416 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
422 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
417 )
423 )
418 self.default_filename = default_filename
424 self.default_filename = default_filename
419
425
420 def compute_etag(self):
426 def compute_etag(self):
421 return None
427 return None
422
428
423 @classmethod
429 @classmethod
424 def get_absolute_path(cls, roots, path):
430 def get_absolute_path(cls, roots, path):
425 """locate a file to serve on our static file search path"""
431 """locate a file to serve on our static file search path"""
426 with cls._lock:
432 with cls._lock:
427 if path in cls._static_paths:
433 if path in cls._static_paths:
428 return cls._static_paths[path]
434 return cls._static_paths[path]
429 try:
435 try:
430 abspath = os.path.abspath(filefind(path, roots))
436 abspath = os.path.abspath(filefind(path, roots))
431 except IOError:
437 except IOError:
432 # IOError means not found
438 # IOError means not found
433 return ''
439 return ''
434
440
435 cls._static_paths[path] = abspath
441 cls._static_paths[path] = abspath
436 return abspath
442 return abspath
437
443
438 def validate_absolute_path(self, root, absolute_path):
444 def validate_absolute_path(self, root, absolute_path):
439 """check if the file should be served (raises 404, 403, etc.)"""
445 """check if the file should be served (raises 404, 403, etc.)"""
440 if absolute_path == '':
446 if absolute_path == '':
441 raise web.HTTPError(404)
447 raise web.HTTPError(404)
442
448
443 for root in self.root:
449 for root in self.root:
444 if (absolute_path + os.sep).startswith(root):
450 if (absolute_path + os.sep).startswith(root):
445 break
451 break
446
452
447 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
453 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
448
454
449
455
450 class ApiVersionHandler(IPythonHandler):
456 class ApiVersionHandler(IPythonHandler):
451
457
452 @json_errors
458 @json_errors
453 def get(self):
459 def get(self):
454 # not authenticated, so give as few info as possible
460 # not authenticated, so give as few info as possible
455 self.finish(json.dumps({"version":IPython.__version__}))
461 self.finish(json.dumps({"version":IPython.__version__}))
456
462
457
463
458 class TrailingSlashHandler(web.RequestHandler):
464 class TrailingSlashHandler(web.RequestHandler):
459 """Simple redirect handler that strips trailing slashes
465 """Simple redirect handler that strips trailing slashes
460
466
461 This should be the first, highest priority handler.
467 This should be the first, highest priority handler.
462 """
468 """
463
469
464 def get(self):
470 def get(self):
465 self.redirect(self.request.uri.rstrip('/'))
471 self.redirect(self.request.uri.rstrip('/'))
466
472
467 post = put = get
473 post = put = get
468
474
469
475
470 class FilesRedirectHandler(IPythonHandler):
476 class FilesRedirectHandler(IPythonHandler):
471 """Handler for redirecting relative URLs to the /files/ handler"""
477 """Handler for redirecting relative URLs to the /files/ handler"""
472
478
473 @staticmethod
479 @staticmethod
474 def redirect_to_files(self, path):
480 def redirect_to_files(self, path):
475 """make redirect logic a reusable static method
481 """make redirect logic a reusable static method
476
482
477 so it can be called from other handlers.
483 so it can be called from other handlers.
478 """
484 """
479 cm = self.contents_manager
485 cm = self.contents_manager
480 if cm.dir_exists(path):
486 if cm.dir_exists(path):
481 # it's a *directory*, redirect to /tree
487 # it's a *directory*, redirect to /tree
482 url = url_path_join(self.base_url, 'tree', path)
488 url = url_path_join(self.base_url, 'tree', path)
483 else:
489 else:
484 orig_path = path
490 orig_path = path
485 # otherwise, redirect to /files
491 # otherwise, redirect to /files
486 parts = path.split('/')
492 parts = path.split('/')
487
493
488 if not cm.file_exists(path=path) and 'files' in parts:
494 if not cm.file_exists(path=path) and 'files' in parts:
489 # redirect without files/ iff it would 404
495 # redirect without files/ iff it would 404
490 # this preserves pre-2.0-style 'files/' links
496 # this preserves pre-2.0-style 'files/' links
491 self.log.warn("Deprecated files/ URL: %s", orig_path)
497 self.log.warn("Deprecated files/ URL: %s", orig_path)
492 parts.remove('files')
498 parts.remove('files')
493 path = '/'.join(parts)
499 path = '/'.join(parts)
494
500
495 if not cm.file_exists(path=path):
501 if not cm.file_exists(path=path):
496 raise web.HTTPError(404)
502 raise web.HTTPError(404)
497
503
498 url = url_path_join(self.base_url, 'files', path)
504 url = url_path_join(self.base_url, 'files', path)
499 url = url_escape(url)
505 url = url_escape(url)
500 self.log.debug("Redirecting %s to %s", self.request.path, url)
506 self.log.debug("Redirecting %s to %s", self.request.path, url)
501 self.redirect(url)
507 self.redirect(url)
502
508
503 def get(self, path=''):
509 def get(self, path=''):
504 return self.redirect_to_files(self, path)
510 return self.redirect_to_files(self, path)
505
511
506
512
507 #-----------------------------------------------------------------------------
513 #-----------------------------------------------------------------------------
508 # URL pattern fragments for re-use
514 # URL pattern fragments for re-use
509 #-----------------------------------------------------------------------------
515 #-----------------------------------------------------------------------------
510
516
511 # path matches any number of `/foo[/bar...]` or just `/` or ''
517 # path matches any number of `/foo[/bar...]` or just `/` or ''
512 path_regex = r"(?P<path>(?:(?:/[^/]+)+|/?))"
518 path_regex = r"(?P<path>(?:(?:/[^/]+)+|/?))"
513
519
514 #-----------------------------------------------------------------------------
520 #-----------------------------------------------------------------------------
515 # URL to handler mappings
521 # URL to handler mappings
516 #-----------------------------------------------------------------------------
522 #-----------------------------------------------------------------------------
517
523
518
524
519 default_handlers = [
525 default_handlers = [
520 (r".*/", TrailingSlashHandler),
526 (r".*/", TrailingSlashHandler),
521 (r"api", ApiVersionHandler)
527 (r"api", ApiVersionHandler)
522 ]
528 ]
@@ -1,1126 +1,1132 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 datetime
10 import datetime
11 import errno
11 import errno
12 import importlib
12 import importlib
13 import io
13 import io
14 import json
14 import json
15 import logging
15 import logging
16 import os
16 import os
17 import random
17 import random
18 import re
18 import re
19 import select
19 import select
20 import signal
20 import signal
21 import socket
21 import socket
22 import ssl
22 import ssl
23 import sys
23 import sys
24 import threading
24 import threading
25 import webbrowser
25 import webbrowser
26
26
27
27
28 from jinja2 import Environment, FileSystemLoader
28 from jinja2 import Environment, FileSystemLoader
29
29
30 # Install the pyzmq ioloop. This has to be done before anything else from
30 # Install the pyzmq ioloop. This has to be done before anything else from
31 # tornado is imported.
31 # tornado is imported.
32 from zmq.eventloop import ioloop
32 from zmq.eventloop import ioloop
33 ioloop.install()
33 ioloop.install()
34
34
35 # check for tornado 3.1.0
35 # check for tornado 3.1.0
36 msg = "The IPython Notebook requires tornado >= 4.0"
36 msg = "The IPython Notebook requires tornado >= 4.0"
37 try:
37 try:
38 import tornado
38 import tornado
39 except ImportError:
39 except ImportError:
40 raise ImportError(msg)
40 raise ImportError(msg)
41 try:
41 try:
42 version_info = tornado.version_info
42 version_info = tornado.version_info
43 except AttributeError:
43 except AttributeError:
44 raise ImportError(msg + ", but you have < 1.1.0")
44 raise ImportError(msg + ", but you have < 1.1.0")
45 if version_info < (4,0):
45 if version_info < (4,0):
46 raise ImportError(msg + ", but you have %s" % tornado.version)
46 raise ImportError(msg + ", but you have %s" % tornado.version)
47
47
48 from tornado import httpserver
48 from tornado import httpserver
49 from tornado import web
49 from tornado import web
50 from tornado.log import LogFormatter, app_log, access_log, gen_log
50 from tornado.log import LogFormatter, app_log, access_log, gen_log
51
51
52 from IPython.html import (
52 from IPython.html import (
53 DEFAULT_STATIC_FILES_PATH,
53 DEFAULT_STATIC_FILES_PATH,
54 DEFAULT_TEMPLATE_PATH_LIST,
54 DEFAULT_TEMPLATE_PATH_LIST,
55 )
55 )
56 from .base.handlers import Template404
56 from .base.handlers import Template404
57 from .log import log_request
57 from .log import log_request
58 from .services.kernels.kernelmanager import MappingKernelManager
58 from .services.kernels.kernelmanager import MappingKernelManager
59 from .services.config import ConfigManager
59 from .services.config import ConfigManager
60 from .services.contents.manager import ContentsManager
60 from .services.contents.manager import ContentsManager
61 from .services.contents.filemanager import FileContentsManager
61 from .services.contents.filemanager import FileContentsManager
62 from .services.clusters.clustermanager import ClusterManager
62 from .services.clusters.clustermanager import ClusterManager
63 from .services.sessions.sessionmanager import SessionManager
63 from .services.sessions.sessionmanager import SessionManager
64
64
65 from .auth.login import LoginHandler
65 from .auth.login import LoginHandler
66 from .auth.logout import LogoutHandler
66 from .auth.logout import LogoutHandler
67 from .base.handlers import IPythonHandler, FileFindHandler
67 from .base.handlers import IPythonHandler, FileFindHandler
68
68
69 from IPython.config import Config
69 from IPython.config import Config
70 from IPython.config.application import catch_config_error, boolean_flag
70 from IPython.config.application import catch_config_error, boolean_flag
71 from IPython.core.application import (
71 from IPython.core.application import (
72 BaseIPythonApplication, base_flags, base_aliases,
72 BaseIPythonApplication, base_flags, base_aliases,
73 )
73 )
74 from IPython.core.profiledir import ProfileDir
74 from IPython.core.profiledir import ProfileDir
75 from IPython.kernel import KernelManager
75 from IPython.kernel import KernelManager
76 from IPython.kernel.kernelspec import KernelSpecManager
76 from IPython.kernel.kernelspec import KernelSpecManager
77 from IPython.kernel.zmq.session import Session
77 from IPython.kernel.zmq.session import Session
78 from IPython.nbformat.sign import NotebookNotary
78 from IPython.nbformat.sign import NotebookNotary
79 from IPython.utils.importstring import import_item
79 from IPython.utils.importstring import import_item
80 from IPython.utils import submodule
80 from IPython.utils import submodule
81 from IPython.utils.traitlets import (
81 from IPython.utils.traitlets import (
82 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
82 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
83 TraitError, Type,
83 TraitError, Type,
84 )
84 )
85 from IPython.utils import py3compat
85 from IPython.utils import py3compat
86 from IPython.utils.path import filefind, get_ipython_dir
86 from IPython.utils.path import filefind, get_ipython_dir
87 from IPython.utils.sysinfo import get_sys_info
87 from IPython.utils.sysinfo import get_sys_info
88
88
89 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
89 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
90 from .utils import url_path_join, check_pid
90 from .utils import url_path_join, check_pid
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # Module globals
93 # Module globals
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 _examples = """
96 _examples = """
97 ipython notebook # start the notebook
97 ipython notebook # start the notebook
98 ipython notebook --profile=sympy # use the sympy profile
98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
99 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 """
100 """
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # Helper functions
103 # Helper functions
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105
105
106 def random_ports(port, n):
106 def random_ports(port, n):
107 """Generate a list of n random ports near the given port.
107 """Generate a list of n random ports near the given port.
108
108
109 The first 5 ports will be sequential, and the remaining n-5 will be
109 The first 5 ports will be sequential, and the remaining n-5 will be
110 randomly selected in the range [port-2*n, port+2*n].
110 randomly selected in the range [port-2*n, port+2*n].
111 """
111 """
112 for i in range(min(5, n)):
112 for i in range(min(5, n)):
113 yield port + i
113 yield port + i
114 for i in range(n-5):
114 for i in range(n-5):
115 yield max(1, port + random.randint(-2*n, 2*n))
115 yield max(1, port + random.randint(-2*n, 2*n))
116
116
117 def load_handlers(name):
117 def load_handlers(name):
118 """Load the (URL pattern, handler) tuples for each component."""
118 """Load the (URL pattern, handler) tuples for each component."""
119 name = 'IPython.html.' + name
119 name = 'IPython.html.' + name
120 mod = __import__(name, fromlist=['default_handlers'])
120 mod = __import__(name, fromlist=['default_handlers'])
121 return mod.default_handlers
121 return mod.default_handlers
122
122
123 #-----------------------------------------------------------------------------
123 #-----------------------------------------------------------------------------
124 # The Tornado web application
124 # The Tornado web application
125 #-----------------------------------------------------------------------------
125 #-----------------------------------------------------------------------------
126
126
127 class NotebookWebApplication(web.Application):
127 class NotebookWebApplication(web.Application):
128
128
129 def __init__(self, ipython_app, kernel_manager, contents_manager,
129 def __init__(self, ipython_app, kernel_manager, contents_manager,
130 cluster_manager, session_manager, kernel_spec_manager,
130 cluster_manager, session_manager, kernel_spec_manager,
131 config_manager, log,
131 config_manager, log,
132 base_url, default_url, settings_overrides, jinja_env_options):
132 base_url, default_url, settings_overrides, jinja_env_options):
133
133
134 settings = self.init_settings(
134 settings = self.init_settings(
135 ipython_app, kernel_manager, contents_manager, cluster_manager,
135 ipython_app, kernel_manager, contents_manager, cluster_manager,
136 session_manager, kernel_spec_manager, config_manager, log, base_url,
136 session_manager, kernel_spec_manager, config_manager, log, base_url,
137 default_url, settings_overrides, jinja_env_options)
137 default_url, settings_overrides, jinja_env_options)
138 handlers = self.init_handlers(settings)
138 handlers = self.init_handlers(settings)
139
139
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
141
141
142 def init_settings(self, ipython_app, kernel_manager, contents_manager,
142 def init_settings(self, ipython_app, kernel_manager, contents_manager,
143 cluster_manager, session_manager, kernel_spec_manager,
143 cluster_manager, session_manager, kernel_spec_manager,
144 config_manager,
144 config_manager,
145 log, base_url, default_url, settings_overrides,
145 log, base_url, default_url, settings_overrides,
146 jinja_env_options=None):
146 jinja_env_options=None):
147
147
148 _template_path = settings_overrides.get(
148 _template_path = settings_overrides.get(
149 "template_path",
149 "template_path",
150 ipython_app.template_file_path,
150 ipython_app.template_file_path,
151 )
151 )
152 if isinstance(_template_path, str):
152 if isinstance(_template_path, str):
153 _template_path = (_template_path,)
153 _template_path = (_template_path,)
154 template_path = [os.path.expanduser(path) for path in _template_path]
154 template_path = [os.path.expanduser(path) for path in _template_path]
155
155
156 jenv_opt = jinja_env_options if jinja_env_options else {}
156 jenv_opt = jinja_env_options if jinja_env_options else {}
157 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
157 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
158
158
159 sys_info = get_sys_info()
159 sys_info = get_sys_info()
160 if sys_info['commit_source'] == 'repository':
160 if sys_info['commit_source'] == 'repository':
161 # don't cache (rely on 304) when working from master
161 # don't cache (rely on 304) when working from master
162 version_hash = ''
162 version_hash = ''
163 else:
163 else:
164 # reset the cache on server restart
164 # reset the cache on server restart
165 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
165 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
166
166
167 settings = dict(
167 settings = dict(
168 # basics
168 # basics
169 log_function=log_request,
169 log_function=log_request,
170 base_url=base_url,
170 base_url=base_url,
171 default_url=default_url,
171 default_url=default_url,
172 template_path=template_path,
172 template_path=template_path,
173 static_path=ipython_app.static_file_path,
173 static_path=ipython_app.static_file_path,
174 static_handler_class = FileFindHandler,
174 static_handler_class = FileFindHandler,
175 static_url_prefix = url_path_join(base_url,'/static/'),
175 static_url_prefix = url_path_join(base_url,'/static/'),
176 static_handler_args = {
176 static_handler_args = {
177 # don't cache custom.js
177 # don't cache custom.js
178 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
178 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
179 },
179 },
180 version_hash=version_hash,
180 version_hash=version_hash,
181
181
182 # authentication
182 # authentication
183 cookie_secret=ipython_app.cookie_secret,
183 cookie_secret=ipython_app.cookie_secret,
184 login_url=url_path_join(base_url,'/login'),
184 login_url=url_path_join(base_url,'/login'),
185 login_handler_class=ipython_app.login_handler_class,
185 login_handler_class=ipython_app.login_handler_class,
186 logout_handler_class=ipython_app.logout_handler_class,
186 logout_handler_class=ipython_app.logout_handler_class,
187 password=ipython_app.password,
187 password=ipython_app.password,
188
188
189 # managers
189 # managers
190 kernel_manager=kernel_manager,
190 kernel_manager=kernel_manager,
191 contents_manager=contents_manager,
191 contents_manager=contents_manager,
192 cluster_manager=cluster_manager,
192 cluster_manager=cluster_manager,
193 session_manager=session_manager,
193 session_manager=session_manager,
194 kernel_spec_manager=kernel_spec_manager,
194 kernel_spec_manager=kernel_spec_manager,
195 config_manager=config_manager,
195 config_manager=config_manager,
196
196
197 # IPython stuff
197 # IPython stuff
198 jinja_template_vars=ipython_app.jinja_template_vars,
198 nbextensions_path=ipython_app.nbextensions_path,
199 nbextensions_path=ipython_app.nbextensions_path,
199 websocket_url=ipython_app.websocket_url,
200 websocket_url=ipython_app.websocket_url,
200 mathjax_url=ipython_app.mathjax_url,
201 mathjax_url=ipython_app.mathjax_url,
201 config=ipython_app.config,
202 config=ipython_app.config,
202 jinja2_env=env,
203 jinja2_env=env,
203 terminals_available=False, # Set later if terminals are available
204 terminals_available=False, # Set later if terminals are available
204 )
205 )
205
206
206 # allow custom overrides for the tornado web app.
207 # allow custom overrides for the tornado web app.
207 settings.update(settings_overrides)
208 settings.update(settings_overrides)
208 return settings
209 return settings
209
210
210 def init_handlers(self, settings):
211 def init_handlers(self, settings):
211 """Load the (URL pattern, handler) tuples for each component."""
212 """Load the (URL pattern, handler) tuples for each component."""
212
213
213 # Order matters. The first handler to match the URL will handle the request.
214 # Order matters. The first handler to match the URL will handle the request.
214 handlers = []
215 handlers = []
215 handlers.extend(load_handlers('tree.handlers'))
216 handlers.extend(load_handlers('tree.handlers'))
216 handlers.extend([(r"/login", settings['login_handler_class'])])
217 handlers.extend([(r"/login", settings['login_handler_class'])])
217 handlers.extend([(r"/logout", settings['logout_handler_class'])])
218 handlers.extend([(r"/logout", settings['logout_handler_class'])])
218 handlers.extend(load_handlers('files.handlers'))
219 handlers.extend(load_handlers('files.handlers'))
219 handlers.extend(load_handlers('notebook.handlers'))
220 handlers.extend(load_handlers('notebook.handlers'))
220 handlers.extend(load_handlers('nbconvert.handlers'))
221 handlers.extend(load_handlers('nbconvert.handlers'))
221 handlers.extend(load_handlers('kernelspecs.handlers'))
222 handlers.extend(load_handlers('kernelspecs.handlers'))
222 handlers.extend(load_handlers('edit.handlers'))
223 handlers.extend(load_handlers('edit.handlers'))
223 handlers.extend(load_handlers('services.config.handlers'))
224 handlers.extend(load_handlers('services.config.handlers'))
224 handlers.extend(load_handlers('services.kernels.handlers'))
225 handlers.extend(load_handlers('services.kernels.handlers'))
225 handlers.extend(load_handlers('services.contents.handlers'))
226 handlers.extend(load_handlers('services.contents.handlers'))
226 handlers.extend(load_handlers('services.clusters.handlers'))
227 handlers.extend(load_handlers('services.clusters.handlers'))
227 handlers.extend(load_handlers('services.sessions.handlers'))
228 handlers.extend(load_handlers('services.sessions.handlers'))
228 handlers.extend(load_handlers('services.nbconvert.handlers'))
229 handlers.extend(load_handlers('services.nbconvert.handlers'))
229 handlers.extend(load_handlers('services.kernelspecs.handlers'))
230 handlers.extend(load_handlers('services.kernelspecs.handlers'))
230 handlers.extend(load_handlers('services.security.handlers'))
231 handlers.extend(load_handlers('services.security.handlers'))
231 handlers.append(
232 handlers.append(
232 (r"/nbextensions/(.*)", FileFindHandler, {
233 (r"/nbextensions/(.*)", FileFindHandler, {
233 'path': settings['nbextensions_path'],
234 'path': settings['nbextensions_path'],
234 'no_cache_paths': ['/'], # don't cache anything in nbextensions
235 'no_cache_paths': ['/'], # don't cache anything in nbextensions
235 }),
236 }),
236 )
237 )
237 # register base handlers last
238 # register base handlers last
238 handlers.extend(load_handlers('base.handlers'))
239 handlers.extend(load_handlers('base.handlers'))
239 # set the URL that will be redirected from `/`
240 # set the URL that will be redirected from `/`
240 handlers.append(
241 handlers.append(
241 (r'/?', web.RedirectHandler, {
242 (r'/?', web.RedirectHandler, {
242 'url' : settings['default_url'],
243 'url' : settings['default_url'],
243 'permanent': False, # want 302, not 301
244 'permanent': False, # want 302, not 301
244 })
245 })
245 )
246 )
246 # prepend base_url onto the patterns that we match
247 # prepend base_url onto the patterns that we match
247 new_handlers = []
248 new_handlers = []
248 for handler in handlers:
249 for handler in handlers:
249 pattern = url_path_join(settings['base_url'], handler[0])
250 pattern = url_path_join(settings['base_url'], handler[0])
250 new_handler = tuple([pattern] + list(handler[1:]))
251 new_handler = tuple([pattern] + list(handler[1:]))
251 new_handlers.append(new_handler)
252 new_handlers.append(new_handler)
252 # add 404 on the end, which will catch everything that falls through
253 # add 404 on the end, which will catch everything that falls through
253 new_handlers.append((r'(.*)', Template404))
254 new_handlers.append((r'(.*)', Template404))
254 return new_handlers
255 return new_handlers
255
256
256
257
257 class NbserverListApp(BaseIPythonApplication):
258 class NbserverListApp(BaseIPythonApplication):
258
259
259 description="List currently running notebook servers in this profile."
260 description="List currently running notebook servers in this profile."
260
261
261 flags = dict(
262 flags = dict(
262 json=({'NbserverListApp': {'json': True}},
263 json=({'NbserverListApp': {'json': True}},
263 "Produce machine-readable JSON output."),
264 "Produce machine-readable JSON output."),
264 )
265 )
265
266
266 json = Bool(False, config=True,
267 json = Bool(False, config=True,
267 help="If True, each line of output will be a JSON object with the "
268 help="If True, each line of output will be a JSON object with the "
268 "details from the server info file.")
269 "details from the server info file.")
269
270
270 def start(self):
271 def start(self):
271 if not self.json:
272 if not self.json:
272 print("Currently running servers:")
273 print("Currently running servers:")
273 for serverinfo in list_running_servers(self.profile):
274 for serverinfo in list_running_servers(self.profile):
274 if self.json:
275 if self.json:
275 print(json.dumps(serverinfo))
276 print(json.dumps(serverinfo))
276 else:
277 else:
277 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
278 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
278
279
279 #-----------------------------------------------------------------------------
280 #-----------------------------------------------------------------------------
280 # Aliases and Flags
281 # Aliases and Flags
281 #-----------------------------------------------------------------------------
282 #-----------------------------------------------------------------------------
282
283
283 flags = dict(base_flags)
284 flags = dict(base_flags)
284 flags['no-browser']=(
285 flags['no-browser']=(
285 {'NotebookApp' : {'open_browser' : False}},
286 {'NotebookApp' : {'open_browser' : False}},
286 "Don't open the notebook in a browser after startup."
287 "Don't open the notebook in a browser after startup."
287 )
288 )
288 flags['pylab']=(
289 flags['pylab']=(
289 {'NotebookApp' : {'pylab' : 'warn'}},
290 {'NotebookApp' : {'pylab' : 'warn'}},
290 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
291 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
291 )
292 )
292 flags['no-mathjax']=(
293 flags['no-mathjax']=(
293 {'NotebookApp' : {'enable_mathjax' : False}},
294 {'NotebookApp' : {'enable_mathjax' : False}},
294 """Disable MathJax
295 """Disable MathJax
295
296
296 MathJax is the javascript library IPython uses to render math/LaTeX. It is
297 MathJax is the javascript library IPython uses to render math/LaTeX. It is
297 very large, so you may want to disable it if you have a slow internet
298 very large, so you may want to disable it if you have a slow internet
298 connection, or for offline use of the notebook.
299 connection, or for offline use of the notebook.
299
300
300 When disabled, equations etc. will appear as their untransformed TeX source.
301 When disabled, equations etc. will appear as their untransformed TeX source.
301 """
302 """
302 )
303 )
303
304
304 # Add notebook manager flags
305 # Add notebook manager flags
305 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
306 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
306 'DEPRECATED, IGNORED',
307 'DEPRECATED, IGNORED',
307 'DEPRECATED, IGNORED'))
308 'DEPRECATED, IGNORED'))
308
309
309 aliases = dict(base_aliases)
310 aliases = dict(base_aliases)
310
311
311 aliases.update({
312 aliases.update({
312 'ip': 'NotebookApp.ip',
313 'ip': 'NotebookApp.ip',
313 'port': 'NotebookApp.port',
314 'port': 'NotebookApp.port',
314 'port-retries': 'NotebookApp.port_retries',
315 'port-retries': 'NotebookApp.port_retries',
315 'transport': 'KernelManager.transport',
316 'transport': 'KernelManager.transport',
316 'keyfile': 'NotebookApp.keyfile',
317 'keyfile': 'NotebookApp.keyfile',
317 'certfile': 'NotebookApp.certfile',
318 'certfile': 'NotebookApp.certfile',
318 'notebook-dir': 'NotebookApp.notebook_dir',
319 'notebook-dir': 'NotebookApp.notebook_dir',
319 'browser': 'NotebookApp.browser',
320 'browser': 'NotebookApp.browser',
320 'pylab': 'NotebookApp.pylab',
321 'pylab': 'NotebookApp.pylab',
321 })
322 })
322
323
323 #-----------------------------------------------------------------------------
324 #-----------------------------------------------------------------------------
324 # NotebookApp
325 # NotebookApp
325 #-----------------------------------------------------------------------------
326 #-----------------------------------------------------------------------------
326
327
327 class NotebookApp(BaseIPythonApplication):
328 class NotebookApp(BaseIPythonApplication):
328
329
329 name = 'ipython-notebook'
330 name = 'ipython-notebook'
330
331
331 description = """
332 description = """
332 The IPython HTML Notebook.
333 The IPython HTML Notebook.
333
334
334 This launches a Tornado based HTML Notebook Server that serves up an
335 This launches a Tornado based HTML Notebook Server that serves up an
335 HTML5/Javascript Notebook client.
336 HTML5/Javascript Notebook client.
336 """
337 """
337 examples = _examples
338 examples = _examples
338 aliases = aliases
339 aliases = aliases
339 flags = flags
340 flags = flags
340
341
341 classes = [
342 classes = [
342 KernelManager, ProfileDir, Session, MappingKernelManager,
343 KernelManager, ProfileDir, Session, MappingKernelManager,
343 ContentsManager, FileContentsManager, NotebookNotary,
344 ContentsManager, FileContentsManager, NotebookNotary,
344 KernelSpecManager,
345 KernelSpecManager,
345 ]
346 ]
346 flags = Dict(flags)
347 flags = Dict(flags)
347 aliases = Dict(aliases)
348 aliases = Dict(aliases)
348
349
349 subcommands = dict(
350 subcommands = dict(
350 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
351 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
351 )
352 )
352
353
353 _log_formatter_cls = LogFormatter
354 _log_formatter_cls = LogFormatter
354
355
355 def _log_level_default(self):
356 def _log_level_default(self):
356 return logging.INFO
357 return logging.INFO
357
358
358 def _log_datefmt_default(self):
359 def _log_datefmt_default(self):
359 """Exclude date from default date format"""
360 """Exclude date from default date format"""
360 return "%H:%M:%S"
361 return "%H:%M:%S"
361
362
362 def _log_format_default(self):
363 def _log_format_default(self):
363 """override default log format to include time"""
364 """override default log format to include time"""
364 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
365 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
365
366
366 # create requested profiles by default, if they don't exist:
367 # create requested profiles by default, if they don't exist:
367 auto_create = Bool(True)
368 auto_create = Bool(True)
368
369
369 # file to be opened in the notebook server
370 # file to be opened in the notebook server
370 file_to_run = Unicode('', config=True)
371 file_to_run = Unicode('', config=True)
371
372
372 # Network related information
373 # Network related information
373
374
374 allow_origin = Unicode('', config=True,
375 allow_origin = Unicode('', config=True,
375 help="""Set the Access-Control-Allow-Origin header
376 help="""Set the Access-Control-Allow-Origin header
376
377
377 Use '*' to allow any origin to access your server.
378 Use '*' to allow any origin to access your server.
378
379
379 Takes precedence over allow_origin_pat.
380 Takes precedence over allow_origin_pat.
380 """
381 """
381 )
382 )
382
383
383 allow_origin_pat = Unicode('', config=True,
384 allow_origin_pat = Unicode('', config=True,
384 help="""Use a regular expression for the Access-Control-Allow-Origin header
385 help="""Use a regular expression for the Access-Control-Allow-Origin header
385
386
386 Requests from an origin matching the expression will get replies with:
387 Requests from an origin matching the expression will get replies with:
387
388
388 Access-Control-Allow-Origin: origin
389 Access-Control-Allow-Origin: origin
389
390
390 where `origin` is the origin of the request.
391 where `origin` is the origin of the request.
391
392
392 Ignored if allow_origin is set.
393 Ignored if allow_origin is set.
393 """
394 """
394 )
395 )
395
396
396 allow_credentials = Bool(False, config=True,
397 allow_credentials = Bool(False, config=True,
397 help="Set the Access-Control-Allow-Credentials: true header"
398 help="Set the Access-Control-Allow-Credentials: true header"
398 )
399 )
399
400
400 default_url = Unicode('/tree', config=True,
401 default_url = Unicode('/tree', config=True,
401 help="The default URL to redirect to from `/`"
402 help="The default URL to redirect to from `/`"
402 )
403 )
403
404
404 ip = Unicode('localhost', config=True,
405 ip = Unicode('localhost', config=True,
405 help="The IP address the notebook server will listen on."
406 help="The IP address the notebook server will listen on."
406 )
407 )
407 def _ip_default(self):
408 def _ip_default(self):
408 """Return localhost if available, 127.0.0.1 otherwise.
409 """Return localhost if available, 127.0.0.1 otherwise.
409
410
410 On some (horribly broken) systems, localhost cannot be bound.
411 On some (horribly broken) systems, localhost cannot be bound.
411 """
412 """
412 s = socket.socket()
413 s = socket.socket()
413 try:
414 try:
414 s.bind(('localhost', 0))
415 s.bind(('localhost', 0))
415 except socket.error as e:
416 except socket.error as e:
416 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
417 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
417 return '127.0.0.1'
418 return '127.0.0.1'
418 else:
419 else:
419 s.close()
420 s.close()
420 return 'localhost'
421 return 'localhost'
421
422
422 def _ip_changed(self, name, old, new):
423 def _ip_changed(self, name, old, new):
423 if new == u'*': self.ip = u''
424 if new == u'*': self.ip = u''
424
425
425 port = Integer(8888, config=True,
426 port = Integer(8888, config=True,
426 help="The port the notebook server will listen on."
427 help="The port the notebook server will listen on."
427 )
428 )
428 port_retries = Integer(50, config=True,
429 port_retries = Integer(50, config=True,
429 help="The number of additional ports to try if the specified port is not available."
430 help="The number of additional ports to try if the specified port is not available."
430 )
431 )
431
432
432 certfile = Unicode(u'', config=True,
433 certfile = Unicode(u'', config=True,
433 help="""The full path to an SSL/TLS certificate file."""
434 help="""The full path to an SSL/TLS certificate file."""
434 )
435 )
435
436
436 keyfile = Unicode(u'', config=True,
437 keyfile = Unicode(u'', config=True,
437 help="""The full path to a private key file for usage with SSL/TLS."""
438 help="""The full path to a private key file for usage with SSL/TLS."""
438 )
439 )
439
440
440 cookie_secret_file = Unicode(config=True,
441 cookie_secret_file = Unicode(config=True,
441 help="""The file where the cookie secret is stored."""
442 help="""The file where the cookie secret is stored."""
442 )
443 )
443 def _cookie_secret_file_default(self):
444 def _cookie_secret_file_default(self):
444 if self.profile_dir is None:
445 if self.profile_dir is None:
445 return ''
446 return ''
446 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
447 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
447
448
448 cookie_secret = Bytes(b'', config=True,
449 cookie_secret = Bytes(b'', config=True,
449 help="""The random bytes used to secure cookies.
450 help="""The random bytes used to secure cookies.
450 By default this is a new random number every time you start the Notebook.
451 By default this is a new random number every time you start the Notebook.
451 Set it to a value in a config file to enable logins to persist across server sessions.
452 Set it to a value in a config file to enable logins to persist across server sessions.
452
453
453 Note: Cookie secrets should be kept private, do not share config files with
454 Note: Cookie secrets should be kept private, do not share config files with
454 cookie_secret stored in plaintext (you can read the value from a file).
455 cookie_secret stored in plaintext (you can read the value from a file).
455 """
456 """
456 )
457 )
457 def _cookie_secret_default(self):
458 def _cookie_secret_default(self):
458 if os.path.exists(self.cookie_secret_file):
459 if os.path.exists(self.cookie_secret_file):
459 with io.open(self.cookie_secret_file, 'rb') as f:
460 with io.open(self.cookie_secret_file, 'rb') as f:
460 return f.read()
461 return f.read()
461 else:
462 else:
462 secret = base64.encodestring(os.urandom(1024))
463 secret = base64.encodestring(os.urandom(1024))
463 self._write_cookie_secret_file(secret)
464 self._write_cookie_secret_file(secret)
464 return secret
465 return secret
465
466
466 def _write_cookie_secret_file(self, secret):
467 def _write_cookie_secret_file(self, secret):
467 """write my secret to my secret_file"""
468 """write my secret to my secret_file"""
468 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
469 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
469 with io.open(self.cookie_secret_file, 'wb') as f:
470 with io.open(self.cookie_secret_file, 'wb') as f:
470 f.write(secret)
471 f.write(secret)
471 try:
472 try:
472 os.chmod(self.cookie_secret_file, 0o600)
473 os.chmod(self.cookie_secret_file, 0o600)
473 except OSError:
474 except OSError:
474 self.log.warn(
475 self.log.warn(
475 "Could not set permissions on %s",
476 "Could not set permissions on %s",
476 self.cookie_secret_file
477 self.cookie_secret_file
477 )
478 )
478
479
479 password = Unicode(u'', config=True,
480 password = Unicode(u'', config=True,
480 help="""Hashed password to use for web authentication.
481 help="""Hashed password to use for web authentication.
481
482
482 To generate, type in a python/IPython shell:
483 To generate, type in a python/IPython shell:
483
484
484 from IPython.lib import passwd; passwd()
485 from IPython.lib import passwd; passwd()
485
486
486 The string should be of the form type:salt:hashed-password.
487 The string should be of the form type:salt:hashed-password.
487 """
488 """
488 )
489 )
489
490
490 open_browser = Bool(True, config=True,
491 open_browser = Bool(True, config=True,
491 help="""Whether to open in a browser after starting.
492 help="""Whether to open in a browser after starting.
492 The specific browser used is platform dependent and
493 The specific browser used is platform dependent and
493 determined by the python standard library `webbrowser`
494 determined by the python standard library `webbrowser`
494 module, unless it is overridden using the --browser
495 module, unless it is overridden using the --browser
495 (NotebookApp.browser) configuration option.
496 (NotebookApp.browser) configuration option.
496 """)
497 """)
497
498
498 browser = Unicode(u'', config=True,
499 browser = Unicode(u'', config=True,
499 help="""Specify what command to use to invoke a web
500 help="""Specify what command to use to invoke a web
500 browser when opening the notebook. If not specified, the
501 browser when opening the notebook. If not specified, the
501 default browser will be determined by the `webbrowser`
502 default browser will be determined by the `webbrowser`
502 standard library module, which allows setting of the
503 standard library module, which allows setting of the
503 BROWSER environment variable to override it.
504 BROWSER environment variable to override it.
504 """)
505 """)
505
506
506 webapp_settings = Dict(config=True,
507 webapp_settings = Dict(config=True,
507 help="DEPRECATED, use tornado_settings"
508 help="DEPRECATED, use tornado_settings"
508 )
509 )
509 def _webapp_settings_changed(self, name, old, new):
510 def _webapp_settings_changed(self, name, old, new):
510 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
511 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
511 self.tornado_settings = new
512 self.tornado_settings = new
512
513
513 tornado_settings = Dict(config=True,
514 tornado_settings = Dict(config=True,
514 help="Supply overrides for the tornado.web.Application that the "
515 help="Supply overrides for the tornado.web.Application that the "
515 "IPython notebook uses.")
516 "IPython notebook uses.")
516
517
517 ssl_options = Dict(config=True,
518 ssl_options = Dict(config=True,
518 help="""Supply SSL options for the tornado HTTPServer.
519 help="""Supply SSL options for the tornado HTTPServer.
519 See the tornado docs for details.""")
520 See the tornado docs for details.""")
520
521
521 jinja_environment_options = Dict(config=True,
522 jinja_environment_options = Dict(config=True,
522 help="Supply extra arguments that will be passed to Jinja environment.")
523 help="Supply extra arguments that will be passed to Jinja environment.")
524
525 jinja_template_vars = Dict(
526 config=True,
527 help="Extra variables to supply to jinja templates when rendering.",
528 )
523
529
524 enable_mathjax = Bool(True, config=True,
530 enable_mathjax = Bool(True, config=True,
525 help="""Whether to enable MathJax for typesetting math/TeX
531 help="""Whether to enable MathJax for typesetting math/TeX
526
532
527 MathJax is the javascript library IPython uses to render math/LaTeX. It is
533 MathJax is the javascript library IPython uses to render math/LaTeX. It is
528 very large, so you may want to disable it if you have a slow internet
534 very large, so you may want to disable it if you have a slow internet
529 connection, or for offline use of the notebook.
535 connection, or for offline use of the notebook.
530
536
531 When disabled, equations etc. will appear as their untransformed TeX source.
537 When disabled, equations etc. will appear as their untransformed TeX source.
532 """
538 """
533 )
539 )
534 def _enable_mathjax_changed(self, name, old, new):
540 def _enable_mathjax_changed(self, name, old, new):
535 """set mathjax url to empty if mathjax is disabled"""
541 """set mathjax url to empty if mathjax is disabled"""
536 if not new:
542 if not new:
537 self.mathjax_url = u''
543 self.mathjax_url = u''
538
544
539 base_url = Unicode('/', config=True,
545 base_url = Unicode('/', config=True,
540 help='''The base URL for the notebook server.
546 help='''The base URL for the notebook server.
541
547
542 Leading and trailing slashes can be omitted,
548 Leading and trailing slashes can be omitted,
543 and will automatically be added.
549 and will automatically be added.
544 ''')
550 ''')
545 def _base_url_changed(self, name, old, new):
551 def _base_url_changed(self, name, old, new):
546 if not new.startswith('/'):
552 if not new.startswith('/'):
547 self.base_url = '/'+new
553 self.base_url = '/'+new
548 elif not new.endswith('/'):
554 elif not new.endswith('/'):
549 self.base_url = new+'/'
555 self.base_url = new+'/'
550
556
551 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
557 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
552 def _base_project_url_changed(self, name, old, new):
558 def _base_project_url_changed(self, name, old, new):
553 self.log.warn("base_project_url is deprecated, use base_url")
559 self.log.warn("base_project_url is deprecated, use base_url")
554 self.base_url = new
560 self.base_url = new
555
561
556 extra_static_paths = List(Unicode, config=True,
562 extra_static_paths = List(Unicode, config=True,
557 help="""Extra paths to search for serving static files.
563 help="""Extra paths to search for serving static files.
558
564
559 This allows adding javascript/css to be available from the notebook server machine,
565 This allows adding javascript/css to be available from the notebook server machine,
560 or overriding individual files in the IPython"""
566 or overriding individual files in the IPython"""
561 )
567 )
562 def _extra_static_paths_default(self):
568 def _extra_static_paths_default(self):
563 return [os.path.join(self.profile_dir.location, 'static')]
569 return [os.path.join(self.profile_dir.location, 'static')]
564
570
565 @property
571 @property
566 def static_file_path(self):
572 def static_file_path(self):
567 """return extra paths + the default location"""
573 """return extra paths + the default location"""
568 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
574 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
569
575
570 extra_template_paths = List(Unicode, config=True,
576 extra_template_paths = List(Unicode, config=True,
571 help="""Extra paths to search for serving jinja templates.
577 help="""Extra paths to search for serving jinja templates.
572
578
573 Can be used to override templates from IPython.html.templates."""
579 Can be used to override templates from IPython.html.templates."""
574 )
580 )
575 def _extra_template_paths_default(self):
581 def _extra_template_paths_default(self):
576 return []
582 return []
577
583
578 @property
584 @property
579 def template_file_path(self):
585 def template_file_path(self):
580 """return extra paths + the default locations"""
586 """return extra paths + the default locations"""
581 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
587 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
582
588
583 extra_nbextensions_path = List(Unicode, config=True,
589 extra_nbextensions_path = List(Unicode, config=True,
584 help="""extra paths to look for Javascript notebook extensions"""
590 help="""extra paths to look for Javascript notebook extensions"""
585 )
591 )
586
592
587 @property
593 @property
588 def nbextensions_path(self):
594 def nbextensions_path(self):
589 """The path to look for Javascript notebook extensions"""
595 """The path to look for Javascript notebook extensions"""
590 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
596 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
591
597
592 websocket_url = Unicode("", config=True,
598 websocket_url = Unicode("", config=True,
593 help="""The base URL for websockets,
599 help="""The base URL for websockets,
594 if it differs from the HTTP server (hint: it almost certainly doesn't).
600 if it differs from the HTTP server (hint: it almost certainly doesn't).
595
601
596 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
602 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
597 """
603 """
598 )
604 )
599 mathjax_url = Unicode("", config=True,
605 mathjax_url = Unicode("", config=True,
600 help="""The url for MathJax.js."""
606 help="""The url for MathJax.js."""
601 )
607 )
602 def _mathjax_url_default(self):
608 def _mathjax_url_default(self):
603 if not self.enable_mathjax:
609 if not self.enable_mathjax:
604 return u''
610 return u''
605 static_url_prefix = self.tornado_settings.get("static_url_prefix",
611 static_url_prefix = self.tornado_settings.get("static_url_prefix",
606 url_path_join(self.base_url, "static")
612 url_path_join(self.base_url, "static")
607 )
613 )
608
614
609 # try local mathjax, either in nbextensions/mathjax or static/mathjax
615 # try local mathjax, either in nbextensions/mathjax or static/mathjax
610 for (url_prefix, search_path) in [
616 for (url_prefix, search_path) in [
611 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
617 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
612 (static_url_prefix, self.static_file_path),
618 (static_url_prefix, self.static_file_path),
613 ]:
619 ]:
614 self.log.debug("searching for local mathjax in %s", search_path)
620 self.log.debug("searching for local mathjax in %s", search_path)
615 try:
621 try:
616 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
622 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
617 except IOError:
623 except IOError:
618 continue
624 continue
619 else:
625 else:
620 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
626 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
621 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
627 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
622 return url
628 return url
623
629
624 # no local mathjax, serve from CDN
630 # no local mathjax, serve from CDN
625 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
631 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
626 self.log.info("Using MathJax from CDN: %s", url)
632 self.log.info("Using MathJax from CDN: %s", url)
627 return url
633 return url
628
634
629 def _mathjax_url_changed(self, name, old, new):
635 def _mathjax_url_changed(self, name, old, new):
630 if new and not self.enable_mathjax:
636 if new and not self.enable_mathjax:
631 # enable_mathjax=False overrides mathjax_url
637 # enable_mathjax=False overrides mathjax_url
632 self.mathjax_url = u''
638 self.mathjax_url = u''
633 else:
639 else:
634 self.log.info("Using MathJax: %s", new)
640 self.log.info("Using MathJax: %s", new)
635
641
636 contents_manager_class = Type(
642 contents_manager_class = Type(
637 default_value=FileContentsManager,
643 default_value=FileContentsManager,
638 klass=ContentsManager,
644 klass=ContentsManager,
639 config=True,
645 config=True,
640 help='The notebook manager class to use.'
646 help='The notebook manager class to use.'
641 )
647 )
642 kernel_manager_class = Type(
648 kernel_manager_class = Type(
643 default_value=MappingKernelManager,
649 default_value=MappingKernelManager,
644 config=True,
650 config=True,
645 help='The kernel manager class to use.'
651 help='The kernel manager class to use.'
646 )
652 )
647 session_manager_class = Type(
653 session_manager_class = Type(
648 default_value=SessionManager,
654 default_value=SessionManager,
649 config=True,
655 config=True,
650 help='The session manager class to use.'
656 help='The session manager class to use.'
651 )
657 )
652 cluster_manager_class = Type(
658 cluster_manager_class = Type(
653 default_value=ClusterManager,
659 default_value=ClusterManager,
654 config=True,
660 config=True,
655 help='The cluster manager class to use.'
661 help='The cluster manager class to use.'
656 )
662 )
657
663
658 config_manager_class = Type(
664 config_manager_class = Type(
659 default_value=ConfigManager,
665 default_value=ConfigManager,
660 config = True,
666 config = True,
661 help='The config manager class to use'
667 help='The config manager class to use'
662 )
668 )
663
669
664 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
670 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
665
671
666 kernel_spec_manager_class = Type(
672 kernel_spec_manager_class = Type(
667 default_value=KernelSpecManager,
673 default_value=KernelSpecManager,
668 config=True,
674 config=True,
669 help="""
675 help="""
670 The kernel spec manager class to use. Should be a subclass
676 The kernel spec manager class to use. Should be a subclass
671 of `IPython.kernel.kernelspec.KernelSpecManager`.
677 of `IPython.kernel.kernelspec.KernelSpecManager`.
672
678
673 The Api of KernelSpecManager is provisional and might change
679 The Api of KernelSpecManager is provisional and might change
674 without warning between this version of IPython and the next stable one.
680 without warning between this version of IPython and the next stable one.
675 """
681 """
676 )
682 )
677
683
678 login_handler_class = Type(
684 login_handler_class = Type(
679 default_value=LoginHandler,
685 default_value=LoginHandler,
680 klass=web.RequestHandler,
686 klass=web.RequestHandler,
681 config=True,
687 config=True,
682 help='The login handler class to use.',
688 help='The login handler class to use.',
683 )
689 )
684
690
685 logout_handler_class = Type(
691 logout_handler_class = Type(
686 default_value=LogoutHandler,
692 default_value=LogoutHandler,
687 klass=web.RequestHandler,
693 klass=web.RequestHandler,
688 config=True,
694 config=True,
689 help='The logout handler class to use.',
695 help='The logout handler class to use.',
690 )
696 )
691
697
692 trust_xheaders = Bool(False, config=True,
698 trust_xheaders = Bool(False, config=True,
693 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
699 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
694 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
700 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
695 )
701 )
696
702
697 info_file = Unicode()
703 info_file = Unicode()
698
704
699 def _info_file_default(self):
705 def _info_file_default(self):
700 info_file = "nbserver-%s.json"%os.getpid()
706 info_file = "nbserver-%s.json"%os.getpid()
701 return os.path.join(self.profile_dir.security_dir, info_file)
707 return os.path.join(self.profile_dir.security_dir, info_file)
702
708
703 pylab = Unicode('disabled', config=True,
709 pylab = Unicode('disabled', config=True,
704 help="""
710 help="""
705 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
711 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
706 """
712 """
707 )
713 )
708 def _pylab_changed(self, name, old, new):
714 def _pylab_changed(self, name, old, new):
709 """when --pylab is specified, display a warning and exit"""
715 """when --pylab is specified, display a warning and exit"""
710 if new != 'warn':
716 if new != 'warn':
711 backend = ' %s' % new
717 backend = ' %s' % new
712 else:
718 else:
713 backend = ''
719 backend = ''
714 self.log.error("Support for specifying --pylab on the command line has been removed.")
720 self.log.error("Support for specifying --pylab on the command line has been removed.")
715 self.log.error(
721 self.log.error(
716 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
722 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
717 )
723 )
718 self.exit(1)
724 self.exit(1)
719
725
720 notebook_dir = Unicode(config=True,
726 notebook_dir = Unicode(config=True,
721 help="The directory to use for notebooks and kernels."
727 help="The directory to use for notebooks and kernels."
722 )
728 )
723
729
724 def _notebook_dir_default(self):
730 def _notebook_dir_default(self):
725 if self.file_to_run:
731 if self.file_to_run:
726 return os.path.dirname(os.path.abspath(self.file_to_run))
732 return os.path.dirname(os.path.abspath(self.file_to_run))
727 else:
733 else:
728 return py3compat.getcwd()
734 return py3compat.getcwd()
729
735
730 def _notebook_dir_changed(self, name, old, new):
736 def _notebook_dir_changed(self, name, old, new):
731 """Do a bit of validation of the notebook dir."""
737 """Do a bit of validation of the notebook dir."""
732 if not os.path.isabs(new):
738 if not os.path.isabs(new):
733 # If we receive a non-absolute path, make it absolute.
739 # If we receive a non-absolute path, make it absolute.
734 self.notebook_dir = os.path.abspath(new)
740 self.notebook_dir = os.path.abspath(new)
735 return
741 return
736 if not os.path.isdir(new):
742 if not os.path.isdir(new):
737 raise TraitError("No such notebook dir: %r" % new)
743 raise TraitError("No such notebook dir: %r" % new)
738
744
739 # setting App.notebook_dir implies setting notebook and kernel dirs as well
745 # setting App.notebook_dir implies setting notebook and kernel dirs as well
740 self.config.FileContentsManager.root_dir = new
746 self.config.FileContentsManager.root_dir = new
741 self.config.MappingKernelManager.root_dir = new
747 self.config.MappingKernelManager.root_dir = new
742
748
743 server_extensions = List(Unicode(), config=True,
749 server_extensions = List(Unicode(), config=True,
744 help=("Python modules to load as notebook server extensions. "
750 help=("Python modules to load as notebook server extensions. "
745 "This is an experimental API, and may change in future releases.")
751 "This is an experimental API, and may change in future releases.")
746 )
752 )
747
753
748 reraise_server_extension_failures = Bool(
754 reraise_server_extension_failures = Bool(
749 False,
755 False,
750 config=True,
756 config=True,
751 help="Reraise exceptions encountered loading server extensions?",
757 help="Reraise exceptions encountered loading server extensions?",
752 )
758 )
753
759
754 def parse_command_line(self, argv=None):
760 def parse_command_line(self, argv=None):
755 super(NotebookApp, self).parse_command_line(argv)
761 super(NotebookApp, self).parse_command_line(argv)
756
762
757 if self.extra_args:
763 if self.extra_args:
758 arg0 = self.extra_args[0]
764 arg0 = self.extra_args[0]
759 f = os.path.abspath(arg0)
765 f = os.path.abspath(arg0)
760 self.argv.remove(arg0)
766 self.argv.remove(arg0)
761 if not os.path.exists(f):
767 if not os.path.exists(f):
762 self.log.critical("No such file or directory: %s", f)
768 self.log.critical("No such file or directory: %s", f)
763 self.exit(1)
769 self.exit(1)
764
770
765 # Use config here, to ensure that it takes higher priority than
771 # Use config here, to ensure that it takes higher priority than
766 # anything that comes from the profile.
772 # anything that comes from the profile.
767 c = Config()
773 c = Config()
768 if os.path.isdir(f):
774 if os.path.isdir(f):
769 c.NotebookApp.notebook_dir = f
775 c.NotebookApp.notebook_dir = f
770 elif os.path.isfile(f):
776 elif os.path.isfile(f):
771 c.NotebookApp.file_to_run = f
777 c.NotebookApp.file_to_run = f
772 self.update_config(c)
778 self.update_config(c)
773
779
774 def init_configurables(self):
780 def init_configurables(self):
775 self.kernel_spec_manager = self.kernel_spec_manager_class(
781 self.kernel_spec_manager = self.kernel_spec_manager_class(
776 parent=self,
782 parent=self,
777 ipython_dir=self.ipython_dir,
783 ipython_dir=self.ipython_dir,
778 )
784 )
779 self.kernel_manager = self.kernel_manager_class(
785 self.kernel_manager = self.kernel_manager_class(
780 parent=self,
786 parent=self,
781 log=self.log,
787 log=self.log,
782 connection_dir=self.profile_dir.security_dir,
788 connection_dir=self.profile_dir.security_dir,
783 )
789 )
784 self.contents_manager = self.contents_manager_class(
790 self.contents_manager = self.contents_manager_class(
785 parent=self,
791 parent=self,
786 log=self.log,
792 log=self.log,
787 )
793 )
788 self.session_manager = self.session_manager_class(
794 self.session_manager = self.session_manager_class(
789 parent=self,
795 parent=self,
790 log=self.log,
796 log=self.log,
791 kernel_manager=self.kernel_manager,
797 kernel_manager=self.kernel_manager,
792 contents_manager=self.contents_manager,
798 contents_manager=self.contents_manager,
793 )
799 )
794 self.cluster_manager = self.cluster_manager_class(
800 self.cluster_manager = self.cluster_manager_class(
795 parent=self,
801 parent=self,
796 log=self.log,
802 log=self.log,
797 )
803 )
798
804
799 self.config_manager = self.config_manager_class(
805 self.config_manager = self.config_manager_class(
800 parent=self,
806 parent=self,
801 log=self.log,
807 log=self.log,
802 profile_dir=self.profile_dir.location,
808 profile_dir=self.profile_dir.location,
803 )
809 )
804
810
805 def init_logging(self):
811 def init_logging(self):
806 # This prevents double log messages because tornado use a root logger that
812 # This prevents double log messages because tornado use a root logger that
807 # self.log is a child of. The logging module dipatches log messages to a log
813 # self.log is a child of. The logging module dipatches log messages to a log
808 # and all of its ancenstors until propagate is set to False.
814 # and all of its ancenstors until propagate is set to False.
809 self.log.propagate = False
815 self.log.propagate = False
810
816
811 for log in app_log, access_log, gen_log:
817 for log in app_log, access_log, gen_log:
812 # consistent log output name (NotebookApp instead of tornado.access, etc.)
818 # consistent log output name (NotebookApp instead of tornado.access, etc.)
813 log.name = self.log.name
819 log.name = self.log.name
814 # hook up tornado 3's loggers to our app handlers
820 # hook up tornado 3's loggers to our app handlers
815 logger = logging.getLogger('tornado')
821 logger = logging.getLogger('tornado')
816 logger.propagate = True
822 logger.propagate = True
817 logger.parent = self.log
823 logger.parent = self.log
818 logger.setLevel(self.log.level)
824 logger.setLevel(self.log.level)
819
825
820 def init_webapp(self):
826 def init_webapp(self):
821 """initialize tornado webapp and httpserver"""
827 """initialize tornado webapp and httpserver"""
822 self.tornado_settings['allow_origin'] = self.allow_origin
828 self.tornado_settings['allow_origin'] = self.allow_origin
823 if self.allow_origin_pat:
829 if self.allow_origin_pat:
824 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
830 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
825 self.tornado_settings['allow_credentials'] = self.allow_credentials
831 self.tornado_settings['allow_credentials'] = self.allow_credentials
826 # ensure default_url starts with base_url
832 # ensure default_url starts with base_url
827 if not self.default_url.startswith(self.base_url):
833 if not self.default_url.startswith(self.base_url):
828 self.default_url = url_path_join(self.base_url, self.default_url)
834 self.default_url = url_path_join(self.base_url, self.default_url)
829
835
830 self.web_app = NotebookWebApplication(
836 self.web_app = NotebookWebApplication(
831 self, self.kernel_manager, self.contents_manager,
837 self, self.kernel_manager, self.contents_manager,
832 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
838 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
833 self.config_manager,
839 self.config_manager,
834 self.log, self.base_url, self.default_url, self.tornado_settings,
840 self.log, self.base_url, self.default_url, self.tornado_settings,
835 self.jinja_environment_options
841 self.jinja_environment_options
836 )
842 )
837 ssl_options = self.ssl_options
843 ssl_options = self.ssl_options
838 if self.certfile:
844 if self.certfile:
839 ssl_options['certfile'] = self.certfile
845 ssl_options['certfile'] = self.certfile
840 if self.keyfile:
846 if self.keyfile:
841 ssl_options['keyfile'] = self.keyfile
847 ssl_options['keyfile'] = self.keyfile
842 if not ssl_options:
848 if not ssl_options:
843 # None indicates no SSL config
849 # None indicates no SSL config
844 ssl_options = None
850 ssl_options = None
845 else:
851 else:
846 # Disable SSLv3, since its use is discouraged.
852 # Disable SSLv3, since its use is discouraged.
847 ssl_options['ssl_version']=ssl.PROTOCOL_TLSv1
853 ssl_options['ssl_version']=ssl.PROTOCOL_TLSv1
848 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
854 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
849 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
855 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
850 xheaders=self.trust_xheaders)
856 xheaders=self.trust_xheaders)
851
857
852 success = None
858 success = None
853 for port in random_ports(self.port, self.port_retries+1):
859 for port in random_ports(self.port, self.port_retries+1):
854 try:
860 try:
855 self.http_server.listen(port, self.ip)
861 self.http_server.listen(port, self.ip)
856 except socket.error as e:
862 except socket.error as e:
857 if e.errno == errno.EADDRINUSE:
863 if e.errno == errno.EADDRINUSE:
858 self.log.info('The port %i is already in use, trying another random port.' % port)
864 self.log.info('The port %i is already in use, trying another random port.' % port)
859 continue
865 continue
860 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
866 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
861 self.log.warn("Permission to listen on port %i denied" % port)
867 self.log.warn("Permission to listen on port %i denied" % port)
862 continue
868 continue
863 else:
869 else:
864 raise
870 raise
865 else:
871 else:
866 self.port = port
872 self.port = port
867 success = True
873 success = True
868 break
874 break
869 if not success:
875 if not success:
870 self.log.critical('ERROR: the notebook server could not be started because '
876 self.log.critical('ERROR: the notebook server could not be started because '
871 'no available port could be found.')
877 'no available port could be found.')
872 self.exit(1)
878 self.exit(1)
873
879
874 @property
880 @property
875 def display_url(self):
881 def display_url(self):
876 ip = self.ip if self.ip else '[all ip addresses on your system]'
882 ip = self.ip if self.ip else '[all ip addresses on your system]'
877 return self._url(ip)
883 return self._url(ip)
878
884
879 @property
885 @property
880 def connection_url(self):
886 def connection_url(self):
881 ip = self.ip if self.ip else 'localhost'
887 ip = self.ip if self.ip else 'localhost'
882 return self._url(ip)
888 return self._url(ip)
883
889
884 def _url(self, ip):
890 def _url(self, ip):
885 proto = 'https' if self.certfile else 'http'
891 proto = 'https' if self.certfile else 'http'
886 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
892 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
887
893
888 def init_terminals(self):
894 def init_terminals(self):
889 try:
895 try:
890 from .terminal import initialize
896 from .terminal import initialize
891 initialize(self.web_app)
897 initialize(self.web_app)
892 self.web_app.settings['terminals_available'] = True
898 self.web_app.settings['terminals_available'] = True
893 except ImportError as e:
899 except ImportError as e:
894 log = self.log.debug if sys.platform == 'win32' else self.log.warn
900 log = self.log.debug if sys.platform == 'win32' else self.log.warn
895 log("Terminals not available (error was %s)", e)
901 log("Terminals not available (error was %s)", e)
896
902
897 def init_signal(self):
903 def init_signal(self):
898 if not sys.platform.startswith('win'):
904 if not sys.platform.startswith('win'):
899 signal.signal(signal.SIGINT, self._handle_sigint)
905 signal.signal(signal.SIGINT, self._handle_sigint)
900 signal.signal(signal.SIGTERM, self._signal_stop)
906 signal.signal(signal.SIGTERM, self._signal_stop)
901 if hasattr(signal, 'SIGUSR1'):
907 if hasattr(signal, 'SIGUSR1'):
902 # Windows doesn't support SIGUSR1
908 # Windows doesn't support SIGUSR1
903 signal.signal(signal.SIGUSR1, self._signal_info)
909 signal.signal(signal.SIGUSR1, self._signal_info)
904 if hasattr(signal, 'SIGINFO'):
910 if hasattr(signal, 'SIGINFO'):
905 # only on BSD-based systems
911 # only on BSD-based systems
906 signal.signal(signal.SIGINFO, self._signal_info)
912 signal.signal(signal.SIGINFO, self._signal_info)
907
913
908 def _handle_sigint(self, sig, frame):
914 def _handle_sigint(self, sig, frame):
909 """SIGINT handler spawns confirmation dialog"""
915 """SIGINT handler spawns confirmation dialog"""
910 # register more forceful signal handler for ^C^C case
916 # register more forceful signal handler for ^C^C case
911 signal.signal(signal.SIGINT, self._signal_stop)
917 signal.signal(signal.SIGINT, self._signal_stop)
912 # request confirmation dialog in bg thread, to avoid
918 # request confirmation dialog in bg thread, to avoid
913 # blocking the App
919 # blocking the App
914 thread = threading.Thread(target=self._confirm_exit)
920 thread = threading.Thread(target=self._confirm_exit)
915 thread.daemon = True
921 thread.daemon = True
916 thread.start()
922 thread.start()
917
923
918 def _restore_sigint_handler(self):
924 def _restore_sigint_handler(self):
919 """callback for restoring original SIGINT handler"""
925 """callback for restoring original SIGINT handler"""
920 signal.signal(signal.SIGINT, self._handle_sigint)
926 signal.signal(signal.SIGINT, self._handle_sigint)
921
927
922 def _confirm_exit(self):
928 def _confirm_exit(self):
923 """confirm shutdown on ^C
929 """confirm shutdown on ^C
924
930
925 A second ^C, or answering 'y' within 5s will cause shutdown,
931 A second ^C, or answering 'y' within 5s will cause shutdown,
926 otherwise original SIGINT handler will be restored.
932 otherwise original SIGINT handler will be restored.
927
933
928 This doesn't work on Windows.
934 This doesn't work on Windows.
929 """
935 """
930 info = self.log.info
936 info = self.log.info
931 info('interrupted')
937 info('interrupted')
932 print(self.notebook_info())
938 print(self.notebook_info())
933 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
939 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
934 sys.stdout.flush()
940 sys.stdout.flush()
935 r,w,x = select.select([sys.stdin], [], [], 5)
941 r,w,x = select.select([sys.stdin], [], [], 5)
936 if r:
942 if r:
937 line = sys.stdin.readline()
943 line = sys.stdin.readline()
938 if line.lower().startswith('y') and 'n' not in line.lower():
944 if line.lower().startswith('y') and 'n' not in line.lower():
939 self.log.critical("Shutdown confirmed")
945 self.log.critical("Shutdown confirmed")
940 ioloop.IOLoop.current().stop()
946 ioloop.IOLoop.current().stop()
941 return
947 return
942 else:
948 else:
943 print("No answer for 5s:", end=' ')
949 print("No answer for 5s:", end=' ')
944 print("resuming operation...")
950 print("resuming operation...")
945 # no answer, or answer is no:
951 # no answer, or answer is no:
946 # set it back to original SIGINT handler
952 # set it back to original SIGINT handler
947 # use IOLoop.add_callback because signal.signal must be called
953 # use IOLoop.add_callback because signal.signal must be called
948 # from main thread
954 # from main thread
949 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
955 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
950
956
951 def _signal_stop(self, sig, frame):
957 def _signal_stop(self, sig, frame):
952 self.log.critical("received signal %s, stopping", sig)
958 self.log.critical("received signal %s, stopping", sig)
953 ioloop.IOLoop.current().stop()
959 ioloop.IOLoop.current().stop()
954
960
955 def _signal_info(self, sig, frame):
961 def _signal_info(self, sig, frame):
956 print(self.notebook_info())
962 print(self.notebook_info())
957
963
958 def init_components(self):
964 def init_components(self):
959 """Check the components submodule, and warn if it's unclean"""
965 """Check the components submodule, and warn if it's unclean"""
960 status = submodule.check_submodule_status()
966 status = submodule.check_submodule_status()
961 if status == 'missing':
967 if status == 'missing':
962 self.log.warn("components submodule missing, running `git submodule update`")
968 self.log.warn("components submodule missing, running `git submodule update`")
963 submodule.update_submodules(submodule.ipython_parent())
969 submodule.update_submodules(submodule.ipython_parent())
964 elif status == 'unclean':
970 elif status == 'unclean':
965 self.log.warn("components submodule unclean, you may see 404s on static/components")
971 self.log.warn("components submodule unclean, you may see 404s on static/components")
966 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
972 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
967
973
968 def init_server_extensions(self):
974 def init_server_extensions(self):
969 """Load any extensions specified by config.
975 """Load any extensions specified by config.
970
976
971 Import the module, then call the load_jupyter_server_extension function,
977 Import the module, then call the load_jupyter_server_extension function,
972 if one exists.
978 if one exists.
973
979
974 The extension API is experimental, and may change in future releases.
980 The extension API is experimental, and may change in future releases.
975 """
981 """
976 for modulename in self.server_extensions:
982 for modulename in self.server_extensions:
977 try:
983 try:
978 mod = importlib.import_module(modulename)
984 mod = importlib.import_module(modulename)
979 func = getattr(mod, 'load_jupyter_server_extension', None)
985 func = getattr(mod, 'load_jupyter_server_extension', None)
980 if func is not None:
986 if func is not None:
981 func(self)
987 func(self)
982 except Exception:
988 except Exception:
983 if self.reraise_server_extension_failures:
989 if self.reraise_server_extension_failures:
984 raise
990 raise
985 self.log.warn("Error loading server extension %s", modulename,
991 self.log.warn("Error loading server extension %s", modulename,
986 exc_info=True)
992 exc_info=True)
987
993
988 @catch_config_error
994 @catch_config_error
989 def initialize(self, argv=None):
995 def initialize(self, argv=None):
990 super(NotebookApp, self).initialize(argv)
996 super(NotebookApp, self).initialize(argv)
991 self.init_logging()
997 self.init_logging()
992 self.init_configurables()
998 self.init_configurables()
993 self.init_components()
999 self.init_components()
994 self.init_webapp()
1000 self.init_webapp()
995 self.init_terminals()
1001 self.init_terminals()
996 self.init_signal()
1002 self.init_signal()
997 self.init_server_extensions()
1003 self.init_server_extensions()
998
1004
999 def cleanup_kernels(self):
1005 def cleanup_kernels(self):
1000 """Shutdown all kernels.
1006 """Shutdown all kernels.
1001
1007
1002 The kernels will shutdown themselves when this process no longer exists,
1008 The kernels will shutdown themselves when this process no longer exists,
1003 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1009 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1004 """
1010 """
1005 self.log.info('Shutting down kernels')
1011 self.log.info('Shutting down kernels')
1006 self.kernel_manager.shutdown_all()
1012 self.kernel_manager.shutdown_all()
1007
1013
1008 def notebook_info(self):
1014 def notebook_info(self):
1009 "Return the current working directory and the server url information"
1015 "Return the current working directory and the server url information"
1010 info = self.contents_manager.info_string() + "\n"
1016 info = self.contents_manager.info_string() + "\n"
1011 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1017 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1012 return info + "The IPython Notebook is running at: %s" % self.display_url
1018 return info + "The IPython Notebook is running at: %s" % self.display_url
1013
1019
1014 def server_info(self):
1020 def server_info(self):
1015 """Return a JSONable dict of information about this server."""
1021 """Return a JSONable dict of information about this server."""
1016 return {'url': self.connection_url,
1022 return {'url': self.connection_url,
1017 'hostname': self.ip if self.ip else 'localhost',
1023 'hostname': self.ip if self.ip else 'localhost',
1018 'port': self.port,
1024 'port': self.port,
1019 'secure': bool(self.certfile),
1025 'secure': bool(self.certfile),
1020 'base_url': self.base_url,
1026 'base_url': self.base_url,
1021 'notebook_dir': os.path.abspath(self.notebook_dir),
1027 'notebook_dir': os.path.abspath(self.notebook_dir),
1022 'pid': os.getpid()
1028 'pid': os.getpid()
1023 }
1029 }
1024
1030
1025 def write_server_info_file(self):
1031 def write_server_info_file(self):
1026 """Write the result of server_info() to the JSON file info_file."""
1032 """Write the result of server_info() to the JSON file info_file."""
1027 with open(self.info_file, 'w') as f:
1033 with open(self.info_file, 'w') as f:
1028 json.dump(self.server_info(), f, indent=2)
1034 json.dump(self.server_info(), f, indent=2)
1029
1035
1030 def remove_server_info_file(self):
1036 def remove_server_info_file(self):
1031 """Remove the nbserver-<pid>.json file created for this server.
1037 """Remove the nbserver-<pid>.json file created for this server.
1032
1038
1033 Ignores the error raised when the file has already been removed.
1039 Ignores the error raised when the file has already been removed.
1034 """
1040 """
1035 try:
1041 try:
1036 os.unlink(self.info_file)
1042 os.unlink(self.info_file)
1037 except OSError as e:
1043 except OSError as e:
1038 if e.errno != errno.ENOENT:
1044 if e.errno != errno.ENOENT:
1039 raise
1045 raise
1040
1046
1041 def start(self):
1047 def start(self):
1042 """ Start the IPython Notebook server app, after initialization
1048 """ Start the IPython Notebook server app, after initialization
1043
1049
1044 This method takes no arguments so all configuration and initialization
1050 This method takes no arguments so all configuration and initialization
1045 must be done prior to calling this method."""
1051 must be done prior to calling this method."""
1046 if self.subapp is not None:
1052 if self.subapp is not None:
1047 return self.subapp.start()
1053 return self.subapp.start()
1048
1054
1049 info = self.log.info
1055 info = self.log.info
1050 for line in self.notebook_info().split("\n"):
1056 for line in self.notebook_info().split("\n"):
1051 info(line)
1057 info(line)
1052 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1058 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1053
1059
1054 self.write_server_info_file()
1060 self.write_server_info_file()
1055
1061
1056 if self.open_browser or self.file_to_run:
1062 if self.open_browser or self.file_to_run:
1057 try:
1063 try:
1058 browser = webbrowser.get(self.browser or None)
1064 browser = webbrowser.get(self.browser or None)
1059 except webbrowser.Error as e:
1065 except webbrowser.Error as e:
1060 self.log.warn('No web browser found: %s.' % e)
1066 self.log.warn('No web browser found: %s.' % e)
1061 browser = None
1067 browser = None
1062
1068
1063 if self.file_to_run:
1069 if self.file_to_run:
1064 if not os.path.exists(self.file_to_run):
1070 if not os.path.exists(self.file_to_run):
1065 self.log.critical("%s does not exist" % self.file_to_run)
1071 self.log.critical("%s does not exist" % self.file_to_run)
1066 self.exit(1)
1072 self.exit(1)
1067
1073
1068 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1074 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1069 uri = url_path_join('notebooks', *relpath.split(os.sep))
1075 uri = url_path_join('notebooks', *relpath.split(os.sep))
1070 else:
1076 else:
1071 uri = 'tree'
1077 uri = 'tree'
1072 if browser:
1078 if browser:
1073 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1079 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1074 new=2)
1080 new=2)
1075 threading.Thread(target=b).start()
1081 threading.Thread(target=b).start()
1076
1082
1077 self.io_loop = ioloop.IOLoop.current()
1083 self.io_loop = ioloop.IOLoop.current()
1078 if sys.platform.startswith('win'):
1084 if sys.platform.startswith('win'):
1079 # add no-op to wake every 5s
1085 # add no-op to wake every 5s
1080 # to handle signals that may be ignored by the inner loop
1086 # to handle signals that may be ignored by the inner loop
1081 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1087 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1082 pc.start()
1088 pc.start()
1083 try:
1089 try:
1084 self.io_loop.start()
1090 self.io_loop.start()
1085 except KeyboardInterrupt:
1091 except KeyboardInterrupt:
1086 info("Interrupted...")
1092 info("Interrupted...")
1087 finally:
1093 finally:
1088 self.cleanup_kernels()
1094 self.cleanup_kernels()
1089 self.remove_server_info_file()
1095 self.remove_server_info_file()
1090
1096
1091 def stop(self):
1097 def stop(self):
1092 def _stop():
1098 def _stop():
1093 self.http_server.stop()
1099 self.http_server.stop()
1094 self.io_loop.stop()
1100 self.io_loop.stop()
1095 self.io_loop.add_callback(_stop)
1101 self.io_loop.add_callback(_stop)
1096
1102
1097
1103
1098 def list_running_servers(profile='default'):
1104 def list_running_servers(profile='default'):
1099 """Iterate over the server info files of running notebook servers.
1105 """Iterate over the server info files of running notebook servers.
1100
1106
1101 Given a profile name, find nbserver-* files in the security directory of
1107 Given a profile name, find nbserver-* files in the security directory of
1102 that profile, and yield dicts of their information, each one pertaining to
1108 that profile, and yield dicts of their information, each one pertaining to
1103 a currently running notebook server instance.
1109 a currently running notebook server instance.
1104 """
1110 """
1105 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1111 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1106 for file in os.listdir(pd.security_dir):
1112 for file in os.listdir(pd.security_dir):
1107 if file.startswith('nbserver-'):
1113 if file.startswith('nbserver-'):
1108 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1114 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1109 info = json.load(f)
1115 info = json.load(f)
1110
1116
1111 # Simple check whether that process is really still running
1117 # Simple check whether that process is really still running
1112 # Also remove leftover files from IPython 2.x without a pid field
1118 # Also remove leftover files from IPython 2.x without a pid field
1113 if ('pid' in info) and check_pid(info['pid']):
1119 if ('pid' in info) and check_pid(info['pid']):
1114 yield info
1120 yield info
1115 else:
1121 else:
1116 # If the process has died, try to delete its info file
1122 # If the process has died, try to delete its info file
1117 try:
1123 try:
1118 os.unlink(file)
1124 os.unlink(file)
1119 except OSError:
1125 except OSError:
1120 pass # TODO: This should warn or log or something
1126 pass # TODO: This should warn or log or something
1121 #-----------------------------------------------------------------------------
1127 #-----------------------------------------------------------------------------
1122 # Main entry point
1128 # Main entry point
1123 #-----------------------------------------------------------------------------
1129 #-----------------------------------------------------------------------------
1124
1130
1125 launch_new_instance = NotebookApp.launch_instance
1131 launch_new_instance = NotebookApp.launch_instance
1126
1132
@@ -1,328 +1,329 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 <link rel="stylesheet" href="" id='kernel-css' type="text/css" />
20 <link rel="stylesheet" href="" id='kernel-css' type="text/css" />
21
21
22 {% endblock %}
22 {% endblock %}
23
23
24 {% block bodyclasses %}notebook_app {{super()}}{% endblock %}
24 {% block bodyclasses %}notebook_app {{super()}}{% endblock %}
25
25
26 {% block params %}
26 {% block params %}
27
27
28 {{super()}}
28 data-project="{{project}}"
29 data-project="{{project}}"
29 data-base-url="{{base_url}}"
30 data-base-url="{{base_url}}"
30 data-ws-url="{{ws_url}}"
31 data-ws-url="{{ws_url}}"
31 data-notebook-name="{{notebook_name}}"
32 data-notebook-name="{{notebook_name}}"
32 data-notebook-path="{{notebook_path}}"
33 data-notebook-path="{{notebook_path}}"
33
34
34 {% endblock %}
35 {% endblock %}
35
36
36
37
37 {% block headercontainer %}
38 {% block headercontainer %}
38
39
39
40
40 <span id="save_widget" class="pull-left save_widget">
41 <span id="save_widget" class="pull-left save_widget">
41 <span id="notebook_name" class="filename"></span>
42 <span id="notebook_name" class="filename"></span>
42 <span class="checkpoint_status"></span>
43 <span class="checkpoint_status"></span>
43 <span class="autosave_status"></span>
44 <span class="autosave_status"></span>
44 </span>
45 </span>
45
46
46 <span id="kernel_logo_widget">
47 <span id="kernel_logo_widget">
47 <img class="current_kernel_logo" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"/>
48 <img class="current_kernel_logo" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"/>
48 </span>
49 </span>
49
50
50 {% endblock headercontainer %}
51 {% endblock headercontainer %}
51
52
52 {% block header %}
53 {% block header %}
53 <div id="menubar-container" class="container">
54 <div id="menubar-container" class="container">
54 <div id="menubar">
55 <div id="menubar">
55 <div id="menus" class="navbar navbar-default" role="navigation">
56 <div id="menus" class="navbar navbar-default" role="navigation">
56 <div class="container-fluid">
57 <div class="container-fluid">
57 <button type="button" class="btn btn-default navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
58 <button type="button" class="btn btn-default navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
58 <i class="fa fa-bars"></i>
59 <i class="fa fa-bars"></i>
59 <span class="navbar-text">Menu</span>
60 <span class="navbar-text">Menu</span>
60 </button>
61 </button>
61 <p id="kernel_indicator" class="navbar-text indicator_area">
62 <p id="kernel_indicator" class="navbar-text indicator_area">
62 <span class="kernel_indicator_name">Kernel</span>
63 <span class="kernel_indicator_name">Kernel</span>
63 <i id="kernel_indicator_icon"></i>
64 <i id="kernel_indicator_icon"></i>
64 </p>
65 </p>
65 <i id="readonly-indicator" class="navbar-text" title='This notebook is read-only'>
66 <i id="readonly-indicator" class="navbar-text" title='This notebook is read-only'>
66 <span class="fa-stack">
67 <span class="fa-stack">
67 <i class="fa fa-save fa-stack-1x"></i>
68 <i class="fa fa-save fa-stack-1x"></i>
68 <i class="fa fa-ban fa-stack-2x text-danger"></i>
69 <i class="fa fa-ban fa-stack-2x text-danger"></i>
69 </span>
70 </span>
70 </i>
71 </i>
71 <i id="modal_indicator" class="navbar-text"></i>
72 <i id="modal_indicator" class="navbar-text"></i>
72 <span id="notification_area"></span>
73 <span id="notification_area"></span>
73 <div class="navbar-collapse collapse">
74 <div class="navbar-collapse collapse">
74 <ul class="nav navbar-nav">
75 <ul class="nav navbar-nav">
75 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
76 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
76 <ul id="file_menu" class="dropdown-menu">
77 <ul id="file_menu" class="dropdown-menu">
77 <li id="new_notebook" class="dropdown-submenu">
78 <li id="new_notebook" class="dropdown-submenu">
78 <a href="#">New Notebook</a>
79 <a href="#">New Notebook</a>
79 <ul class="dropdown-menu" id="menu-new-notebook-submenu"></ul>
80 <ul class="dropdown-menu" id="menu-new-notebook-submenu"></ul>
80 </li>
81 </li>
81 <li id="open_notebook"
82 <li id="open_notebook"
82 title="Opens a new window with the Dashboard view">
83 title="Opens a new window with the Dashboard view">
83 <a href="#">Open...</a></li>
84 <a href="#">Open...</a></li>
84 <!-- <hr/> -->
85 <!-- <hr/> -->
85 <li class="divider"></li>
86 <li class="divider"></li>
86 <li id="copy_notebook"
87 <li id="copy_notebook"
87 title="Open a copy of this notebook's contents and start a new kernel">
88 title="Open a copy of this notebook's contents and start a new kernel">
88 <a href="#">Make a Copy...</a></li>
89 <a href="#">Make a Copy...</a></li>
89 <li id="rename_notebook"><a href="#">Rename...</a></li>
90 <li id="rename_notebook"><a href="#">Rename...</a></li>
90 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
91 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
91 <!-- <hr/> -->
92 <!-- <hr/> -->
92 <li class="divider"></li>
93 <li class="divider"></li>
93 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
94 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
94 <ul class="dropdown-menu">
95 <ul class="dropdown-menu">
95 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
99 <li><a href="#"></a></li>
99 <li><a href="#"></a></li>
100 <li><a href="#"></a></li>
100 </ul>
101 </ul>
101 </li>
102 </li>
102 <li class="divider"></li>
103 <li class="divider"></li>
103 <li id="print_preview"><a href="#">Print Preview</a></li>
104 <li id="print_preview"><a href="#">Print Preview</a></li>
104 <li class="dropdown-submenu"><a href="#">Download as</a>
105 <li class="dropdown-submenu"><a href="#">Download as</a>
105 <ul class="dropdown-menu">
106 <ul class="dropdown-menu">
106 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
107 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
107 <li id="download_script"><a href="#">Script</a></li>
108 <li id="download_script"><a href="#">Script</a></li>
108 <li id="download_html"><a href="#">HTML (.html)</a></li>
109 <li id="download_html"><a href="#">HTML (.html)</a></li>
109 <li id="download_markdown"><a href="#">Markdown (.md)</a></li>
110 <li id="download_markdown"><a href="#">Markdown (.md)</a></li>
110 <li id="download_rst"><a href="#">reST (.rst)</a></li>
111 <li id="download_rst"><a href="#">reST (.rst)</a></li>
111 <li id="download_pdf"><a href="#">PDF via LaTeX (.pdf)</a></li>
112 <li id="download_pdf"><a href="#">PDF via LaTeX (.pdf)</a></li>
112 </ul>
113 </ul>
113 </li>
114 </li>
114 <li class="divider"></li>
115 <li class="divider"></li>
115 <li id="trust_notebook"
116 <li id="trust_notebook"
116 title="Trust the output of this notebook">
117 title="Trust the output of this notebook">
117 <a href="#" >Trust Notebook</a></li>
118 <a href="#" >Trust Notebook</a></li>
118 <li class="divider"></li>
119 <li class="divider"></li>
119 <li id="kill_and_exit"
120 <li id="kill_and_exit"
120 title="Shutdown this notebook's kernel, and close this window">
121 title="Shutdown this notebook's kernel, and close this window">
121 <a href="#" >Close and Halt</a></li>
122 <a href="#" >Close and Halt</a></li>
122 </ul>
123 </ul>
123 </li>
124 </li>
124 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
125 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
125 <ul id="edit_menu" class="dropdown-menu">
126 <ul id="edit_menu" class="dropdown-menu">
126 <li id="cut_cell"><a href="#">Cut Cell</a></li>
127 <li id="cut_cell"><a href="#">Cut Cell</a></li>
127 <li id="copy_cell"><a href="#">Copy Cell</a></li>
128 <li id="copy_cell"><a href="#">Copy Cell</a></li>
128 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
129 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
129 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
130 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
130 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
131 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
131 <li id="delete_cell"><a href="#">Delete Cell</a></li>
132 <li id="delete_cell"><a href="#">Delete Cell</a></li>
132 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
133 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
133 <li class="divider"></li>
134 <li class="divider"></li>
134 <li id="split_cell"><a href="#">Split Cell</a></li>
135 <li id="split_cell"><a href="#">Split Cell</a></li>
135 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
136 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
136 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
137 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
137 <li class="divider"></li>
138 <li class="divider"></li>
138 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
139 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
139 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
140 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
140 <li class="divider"></li>
141 <li class="divider"></li>
141 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
142 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
142 </ul>
143 </ul>
143 </li>
144 </li>
144 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
145 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
145 <ul id="view_menu" class="dropdown-menu">
146 <ul id="view_menu" class="dropdown-menu">
146 <li id="toggle_header"
147 <li id="toggle_header"
147 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
148 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
148 <a href="#">Toggle Header</a></li>
149 <a href="#">Toggle Header</a></li>
149 <li id="toggle_toolbar"
150 <li id="toggle_toolbar"
150 title="Show/Hide the action icons (below menu bar)">
151 title="Show/Hide the action icons (below menu bar)">
151 <a href="#">Toggle Toolbar</a></li>
152 <a href="#">Toggle Toolbar</a></li>
152 </ul>
153 </ul>
153 </li>
154 </li>
154 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
155 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
155 <ul id="insert_menu" class="dropdown-menu">
156 <ul id="insert_menu" class="dropdown-menu">
156 <li id="insert_cell_above"
157 <li id="insert_cell_above"
157 title="Insert an empty Code cell above the currently active cell">
158 title="Insert an empty Code cell above the currently active cell">
158 <a href="#">Insert Cell Above</a></li>
159 <a href="#">Insert Cell Above</a></li>
159 <li id="insert_cell_below"
160 <li id="insert_cell_below"
160 title="Insert an empty Code cell below the currently active cell">
161 title="Insert an empty Code cell below the currently active cell">
161 <a href="#">Insert Cell Below</a></li>
162 <a href="#">Insert Cell Below</a></li>
162 </ul>
163 </ul>
163 </li>
164 </li>
164 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
165 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
165 <ul id="cell_menu" class="dropdown-menu">
166 <ul id="cell_menu" class="dropdown-menu">
166 <li id="run_cell" title="Run this cell, and move cursor to the next one">
167 <li id="run_cell" title="Run this cell, and move cursor to the next one">
167 <a href="#">Run</a></li>
168 <a href="#">Run</a></li>
168 <li id="run_cell_select_below" title="Run this cell, select below">
169 <li id="run_cell_select_below" title="Run this cell, select below">
169 <a href="#">Run and Select Below</a></li>
170 <a href="#">Run and Select Below</a></li>
170 <li id="run_cell_insert_below" title="Run this cell, insert below">
171 <li id="run_cell_insert_below" title="Run this cell, insert below">
171 <a href="#">Run and Insert Below</a></li>
172 <a href="#">Run and Insert Below</a></li>
172 <li id="run_all_cells" title="Run all cells in the notebook">
173 <li id="run_all_cells" title="Run all cells in the notebook">
173 <a href="#">Run All</a></li>
174 <a href="#">Run All</a></li>
174 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
175 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
175 <a href="#">Run All Above</a></li>
176 <a href="#">Run All Above</a></li>
176 <li id="run_all_cells_below" title="Run this cell and all cells below it">
177 <li id="run_all_cells_below" title="Run this cell and all cells below it">
177 <a href="#">Run All Below</a></li>
178 <a href="#">Run All Below</a></li>
178 <li class="divider"></li>
179 <li class="divider"></li>
179 <li id="change_cell_type" class="dropdown-submenu"
180 <li id="change_cell_type" class="dropdown-submenu"
180 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
181 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
181 <a href="#">Cell Type</a>
182 <a href="#">Cell Type</a>
182 <ul class="dropdown-menu">
183 <ul class="dropdown-menu">
183 <li id="to_code"
184 <li id="to_code"
184 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
185 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
185 <a href="#">Code</a></li>
186 <a href="#">Code</a></li>
186 <li id="to_markdown"
187 <li id="to_markdown"
187 title="Contents will be rendered as HTML and serve as explanatory text">
188 title="Contents will be rendered as HTML and serve as explanatory text">
188 <a href="#">Markdown</a></li>
189 <a href="#">Markdown</a></li>
189 <li id="to_raw"
190 <li id="to_raw"
190 title="Contents will pass through nbconvert unmodified">
191 title="Contents will pass through nbconvert unmodified">
191 <a href="#">Raw NBConvert</a></li>
192 <a href="#">Raw NBConvert</a></li>
192 </ul>
193 </ul>
193 </li>
194 </li>
194 <li class="divider"></li>
195 <li class="divider"></li>
195 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
196 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
196 <ul class="dropdown-menu">
197 <ul class="dropdown-menu">
197 <li id="toggle_current_output"
198 <li id="toggle_current_output"
198 title="Hide/Show the output of the current cell">
199 title="Hide/Show the output of the current cell">
199 <a href="#">Toggle</a>
200 <a href="#">Toggle</a>
200 </li>
201 </li>
201 <li id="toggle_current_output_scroll"
202 <li id="toggle_current_output_scroll"
202 title="Scroll the output of the current cell">
203 title="Scroll the output of the current cell">
203 <a href="#">Toggle Scrolling</a>
204 <a href="#">Toggle Scrolling</a>
204 </li>
205 </li>
205 <li id="clear_current_output"
206 <li id="clear_current_output"
206 title="Clear the output of the current cell">
207 title="Clear the output of the current cell">
207 <a href="#">Clear</a>
208 <a href="#">Clear</a>
208 </li>
209 </li>
209 </ul>
210 </ul>
210 </li>
211 </li>
211 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
212 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
212 <ul class="dropdown-menu">
213 <ul class="dropdown-menu">
213 <li id="toggle_all_output"
214 <li id="toggle_all_output"
214 title="Hide/Show the output of all cells">
215 title="Hide/Show the output of all cells">
215 <a href="#">Toggle</a>
216 <a href="#">Toggle</a>
216 </li>
217 </li>
217 <li id="toggle_all_output_scroll"
218 <li id="toggle_all_output_scroll"
218 title="Scroll the output of all cells">
219 title="Scroll the output of all cells">
219 <a href="#">Toggle Scrolling</a>
220 <a href="#">Toggle Scrolling</a>
220 </li>
221 </li>
221 <li id="clear_all_output"
222 <li id="clear_all_output"
222 title="Clear the output of all cells">
223 title="Clear the output of all cells">
223 <a href="#">Clear</a>
224 <a href="#">Clear</a>
224 </li>
225 </li>
225 </ul>
226 </ul>
226 </li>
227 </li>
227 </ul>
228 </ul>
228 </li>
229 </li>
229 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
230 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
230 <ul id="kernel_menu" class="dropdown-menu">
231 <ul id="kernel_menu" class="dropdown-menu">
231 <li id="int_kernel"
232 <li id="int_kernel"
232 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
233 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
233 <a href="#">Interrupt</a>
234 <a href="#">Interrupt</a>
234 </li>
235 </li>
235 <li id="restart_kernel"
236 <li id="restart_kernel"
236 title="Restart the Kernel">
237 title="Restart the Kernel">
237 <a href="#">Restart</a>
238 <a href="#">Restart</a>
238 </li>
239 </li>
239 <li id="reconnect_kernel"
240 <li id="reconnect_kernel"
240 title="Reconnect to the Kernel">
241 title="Reconnect to the Kernel">
241 <a href="#">Reconnect</a>
242 <a href="#">Reconnect</a>
242 </li>
243 </li>
243 <li class="divider"></li>
244 <li class="divider"></li>
244 <li id="menu-change-kernel" class="dropdown-submenu">
245 <li id="menu-change-kernel" class="dropdown-submenu">
245 <a href="#">Change kernel</a>
246 <a href="#">Change kernel</a>
246 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
247 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
247 </li>
248 </li>
248 </ul>
249 </ul>
249 </li>
250 </li>
250 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
251 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
251 <ul id="help_menu" class="dropdown-menu">
252 <ul id="help_menu" class="dropdown-menu">
252 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
253 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
253 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
254 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
254 <li class="divider"></li>
255 <li class="divider"></li>
255 {% set
256 {% set
256 sections = (
257 sections = (
257 (
258 (
258 ("http://nbviewer.ipython.org/github/ipython/ipython/blob/3.x/examples/Notebook/Index.ipynb", "Notebook Help", True),
259 ("http://nbviewer.ipython.org/github/ipython/ipython/blob/3.x/examples/Notebook/Index.ipynb", "Notebook Help", True),
259 ("https://help.github.com/articles/markdown-basics/","Markdown",True),
260 ("https://help.github.com/articles/markdown-basics/","Markdown",True),
260 ),
261 ),
261 )
262 )
262 %}
263 %}
263
264
264 {% for helplinks in sections %}
265 {% for helplinks in sections %}
265 {% for link in helplinks %}
266 {% for link in helplinks %}
266 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
267 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
267 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
268 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
268 {{link[1]}}
269 {{link[1]}}
269 </a></li>
270 </a></li>
270 {% endfor %}
271 {% endfor %}
271 {% if not loop.last %}
272 {% if not loop.last %}
272 <li class="divider"></li>
273 <li class="divider"></li>
273 {% endif %}
274 {% endif %}
274 {% endfor %}
275 {% endfor %}
275 <li class="divider"></li>
276 <li class="divider"></li>
276 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
277 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
277 </ul>
278 </ul>
278 </li>
279 </li>
279 </ul>
280 </ul>
280 </div>
281 </div>
281 </div>
282 </div>
282 </div>
283 </div>
283 </div>
284 </div>
284
285
285 <div id="maintoolbar" class="navbar">
286 <div id="maintoolbar" class="navbar">
286 <div class="toolbar-inner navbar-inner navbar-nobg">
287 <div class="toolbar-inner navbar-inner navbar-nobg">
287 <div id="maintoolbar-container" class="container"></div>
288 <div id="maintoolbar-container" class="container"></div>
288 </div>
289 </div>
289 </div>
290 </div>
290 </div>
291 </div>
291
292
292 <div class="lower-header-bar"></div>
293 <div class="lower-header-bar"></div>
293 {% endblock header %}
294 {% endblock header %}
294
295
295 {% block site %}
296 {% block site %}
296
297
297 <div id="ipython-main-app">
298 <div id="ipython-main-app">
298 <div id="notebook_panel">
299 <div id="notebook_panel">
299 <div id="notebook"></div>
300 <div id="notebook"></div>
300 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
301 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
301 </div>
302 </div>
302 </div>
303 </div>
303
304
304
305
305 {% endblock %}
306 {% endblock %}
306
307
307 {% block after_site %}
308 {% block after_site %}
308
309
309 <div id="pager">
310 <div id="pager">
310 <div id="pager-contents">
311 <div id="pager-contents">
311 <div id="pager-container" class="container"></div>
312 <div id="pager-container" class="container"></div>
312 </div>
313 </div>
313 <div id='pager-button-area'></div>
314 <div id='pager-button-area'></div>
314 </div>
315 </div>
315
316
316 {% endblock %}
317 {% endblock %}
317
318
318 {% block script %}
319 {% block script %}
319 {{super()}}
320 {{super()}}
320 <script type="text/javascript">
321 <script type="text/javascript">
321 sys_info = {{sys_info}};
322 sys_info = {{sys_info}};
322 </script>
323 </script>
323
324
324 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
325 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
325
326
326 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
327
328
328 {% endblock %}
329 {% endblock %}
@@ -1,187 +1,187 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block title %}{{page_title}}{% endblock %}
3 {% block title %}{{page_title}}{% endblock %}
4
4
5
5
6 {% block params %}
6 {% block params %}
7
7 {{super()}}
8 data-base-url="{{base_url}}"
8 data-base-url="{{base_url}}"
9 data-notebook-path="{{notebook_path}}"
9 data-notebook-path="{{notebook_path}}"
10 data-terminals-available="{{terminals_available}}"
10 data-terminals-available="{{terminals_available}}"
11
11
12 {% endblock %}
12 {% endblock %}
13
13
14
14
15 {% block site %}
15 {% block site %}
16
16
17 <div id="ipython-main-app" class="container">
17 <div id="ipython-main-app" class="container">
18 <div id="tab_content" class="tabbable">
18 <div id="tab_content" class="tabbable">
19 <ul id="tabs" class="nav nav-tabs">
19 <ul id="tabs" class="nav nav-tabs">
20 <li class="active"><a href="#notebooks" data-toggle="tab">Files</a></li>
20 <li class="active"><a href="#notebooks" data-toggle="tab">Files</a></li>
21 <li><a href="#running" data-toggle="tab">Running</a></li>
21 <li><a href="#running" data-toggle="tab">Running</a></li>
22 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
22 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
23 </ul>
23 </ul>
24 <div class="tab-content">
24 <div class="tab-content">
25 <div id="notebooks" class="tab-pane active">
25 <div id="notebooks" class="tab-pane active">
26 <div id="notebook_toolbar" class="row">
26 <div id="notebook_toolbar" class="row">
27 <div class="col-sm-8 no-padding">
27 <div class="col-sm-8 no-padding">
28 <div class="dynamic-instructions">
28 <div class="dynamic-instructions">
29 Select items to perform actions on them.
29 Select items to perform actions on them.
30 </div>
30 </div>
31 <div class="dynamic-buttons">
31 <div class="dynamic-buttons">
32 <button title="Duplicate selected" class="duplicate-button btn btn-default btn-xs">Duplicate</button>
32 <button title="Duplicate selected" class="duplicate-button btn btn-default btn-xs">Duplicate</button>
33 <button title="Rename selected" class="rename-button btn btn-default btn-xs">Rename</button>
33 <button title="Rename selected" class="rename-button btn btn-default btn-xs">Rename</button>
34 <button title="Shutdown selected notebook(s)" class="shutdown-button btn btn-default btn-xs btn-warning">Shutdown</button>
34 <button title="Shutdown selected notebook(s)" class="shutdown-button btn btn-default btn-xs btn-warning">Shutdown</button>
35 <button title="Deleted selected" class="delete-button btn btn-default btn-xs btn-danger"><i class="fa fa-trash"></i></button>
35 <button title="Deleted selected" class="delete-button btn btn-default btn-xs btn-danger"><i class="fa fa-trash"></i></button>
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="col-sm-4 no-padding tree-buttons">
38 <div class="col-sm-4 no-padding tree-buttons">
39 <div class="pull-right">
39 <div class="pull-right">
40 <form id='alternate_upload' class='alternate_upload'>
40 <form id='alternate_upload' class='alternate_upload'>
41 <span id="notebook_list_info">
41 <span id="notebook_list_info">
42 <span class="btn btn-xs btn-default btn-upload">
42 <span class="btn btn-xs btn-default btn-upload">
43 <input title="Click to browse for a file to upload." type="file" name="datafile" class="fileinput" multiple='multiple'>
43 <input title="Click to browse for a file to upload." type="file" name="datafile" class="fileinput" multiple='multiple'>
44 Upload
44 Upload
45 </span>
45 </span>
46 </span>
46 </span>
47 </form>
47 </form>
48 <div id="new-buttons" class="btn-group">
48 <div id="new-buttons" class="btn-group">
49 <button class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown">
49 <button class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown">
50 <span>New</span>
50 <span>New</span>
51 <span class="caret"></span>
51 <span class="caret"></span>
52 </button>
52 </button>
53 <ul id="new-menu" class="dropdown-menu">
53 <ul id="new-menu" class="dropdown-menu">
54 <li role="presentation" id="new-file">
54 <li role="presentation" id="new-file">
55 <a role="menuitem" tabindex="-1" href="#">Text File</a>
55 <a role="menuitem" tabindex="-1" href="#">Text File</a>
56 </li>
56 </li>
57 <li role="presentation" id="new-folder">
57 <li role="presentation" id="new-folder">
58 <a role="menuitem" tabindex="-1" href="#">Folder</a>
58 <a role="menuitem" tabindex="-1" href="#">Folder</a>
59 </li>
59 </li>
60 {% if terminals_available %}
60 {% if terminals_available %}
61 <li role="presentation" id="new-terminal">
61 <li role="presentation" id="new-terminal">
62 <a role="menuitem" tabindex="-1" href="#">Terminal</a>
62 <a role="menuitem" tabindex="-1" href="#">Terminal</a>
63 </li>
63 </li>
64 {% else %}
64 {% else %}
65 <li role="presentation" id="new-terminal-disabled" class="disabled">
65 <li role="presentation" id="new-terminal-disabled" class="disabled">
66 <a role="menuitem" tabindex="-1" href="#">Terminals Unavailable</a>
66 <a role="menuitem" tabindex="-1" href="#">Terminals Unavailable</a>
67 </li>
67 </li>
68 {% endif %}
68 {% endif %}
69 <li role="presentation" class="divider"></li>
69 <li role="presentation" class="divider"></li>
70 <li role="presentation" class="dropdown-header" id="notebook-kernels">Notebooks</li>
70 <li role="presentation" class="dropdown-header" id="notebook-kernels">Notebooks</li>
71 </ul>
71 </ul>
72 </div>
72 </div>
73 <div class="btn-group">
73 <div class="btn-group">
74 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
74 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
75 </div>
75 </div>
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
79 <div id="notebook_list">
79 <div id="notebook_list">
80 <div id="notebook_list_header" class="row list_header">
80 <div id="notebook_list_header" class="row list_header">
81 <div class="btn-group dropdown" id="tree-selector">
81 <div class="btn-group dropdown" id="tree-selector">
82 <button title="Select All / None" type="button" class="btn btn-default btn-xs" id="button-select-all">
82 <button title="Select All / None" type="button" class="btn btn-default btn-xs" id="button-select-all">
83 <input type="checkbox" class="pull-left tree-selector" id="select-all"><span id="counter-select-all">&nbsp;</span></input>
83 <input type="checkbox" class="pull-left tree-selector" id="select-all"><span id="counter-select-all">&nbsp;</span></input>
84 </button>
84 </button>
85 <button title="Select..." class="btn btn-default btn-xs dropdown-toggle" type="button" id="tree-selector-btn" data-toggle="dropdown" aria-expanded="true">
85 <button title="Select..." class="btn btn-default btn-xs dropdown-toggle" type="button" id="tree-selector-btn" data-toggle="dropdown" aria-expanded="true">
86 <span class="caret"></span>
86 <span class="caret"></span>
87 <span class="sr-only">Toggle Dropdown</span>
87 <span class="sr-only">Toggle Dropdown</span>
88 </button>
88 </button>
89 <ul id='selector-menu' class="dropdown-menu" role="menu" aria-labelledby="tree-selector-btn">
89 <ul id='selector-menu' class="dropdown-menu" role="menu" aria-labelledby="tree-selector-btn">
90 <li role="presentation"><a id="select-folders" role="menuitem" tabindex="-1" href="#" title="Select All Folders"><i class="menu_icon folder_icon icon-fixed-width"></i> Folders</a></li>
90 <li role="presentation"><a id="select-folders" role="menuitem" tabindex="-1" href="#" title="Select All Folders"><i class="menu_icon folder_icon icon-fixed-width"></i> Folders</a></li>
91 <li role="presentation"><a id="select-notebooks" role="menuitem" tabindex="-1" href="#" title="Select All Notebooks"><i class="menu_icon notebook_icon icon-fixed-width"></i> All Notebooks</a></li>
91 <li role="presentation"><a id="select-notebooks" role="menuitem" tabindex="-1" href="#" title="Select All Notebooks"><i class="menu_icon notebook_icon icon-fixed-width"></i> All Notebooks</a></li>
92 <li role="presentation"><a id="select-running-notebooks" role="menuitem" tabindex="-1" href="#" title="Select Running Notebooks"><i class="menu_icon running_notebook_icon icon-fixed-width"></i> Running</a></li>
92 <li role="presentation"><a id="select-running-notebooks" role="menuitem" tabindex="-1" href="#" title="Select Running Notebooks"><i class="menu_icon running_notebook_icon icon-fixed-width"></i> Running</a></li>
93 <li role="presentation"><a id="select-files" role="menuitem" tabindex="-1" href="#" title="Select All Files"><i class="menu_icon file_icon icon-fixed-width"></i> Files</a></li>
93 <li role="presentation"><a id="select-files" role="menuitem" tabindex="-1" href="#" title="Select All Files"><i class="menu_icon file_icon icon-fixed-width"></i> Files</a></li>
94 </ul>
94 </ul>
95 </div>
95 </div>
96 <div id="project_name">
96 <div id="project_name">
97 <ul class="breadcrumb">
97 <ul class="breadcrumb">
98 <li><a href="{{breadcrumbs[0][0]}}"><i class="fa fa-home"></i></a></li>
98 <li><a href="{{breadcrumbs[0][0]}}"><i class="fa fa-home"></i></a></li>
99 {% for crumb in breadcrumbs[1:] %}
99 {% for crumb in breadcrumbs[1:] %}
100 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a></li>
100 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a></li>
101 {% endfor %}
101 {% endfor %}
102 </ul>
102 </ul>
103 </div>
103 </div>
104 </div>
104 </div>
105 </div>
105 </div>
106 </div>
106 </div>
107 <div id="running" class="tab-pane">
107 <div id="running" class="tab-pane">
108 <div id="running_toolbar" class="row">
108 <div id="running_toolbar" class="row">
109 <div class="col-sm-8 no-padding">
109 <div class="col-sm-8 no-padding">
110 <span id="running_list_info">Currently running Jupyter processes</span>
110 <span id="running_list_info">Currently running Jupyter processes</span>
111 </div>
111 </div>
112 <div class="col-sm-4 no-padding tree-buttons">
112 <div class="col-sm-4 no-padding tree-buttons">
113 <span id="running_buttons" class="pull-right">
113 <span id="running_buttons" class="pull-right">
114 <button id="refresh_running_list" title="Refresh running list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
114 <button id="refresh_running_list" title="Refresh running list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
115 </span>
115 </span>
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="panel-group" id="accordion" >
118 <div class="panel-group" id="accordion" >
119 <div class="panel panel-default">
119 <div class="panel panel-default">
120 <div class="panel-heading">
120 <div class="panel-heading">
121 <a data-toggle="collapse" data-target="#collapseOne" href="#">
121 <a data-toggle="collapse" data-target="#collapseOne" href="#">
122 Terminals
122 Terminals
123 </a>
123 </a>
124 </div>
124 </div>
125 <div id="collapseOne" class=" collapse in">
125 <div id="collapseOne" class=" collapse in">
126 <div class="panel-body">
126 <div class="panel-body">
127 <div id="terminal_list">
127 <div id="terminal_list">
128 <div id="terminal_list_header" class="row list_placeholder">
128 <div id="terminal_list_header" class="row list_placeholder">
129 {% if terminals_available %}
129 {% if terminals_available %}
130 <div> There are no terminals running. </div>
130 <div> There are no terminals running. </div>
131 {% else %}
131 {% else %}
132 <div> Terminals are unavailable. </div>
132 <div> Terminals are unavailable. </div>
133 {% endif %}
133 {% endif %}
134 </div>
134 </div>
135 </div>
135 </div>
136 </div>
136 </div>
137 </div>
137 </div>
138 </div>
138 </div>
139 <div class="panel panel-default">
139 <div class="panel panel-default">
140 <div class="panel-heading">
140 <div class="panel-heading">
141 <a data-toggle="collapse" data-target="#collapseTwo" href="#">
141 <a data-toggle="collapse" data-target="#collapseTwo" href="#">
142 Notebooks
142 Notebooks
143 </a>
143 </a>
144 </div>
144 </div>
145 <div id="collapseTwo" class=" collapse in">
145 <div id="collapseTwo" class=" collapse in">
146 <div class="panel-body">
146 <div class="panel-body">
147 <div id="running_list">
147 <div id="running_list">
148 <div id="running_list_placeholder" class="row list_placeholder">
148 <div id="running_list_placeholder" class="row list_placeholder">
149 <div> There are no notebooks running. </div>
149 <div> There are no notebooks running. </div>
150 </div>
150 </div>
151 </div>
151 </div>
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 <div id="clusters" class="tab-pane">
157 <div id="clusters" class="tab-pane">
158 <div id="cluster_toolbar" class="row">
158 <div id="cluster_toolbar" class="row">
159 <div class="col-xs-8 no-padding">
159 <div class="col-xs-8 no-padding">
160 <span id="cluster_list_info">IPython parallel computing clusters</span>
160 <span id="cluster_list_info">IPython parallel computing clusters</span>
161 </div>
161 </div>
162 <div class="col-xs-4 no-padding tree-buttons">
162 <div class="col-xs-4 no-padding tree-buttons">
163 <span id="cluster_buttons" class="pull-right">
163 <span id="cluster_buttons" class="pull-right">
164 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
164 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
165 </span>
165 </span>
166 </div>
166 </div>
167 </div>
167 </div>
168 <div id="cluster_list">
168 <div id="cluster_list">
169 <div id="cluster_list_header" class="row list_header">
169 <div id="cluster_list_header" class="row list_header">
170 <div class="profile_col col-xs-4">profile</div>
170 <div class="profile_col col-xs-4">profile</div>
171 <div class="status_col col-xs-3">status</div>
171 <div class="status_col col-xs-3">status</div>
172 <div class="engines_col col-xs-3" title="Enter the number of engines to start or empty for default"># of engines</div>
172 <div class="engines_col col-xs-3" title="Enter the number of engines to start or empty for default"># of engines</div>
173 <div class="action_col col-xs-2">action</div>
173 <div class="action_col col-xs-2">action</div>
174 </div>
174 </div>
175 </div>
175 </div>
176 </div>
176 </div>
177 </div><!-- class:tab-content -->
177 </div><!-- class:tab-content -->
178 </div><!-- id:tab_content -->
178 </div><!-- id:tab_content -->
179 </div><!-- ipython-main-app -->
179 </div><!-- ipython-main-app -->
180
180
181 {% endblock %}
181 {% endblock %}
182
182
183 {% block script %}
183 {% block script %}
184 {{super()}}
184 {{super()}}
185
185
186 <script src="{{ static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
186 <script src="{{ static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
187 {% endblock %}
187 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now