##// END OF EJS Templates
remove base_kernel_url
MinRK -
Show More
@@ -1,387 +1,382 b''
1 """Base Tornado handlers for the notebook.
1 """Base Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import functools
20 import functools
21 import json
21 import json
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 import traceback
26 import traceback
27 try:
27 try:
28 # py3
28 # py3
29 from http.client import responses
29 from http.client import responses
30 except ImportError:
30 except ImportError:
31 from httplib import responses
31 from httplib import responses
32
32
33 from jinja2 import TemplateNotFound
33 from jinja2 import TemplateNotFound
34 from tornado import web
34 from tornado import web
35
35
36 try:
36 try:
37 from tornado.log import app_log
37 from tornado.log import app_log
38 except ImportError:
38 except ImportError:
39 app_log = logging.getLogger()
39 app_log = logging.getLogger()
40
40
41 from IPython.config import Application
41 from IPython.config import Application
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import string_types
43 from IPython.utils.py3compat import string_types
44 from IPython.html.utils import is_hidden
44 from IPython.html.utils import is_hidden
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Top-level handlers
47 # Top-level handlers
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
49 non_alphanum = re.compile(r'[^A-Za-z0-9]')
50
50
51 class AuthenticatedHandler(web.RequestHandler):
51 class AuthenticatedHandler(web.RequestHandler):
52 """A RequestHandler with an authenticated user."""
52 """A RequestHandler with an authenticated user."""
53
53
54 def clear_login_cookie(self):
54 def clear_login_cookie(self):
55 self.clear_cookie(self.cookie_name)
55 self.clear_cookie(self.cookie_name)
56
56
57 def get_current_user(self):
57 def get_current_user(self):
58 user_id = self.get_secure_cookie(self.cookie_name)
58 user_id = self.get_secure_cookie(self.cookie_name)
59 # For now the user_id should not return empty, but it could eventually
59 # For now the user_id should not return empty, but it could eventually
60 if user_id == '':
60 if user_id == '':
61 user_id = 'anonymous'
61 user_id = 'anonymous'
62 if user_id is None:
62 if user_id is None:
63 # prevent extra Invalid cookie sig warnings:
63 # prevent extra Invalid cookie sig warnings:
64 self.clear_login_cookie()
64 self.clear_login_cookie()
65 if not self.login_available:
65 if not self.login_available:
66 user_id = 'anonymous'
66 user_id = 'anonymous'
67 return user_id
67 return user_id
68
68
69 @property
69 @property
70 def cookie_name(self):
70 def cookie_name(self):
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
71 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
72 self.request.host
72 self.request.host
73 ))
73 ))
74 return self.settings.get('cookie_name', default_cookie_name)
74 return self.settings.get('cookie_name', default_cookie_name)
75
75
76 @property
76 @property
77 def password(self):
77 def password(self):
78 """our password"""
78 """our password"""
79 return self.settings.get('password', '')
79 return self.settings.get('password', '')
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
84
85 """
85 """
86 user = self.get_current_user()
86 user = self.get_current_user()
87 return (user and not user == 'anonymous')
87 return (user and not user == 'anonymous')
88
88
89 @property
89 @property
90 def login_available(self):
90 def login_available(self):
91 """May a user proceed to log in?
91 """May a user proceed to log in?
92
92
93 This returns True if login capability is available, irrespective of
93 This returns True if login capability is available, irrespective of
94 whether the user is already logged in or not.
94 whether the user is already logged in or not.
95
95
96 """
96 """
97 return bool(self.settings.get('password', ''))
97 return bool(self.settings.get('password', ''))
98
98
99
99
100 class IPythonHandler(AuthenticatedHandler):
100 class IPythonHandler(AuthenticatedHandler):
101 """IPython-specific extensions to authenticated handling
101 """IPython-specific extensions to authenticated handling
102
102
103 Mostly property shortcuts to IPython-specific settings.
103 Mostly property shortcuts to IPython-specific settings.
104 """
104 """
105
105
106 @property
106 @property
107 def config(self):
107 def config(self):
108 return self.settings.get('config', None)
108 return self.settings.get('config', None)
109
109
110 @property
110 @property
111 def log(self):
111 def log(self):
112 """use the IPython log by default, falling back on tornado's logger"""
112 """use the IPython log by default, falling back on tornado's logger"""
113 if Application.initialized():
113 if Application.initialized():
114 return Application.instance().log
114 return Application.instance().log
115 else:
115 else:
116 return app_log
116 return app_log
117
117
118 #---------------------------------------------------------------
118 #---------------------------------------------------------------
119 # URLs
119 # URLs
120 #---------------------------------------------------------------
120 #---------------------------------------------------------------
121
121
122 @property
122 @property
123 def ws_url(self):
123 def ws_url(self):
124 """websocket url matching the current request
124 """websocket url matching the current request
125
125
126 By default, this is just `''`, indicating that it should match
126 By default, this is just `''`, indicating that it should match
127 the same host, protocol, port, etc.
127 the same host, protocol, port, etc.
128 """
128 """
129 return self.settings.get('websocket_url', '')
129 return self.settings.get('websocket_url', '')
130
130
131 @property
131 @property
132 def mathjax_url(self):
132 def mathjax_url(self):
133 return self.settings.get('mathjax_url', '')
133 return self.settings.get('mathjax_url', '')
134
134
135 @property
135 @property
136 def base_url(self):
136 def base_url(self):
137 return self.settings.get('base_url', '/')
137 return self.settings.get('base_url', '/')
138
138
139 @property
140 def base_kernel_url(self):
141 return self.settings.get('base_kernel_url', '/')
142
143 #---------------------------------------------------------------
139 #---------------------------------------------------------------
144 # Manager objects
140 # Manager objects
145 #---------------------------------------------------------------
141 #---------------------------------------------------------------
146
142
147 @property
143 @property
148 def kernel_manager(self):
144 def kernel_manager(self):
149 return self.settings['kernel_manager']
145 return self.settings['kernel_manager']
150
146
151 @property
147 @property
152 def notebook_manager(self):
148 def notebook_manager(self):
153 return self.settings['notebook_manager']
149 return self.settings['notebook_manager']
154
150
155 @property
151 @property
156 def cluster_manager(self):
152 def cluster_manager(self):
157 return self.settings['cluster_manager']
153 return self.settings['cluster_manager']
158
154
159 @property
155 @property
160 def session_manager(self):
156 def session_manager(self):
161 return self.settings['session_manager']
157 return self.settings['session_manager']
162
158
163 @property
159 @property
164 def project_dir(self):
160 def project_dir(self):
165 return self.notebook_manager.notebook_dir
161 return self.notebook_manager.notebook_dir
166
162
167 #---------------------------------------------------------------
163 #---------------------------------------------------------------
168 # template rendering
164 # template rendering
169 #---------------------------------------------------------------
165 #---------------------------------------------------------------
170
166
171 def get_template(self, name):
167 def get_template(self, name):
172 """Return the jinja template object for a given name"""
168 """Return the jinja template object for a given name"""
173 return self.settings['jinja2_env'].get_template(name)
169 return self.settings['jinja2_env'].get_template(name)
174
170
175 def render_template(self, name, **ns):
171 def render_template(self, name, **ns):
176 ns.update(self.template_namespace)
172 ns.update(self.template_namespace)
177 template = self.get_template(name)
173 template = self.get_template(name)
178 return template.render(**ns)
174 return template.render(**ns)
179
175
180 @property
176 @property
181 def template_namespace(self):
177 def template_namespace(self):
182 return dict(
178 return dict(
183 base_url=self.base_url,
179 base_url=self.base_url,
184 base_kernel_url=self.base_kernel_url,
185 logged_in=self.logged_in,
180 logged_in=self.logged_in,
186 login_available=self.login_available,
181 login_available=self.login_available,
187 static_url=self.static_url,
182 static_url=self.static_url,
188 )
183 )
189
184
190 def get_json_body(self):
185 def get_json_body(self):
191 """Return the body of the request as JSON data."""
186 """Return the body of the request as JSON data."""
192 if not self.request.body:
187 if not self.request.body:
193 return None
188 return None
194 # Do we need to call body.decode('utf-8') here?
189 # Do we need to call body.decode('utf-8') here?
195 body = self.request.body.strip().decode(u'utf-8')
190 body = self.request.body.strip().decode(u'utf-8')
196 try:
191 try:
197 model = json.loads(body)
192 model = json.loads(body)
198 except Exception:
193 except Exception:
199 self.log.debug("Bad JSON: %r", body)
194 self.log.debug("Bad JSON: %r", body)
200 self.log.error("Couldn't parse JSON", exc_info=True)
195 self.log.error("Couldn't parse JSON", exc_info=True)
201 raise web.HTTPError(400, u'Invalid JSON in body of request')
196 raise web.HTTPError(400, u'Invalid JSON in body of request')
202 return model
197 return model
203
198
204 def get_error_html(self, status_code, **kwargs):
199 def get_error_html(self, status_code, **kwargs):
205 """render custom error pages"""
200 """render custom error pages"""
206 exception = kwargs.get('exception')
201 exception = kwargs.get('exception')
207 message = ''
202 message = ''
208 status_message = responses.get(status_code, 'Unknown HTTP Error')
203 status_message = responses.get(status_code, 'Unknown HTTP Error')
209 if exception:
204 if exception:
210 # get the custom message, if defined
205 # get the custom message, if defined
211 try:
206 try:
212 message = exception.log_message % exception.args
207 message = exception.log_message % exception.args
213 except Exception:
208 except Exception:
214 pass
209 pass
215
210
216 # construct the custom reason, if defined
211 # construct the custom reason, if defined
217 reason = getattr(exception, 'reason', '')
212 reason = getattr(exception, 'reason', '')
218 if reason:
213 if reason:
219 status_message = reason
214 status_message = reason
220
215
221 # build template namespace
216 # build template namespace
222 ns = dict(
217 ns = dict(
223 status_code=status_code,
218 status_code=status_code,
224 status_message=status_message,
219 status_message=status_message,
225 message=message,
220 message=message,
226 exception=exception,
221 exception=exception,
227 )
222 )
228
223
229 # render the template
224 # render the template
230 try:
225 try:
231 html = self.render_template('%s.html' % status_code, **ns)
226 html = self.render_template('%s.html' % status_code, **ns)
232 except TemplateNotFound:
227 except TemplateNotFound:
233 self.log.debug("No template for %d", status_code)
228 self.log.debug("No template for %d", status_code)
234 html = self.render_template('error.html', **ns)
229 html = self.render_template('error.html', **ns)
235 return html
230 return html
236
231
237
232
238 class Template404(IPythonHandler):
233 class Template404(IPythonHandler):
239 """Render our 404 template"""
234 """Render our 404 template"""
240 def prepare(self):
235 def prepare(self):
241 raise web.HTTPError(404)
236 raise web.HTTPError(404)
242
237
243
238
244 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
239 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
245 """static files should only be accessible when logged in"""
240 """static files should only be accessible when logged in"""
246
241
247 @web.authenticated
242 @web.authenticated
248 def get(self, path):
243 def get(self, path):
249 if os.path.splitext(path)[1] == '.ipynb':
244 if os.path.splitext(path)[1] == '.ipynb':
250 name = os.path.basename(path)
245 name = os.path.basename(path)
251 self.set_header('Content-Type', 'application/json')
246 self.set_header('Content-Type', 'application/json')
252 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
247 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
253
248
254 return web.StaticFileHandler.get(self, path)
249 return web.StaticFileHandler.get(self, path)
255
250
256 def compute_etag(self):
251 def compute_etag(self):
257 return None
252 return None
258
253
259 def validate_absolute_path(self, root, absolute_path):
254 def validate_absolute_path(self, root, absolute_path):
260 """Validate and return the absolute path.
255 """Validate and return the absolute path.
261
256
262 Requires tornado 3.1
257 Requires tornado 3.1
263
258
264 Adding to tornado's own handling, forbids the serving of hidden files.
259 Adding to tornado's own handling, forbids the serving of hidden files.
265 """
260 """
266 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
261 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
267 abs_root = os.path.abspath(root)
262 abs_root = os.path.abspath(root)
268 if is_hidden(abs_path, abs_root):
263 if is_hidden(abs_path, abs_root):
269 raise web.HTTPError(404)
264 raise web.HTTPError(404)
270 return abs_path
265 return abs_path
271
266
272
267
273 def json_errors(method):
268 def json_errors(method):
274 """Decorate methods with this to return GitHub style JSON errors.
269 """Decorate methods with this to return GitHub style JSON errors.
275
270
276 This should be used on any JSON API on any handler method that can raise HTTPErrors.
271 This should be used on any JSON API on any handler method that can raise HTTPErrors.
277
272
278 This will grab the latest HTTPError exception using sys.exc_info
273 This will grab the latest HTTPError exception using sys.exc_info
279 and then:
274 and then:
280
275
281 1. Set the HTTP status code based on the HTTPError
276 1. Set the HTTP status code based on the HTTPError
282 2. Create and return a JSON body with a message field describing
277 2. Create and return a JSON body with a message field describing
283 the error in a human readable form.
278 the error in a human readable form.
284 """
279 """
285 @functools.wraps(method)
280 @functools.wraps(method)
286 def wrapper(self, *args, **kwargs):
281 def wrapper(self, *args, **kwargs):
287 try:
282 try:
288 result = method(self, *args, **kwargs)
283 result = method(self, *args, **kwargs)
289 except web.HTTPError as e:
284 except web.HTTPError as e:
290 status = e.status_code
285 status = e.status_code
291 message = e.log_message
286 message = e.log_message
292 self.set_status(e.status_code)
287 self.set_status(e.status_code)
293 self.finish(json.dumps(dict(message=message)))
288 self.finish(json.dumps(dict(message=message)))
294 except Exception:
289 except Exception:
295 self.log.error("Unhandled error in API request", exc_info=True)
290 self.log.error("Unhandled error in API request", exc_info=True)
296 status = 500
291 status = 500
297 message = "Unknown server error"
292 message = "Unknown server error"
298 t, value, tb = sys.exc_info()
293 t, value, tb = sys.exc_info()
299 self.set_status(status)
294 self.set_status(status)
300 tb_text = ''.join(traceback.format_exception(t, value, tb))
295 tb_text = ''.join(traceback.format_exception(t, value, tb))
301 reply = dict(message=message, traceback=tb_text)
296 reply = dict(message=message, traceback=tb_text)
302 self.finish(json.dumps(reply))
297 self.finish(json.dumps(reply))
303 else:
298 else:
304 return result
299 return result
305 return wrapper
300 return wrapper
306
301
307
302
308
303
309 #-----------------------------------------------------------------------------
304 #-----------------------------------------------------------------------------
310 # File handler
305 # File handler
311 #-----------------------------------------------------------------------------
306 #-----------------------------------------------------------------------------
312
307
313 # to minimize subclass changes:
308 # to minimize subclass changes:
314 HTTPError = web.HTTPError
309 HTTPError = web.HTTPError
315
310
316 class FileFindHandler(web.StaticFileHandler):
311 class FileFindHandler(web.StaticFileHandler):
317 """subclass of StaticFileHandler for serving files from a search path"""
312 """subclass of StaticFileHandler for serving files from a search path"""
318
313
319 # cache search results, don't search for files more than once
314 # cache search results, don't search for files more than once
320 _static_paths = {}
315 _static_paths = {}
321
316
322 def initialize(self, path, default_filename=None):
317 def initialize(self, path, default_filename=None):
323 if isinstance(path, string_types):
318 if isinstance(path, string_types):
324 path = [path]
319 path = [path]
325
320
326 self.root = tuple(
321 self.root = tuple(
327 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
322 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
328 )
323 )
329 self.default_filename = default_filename
324 self.default_filename = default_filename
330
325
331 def compute_etag(self):
326 def compute_etag(self):
332 return None
327 return None
333
328
334 @classmethod
329 @classmethod
335 def get_absolute_path(cls, roots, path):
330 def get_absolute_path(cls, roots, path):
336 """locate a file to serve on our static file search path"""
331 """locate a file to serve on our static file search path"""
337 with cls._lock:
332 with cls._lock:
338 if path in cls._static_paths:
333 if path in cls._static_paths:
339 return cls._static_paths[path]
334 return cls._static_paths[path]
340 try:
335 try:
341 abspath = os.path.abspath(filefind(path, roots))
336 abspath = os.path.abspath(filefind(path, roots))
342 except IOError:
337 except IOError:
343 # IOError means not found
338 # IOError means not found
344 return ''
339 return ''
345
340
346 cls._static_paths[path] = abspath
341 cls._static_paths[path] = abspath
347 return abspath
342 return abspath
348
343
349 def validate_absolute_path(self, root, absolute_path):
344 def validate_absolute_path(self, root, absolute_path):
350 """check if the file should be served (raises 404, 403, etc.)"""
345 """check if the file should be served (raises 404, 403, etc.)"""
351 if absolute_path == '':
346 if absolute_path == '':
352 raise web.HTTPError(404)
347 raise web.HTTPError(404)
353
348
354 for root in self.root:
349 for root in self.root:
355 if (absolute_path + os.sep).startswith(root):
350 if (absolute_path + os.sep).startswith(root):
356 break
351 break
357
352
358 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
353 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
359
354
360
355
361 class TrailingSlashHandler(web.RequestHandler):
356 class TrailingSlashHandler(web.RequestHandler):
362 """Simple redirect handler that strips trailing slashes
357 """Simple redirect handler that strips trailing slashes
363
358
364 This should be the first, highest priority handler.
359 This should be the first, highest priority handler.
365 """
360 """
366
361
367 SUPPORTED_METHODS = ['GET']
362 SUPPORTED_METHODS = ['GET']
368
363
369 def get(self):
364 def get(self):
370 self.redirect(self.request.uri.rstrip('/'))
365 self.redirect(self.request.uri.rstrip('/'))
371
366
372 #-----------------------------------------------------------------------------
367 #-----------------------------------------------------------------------------
373 # URL pattern fragments for re-use
368 # URL pattern fragments for re-use
374 #-----------------------------------------------------------------------------
369 #-----------------------------------------------------------------------------
375
370
376 path_regex = r"(?P<path>(?:/.*)*)"
371 path_regex = r"(?P<path>(?:/.*)*)"
377 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
372 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
378 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
373 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
379
374
380 #-----------------------------------------------------------------------------
375 #-----------------------------------------------------------------------------
381 # URL to handler mappings
376 # URL to handler mappings
382 #-----------------------------------------------------------------------------
377 #-----------------------------------------------------------------------------
383
378
384
379
385 default_handlers = [
380 default_handlers = [
386 (r".*/", TrailingSlashHandler)
381 (r".*/", TrailingSlashHandler)
387 ]
382 ]
@@ -1,842 +1,829 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 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import select
27 import select
28 import signal
28 import signal
29 import socket
29 import socket
30 import sys
30 import sys
31 import threading
31 import threading
32 import time
32 import time
33 import webbrowser
33 import webbrowser
34
34
35
35
36 # Third party
36 # Third party
37 # check for pyzmq 2.1.11
37 # check for pyzmq 2.1.11
38 from IPython.utils.zmqrelated import check_for_zmq
38 from IPython.utils.zmqrelated import check_for_zmq
39 check_for_zmq('2.1.11', 'IPython.html')
39 check_for_zmq('2.1.11', 'IPython.html')
40
40
41 from jinja2 import Environment, FileSystemLoader
41 from jinja2 import Environment, FileSystemLoader
42
42
43 # Install the pyzmq ioloop. This has to be done before anything else from
43 # Install the pyzmq ioloop. This has to be done before anything else from
44 # tornado is imported.
44 # tornado is imported.
45 from zmq.eventloop import ioloop
45 from zmq.eventloop import ioloop
46 ioloop.install()
46 ioloop.install()
47
47
48 # check for tornado 3.1.0
48 # check for tornado 3.1.0
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 try:
50 try:
51 import tornado
51 import tornado
52 except ImportError:
52 except ImportError:
53 raise ImportError(msg)
53 raise ImportError(msg)
54 try:
54 try:
55 version_info = tornado.version_info
55 version_info = tornado.version_info
56 except AttributeError:
56 except AttributeError:
57 raise ImportError(msg + ", but you have < 1.1.0")
57 raise ImportError(msg + ", but you have < 1.1.0")
58 if version_info < (3,1,0):
58 if version_info < (3,1,0):
59 raise ImportError(msg + ", but you have %s" % tornado.version)
59 raise ImportError(msg + ", but you have %s" % tornado.version)
60
60
61 from tornado import httpserver
61 from tornado import httpserver
62 from tornado import web
62 from tornado import web
63
63
64 # Our own libraries
64 # Our own libraries
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from .base.handlers import Template404
66 from .base.handlers import Template404
67 from .log import log_request
67 from .log import log_request
68 from .services.kernels.kernelmanager import MappingKernelManager
68 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.notebooks.nbmanager import NotebookManager
69 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.clusters.clustermanager import ClusterManager
71 from .services.clusters.clustermanager import ClusterManager
72 from .services.sessions.sessionmanager import SessionManager
72 from .services.sessions.sessionmanager import SessionManager
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config.application import catch_config_error, boolean_flag
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
78 from IPython.core.profiledir import ProfileDir
79 from IPython.consoleapp import IPythonConsoleApp
79 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.kernel import swallow_argv
80 from IPython.kernel import swallow_argv
81 from IPython.kernel.zmq.session import default_secure
81 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.kernelapp import (
82 from IPython.kernel.zmq.kernelapp import (
83 kernel_flags,
83 kernel_flags,
84 kernel_aliases,
84 kernel_aliases,
85 )
85 )
86 from IPython.utils.importstring import import_item
86 from IPython.utils.importstring import import_item
87 from IPython.utils.localinterfaces import localhost
87 from IPython.utils.localinterfaces import localhost
88 from IPython.utils import submodule
88 from IPython.utils import submodule
89 from IPython.utils.traitlets import (
89 from IPython.utils.traitlets import (
90 Dict, Unicode, Integer, List, Bool, Bytes,
90 Dict, Unicode, Integer, List, Bool, Bytes,
91 DottedObjectName
91 DottedObjectName
92 )
92 )
93 from IPython.utils import py3compat
93 from IPython.utils import py3compat
94 from IPython.utils.path import filefind, get_ipython_dir
94 from IPython.utils.path import filefind, get_ipython_dir
95
95
96 from .utils import url_path_join
96 from .utils import url_path_join
97
97
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99 # Module globals
99 # Module globals
100 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
101
101
102 _examples = """
102 _examples = """
103 ipython notebook # start the notebook
103 ipython notebook # start the notebook
104 ipython notebook --profile=sympy # use the sympy profile
104 ipython notebook --profile=sympy # use the sympy profile
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
106 """
106 """
107
107
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109 # Helper functions
109 # Helper functions
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111
111
112 def random_ports(port, n):
112 def random_ports(port, n):
113 """Generate a list of n random ports near the given port.
113 """Generate a list of n random ports near the given port.
114
114
115 The first 5 ports will be sequential, and the remaining n-5 will be
115 The first 5 ports will be sequential, and the remaining n-5 will be
116 randomly selected in the range [port-2*n, port+2*n].
116 randomly selected in the range [port-2*n, port+2*n].
117 """
117 """
118 for i in range(min(5, n)):
118 for i in range(min(5, n)):
119 yield port + i
119 yield port + i
120 for i in range(n-5):
120 for i in range(n-5):
121 yield max(1, port + random.randint(-2*n, 2*n))
121 yield max(1, port + random.randint(-2*n, 2*n))
122
122
123 def load_handlers(name):
123 def load_handlers(name):
124 """Load the (URL pattern, handler) tuples for each component."""
124 """Load the (URL pattern, handler) tuples for each component."""
125 name = 'IPython.html.' + name
125 name = 'IPython.html.' + name
126 mod = __import__(name, fromlist=['default_handlers'])
126 mod = __import__(name, fromlist=['default_handlers'])
127 return mod.default_handlers
127 return mod.default_handlers
128
128
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130 # The Tornado web application
130 # The Tornado web application
131 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
132
132
133 class NotebookWebApplication(web.Application):
133 class NotebookWebApplication(web.Application):
134
134
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, log, base_url,
136 cluster_manager, session_manager, log, base_url,
137 settings_overrides):
137 settings_overrides):
138
138
139 settings = self.init_settings(
139 settings = self.init_settings(
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 session_manager, log, base_url, settings_overrides)
141 session_manager, log, base_url, settings_overrides)
142 handlers = self.init_handlers(settings)
142 handlers = self.init_handlers(settings)
143
143
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
145
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 cluster_manager, session_manager, log, base_url,
147 cluster_manager, session_manager, log, base_url,
148 settings_overrides):
148 settings_overrides):
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # base_url will always be unicode, which will in turn
150 # base_url will always be unicode, which will in turn
151 # make the patterns unicode, and ultimately result in unicode
151 # make the patterns unicode, and ultimately result in unicode
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # This enforces that base_url be ascii in that situation.
153 # This enforces that base_url be ascii in that situation.
154 #
154 #
155 # Note that the URLs these patterns check against are escaped,
155 # Note that the URLs these patterns check against are escaped,
156 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
156 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
157 base_url = py3compat.unicode_to_str(base_url, 'ascii')
157 base_url = py3compat.unicode_to_str(base_url, 'ascii')
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 settings = dict(
159 settings = dict(
160 # basics
160 # basics
161 log_function=log_request,
161 log_function=log_request,
162 base_url=base_url,
162 base_url=base_url,
163 base_kernel_url=ipython_app.base_kernel_url,
164 template_path=template_path,
163 template_path=template_path,
165 static_path=ipython_app.static_file_path,
164 static_path=ipython_app.static_file_path,
166 static_handler_class = FileFindHandler,
165 static_handler_class = FileFindHandler,
167 static_url_prefix = url_path_join(base_url,'/static/'),
166 static_url_prefix = url_path_join(base_url,'/static/'),
168
167
169 # authentication
168 # authentication
170 cookie_secret=ipython_app.cookie_secret,
169 cookie_secret=ipython_app.cookie_secret,
171 login_url=url_path_join(base_url,'/login'),
170 login_url=url_path_join(base_url,'/login'),
172 password=ipython_app.password,
171 password=ipython_app.password,
173
172
174 # managers
173 # managers
175 kernel_manager=kernel_manager,
174 kernel_manager=kernel_manager,
176 notebook_manager=notebook_manager,
175 notebook_manager=notebook_manager,
177 cluster_manager=cluster_manager,
176 cluster_manager=cluster_manager,
178 session_manager=session_manager,
177 session_manager=session_manager,
179
178
180 # IPython stuff
179 # IPython stuff
181 nbextensions_path = ipython_app.nbextensions_path,
180 nbextensions_path = ipython_app.nbextensions_path,
182 mathjax_url=ipython_app.mathjax_url,
181 mathjax_url=ipython_app.mathjax_url,
183 config=ipython_app.config,
182 config=ipython_app.config,
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
183 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
185 )
184 )
186
185
187 # allow custom overrides for the tornado web app.
186 # allow custom overrides for the tornado web app.
188 settings.update(settings_overrides)
187 settings.update(settings_overrides)
189 return settings
188 return settings
190
189
191 def init_handlers(self, settings):
190 def init_handlers(self, settings):
192 # Load the (URL pattern, handler) tuples for each component.
191 # Load the (URL pattern, handler) tuples for each component.
193 handlers = []
192 handlers = []
194 handlers.extend(load_handlers('base.handlers'))
193 handlers.extend(load_handlers('base.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
194 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('auth.login'))
195 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.logout'))
196 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('notebook.handlers'))
197 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
198 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
199 handlers.extend(load_handlers('services.kernels.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
200 handlers.extend(load_handlers('services.notebooks.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
201 handlers.extend(load_handlers('services.clusters.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
202 handlers.extend(load_handlers('services.sessions.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
203 handlers.extend(load_handlers('services.nbconvert.handlers'))
205 handlers.extend([
204 handlers.extend([
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
206 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 ])
207 ])
209 # prepend base_url onto the patterns that we match
208 # prepend base_url onto the patterns that we match
210 new_handlers = []
209 new_handlers = []
211 for handler in handlers:
210 for handler in handlers:
212 pattern = url_path_join(settings['base_url'], handler[0])
211 pattern = url_path_join(settings['base_url'], handler[0])
213 new_handler = tuple([pattern] + list(handler[1:]))
212 new_handler = tuple([pattern] + list(handler[1:]))
214 new_handlers.append(new_handler)
213 new_handlers.append(new_handler)
215 # add 404 on the end, which will catch everything that falls through
214 # add 404 on the end, which will catch everything that falls through
216 new_handlers.append((r'(.*)', Template404))
215 new_handlers.append((r'(.*)', Template404))
217 return new_handlers
216 return new_handlers
218
217
219
218
220 class NbserverListApp(BaseIPythonApplication):
219 class NbserverListApp(BaseIPythonApplication):
221
220
222 description="List currently running notebook servers in this profile."
221 description="List currently running notebook servers in this profile."
223
222
224 flags = dict(
223 flags = dict(
225 json=({'NbserverListApp': {'json': True}},
224 json=({'NbserverListApp': {'json': True}},
226 "Produce machine-readable JSON output."),
225 "Produce machine-readable JSON output."),
227 )
226 )
228
227
229 json = Bool(False, config=True,
228 json = Bool(False, config=True,
230 help="If True, each line of output will be a JSON object with the "
229 help="If True, each line of output will be a JSON object with the "
231 "details from the server info file.")
230 "details from the server info file.")
232
231
233 def start(self):
232 def start(self):
234 if not self.json:
233 if not self.json:
235 print("Currently running servers:")
234 print("Currently running servers:")
236 for serverinfo in list_running_servers(self.profile):
235 for serverinfo in list_running_servers(self.profile):
237 if self.json:
236 if self.json:
238 print(json.dumps(serverinfo))
237 print(json.dumps(serverinfo))
239 else:
238 else:
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
239 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
241
240
242 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
243 # Aliases and Flags
242 # Aliases and Flags
244 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
245
244
246 flags = dict(kernel_flags)
245 flags = dict(kernel_flags)
247 flags['no-browser']=(
246 flags['no-browser']=(
248 {'NotebookApp' : {'open_browser' : False}},
247 {'NotebookApp' : {'open_browser' : False}},
249 "Don't open the notebook in a browser after startup."
248 "Don't open the notebook in a browser after startup."
250 )
249 )
251 flags['no-mathjax']=(
250 flags['no-mathjax']=(
252 {'NotebookApp' : {'enable_mathjax' : False}},
251 {'NotebookApp' : {'enable_mathjax' : False}},
253 """Disable MathJax
252 """Disable MathJax
254
253
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
254 MathJax is the javascript library IPython uses to render math/LaTeX. It is
256 very large, so you may want to disable it if you have a slow internet
255 very large, so you may want to disable it if you have a slow internet
257 connection, or for offline use of the notebook.
256 connection, or for offline use of the notebook.
258
257
259 When disabled, equations etc. will appear as their untransformed TeX source.
258 When disabled, equations etc. will appear as their untransformed TeX source.
260 """
259 """
261 )
260 )
262
261
263 # Add notebook manager flags
262 # Add notebook manager flags
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
266 'Do not auto-save .py scripts for every notebook'))
265 'Do not auto-save .py scripts for every notebook'))
267
266
268 # the flags that are specific to the frontend
267 # the flags that are specific to the frontend
269 # these must be scrubbed before being passed to the kernel,
268 # these must be scrubbed before being passed to the kernel,
270 # or it will raise an error on unrecognized flags
269 # or it will raise an error on unrecognized flags
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
270 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
272
271
273 aliases = dict(kernel_aliases)
272 aliases = dict(kernel_aliases)
274
273
275 aliases.update({
274 aliases.update({
276 'ip': 'NotebookApp.ip',
275 'ip': 'NotebookApp.ip',
277 'port': 'NotebookApp.port',
276 'port': 'NotebookApp.port',
278 'port-retries': 'NotebookApp.port_retries',
277 'port-retries': 'NotebookApp.port_retries',
279 'transport': 'KernelManager.transport',
278 'transport': 'KernelManager.transport',
280 'keyfile': 'NotebookApp.keyfile',
279 'keyfile': 'NotebookApp.keyfile',
281 'certfile': 'NotebookApp.certfile',
280 'certfile': 'NotebookApp.certfile',
282 'notebook-dir': 'NotebookManager.notebook_dir',
281 'notebook-dir': 'NotebookManager.notebook_dir',
283 'browser': 'NotebookApp.browser',
282 'browser': 'NotebookApp.browser',
284 })
283 })
285
284
286 # remove ipkernel flags that are singletons, and don't make sense in
285 # remove ipkernel flags that are singletons, and don't make sense in
287 # multi-kernel evironment:
286 # multi-kernel evironment:
288 aliases.pop('f', None)
287 aliases.pop('f', None)
289
288
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
289 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
291 u'notebook-dir', u'profile', u'profile-dir']
290 u'notebook-dir', u'profile', u'profile-dir']
292
291
293 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
294 # NotebookApp
293 # NotebookApp
295 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
296
295
297 class NotebookApp(BaseIPythonApplication):
296 class NotebookApp(BaseIPythonApplication):
298
297
299 name = 'ipython-notebook'
298 name = 'ipython-notebook'
300
299
301 description = """
300 description = """
302 The IPython HTML Notebook.
301 The IPython HTML Notebook.
303
302
304 This launches a Tornado based HTML Notebook Server that serves up an
303 This launches a Tornado based HTML Notebook Server that serves up an
305 HTML5/Javascript Notebook client.
304 HTML5/Javascript Notebook client.
306 """
305 """
307 examples = _examples
306 examples = _examples
308
307
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
308 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
310 FileNotebookManager]
309 FileNotebookManager]
311 flags = Dict(flags)
310 flags = Dict(flags)
312 aliases = Dict(aliases)
311 aliases = Dict(aliases)
313
312
314 subcommands = dict(
313 subcommands = dict(
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
314 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
316 )
315 )
317
316
318 kernel_argv = List(Unicode)
317 kernel_argv = List(Unicode)
319
318
320 def _log_level_default(self):
319 def _log_level_default(self):
321 return logging.INFO
320 return logging.INFO
322
321
323 def _log_format_default(self):
322 def _log_format_default(self):
324 """override default log format to include time"""
323 """override default log format to include time"""
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
324 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
326
325
327 # create requested profiles by default, if they don't exist:
326 # create requested profiles by default, if they don't exist:
328 auto_create = Bool(True)
327 auto_create = Bool(True)
329
328
330 # file to be opened in the notebook server
329 # file to be opened in the notebook server
331 file_to_run = Unicode('')
330 file_to_run = Unicode('')
332
331
333 # Network related information.
332 # Network related information.
334
333
335 ip = Unicode(config=True,
334 ip = Unicode(config=True,
336 help="The IP address the notebook server will listen on."
335 help="The IP address the notebook server will listen on."
337 )
336 )
338 def _ip_default(self):
337 def _ip_default(self):
339 return localhost()
338 return localhost()
340
339
341 def _ip_changed(self, name, old, new):
340 def _ip_changed(self, name, old, new):
342 if new == u'*': self.ip = u''
341 if new == u'*': self.ip = u''
343
342
344 port = Integer(8888, config=True,
343 port = Integer(8888, config=True,
345 help="The port the notebook server will listen on."
344 help="The port the notebook server will listen on."
346 )
345 )
347 port_retries = Integer(50, config=True,
346 port_retries = Integer(50, config=True,
348 help="The number of additional ports to try if the specified port is not available."
347 help="The number of additional ports to try if the specified port is not available."
349 )
348 )
350
349
351 certfile = Unicode(u'', config=True,
350 certfile = Unicode(u'', config=True,
352 help="""The full path to an SSL/TLS certificate file."""
351 help="""The full path to an SSL/TLS certificate file."""
353 )
352 )
354
353
355 keyfile = Unicode(u'', config=True,
354 keyfile = Unicode(u'', config=True,
356 help="""The full path to a private key file for usage with SSL/TLS."""
355 help="""The full path to a private key file for usage with SSL/TLS."""
357 )
356 )
358
357
359 cookie_secret = Bytes(b'', config=True,
358 cookie_secret = Bytes(b'', config=True,
360 help="""The random bytes used to secure cookies.
359 help="""The random bytes used to secure cookies.
361 By default this is a new random number every time you start the Notebook.
360 By default this is a new random number every time you start the Notebook.
362 Set it to a value in a config file to enable logins to persist across server sessions.
361 Set it to a value in a config file to enable logins to persist across server sessions.
363
362
364 Note: Cookie secrets should be kept private, do not share config files with
363 Note: Cookie secrets should be kept private, do not share config files with
365 cookie_secret stored in plaintext (you can read the value from a file).
364 cookie_secret stored in plaintext (you can read the value from a file).
366 """
365 """
367 )
366 )
368 def _cookie_secret_default(self):
367 def _cookie_secret_default(self):
369 return os.urandom(1024)
368 return os.urandom(1024)
370
369
371 password = Unicode(u'', config=True,
370 password = Unicode(u'', config=True,
372 help="""Hashed password to use for web authentication.
371 help="""Hashed password to use for web authentication.
373
372
374 To generate, type in a python/IPython shell:
373 To generate, type in a python/IPython shell:
375
374
376 from IPython.lib import passwd; passwd()
375 from IPython.lib import passwd; passwd()
377
376
378 The string should be of the form type:salt:hashed-password.
377 The string should be of the form type:salt:hashed-password.
379 """
378 """
380 )
379 )
381
380
382 open_browser = Bool(True, config=True,
381 open_browser = Bool(True, config=True,
383 help="""Whether to open in a browser after starting.
382 help="""Whether to open in a browser after starting.
384 The specific browser used is platform dependent and
383 The specific browser used is platform dependent and
385 determined by the python standard library `webbrowser`
384 determined by the python standard library `webbrowser`
386 module, unless it is overridden using the --browser
385 module, unless it is overridden using the --browser
387 (NotebookApp.browser) configuration option.
386 (NotebookApp.browser) configuration option.
388 """)
387 """)
389
388
390 browser = Unicode(u'', config=True,
389 browser = Unicode(u'', config=True,
391 help="""Specify what command to use to invoke a web
390 help="""Specify what command to use to invoke a web
392 browser when opening the notebook. If not specified, the
391 browser when opening the notebook. If not specified, the
393 default browser will be determined by the `webbrowser`
392 default browser will be determined by the `webbrowser`
394 standard library module, which allows setting of the
393 standard library module, which allows setting of the
395 BROWSER environment variable to override it.
394 BROWSER environment variable to override it.
396 """)
395 """)
397
396
398 webapp_settings = Dict(config=True,
397 webapp_settings = Dict(config=True,
399 help="Supply overrides for the tornado.web.Application that the "
398 help="Supply overrides for the tornado.web.Application that the "
400 "IPython notebook uses.")
399 "IPython notebook uses.")
401
400
402 enable_mathjax = Bool(True, config=True,
401 enable_mathjax = Bool(True, config=True,
403 help="""Whether to enable MathJax for typesetting math/TeX
402 help="""Whether to enable MathJax for typesetting math/TeX
404
403
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
404 MathJax is the javascript library IPython uses to render math/LaTeX. It is
406 very large, so you may want to disable it if you have a slow internet
405 very large, so you may want to disable it if you have a slow internet
407 connection, or for offline use of the notebook.
406 connection, or for offline use of the notebook.
408
407
409 When disabled, equations etc. will appear as their untransformed TeX source.
408 When disabled, equations etc. will appear as their untransformed TeX source.
410 """
409 """
411 )
410 )
412 def _enable_mathjax_changed(self, name, old, new):
411 def _enable_mathjax_changed(self, name, old, new):
413 """set mathjax url to empty if mathjax is disabled"""
412 """set mathjax url to empty if mathjax is disabled"""
414 if not new:
413 if not new:
415 self.mathjax_url = u''
414 self.mathjax_url = u''
416
415
417 base_url = Unicode('/', config=True,
416 base_url = Unicode('/', config=True,
418 help='''The base URL for the notebook server.
417 help='''The base URL for the notebook server.
419
418
420 Leading and trailing slashes can be omitted,
419 Leading and trailing slashes can be omitted,
421 and will automatically be added.
420 and will automatically be added.
422 ''')
421 ''')
423 def _base_url_changed(self, name, old, new):
422 def _base_url_changed(self, name, old, new):
424 if not new.startswith('/'):
423 if not new.startswith('/'):
425 self.base_url = '/'+new
424 self.base_url = '/'+new
426 elif not new.endswith('/'):
425 elif not new.endswith('/'):
427 self.base_url = new+'/'
426 self.base_url = new+'/'
428
427
429 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
428 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
430 def _base_project_url_changed(self, name, old, new):
429 def _base_project_url_changed(self, name, old, new):
431 self.log.warn("base_project_url is deprecated, use base_url")
430 self.log.warn("base_project_url is deprecated, use base_url")
432 self.base_url = new
431 self.base_url = new
433
432
434 base_kernel_url = Unicode('/', config=True,
435 help='''The base URL for the kernel server
436
437 Leading and trailing slashes can be omitted,
438 and will automatically be added.
439 ''')
440 def _base_kernel_url_changed(self, name, old, new):
441 if not new.startswith('/'):
442 self.base_kernel_url = '/'+new
443 elif not new.endswith('/'):
444 self.base_kernel_url = new+'/'
445
446 websocket_url = Unicode("", config=True,
433 websocket_url = Unicode("", config=True,
447 help="""The base URL for the websocket server,
434 help="""The base URL for the websocket server,
448 if it differs from the HTTP server (hint: it almost certainly doesn't).
435 if it differs from the HTTP server (hint: it almost certainly doesn't).
449
436
450 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
437 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
451 """
438 """
452 )
439 )
453
440
454 extra_static_paths = List(Unicode, config=True,
441 extra_static_paths = List(Unicode, config=True,
455 help="""Extra paths to search for serving static files.
442 help="""Extra paths to search for serving static files.
456
443
457 This allows adding javascript/css to be available from the notebook server machine,
444 This allows adding javascript/css to be available from the notebook server machine,
458 or overriding individual files in the IPython"""
445 or overriding individual files in the IPython"""
459 )
446 )
460 def _extra_static_paths_default(self):
447 def _extra_static_paths_default(self):
461 return [os.path.join(self.profile_dir.location, 'static')]
448 return [os.path.join(self.profile_dir.location, 'static')]
462
449
463 @property
450 @property
464 def static_file_path(self):
451 def static_file_path(self):
465 """return extra paths + the default location"""
452 """return extra paths + the default location"""
466 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
453 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
467
454
468 nbextensions_path = List(Unicode, config=True,
455 nbextensions_path = List(Unicode, config=True,
469 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
456 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
470 )
457 )
471 def _nbextensions_path_default(self):
458 def _nbextensions_path_default(self):
472 return [os.path.join(get_ipython_dir(), 'nbextensions')]
459 return [os.path.join(get_ipython_dir(), 'nbextensions')]
473
460
474 mathjax_url = Unicode("", config=True,
461 mathjax_url = Unicode("", config=True,
475 help="""The url for MathJax.js."""
462 help="""The url for MathJax.js."""
476 )
463 )
477 def _mathjax_url_default(self):
464 def _mathjax_url_default(self):
478 if not self.enable_mathjax:
465 if not self.enable_mathjax:
479 return u''
466 return u''
480 static_url_prefix = self.webapp_settings.get("static_url_prefix",
467 static_url_prefix = self.webapp_settings.get("static_url_prefix",
481 url_path_join(self.base_url, "static")
468 url_path_join(self.base_url, "static")
482 )
469 )
483
470
484 # try local mathjax, either in nbextensions/mathjax or static/mathjax
471 # try local mathjax, either in nbextensions/mathjax or static/mathjax
485 for (url_prefix, search_path) in [
472 for (url_prefix, search_path) in [
486 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
473 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
487 (static_url_prefix, self.static_file_path),
474 (static_url_prefix, self.static_file_path),
488 ]:
475 ]:
489 self.log.debug("searching for local mathjax in %s", search_path)
476 self.log.debug("searching for local mathjax in %s", search_path)
490 try:
477 try:
491 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
478 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
492 except IOError:
479 except IOError:
493 continue
480 continue
494 else:
481 else:
495 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
482 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
496 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
483 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
497 return url
484 return url
498
485
499 # no local mathjax, serve from CDN
486 # no local mathjax, serve from CDN
500 if self.certfile:
487 if self.certfile:
501 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
488 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
502 host = u"https://c328740.ssl.cf1.rackcdn.com"
489 host = u"https://c328740.ssl.cf1.rackcdn.com"
503 else:
490 else:
504 host = u"http://cdn.mathjax.org"
491 host = u"http://cdn.mathjax.org"
505
492
506 url = host + u"/mathjax/latest/MathJax.js"
493 url = host + u"/mathjax/latest/MathJax.js"
507 self.log.info("Using MathJax from CDN: %s", url)
494 self.log.info("Using MathJax from CDN: %s", url)
508 return url
495 return url
509
496
510 def _mathjax_url_changed(self, name, old, new):
497 def _mathjax_url_changed(self, name, old, new):
511 if new and not self.enable_mathjax:
498 if new and not self.enable_mathjax:
512 # enable_mathjax=False overrides mathjax_url
499 # enable_mathjax=False overrides mathjax_url
513 self.mathjax_url = u''
500 self.mathjax_url = u''
514 else:
501 else:
515 self.log.info("Using MathJax: %s", new)
502 self.log.info("Using MathJax: %s", new)
516
503
517 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
504 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
518 config=True,
505 config=True,
519 help='The notebook manager class to use.')
506 help='The notebook manager class to use.')
520
507
521 trust_xheaders = Bool(False, config=True,
508 trust_xheaders = Bool(False, config=True,
522 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
509 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
523 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
510 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
524 )
511 )
525
512
526 info_file = Unicode()
513 info_file = Unicode()
527
514
528 def _info_file_default(self):
515 def _info_file_default(self):
529 info_file = "nbserver-%s.json"%os.getpid()
516 info_file = "nbserver-%s.json"%os.getpid()
530 return os.path.join(self.profile_dir.security_dir, info_file)
517 return os.path.join(self.profile_dir.security_dir, info_file)
531
518
532 def parse_command_line(self, argv=None):
519 def parse_command_line(self, argv=None):
533 super(NotebookApp, self).parse_command_line(argv)
520 super(NotebookApp, self).parse_command_line(argv)
534
521
535 if self.extra_args:
522 if self.extra_args:
536 arg0 = self.extra_args[0]
523 arg0 = self.extra_args[0]
537 f = os.path.abspath(arg0)
524 f = os.path.abspath(arg0)
538 self.argv.remove(arg0)
525 self.argv.remove(arg0)
539 if not os.path.exists(f):
526 if not os.path.exists(f):
540 self.log.critical("No such file or directory: %s", f)
527 self.log.critical("No such file or directory: %s", f)
541 self.exit(1)
528 self.exit(1)
542 if os.path.isdir(f):
529 if os.path.isdir(f):
543 self.config.FileNotebookManager.notebook_dir = f
530 self.config.FileNotebookManager.notebook_dir = f
544 elif os.path.isfile(f):
531 elif os.path.isfile(f):
545 self.file_to_run = f
532 self.file_to_run = f
546
533
547 def init_kernel_argv(self):
534 def init_kernel_argv(self):
548 """construct the kernel arguments"""
535 """construct the kernel arguments"""
549 # Scrub frontend-specific flags
536 # Scrub frontend-specific flags
550 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
537 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
551 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
538 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
552 self.log.warn('\n '.join([
539 self.log.warn('\n '.join([
553 "Starting all kernels in pylab mode is not recommended,",
540 "Starting all kernels in pylab mode is not recommended,",
554 "and will be disabled in a future release.",
541 "and will be disabled in a future release.",
555 "Please use the %matplotlib magic to enable matplotlib instead.",
542 "Please use the %matplotlib magic to enable matplotlib instead.",
556 "pylab implies many imports, which can have confusing side effects",
543 "pylab implies many imports, which can have confusing side effects",
557 "and harm the reproducibility of your notebooks.",
544 "and harm the reproducibility of your notebooks.",
558 ]))
545 ]))
559 # Kernel should inherit default config file from frontend
546 # Kernel should inherit default config file from frontend
560 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
547 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
561 # Kernel should get *absolute* path to profile directory
548 # Kernel should get *absolute* path to profile directory
562 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
549 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
563
550
564 def init_configurables(self):
551 def init_configurables(self):
565 # force Session default to be secure
552 # force Session default to be secure
566 default_secure(self.config)
553 default_secure(self.config)
567 self.kernel_manager = MappingKernelManager(
554 self.kernel_manager = MappingKernelManager(
568 parent=self, log=self.log, kernel_argv=self.kernel_argv,
555 parent=self, log=self.log, kernel_argv=self.kernel_argv,
569 connection_dir = self.profile_dir.security_dir,
556 connection_dir = self.profile_dir.security_dir,
570 )
557 )
571 kls = import_item(self.notebook_manager_class)
558 kls = import_item(self.notebook_manager_class)
572 self.notebook_manager = kls(parent=self, log=self.log)
559 self.notebook_manager = kls(parent=self, log=self.log)
573 self.session_manager = SessionManager(parent=self, log=self.log)
560 self.session_manager = SessionManager(parent=self, log=self.log)
574 self.cluster_manager = ClusterManager(parent=self, log=self.log)
561 self.cluster_manager = ClusterManager(parent=self, log=self.log)
575 self.cluster_manager.update_profiles()
562 self.cluster_manager.update_profiles()
576
563
577 def init_logging(self):
564 def init_logging(self):
578 # This prevents double log messages because tornado use a root logger that
565 # This prevents double log messages because tornado use a root logger that
579 # self.log is a child of. The logging module dipatches log messages to a log
566 # self.log is a child of. The logging module dipatches log messages to a log
580 # and all of its ancenstors until propagate is set to False.
567 # and all of its ancenstors until propagate is set to False.
581 self.log.propagate = False
568 self.log.propagate = False
582
569
583 # hook up tornado 3's loggers to our app handlers
570 # hook up tornado 3's loggers to our app handlers
584 for name in ('access', 'application', 'general'):
571 for name in ('access', 'application', 'general'):
585 logger = logging.getLogger('tornado.%s' % name)
572 logger = logging.getLogger('tornado.%s' % name)
586 logger.parent = self.log
573 logger.parent = self.log
587 logger.setLevel(self.log.level)
574 logger.setLevel(self.log.level)
588
575
589 def init_webapp(self):
576 def init_webapp(self):
590 """initialize tornado webapp and httpserver"""
577 """initialize tornado webapp and httpserver"""
591 self.web_app = NotebookWebApplication(
578 self.web_app = NotebookWebApplication(
592 self, self.kernel_manager, self.notebook_manager,
579 self, self.kernel_manager, self.notebook_manager,
593 self.cluster_manager, self.session_manager,
580 self.cluster_manager, self.session_manager,
594 self.log, self.base_url, self.webapp_settings
581 self.log, self.base_url, self.webapp_settings
595 )
582 )
596 if self.certfile:
583 if self.certfile:
597 ssl_options = dict(certfile=self.certfile)
584 ssl_options = dict(certfile=self.certfile)
598 if self.keyfile:
585 if self.keyfile:
599 ssl_options['keyfile'] = self.keyfile
586 ssl_options['keyfile'] = self.keyfile
600 else:
587 else:
601 ssl_options = None
588 ssl_options = None
602 self.web_app.password = self.password
589 self.web_app.password = self.password
603 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
590 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
604 xheaders=self.trust_xheaders)
591 xheaders=self.trust_xheaders)
605 if not self.ip:
592 if not self.ip:
606 warning = "WARNING: The notebook server is listening on all IP addresses"
593 warning = "WARNING: The notebook server is listening on all IP addresses"
607 if ssl_options is None:
594 if ssl_options is None:
608 self.log.critical(warning + " and not using encryption. This "
595 self.log.critical(warning + " and not using encryption. This "
609 "is not recommended.")
596 "is not recommended.")
610 if not self.password:
597 if not self.password:
611 self.log.critical(warning + " and not using authentication. "
598 self.log.critical(warning + " and not using authentication. "
612 "This is highly insecure and not recommended.")
599 "This is highly insecure and not recommended.")
613 success = None
600 success = None
614 for port in random_ports(self.port, self.port_retries+1):
601 for port in random_ports(self.port, self.port_retries+1):
615 try:
602 try:
616 self.http_server.listen(port, self.ip)
603 self.http_server.listen(port, self.ip)
617 except socket.error as e:
604 except socket.error as e:
618 if e.errno == errno.EADDRINUSE:
605 if e.errno == errno.EADDRINUSE:
619 self.log.info('The port %i is already in use, trying another random port.' % port)
606 self.log.info('The port %i is already in use, trying another random port.' % port)
620 continue
607 continue
621 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
608 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
622 self.log.warn("Permission to listen on port %i denied" % port)
609 self.log.warn("Permission to listen on port %i denied" % port)
623 continue
610 continue
624 else:
611 else:
625 raise
612 raise
626 else:
613 else:
627 self.port = port
614 self.port = port
628 success = True
615 success = True
629 break
616 break
630 if not success:
617 if not success:
631 self.log.critical('ERROR: the notebook server could not be started because '
618 self.log.critical('ERROR: the notebook server could not be started because '
632 'no available port could be found.')
619 'no available port could be found.')
633 self.exit(1)
620 self.exit(1)
634
621
635 @property
622 @property
636 def display_url(self):
623 def display_url(self):
637 ip = self.ip if self.ip else '[all ip addresses on your system]'
624 ip = self.ip if self.ip else '[all ip addresses on your system]'
638 return self._url(ip)
625 return self._url(ip)
639
626
640 @property
627 @property
641 def connection_url(self):
628 def connection_url(self):
642 ip = self.ip if self.ip else localhost()
629 ip = self.ip if self.ip else localhost()
643 return self._url(ip)
630 return self._url(ip)
644
631
645 def _url(self, ip):
632 def _url(self, ip):
646 proto = 'https' if self.certfile else 'http'
633 proto = 'https' if self.certfile else 'http'
647 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
634 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
648
635
649 def init_signal(self):
636 def init_signal(self):
650 if not sys.platform.startswith('win'):
637 if not sys.platform.startswith('win'):
651 signal.signal(signal.SIGINT, self._handle_sigint)
638 signal.signal(signal.SIGINT, self._handle_sigint)
652 signal.signal(signal.SIGTERM, self._signal_stop)
639 signal.signal(signal.SIGTERM, self._signal_stop)
653 if hasattr(signal, 'SIGUSR1'):
640 if hasattr(signal, 'SIGUSR1'):
654 # Windows doesn't support SIGUSR1
641 # Windows doesn't support SIGUSR1
655 signal.signal(signal.SIGUSR1, self._signal_info)
642 signal.signal(signal.SIGUSR1, self._signal_info)
656 if hasattr(signal, 'SIGINFO'):
643 if hasattr(signal, 'SIGINFO'):
657 # only on BSD-based systems
644 # only on BSD-based systems
658 signal.signal(signal.SIGINFO, self._signal_info)
645 signal.signal(signal.SIGINFO, self._signal_info)
659
646
660 def _handle_sigint(self, sig, frame):
647 def _handle_sigint(self, sig, frame):
661 """SIGINT handler spawns confirmation dialog"""
648 """SIGINT handler spawns confirmation dialog"""
662 # register more forceful signal handler for ^C^C case
649 # register more forceful signal handler for ^C^C case
663 signal.signal(signal.SIGINT, self._signal_stop)
650 signal.signal(signal.SIGINT, self._signal_stop)
664 # request confirmation dialog in bg thread, to avoid
651 # request confirmation dialog in bg thread, to avoid
665 # blocking the App
652 # blocking the App
666 thread = threading.Thread(target=self._confirm_exit)
653 thread = threading.Thread(target=self._confirm_exit)
667 thread.daemon = True
654 thread.daemon = True
668 thread.start()
655 thread.start()
669
656
670 def _restore_sigint_handler(self):
657 def _restore_sigint_handler(self):
671 """callback for restoring original SIGINT handler"""
658 """callback for restoring original SIGINT handler"""
672 signal.signal(signal.SIGINT, self._handle_sigint)
659 signal.signal(signal.SIGINT, self._handle_sigint)
673
660
674 def _confirm_exit(self):
661 def _confirm_exit(self):
675 """confirm shutdown on ^C
662 """confirm shutdown on ^C
676
663
677 A second ^C, or answering 'y' within 5s will cause shutdown,
664 A second ^C, or answering 'y' within 5s will cause shutdown,
678 otherwise original SIGINT handler will be restored.
665 otherwise original SIGINT handler will be restored.
679
666
680 This doesn't work on Windows.
667 This doesn't work on Windows.
681 """
668 """
682 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
669 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
683 time.sleep(0.1)
670 time.sleep(0.1)
684 info = self.log.info
671 info = self.log.info
685 info('interrupted')
672 info('interrupted')
686 print(self.notebook_info())
673 print(self.notebook_info())
687 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
674 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
688 sys.stdout.flush()
675 sys.stdout.flush()
689 r,w,x = select.select([sys.stdin], [], [], 5)
676 r,w,x = select.select([sys.stdin], [], [], 5)
690 if r:
677 if r:
691 line = sys.stdin.readline()
678 line = sys.stdin.readline()
692 if line.lower().startswith('y'):
679 if line.lower().startswith('y'):
693 self.log.critical("Shutdown confirmed")
680 self.log.critical("Shutdown confirmed")
694 ioloop.IOLoop.instance().stop()
681 ioloop.IOLoop.instance().stop()
695 return
682 return
696 else:
683 else:
697 print("No answer for 5s:", end=' ')
684 print("No answer for 5s:", end=' ')
698 print("resuming operation...")
685 print("resuming operation...")
699 # no answer, or answer is no:
686 # no answer, or answer is no:
700 # set it back to original SIGINT handler
687 # set it back to original SIGINT handler
701 # use IOLoop.add_callback because signal.signal must be called
688 # use IOLoop.add_callback because signal.signal must be called
702 # from main thread
689 # from main thread
703 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
690 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
704
691
705 def _signal_stop(self, sig, frame):
692 def _signal_stop(self, sig, frame):
706 self.log.critical("received signal %s, stopping", sig)
693 self.log.critical("received signal %s, stopping", sig)
707 ioloop.IOLoop.instance().stop()
694 ioloop.IOLoop.instance().stop()
708
695
709 def _signal_info(self, sig, frame):
696 def _signal_info(self, sig, frame):
710 print(self.notebook_info())
697 print(self.notebook_info())
711
698
712 def init_components(self):
699 def init_components(self):
713 """Check the components submodule, and warn if it's unclean"""
700 """Check the components submodule, and warn if it's unclean"""
714 status = submodule.check_submodule_status()
701 status = submodule.check_submodule_status()
715 if status == 'missing':
702 if status == 'missing':
716 self.log.warn("components submodule missing, running `git submodule update`")
703 self.log.warn("components submodule missing, running `git submodule update`")
717 submodule.update_submodules(submodule.ipython_parent())
704 submodule.update_submodules(submodule.ipython_parent())
718 elif status == 'unclean':
705 elif status == 'unclean':
719 self.log.warn("components submodule unclean, you may see 404s on static/components")
706 self.log.warn("components submodule unclean, you may see 404s on static/components")
720 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
707 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
721
708
722 @catch_config_error
709 @catch_config_error
723 def initialize(self, argv=None):
710 def initialize(self, argv=None):
724 super(NotebookApp, self).initialize(argv)
711 super(NotebookApp, self).initialize(argv)
725 self.init_logging()
712 self.init_logging()
726 self.init_kernel_argv()
713 self.init_kernel_argv()
727 self.init_configurables()
714 self.init_configurables()
728 self.init_components()
715 self.init_components()
729 self.init_webapp()
716 self.init_webapp()
730 self.init_signal()
717 self.init_signal()
731
718
732 def cleanup_kernels(self):
719 def cleanup_kernels(self):
733 """Shutdown all kernels.
720 """Shutdown all kernels.
734
721
735 The kernels will shutdown themselves when this process no longer exists,
722 The kernels will shutdown themselves when this process no longer exists,
736 but explicit shutdown allows the KernelManagers to cleanup the connection files.
723 but explicit shutdown allows the KernelManagers to cleanup the connection files.
737 """
724 """
738 self.log.info('Shutting down kernels')
725 self.log.info('Shutting down kernels')
739 self.kernel_manager.shutdown_all()
726 self.kernel_manager.shutdown_all()
740
727
741 def notebook_info(self):
728 def notebook_info(self):
742 "Return the current working directory and the server url information"
729 "Return the current working directory and the server url information"
743 info = self.notebook_manager.info_string() + "\n"
730 info = self.notebook_manager.info_string() + "\n"
744 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
731 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
745 return info + "The IPython Notebook is running at: %s" % self.display_url
732 return info + "The IPython Notebook is running at: %s" % self.display_url
746
733
747 def server_info(self):
734 def server_info(self):
748 """Return a JSONable dict of information about this server."""
735 """Return a JSONable dict of information about this server."""
749 return {'url': self.connection_url,
736 return {'url': self.connection_url,
750 'hostname': self.ip if self.ip else 'localhost',
737 'hostname': self.ip if self.ip else 'localhost',
751 'port': self.port,
738 'port': self.port,
752 'secure': bool(self.certfile),
739 'secure': bool(self.certfile),
753 'base_url': self.base_url,
740 'base_url': self.base_url,
754 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
741 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
755 }
742 }
756
743
757 def write_server_info_file(self):
744 def write_server_info_file(self):
758 """Write the result of server_info() to the JSON file info_file."""
745 """Write the result of server_info() to the JSON file info_file."""
759 with open(self.info_file, 'w') as f:
746 with open(self.info_file, 'w') as f:
760 json.dump(self.server_info(), f, indent=2)
747 json.dump(self.server_info(), f, indent=2)
761
748
762 def remove_server_info_file(self):
749 def remove_server_info_file(self):
763 """Remove the nbserver-<pid>.json file created for this server.
750 """Remove the nbserver-<pid>.json file created for this server.
764
751
765 Ignores the error raised when the file has already been removed.
752 Ignores the error raised when the file has already been removed.
766 """
753 """
767 try:
754 try:
768 os.unlink(self.info_file)
755 os.unlink(self.info_file)
769 except OSError as e:
756 except OSError as e:
770 if e.errno != errno.ENOENT:
757 if e.errno != errno.ENOENT:
771 raise
758 raise
772
759
773 def start(self):
760 def start(self):
774 """ Start the IPython Notebook server app, after initialization
761 """ Start the IPython Notebook server app, after initialization
775
762
776 This method takes no arguments so all configuration and initialization
763 This method takes no arguments so all configuration and initialization
777 must be done prior to calling this method."""
764 must be done prior to calling this method."""
778 if self.subapp is not None:
765 if self.subapp is not None:
779 return self.subapp.start()
766 return self.subapp.start()
780
767
781 info = self.log.info
768 info = self.log.info
782 for line in self.notebook_info().split("\n"):
769 for line in self.notebook_info().split("\n"):
783 info(line)
770 info(line)
784 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
771 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
785
772
786 self.write_server_info_file()
773 self.write_server_info_file()
787
774
788 if self.open_browser or self.file_to_run:
775 if self.open_browser or self.file_to_run:
789 try:
776 try:
790 browser = webbrowser.get(self.browser or None)
777 browser = webbrowser.get(self.browser or None)
791 except webbrowser.Error as e:
778 except webbrowser.Error as e:
792 self.log.warn('No web browser found: %s.' % e)
779 self.log.warn('No web browser found: %s.' % e)
793 browser = None
780 browser = None
794
781
795 f = self.file_to_run
782 f = self.file_to_run
796 if f:
783 if f:
797 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
784 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
798 if f.startswith(nbdir):
785 if f.startswith(nbdir):
799 f = f[len(nbdir):]
786 f = f[len(nbdir):]
800 else:
787 else:
801 self.log.warn(
788 self.log.warn(
802 "Probably won't be able to open notebook %s "
789 "Probably won't be able to open notebook %s "
803 "because it is not in notebook_dir %s",
790 "because it is not in notebook_dir %s",
804 f, nbdir,
791 f, nbdir,
805 )
792 )
806
793
807 if os.path.isfile(self.file_to_run):
794 if os.path.isfile(self.file_to_run):
808 url = url_path_join('notebooks', f)
795 url = url_path_join('notebooks', f)
809 else:
796 else:
810 url = url_path_join('tree', f)
797 url = url_path_join('tree', f)
811 if browser:
798 if browser:
812 b = lambda : browser.open("%s%s" % (self.connection_url, url),
799 b = lambda : browser.open("%s%s" % (self.connection_url, url),
813 new=2)
800 new=2)
814 threading.Thread(target=b).start()
801 threading.Thread(target=b).start()
815 try:
802 try:
816 ioloop.IOLoop.instance().start()
803 ioloop.IOLoop.instance().start()
817 except KeyboardInterrupt:
804 except KeyboardInterrupt:
818 info("Interrupted...")
805 info("Interrupted...")
819 finally:
806 finally:
820 self.cleanup_kernels()
807 self.cleanup_kernels()
821 self.remove_server_info_file()
808 self.remove_server_info_file()
822
809
823
810
824 def list_running_servers(profile='default'):
811 def list_running_servers(profile='default'):
825 """Iterate over the server info files of running notebook servers.
812 """Iterate over the server info files of running notebook servers.
826
813
827 Given a profile name, find nbserver-* files in the security directory of
814 Given a profile name, find nbserver-* files in the security directory of
828 that profile, and yield dicts of their information, each one pertaining to
815 that profile, and yield dicts of their information, each one pertaining to
829 a currently running notebook server instance.
816 a currently running notebook server instance.
830 """
817 """
831 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
818 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
832 for file in os.listdir(pd.security_dir):
819 for file in os.listdir(pd.security_dir):
833 if file.startswith('nbserver-'):
820 if file.startswith('nbserver-'):
834 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
821 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
835 yield json.load(f)
822 yield json.load(f)
836
823
837 #-----------------------------------------------------------------------------
824 #-----------------------------------------------------------------------------
838 # Main entry point
825 # Main entry point
839 #-----------------------------------------------------------------------------
826 #-----------------------------------------------------------------------------
840
827
841 launch_new_instance = NotebookApp.launch_instance
828 launch_new_instance = NotebookApp.launch_instance
842
829
@@ -1,195 +1,195 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
19 import logging
20 from tornado import web
20 from tornado import web
21
21
22 from zmq.utils import jsonapi
22 from zmq.utils import jsonapi
23
23
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25 from IPython.html.utils import url_path_join, url_escape
25 from IPython.html.utils import url_path_join, url_escape
26
26
27 from ...base.handlers import IPythonHandler, json_errors
27 from ...base.handlers import IPythonHandler, json_errors
28 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
28 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Kernel handlers
31 # Kernel handlers
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class MainKernelHandler(IPythonHandler):
35 class MainKernelHandler(IPythonHandler):
36
36
37 @web.authenticated
37 @web.authenticated
38 @json_errors
38 @json_errors
39 def get(self):
39 def get(self):
40 km = self.kernel_manager
40 km = self.kernel_manager
41 self.finish(jsonapi.dumps(km.list_kernels(self.ws_url)))
41 self.finish(jsonapi.dumps(km.list_kernels(self.ws_url)))
42
42
43 @web.authenticated
43 @web.authenticated
44 @json_errors
44 @json_errors
45 def post(self):
45 def post(self):
46 km = self.kernel_manager
46 km = self.kernel_manager
47 kernel_id = km.start_kernel()
47 kernel_id = km.start_kernel()
48 model = km.kernel_model(kernel_id, self.ws_url)
48 model = km.kernel_model(kernel_id, self.ws_url)
49 location = url_path_join(self.base_kernel_url, 'api', 'kernels', kernel_id)
49 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
50 self.set_header('Location', url_escape(location))
50 self.set_header('Location', url_escape(location))
51 self.set_status(201)
51 self.set_status(201)
52 self.finish(jsonapi.dumps(model))
52 self.finish(jsonapi.dumps(model))
53
53
54
54
55 class KernelHandler(IPythonHandler):
55 class KernelHandler(IPythonHandler):
56
56
57 SUPPORTED_METHODS = ('DELETE', 'GET')
57 SUPPORTED_METHODS = ('DELETE', 'GET')
58
58
59 @web.authenticated
59 @web.authenticated
60 @json_errors
60 @json_errors
61 def get(self, kernel_id):
61 def get(self, kernel_id):
62 km = self.kernel_manager
62 km = self.kernel_manager
63 km._check_kernel_id(kernel_id)
63 km._check_kernel_id(kernel_id)
64 model = km.kernel_model(kernel_id, self.ws_url)
64 model = km.kernel_model(kernel_id, self.ws_url)
65 self.finish(jsonapi.dumps(model))
65 self.finish(jsonapi.dumps(model))
66
66
67 @web.authenticated
67 @web.authenticated
68 @json_errors
68 @json_errors
69 def delete(self, kernel_id):
69 def delete(self, kernel_id):
70 km = self.kernel_manager
70 km = self.kernel_manager
71 km.shutdown_kernel(kernel_id)
71 km.shutdown_kernel(kernel_id)
72 self.set_status(204)
72 self.set_status(204)
73 self.finish()
73 self.finish()
74
74
75
75
76 class KernelActionHandler(IPythonHandler):
76 class KernelActionHandler(IPythonHandler):
77
77
78 @web.authenticated
78 @web.authenticated
79 @json_errors
79 @json_errors
80 def post(self, kernel_id, action):
80 def post(self, kernel_id, action):
81 km = self.kernel_manager
81 km = self.kernel_manager
82 if action == 'interrupt':
82 if action == 'interrupt':
83 km.interrupt_kernel(kernel_id)
83 km.interrupt_kernel(kernel_id)
84 self.set_status(204)
84 self.set_status(204)
85 if action == 'restart':
85 if action == 'restart':
86 km.restart_kernel(kernel_id)
86 km.restart_kernel(kernel_id)
87 model = km.kernel_model(kernel_id, self.ws_url)
87 model = km.kernel_model(kernel_id, self.ws_url)
88 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_kernel_url, kernel_id))
88 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
89 self.write(jsonapi.dumps(model))
89 self.write(jsonapi.dumps(model))
90 self.finish()
90 self.finish()
91
91
92
92
93 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
93 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
94
94
95 def create_stream(self):
95 def create_stream(self):
96 km = self.kernel_manager
96 km = self.kernel_manager
97 meth = getattr(km, 'connect_%s' % self.channel)
97 meth = getattr(km, 'connect_%s' % self.channel)
98 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
98 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
99
99
100 def initialize(self, *args, **kwargs):
100 def initialize(self, *args, **kwargs):
101 self.zmq_stream = None
101 self.zmq_stream = None
102
102
103 def on_first_message(self, msg):
103 def on_first_message(self, msg):
104 try:
104 try:
105 super(ZMQChannelHandler, self).on_first_message(msg)
105 super(ZMQChannelHandler, self).on_first_message(msg)
106 except web.HTTPError:
106 except web.HTTPError:
107 self.close()
107 self.close()
108 return
108 return
109 try:
109 try:
110 self.create_stream()
110 self.create_stream()
111 except web.HTTPError:
111 except web.HTTPError:
112 # WebSockets don't response to traditional error codes so we
112 # WebSockets don't response to traditional error codes so we
113 # close the connection.
113 # close the connection.
114 if not self.stream.closed():
114 if not self.stream.closed():
115 self.stream.close()
115 self.stream.close()
116 self.close()
116 self.close()
117 else:
117 else:
118 self.zmq_stream.on_recv(self._on_zmq_reply)
118 self.zmq_stream.on_recv(self._on_zmq_reply)
119
119
120 def on_message(self, msg):
120 def on_message(self, msg):
121 msg = jsonapi.loads(msg)
121 msg = jsonapi.loads(msg)
122 self.session.send(self.zmq_stream, msg)
122 self.session.send(self.zmq_stream, msg)
123
123
124 def on_close(self):
124 def on_close(self):
125 # This method can be called twice, once by self.kernel_died and once
125 # This method can be called twice, once by self.kernel_died and once
126 # from the WebSocket close event. If the WebSocket connection is
126 # from the WebSocket close event. If the WebSocket connection is
127 # closed before the ZMQ streams are setup, they could be None.
127 # closed before the ZMQ streams are setup, they could be None.
128 if self.zmq_stream is not None and not self.zmq_stream.closed():
128 if self.zmq_stream is not None and not self.zmq_stream.closed():
129 self.zmq_stream.on_recv(None)
129 self.zmq_stream.on_recv(None)
130 self.zmq_stream.close()
130 self.zmq_stream.close()
131
131
132
132
133 class IOPubHandler(ZMQChannelHandler):
133 class IOPubHandler(ZMQChannelHandler):
134 channel = 'iopub'
134 channel = 'iopub'
135
135
136 def create_stream(self):
136 def create_stream(self):
137 super(IOPubHandler, self).create_stream()
137 super(IOPubHandler, self).create_stream()
138 km = self.kernel_manager
138 km = self.kernel_manager
139 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
139 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
140 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
140 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
141
141
142 def on_close(self):
142 def on_close(self):
143 km = self.kernel_manager
143 km = self.kernel_manager
144 if self.kernel_id in km:
144 if self.kernel_id in km:
145 km.remove_restart_callback(
145 km.remove_restart_callback(
146 self.kernel_id, self.on_kernel_restarted,
146 self.kernel_id, self.on_kernel_restarted,
147 )
147 )
148 km.remove_restart_callback(
148 km.remove_restart_callback(
149 self.kernel_id, self.on_restart_failed, 'dead',
149 self.kernel_id, self.on_restart_failed, 'dead',
150 )
150 )
151 super(IOPubHandler, self).on_close()
151 super(IOPubHandler, self).on_close()
152
152
153 def _send_status_message(self, status):
153 def _send_status_message(self, status):
154 msg = self.session.msg("status",
154 msg = self.session.msg("status",
155 {'execution_state': status}
155 {'execution_state': status}
156 )
156 )
157 self.write_message(jsonapi.dumps(msg, default=date_default))
157 self.write_message(jsonapi.dumps(msg, default=date_default))
158
158
159 def on_kernel_restarted(self):
159 def on_kernel_restarted(self):
160 logging.warn("kernel %s restarted", self.kernel_id)
160 logging.warn("kernel %s restarted", self.kernel_id)
161 self._send_status_message('restarting')
161 self._send_status_message('restarting')
162
162
163 def on_restart_failed(self):
163 def on_restart_failed(self):
164 logging.error("kernel %s restarted failed!", self.kernel_id)
164 logging.error("kernel %s restarted failed!", self.kernel_id)
165 self._send_status_message('dead')
165 self._send_status_message('dead')
166
166
167 def on_message(self, msg):
167 def on_message(self, msg):
168 """IOPub messages make no sense"""
168 """IOPub messages make no sense"""
169 pass
169 pass
170
170
171
171
172 class ShellHandler(ZMQChannelHandler):
172 class ShellHandler(ZMQChannelHandler):
173 channel = 'shell'
173 channel = 'shell'
174
174
175
175
176 class StdinHandler(ZMQChannelHandler):
176 class StdinHandler(ZMQChannelHandler):
177 channel = 'stdin'
177 channel = 'stdin'
178
178
179
179
180 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
181 # URL to handler mappings
181 # URL to handler mappings
182 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
183
183
184
184
185 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
185 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
186 _kernel_action_regex = r"(?P<action>restart|interrupt)"
186 _kernel_action_regex = r"(?P<action>restart|interrupt)"
187
187
188 default_handlers = [
188 default_handlers = [
189 (r"/api/kernels", MainKernelHandler),
189 (r"/api/kernels", MainKernelHandler),
190 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
190 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
191 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
191 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
192 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
192 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
193 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
193 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
194 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
194 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
195 ]
195 ]
@@ -1,127 +1,127 b''
1 """Tornado handlers for the sessions web service.
1 """Tornado handlers for the sessions web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Zach Sailer
5 * Zach Sailer
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import json
19 import json
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from ...base.handlers import IPythonHandler, json_errors
23 from ...base.handlers import IPythonHandler, json_errors
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25 from IPython.html.utils import url_path_join, url_escape
25 from IPython.html.utils import url_path_join, url_escape
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Session web service handlers
28 # Session web service handlers
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class SessionRootHandler(IPythonHandler):
32 class SessionRootHandler(IPythonHandler):
33
33
34 @web.authenticated
34 @web.authenticated
35 @json_errors
35 @json_errors
36 def get(self):
36 def get(self):
37 # Return a list of running sessions
37 # Return a list of running sessions
38 sm = self.session_manager
38 sm = self.session_manager
39 sessions = sm.list_sessions()
39 sessions = sm.list_sessions()
40 self.finish(json.dumps(sessions, default=date_default))
40 self.finish(json.dumps(sessions, default=date_default))
41
41
42 @web.authenticated
42 @web.authenticated
43 @json_errors
43 @json_errors
44 def post(self):
44 def post(self):
45 # Creates a new session
45 # Creates a new session
46 #(unless a session already exists for the named nb)
46 #(unless a session already exists for the named nb)
47 sm = self.session_manager
47 sm = self.session_manager
48 nbm = self.notebook_manager
48 nbm = self.notebook_manager
49 km = self.kernel_manager
49 km = self.kernel_manager
50 model = self.get_json_body()
50 model = self.get_json_body()
51 if model is None:
51 if model is None:
52 raise web.HTTPError(400, "No JSON data provided")
52 raise web.HTTPError(400, "No JSON data provided")
53 try:
53 try:
54 name = model['notebook']['name']
54 name = model['notebook']['name']
55 except KeyError:
55 except KeyError:
56 raise web.HTTPError(400, "Missing field in JSON data: name")
56 raise web.HTTPError(400, "Missing field in JSON data: name")
57 try:
57 try:
58 path = model['notebook']['path']
58 path = model['notebook']['path']
59 except KeyError:
59 except KeyError:
60 raise web.HTTPError(400, "Missing field in JSON data: path")
60 raise web.HTTPError(400, "Missing field in JSON data: path")
61 # Check to see if session exists
61 # Check to see if session exists
62 if sm.session_exists(name=name, path=path):
62 if sm.session_exists(name=name, path=path):
63 model = sm.get_session(name=name, path=path)
63 model = sm.get_session(name=name, path=path)
64 else:
64 else:
65 kernel_id = km.start_kernel(cwd=nbm.get_os_path(path))
65 kernel_id = km.start_kernel(cwd=nbm.get_os_path(path))
66 model = sm.create_session(name=name, path=path, kernel_id=kernel_id, ws_url=self.ws_url)
66 model = sm.create_session(name=name, path=path, kernel_id=kernel_id, ws_url=self.ws_url)
67 location = url_path_join(self.base_kernel_url, 'api', 'sessions', model['id'])
67 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
68 self.set_header('Location', url_escape(location))
68 self.set_header('Location', url_escape(location))
69 self.set_status(201)
69 self.set_status(201)
70 self.finish(json.dumps(model, default=date_default))
70 self.finish(json.dumps(model, default=date_default))
71
71
72 class SessionHandler(IPythonHandler):
72 class SessionHandler(IPythonHandler):
73
73
74 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
74 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
75
75
76 @web.authenticated
76 @web.authenticated
77 @json_errors
77 @json_errors
78 def get(self, session_id):
78 def get(self, session_id):
79 # Returns the JSON model for a single session
79 # Returns the JSON model for a single session
80 sm = self.session_manager
80 sm = self.session_manager
81 model = sm.get_session(session_id=session_id)
81 model = sm.get_session(session_id=session_id)
82 self.finish(json.dumps(model, default=date_default))
82 self.finish(json.dumps(model, default=date_default))
83
83
84 @web.authenticated
84 @web.authenticated
85 @json_errors
85 @json_errors
86 def patch(self, session_id):
86 def patch(self, session_id):
87 # Currently, this handler is strictly for renaming notebooks
87 # Currently, this handler is strictly for renaming notebooks
88 sm = self.session_manager
88 sm = self.session_manager
89 model = self.get_json_body()
89 model = self.get_json_body()
90 if model is None:
90 if model is None:
91 raise web.HTTPError(400, "No JSON data provided")
91 raise web.HTTPError(400, "No JSON data provided")
92 changes = {}
92 changes = {}
93 if 'notebook' in model:
93 if 'notebook' in model:
94 notebook = model['notebook']
94 notebook = model['notebook']
95 if 'name' in notebook:
95 if 'name' in notebook:
96 changes['name'] = notebook['name']
96 changes['name'] = notebook['name']
97 if 'path' in notebook:
97 if 'path' in notebook:
98 changes['path'] = notebook['path']
98 changes['path'] = notebook['path']
99
99
100 sm.update_session(session_id, **changes)
100 sm.update_session(session_id, **changes)
101 model = sm.get_session(session_id=session_id)
101 model = sm.get_session(session_id=session_id)
102 self.finish(json.dumps(model, default=date_default))
102 self.finish(json.dumps(model, default=date_default))
103
103
104 @web.authenticated
104 @web.authenticated
105 @json_errors
105 @json_errors
106 def delete(self, session_id):
106 def delete(self, session_id):
107 # Deletes the session with given session_id
107 # Deletes the session with given session_id
108 sm = self.session_manager
108 sm = self.session_manager
109 km = self.kernel_manager
109 km = self.kernel_manager
110 session = sm.get_session(session_id=session_id)
110 session = sm.get_session(session_id=session_id)
111 sm.delete_session(session_id)
111 sm.delete_session(session_id)
112 km.shutdown_kernel(session['kernel']['id'])
112 km.shutdown_kernel(session['kernel']['id'])
113 self.set_status(204)
113 self.set_status(204)
114 self.finish()
114 self.finish()
115
115
116
116
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118 # URL to handler mappings
118 # URL to handler mappings
119 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
120
120
121 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
121 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
122
122
123 default_handlers = [
123 default_handlers = [
124 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
124 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
125 (r"/api/sessions", SessionRootHandler)
125 (r"/api/sessions", SessionRootHandler)
126 ]
126 ]
127
127
@@ -1,122 +1,121 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2011 The IPython Development Team
2 // Copyright (C) 2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // On document ready
9 // On document ready
10 //============================================================================
10 //============================================================================
11
11
12 // for the time beeing, we have to pass marked as a parameter here,
12 // for the time beeing, we have to pass marked as a parameter here,
13 // as injecting require.js make marked not to put itself in the globals,
13 // as injecting require.js make marked not to put itself in the globals,
14 // which make both this file fail at setting marked configuration, and textcell.js
14 // which make both this file fail at setting marked configuration, and textcell.js
15 // which search marked into global.
15 // which search marked into global.
16 require(['components/marked/lib/marked',
16 require(['components/marked/lib/marked',
17 'notebook/js/widgets/init'],
17 'notebook/js/widgets/init'],
18
18
19 function (marked) {
19 function (marked) {
20 "use strict";
20 "use strict";
21
21
22 window.marked = marked;
22 window.marked = marked;
23
23
24 // monkey patch CM to be able to syntax highlight cell magics
24 // monkey patch CM to be able to syntax highlight cell magics
25 // bug reported upstream,
25 // bug reported upstream,
26 // see https://github.com/marijnh/CodeMirror2/issues/670
26 // see https://github.com/marijnh/CodeMirror2/issues/670
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
27 if(CodeMirror.getMode(1,'text/plain').indent === undefined ){
28 console.log('patching CM for undefined indent');
28 console.log('patching CM for undefined indent');
29 CodeMirror.modes.null = function() {
29 CodeMirror.modes.null = function() {
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
30 return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}};
31 };
31 };
32 }
32 }
33
33
34 CodeMirror.patchedGetMode = function(config, mode){
34 CodeMirror.patchedGetMode = function(config, mode){
35 var cmmode = CodeMirror.getMode(config, mode);
35 var cmmode = CodeMirror.getMode(config, mode);
36 if(cmmode.indent === null) {
36 if(cmmode.indent === null) {
37 console.log('patch mode "' , mode, '" on the fly');
37 console.log('patch mode "' , mode, '" on the fly');
38 cmmode.indent = function(){return 0;};
38 cmmode.indent = function(){return 0;};
39 }
39 }
40 return cmmode;
40 return cmmode;
41 };
41 };
42 // end monkey patching CodeMirror
42 // end monkey patching CodeMirror
43
43
44 IPython.mathjaxutils.init();
44 IPython.mathjaxutils.init();
45
45
46 $('#ipython-main-app').addClass('border-box-sizing');
46 $('#ipython-main-app').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
47 $('div#notebook_panel').addClass('border-box-sizing');
48
48
49 var opts = {
49 var opts = {
50 base_url : IPython.utils.get_body_data("baseUrl"),
50 base_url : IPython.utils.get_body_data("baseUrl"),
51 base_kernel_url : IPython.utils.get_body_data("baseKernelUrl"),
52 notebook_path : IPython.utils.get_body_data("notebookPath"),
51 notebook_path : IPython.utils.get_body_data("notebookPath"),
53 notebook_name : IPython.utils.get_body_data('notebookName')
52 notebook_name : IPython.utils.get_body_data('notebookName')
54 };
53 };
55
54
56 IPython.page = new IPython.Page();
55 IPython.page = new IPython.Page();
57 IPython.layout_manager = new IPython.LayoutManager();
56 IPython.layout_manager = new IPython.LayoutManager();
58 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
57 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
59 IPython.quick_help = new IPython.QuickHelp();
58 IPython.quick_help = new IPython.QuickHelp();
60 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
59 IPython.login_widget = new IPython.LoginWidget('span#login_widget', opts);
61 IPython.notebook = new IPython.Notebook('div#notebook', opts);
60 IPython.notebook = new IPython.Notebook('div#notebook', opts);
62 IPython.keyboard_manager = new IPython.KeyboardManager();
61 IPython.keyboard_manager = new IPython.KeyboardManager();
63 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
62 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
64 IPython.menubar = new IPython.MenuBar('#menubar', opts);
63 IPython.menubar = new IPython.MenuBar('#menubar', opts);
65 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
64 IPython.toolbar = new IPython.MainToolBar('#maintoolbar-container');
66 IPython.tooltip = new IPython.Tooltip();
65 IPython.tooltip = new IPython.Tooltip();
67 IPython.notification_area = new IPython.NotificationArea('#notification_area');
66 IPython.notification_area = new IPython.NotificationArea('#notification_area');
68 IPython.notification_area.init_notification_widgets();
67 IPython.notification_area.init_notification_widgets();
69
68
70 IPython.layout_manager.do_resize();
69 IPython.layout_manager.do_resize();
71
70
72 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
71 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
73 '<span id="test2" style="font-weight: bold;">x</span>'+
72 '<span id="test2" style="font-weight: bold;">x</span>'+
74 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
73 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
75 var nh = $('#test1').innerHeight();
74 var nh = $('#test1').innerHeight();
76 var bh = $('#test2').innerHeight();
75 var bh = $('#test2').innerHeight();
77 var ih = $('#test3').innerHeight();
76 var ih = $('#test3').innerHeight();
78 if(nh != bh || nh != ih) {
77 if(nh != bh || nh != ih) {
79 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
78 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
80 }
79 }
81 $('#fonttest').remove();
80 $('#fonttest').remove();
82
81
83 IPython.page.show();
82 IPython.page.show();
84
83
85 IPython.layout_manager.do_resize();
84 IPython.layout_manager.do_resize();
86 var first_load = function () {
85 var first_load = function () {
87 IPython.layout_manager.do_resize();
86 IPython.layout_manager.do_resize();
88 var hash = document.location.hash;
87 var hash = document.location.hash;
89 if (hash) {
88 if (hash) {
90 document.location.hash = '';
89 document.location.hash = '';
91 document.location.hash = hash;
90 document.location.hash = hash;
92 }
91 }
93 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
92 IPython.notebook.set_autosave_interval(IPython.notebook.minimum_autosave_interval);
94 // only do this once
93 // only do this once
95 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
94 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
96 };
95 };
97
96
98 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
97 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
99 $([IPython.events]).trigger('app_initialized.NotebookApp');
98 $([IPython.events]).trigger('app_initialized.NotebookApp');
100 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
99 IPython.notebook.load_notebook(opts.notebook_name, opts.notebook_path);
101
100
102 if (marked) {
101 if (marked) {
103 marked.setOptions({
102 marked.setOptions({
104 gfm : true,
103 gfm : true,
105 tables: true,
104 tables: true,
106 langPrefix: "language-",
105 langPrefix: "language-",
107 highlight: function(code, lang) {
106 highlight: function(code, lang) {
108 if (!lang) {
107 if (!lang) {
109 // no language, no highlight
108 // no language, no highlight
110 return code;
109 return code;
111 }
110 }
112 var highlighted;
111 var highlighted;
113 try {
112 try {
114 highlighted = hljs.highlight(lang, code, false);
113 highlighted = hljs.highlight(lang, code, false);
115 } catch(err) {
114 } catch(err) {
116 highlighted = hljs.highlightAuto(code);
115 highlighted = hljs.highlightAuto(code);
117 }
116 }
118 return highlighted.value;
117 return highlighted.value;
119 }
118 }
120 });
119 });
121 }
120 }
122 });
121 });
@@ -1,119 +1,118 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Notebook
9 // Notebook
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13 "use strict";
13 "use strict";
14
14
15 var utils = IPython.utils;
15 var utils = IPython.utils;
16
16
17 var Session = function(notebook, options){
17 var Session = function(notebook, options){
18 this.kernel = null;
18 this.kernel = null;
19 this.id = null;
19 this.id = null;
20 this.notebook = notebook;
20 this.notebook = notebook;
21 this.name = notebook.notebook_name;
21 this.name = notebook.notebook_name;
22 this.path = notebook.notebook_path;
22 this.path = notebook.notebook_path;
23 this.base_url = notebook.base_url;
23 this.base_url = notebook.base_url;
24 this.base_kernel_url = options.base_kernel_url || utils.get_body_data("baseKernelUrl");
25 };
24 };
26
25
27 Session.prototype.start = function(callback) {
26 Session.prototype.start = function(callback) {
28 var that = this;
27 var that = this;
29 var model = {
28 var model = {
30 notebook : {
29 notebook : {
31 name : this.name,
30 name : this.name,
32 path : this.path
31 path : this.path
33 }
32 }
34 };
33 };
35 var settings = {
34 var settings = {
36 processData : false,
35 processData : false,
37 cache : false,
36 cache : false,
38 type : "POST",
37 type : "POST",
39 data: JSON.stringify(model),
38 data: JSON.stringify(model),
40 dataType : "json",
39 dataType : "json",
41 success : function (data, status, xhr) {
40 success : function (data, status, xhr) {
42 that._handle_start_success(data);
41 that._handle_start_success(data);
43 if (callback) {
42 if (callback) {
44 callback(data, status, xhr);
43 callback(data, status, xhr);
45 }
44 }
46 },
45 },
47 };
46 };
48 var url = utils.url_join_encode(this.base_url, 'api/sessions');
47 var url = utils.url_join_encode(this.base_url, 'api/sessions');
49 $.ajax(url, settings);
48 $.ajax(url, settings);
50 };
49 };
51
50
52 Session.prototype.rename_notebook = function (name, path) {
51 Session.prototype.rename_notebook = function (name, path) {
53 this.name = name;
52 this.name = name;
54 this.path = path;
53 this.path = path;
55 var model = {
54 var model = {
56 notebook : {
55 notebook : {
57 name : this.name,
56 name : this.name,
58 path : this.path
57 path : this.path
59 }
58 }
60 };
59 };
61 var settings = {
60 var settings = {
62 processData : false,
61 processData : false,
63 cache : false,
62 cache : false,
64 type : "PATCH",
63 type : "PATCH",
65 data: JSON.stringify(model),
64 data: JSON.stringify(model),
66 dataType : "json",
65 dataType : "json",
67 };
66 };
68 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
67 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
69 $.ajax(url, settings);
68 $.ajax(url, settings);
70 };
69 };
71
70
72 Session.prototype.delete = function() {
71 Session.prototype.delete = function() {
73 var settings = {
72 var settings = {
74 processData : false,
73 processData : false,
75 cache : false,
74 cache : false,
76 type : "DELETE",
75 type : "DELETE",
77 dataType : "json",
76 dataType : "json",
78 };
77 };
79 this.kernel.running = false;
78 this.kernel.running = false;
80 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
79 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
81 $.ajax(url, settings);
80 $.ajax(url, settings);
82 };
81 };
83
82
84 // Kernel related things
83 // Kernel related things
85 /**
84 /**
86 * Create the Kernel object associated with this Session.
85 * Create the Kernel object associated with this Session.
87 *
86 *
88 * @method _handle_start_success
87 * @method _handle_start_success
89 */
88 */
90 Session.prototype._handle_start_success = function (data, status, xhr) {
89 Session.prototype._handle_start_success = function (data, status, xhr) {
91 this.id = data.id;
90 this.id = data.id;
92 var kernel_service_url = utils.url_path_join(this.base_kernel_url, "api/kernels");
91 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
93 this.kernel = new IPython.Kernel(kernel_service_url);
92 this.kernel = new IPython.Kernel(kernel_service_url);
94 this.kernel._kernel_started(data.kernel);
93 this.kernel._kernel_started(data.kernel);
95 };
94 };
96
95
97 /**
96 /**
98 * Prompt the user to restart the IPython kernel.
97 * Prompt the user to restart the IPython kernel.
99 *
98 *
100 * @method restart_kernel
99 * @method restart_kernel
101 */
100 */
102 Session.prototype.restart_kernel = function () {
101 Session.prototype.restart_kernel = function () {
103 this.kernel.restart();
102 this.kernel.restart();
104 };
103 };
105
104
106 Session.prototype.interrupt_kernel = function() {
105 Session.prototype.interrupt_kernel = function() {
107 this.kernel.interrupt();
106 this.kernel.interrupt();
108 };
107 };
109
108
110
109
111 Session.prototype.kill_kernel = function() {
110 Session.prototype.kill_kernel = function() {
112 this.kernel.kill();
111 this.kernel.kill();
113 };
112 };
114
113
115 IPython.Session = Session;
114 IPython.Session = Session;
116
115
117 return IPython;
116 return IPython;
118
117
119 }(IPython));
118 }(IPython));
@@ -1,352 +1,351 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/codemirror/lib/codemirror.css") }}">
14 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15
15
16 {{super()}}
16 {{super()}}
17
17
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
18 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19
19
20 {% endblock %}
20 {% endblock %}
21
21
22 {% block params %}
22 {% block params %}
23
23
24 data-project="{{project}}"
24 data-project="{{project}}"
25 data-base-url="{{base_url}}"
25 data-base-url="{{base_url}}"
26 data-base-kernel-url="{{base_kernel_url}}"
27 data-notebook-name="{{notebook_name}}"
26 data-notebook-name="{{notebook_name}}"
28 data-notebook-path="{{notebook_path}}"
27 data-notebook-path="{{notebook_path}}"
29 class="notebook_app"
28 class="notebook_app"
30
29
31 {% endblock %}
30 {% endblock %}
32
31
33
32
34 {% block header %}
33 {% block header %}
35
34
36 <span id="save_widget" class="nav pull-left">
35 <span id="save_widget" class="nav pull-left">
37 <span id="notebook_name"></span>
36 <span id="notebook_name"></span>
38 <span id="checkpoint_status"></span>
37 <span id="checkpoint_status"></span>
39 <span id="autosave_status"></span>
38 <span id="autosave_status"></span>
40 </span>
39 </span>
41
40
42 {% endblock %}
41 {% endblock %}
43
42
44
43
45 {% block site %}
44 {% block site %}
46
45
47 <div id="menubar-container" class="container">
46 <div id="menubar-container" class="container">
48 <div id="menubar">
47 <div id="menubar">
49 <div class="navbar">
48 <div class="navbar">
50 <div class="navbar-inner">
49 <div class="navbar-inner">
51 <div class="container">
50 <div class="container">
52 <ul id="menus" class="nav">
51 <ul id="menus" class="nav">
53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
52 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
54 <ul id="file_menu" class="dropdown-menu">
53 <ul id="file_menu" class="dropdown-menu">
55 <li id="new_notebook"
54 <li id="new_notebook"
56 title="Make a new notebook (Opens a new window)">
55 title="Make a new notebook (Opens a new window)">
57 <a href="#">New</a></li>
56 <a href="#">New</a></li>
58 <li id="open_notebook"
57 <li id="open_notebook"
59 title="Opens a new window with the Dashboard view">
58 title="Opens a new window with the Dashboard view">
60 <a href="#">Open...</a></li>
59 <a href="#">Open...</a></li>
61 <!-- <hr/> -->
60 <!-- <hr/> -->
62 <li class="divider"></li>
61 <li class="divider"></li>
63 <li id="copy_notebook"
62 <li id="copy_notebook"
64 title="Open a copy of this notebook's contents and start a new kernel">
63 title="Open a copy of this notebook's contents and start a new kernel">
65 <a href="#">Make a Copy...</a></li>
64 <a href="#">Make a Copy...</a></li>
66 <li id="rename_notebook"><a href="#">Rename...</a></li>
65 <li id="rename_notebook"><a href="#">Rename...</a></li>
67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
66 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
68 <!-- <hr/> -->
67 <!-- <hr/> -->
69 <li class="divider"></li>
68 <li class="divider"></li>
70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
69 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
71 <ul class="dropdown-menu">
70 <ul class="dropdown-menu">
72 <li><a href="#"></a></li>
71 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
72 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
73 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
74 <li><a href="#"></a></li>
76 <li><a href="#"></a></li>
75 <li><a href="#"></a></li>
77 </ul>
76 </ul>
78 </li>
77 </li>
79 <li class="divider"></li>
78 <li class="divider"></li>
80 <li id="print_preview"><a href="#">Print Preview</a></li>
79 <li id="print_preview"><a href="#">Print Preview</a></li>
81 <li class="dropdown-submenu"><a href="#">Download as</a>
80 <li class="dropdown-submenu"><a href="#">Download as</a>
82 <ul class="dropdown-menu">
81 <ul class="dropdown-menu">
83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
82 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
84 <li id="download_py"><a href="#">Python (.py)</a></li>
83 <li id="download_py"><a href="#">Python (.py)</a></li>
85 <li id="download_html"><a href="#">HTML (.html)</a></li>
84 <li id="download_html"><a href="#">HTML (.html)</a></li>
86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
85 <li id="download_rst"><a href="#">reST (.rst)</a></li>
87 </ul>
86 </ul>
88 </li>
87 </li>
89 <li class="divider"></li>
88 <li class="divider"></li>
90
89
91 <li id="kill_and_exit"
90 <li id="kill_and_exit"
92 title="Shutdown this notebook's kernel, and close this window">
91 title="Shutdown this notebook's kernel, and close this window">
93 <a href="#" >Close and halt</a></li>
92 <a href="#" >Close and halt</a></li>
94 </ul>
93 </ul>
95 </li>
94 </li>
96 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
95 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
97 <ul id="edit_menu" class="dropdown-menu">
96 <ul id="edit_menu" class="dropdown-menu">
98 <li id="cut_cell"><a href="#">Cut Cell</a></li>
97 <li id="cut_cell"><a href="#">Cut Cell</a></li>
99 <li id="copy_cell"><a href="#">Copy Cell</a></li>
98 <li id="copy_cell"><a href="#">Copy Cell</a></li>
100 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
99 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
101 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
100 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
102 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
101 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
103 <li id="delete_cell"><a href="#">Delete Cell</a></li>
102 <li id="delete_cell"><a href="#">Delete Cell</a></li>
104 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
103 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
105 <li class="divider"></li>
104 <li class="divider"></li>
106 <li id="split_cell"><a href="#">Split Cell</a></li>
105 <li id="split_cell"><a href="#">Split Cell</a></li>
107 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
106 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
108 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
107 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
109 <li class="divider"></li>
108 <li class="divider"></li>
110 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
109 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
111 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
110 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
112 <li class="divider"></li>
111 <li class="divider"></li>
113 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
112 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
114 </ul>
113 </ul>
115 </li>
114 </li>
116 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
115 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
117 <ul id="view_menu" class="dropdown-menu">
116 <ul id="view_menu" class="dropdown-menu">
118 <li id="toggle_header"
117 <li id="toggle_header"
119 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
118 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
120 <a href="#">Toggle Header</a></li>
119 <a href="#">Toggle Header</a></li>
121 <li id="toggle_toolbar"
120 <li id="toggle_toolbar"
122 title="Show/Hide the action icons (below menu bar)">
121 title="Show/Hide the action icons (below menu bar)">
123 <a href="#">Toggle Toolbar</a></li>
122 <a href="#">Toggle Toolbar</a></li>
124 </ul>
123 </ul>
125 </li>
124 </li>
126 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
125 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
127 <ul id="insert_menu" class="dropdown-menu">
126 <ul id="insert_menu" class="dropdown-menu">
128 <li id="insert_cell_above"
127 <li id="insert_cell_above"
129 title="Insert an empty Code cell above the currently active cell">
128 title="Insert an empty Code cell above the currently active cell">
130 <a href="#">Insert Cell Above</a></li>
129 <a href="#">Insert Cell Above</a></li>
131 <li id="insert_cell_below"
130 <li id="insert_cell_below"
132 title="Insert an empty Code cell below the currently active cell">
131 title="Insert an empty Code cell below the currently active cell">
133 <a href="#">Insert Cell Below</a></li>
132 <a href="#">Insert Cell Below</a></li>
134 </ul>
133 </ul>
135 </li>
134 </li>
136 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
135 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
137 <ul id="cell_menu" class="dropdown-menu">
136 <ul id="cell_menu" class="dropdown-menu">
138 <li id="run_cell" title="Run this cell, and move cursor to the next one">
137 <li id="run_cell" title="Run this cell, and move cursor to the next one">
139 <a href="#">Run</a></li>
138 <a href="#">Run</a></li>
140 <li id="run_cell_select_below" title="Run this cell, select below">
139 <li id="run_cell_select_below" title="Run this cell, select below">
141 <a href="#">Run and Select Below</a></li>
140 <a href="#">Run and Select Below</a></li>
142 <li id="run_cell_insert_below" title="Run this cell, insert below">
141 <li id="run_cell_insert_below" title="Run this cell, insert below">
143 <a href="#">Run and Insert Below</a></li>
142 <a href="#">Run and Insert Below</a></li>
144 <li id="run_all_cells" title="Run all cells in the notebook">
143 <li id="run_all_cells" title="Run all cells in the notebook">
145 <a href="#">Run All</a></li>
144 <a href="#">Run All</a></li>
146 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
145 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
147 <a href="#">Run All Above</a></li>
146 <a href="#">Run All Above</a></li>
148 <li id="run_all_cells_below" title="Run this cell and all cells below it">
147 <li id="run_all_cells_below" title="Run this cell and all cells below it">
149 <a href="#">Run All Below</a></li>
148 <a href="#">Run All Below</a></li>
150 <li class="divider"></li>
149 <li class="divider"></li>
151 <li id="change_cell_type" class="dropdown-submenu"
150 <li id="change_cell_type" class="dropdown-submenu"
152 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
151 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
153 <a href="#">Cell Type</a>
152 <a href="#">Cell Type</a>
154 <ul class="dropdown-menu">
153 <ul class="dropdown-menu">
155 <li id="to_code"
154 <li id="to_code"
156 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
155 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
157 <a href="#">Code</a></li>
156 <a href="#">Code</a></li>
158 <li id="to_markdown"
157 <li id="to_markdown"
159 title="Contents will be rendered as HTML and serve as explanatory text">
158 title="Contents will be rendered as HTML and serve as explanatory text">
160 <a href="#">Markdown</a></li>
159 <a href="#">Markdown</a></li>
161 <li id="to_raw"
160 <li id="to_raw"
162 title="Contents will pass through nbconvert unmodified">
161 title="Contents will pass through nbconvert unmodified">
163 <a href="#">Raw NBConvert</a></li>
162 <a href="#">Raw NBConvert</a></li>
164 <li id="to_heading1"><a href="#">Heading 1</a></li>
163 <li id="to_heading1"><a href="#">Heading 1</a></li>
165 <li id="to_heading2"><a href="#">Heading 2</a></li>
164 <li id="to_heading2"><a href="#">Heading 2</a></li>
166 <li id="to_heading3"><a href="#">Heading 3</a></li>
165 <li id="to_heading3"><a href="#">Heading 3</a></li>
167 <li id="to_heading4"><a href="#">Heading 4</a></li>
166 <li id="to_heading4"><a href="#">Heading 4</a></li>
168 <li id="to_heading5"><a href="#">Heading 5</a></li>
167 <li id="to_heading5"><a href="#">Heading 5</a></li>
169 <li id="to_heading6"><a href="#">Heading 6</a></li>
168 <li id="to_heading6"><a href="#">Heading 6</a></li>
170 </ul>
169 </ul>
171 </li>
170 </li>
172 <li class="divider"></li>
171 <li class="divider"></li>
173 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
172 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
174 <ul class="dropdown-menu">
173 <ul class="dropdown-menu">
175 <li id="toggle_current_output"
174 <li id="toggle_current_output"
176 title="Hide/Show the output of the current cell">
175 title="Hide/Show the output of the current cell">
177 <a href="#">Toggle</a>
176 <a href="#">Toggle</a>
178 </li>
177 </li>
179 <li id="toggle_current_output_scroll"
178 <li id="toggle_current_output_scroll"
180 title="Scroll the output of the current cell">
179 title="Scroll the output of the current cell">
181 <a href="#">Toggle Scrolling</a>
180 <a href="#">Toggle Scrolling</a>
182 </li>
181 </li>
183 <li id="clear_current_output"
182 <li id="clear_current_output"
184 title="Clear the output of the current cell">
183 title="Clear the output of the current cell">
185 <a href="#">Clear</a>
184 <a href="#">Clear</a>
186 </li>
185 </li>
187 </ul>
186 </ul>
188 </li>
187 </li>
189 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
188 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
190 <ul class="dropdown-menu">
189 <ul class="dropdown-menu">
191 <li id="toggle_all_output"
190 <li id="toggle_all_output"
192 title="Hide/Show the output of all cells">
191 title="Hide/Show the output of all cells">
193 <a href="#">Toggle</a>
192 <a href="#">Toggle</a>
194 </li>
193 </li>
195 <li id="toggle_all_output_scroll"
194 <li id="toggle_all_output_scroll"
196 title="Scroll the output of all cells">
195 title="Scroll the output of all cells">
197 <a href="#">Toggle Scrolling</a>
196 <a href="#">Toggle Scrolling</a>
198 </li>
197 </li>
199 <li id="clear_all_output"
198 <li id="clear_all_output"
200 title="Clear the output of all cells">
199 title="Clear the output of all cells">
201 <a href="#">Clear</a>
200 <a href="#">Clear</a>
202 </li>
201 </li>
203 </ul>
202 </ul>
204 </li>
203 </li>
205 </ul>
204 </ul>
206 </li>
205 </li>
207 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
206 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
208 <ul id="kernel_menu" class="dropdown-menu">
207 <ul id="kernel_menu" class="dropdown-menu">
209 <li id="int_kernel"
208 <li id="int_kernel"
210 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
209 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
211 <a href="#">Interrupt</a></li>
210 <a href="#">Interrupt</a></li>
212 <li id="restart_kernel"
211 <li id="restart_kernel"
213 title="Restart the Kernel">
212 title="Restart the Kernel">
214 <a href="#">Restart</a></li>
213 <a href="#">Restart</a></li>
215 </ul>
214 </ul>
216 </li>
215 </li>
217 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
216 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
218 <ul id="help_menu" class="dropdown-menu">
217 <ul id="help_menu" class="dropdown-menu">
219 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
218 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
220 <li class="divider"></li>
219 <li class="divider"></li>
221 {% set
220 {% set
222 sections = (
221 sections = (
223 (
222 (
224 ("http://ipython.org/documentation.html","IPython Help",True),
223 ("http://ipython.org/documentation.html","IPython Help",True),
225 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
224 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/master/examples/notebooks/", "Notebook Examples", True),
226 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
225 ("http://ipython.org/ipython-doc/stable/interactive/notebook.html","Notebook Help",True),
227 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
226 ("http://ipython.org/ipython-doc/dev/interactive/cm_keyboard.html","Editor Shortcuts",True),
228 ),(
227 ),(
229 ("http://docs.python.org","Python",True),
228 ("http://docs.python.org","Python",True),
230 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
229 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
231 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
230 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
232 ("http://matplotlib.org/contents.html","Matplotlib",True),
231 ("http://matplotlib.org/contents.html","Matplotlib",True),
233 ("http://docs.sympy.org/dev/index.html","SymPy",True),
232 ("http://docs.sympy.org/dev/index.html","SymPy",True),
234 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
233 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
235 )
234 )
236 )
235 )
237 %}
236 %}
238
237
239 {% for helplinks in sections %}
238 {% for helplinks in sections %}
240 {% for link in helplinks %}
239 {% for link in helplinks %}
241 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
240 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
242 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
241 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
243 {{link[1]}}
242 {{link[1]}}
244 </a></li>
243 </a></li>
245 {% endfor %}
244 {% endfor %}
246 {% if not loop.last %}
245 {% if not loop.last %}
247 <li class="divider"></li>
246 <li class="divider"></li>
248 {% endif %}
247 {% endif %}
249 {% endfor %}
248 {% endfor %}
250 </li>
249 </li>
251 </ul>
250 </ul>
252 </li>
251 </li>
253 </ul>
252 </ul>
254 <div id="kernel_indicator" class="indicator_area pull-right">
253 <div id="kernel_indicator" class="indicator_area pull-right">
255 <i id="kernel_indicator_icon"></i>
254 <i id="kernel_indicator_icon"></i>
256 </div>
255 </div>
257 <div id="modal_indicator" class="indicator_area pull-right">
256 <div id="modal_indicator" class="indicator_area pull-right">
258 <i id="modal_indicator_icon"></i>
257 <i id="modal_indicator_icon"></i>
259 </div>
258 </div>
260 <div id="notification_area"></div>
259 <div id="notification_area"></div>
261 </div>
260 </div>
262 </div>
261 </div>
263 </div>
262 </div>
264 </div>
263 </div>
265 <div id="maintoolbar" class="navbar">
264 <div id="maintoolbar" class="navbar">
266 <div class="toolbar-inner navbar-inner navbar-nobg">
265 <div class="toolbar-inner navbar-inner navbar-nobg">
267 <div id="maintoolbar-container" class="container"></div>
266 <div id="maintoolbar-container" class="container"></div>
268 </div>
267 </div>
269 </div>
268 </div>
270 </div>
269 </div>
271
270
272 <div id="ipython-main-app">
271 <div id="ipython-main-app">
273
272
274 <div id="notebook_panel">
273 <div id="notebook_panel">
275 <div id="notebook"></div>
274 <div id="notebook"></div>
276 <div id="pager_splitter"></div>
275 <div id="pager_splitter"></div>
277 <div id="pager">
276 <div id="pager">
278 <div id='pager_button_area'>
277 <div id='pager_button_area'>
279 </div>
278 </div>
280 <div id="pager-container" class="container"></div>
279 <div id="pager-container" class="container"></div>
281 </div>
280 </div>
282 </div>
281 </div>
283
282
284 </div>
283 </div>
285 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
284 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
286
285
287
286
288 {% endblock %}
287 {% endblock %}
289
288
290
289
291 {% block script %}
290 {% block script %}
292
291
293 {{super()}}
292 {{super()}}
294
293
295 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
294 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
296 <script type="text/javascript">
295 <script type="text/javascript">
297 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
296 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js") }}";
298 </script>
297 </script>
299 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
298 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
299 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
300 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
301 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
302 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
303 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
304 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
305 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
306 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
307 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
308 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
309 <script src="{{ static_url("components/codemirror/mode/gfm/gfm.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
310 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
312 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
311 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
313
312
314 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
313 <script src="{{ static_url("components/highlight.js/build/highlight.pack.js") }}" charset="utf-8"></script>
315
314
316 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
315 <script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
317
316
318 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
317 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
318 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
319 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
320 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
321 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
322 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
323 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
324 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
325 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
326 <script src="{{ static_url("notebook/js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
327 <script src="{{ static_url("notebook/js/celltoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
328 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
329 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
330 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
332 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
333 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
334 <script src="{{ static_url("notebook/js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
335 <script src="{{ static_url("notebook/js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
336 <script src="{{ static_url("notebook/js/maintoolbar.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
337 <script src="{{ static_url("notebook/js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
338 <script src="{{ static_url("notebook/js/keyboardmanager.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
339 <script src="{{ static_url("notebook/js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
340 <script src="{{ static_url("notebook/js/notificationarea.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
341 <script src="{{ static_url("notebook/js/tooltip.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
342 <script src="{{ static_url("notebook/js/config.js") }}" type="text/javascript" charset="utf-8"></script>
344 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
343 <script src="{{ static_url("notebook/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
345
344
346 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
345 <script src="{{ static_url("notebook/js/contexthint.js") }}" charset="utf-8"></script>
347
346
348 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
347 <script src="{{ static_url("notebook/js/celltoolbarpresets/default.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
348 <script src="{{ static_url("notebook/js/celltoolbarpresets/rawcell.js") }}" type="text/javascript" charset="utf-8"></script>
350 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
349 <script src="{{ static_url("notebook/js/celltoolbarpresets/slideshow.js") }}" type="text/javascript" charset="utf-8"></script>
351
350
352 {% endblock %}
351 {% endblock %}
@@ -1,99 +1,98 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 stylesheet %}
6 {% block stylesheet %}
7 {{super()}}
7 {{super()}}
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
8 <link rel="stylesheet" href="{{ static_url("tree/css/override.css") }}" type="text/css" />
9 {% endblock %}
9 {% endblock %}
10
10
11 {% block params %}
11 {% block params %}
12
12
13 data-project="{{project}}"
13 data-project="{{project}}"
14 data-base-url="{{base_url}}"
14 data-base-url="{{base_url}}"
15 data-notebook-path="{{notebook_path}}"
15 data-notebook-path="{{notebook_path}}"
16 data-base-kernel-url="{{base_kernel_url}}"
17
16
18 {% endblock %}
17 {% endblock %}
19
18
20
19
21 {% block site %}
20 {% block site %}
22
21
23 <div id="ipython-main-app" class="container">
22 <div id="ipython-main-app" class="container">
24
23
25 <div id="tab_content" class="tabbable">
24 <div id="tab_content" class="tabbable">
26 <ul id="tabs" class="nav nav-tabs">
25 <ul id="tabs" class="nav nav-tabs">
27 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
26 <li class="active"><a href="#notebooks" data-toggle="tab">Notebooks</a></li>
28 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
27 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
29 </ul>
28 </ul>
30
29
31 <div class="tab-content">
30 <div class="tab-content">
32 <div id="notebooks" class="tab-pane active">
31 <div id="notebooks" class="tab-pane active">
33 <div id="notebook_toolbar" class="row-fluid">
32 <div id="notebook_toolbar" class="row-fluid">
34 <div class="span8">
33 <div class="span8">
35 <form id='alternate_upload' class='alternate_upload' >
34 <form id='alternate_upload' class='alternate_upload' >
36 <span id="drag_info" style="position:absolute" >
35 <span id="drag_info" style="position:absolute" >
37 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
36 To import a notebook, drag the file onto the listing below or <strong>click here</strong>.
38 </span>
37 </span>
39 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
38 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
40 </form>
39 </form>
41 </div>
40 </div>
42 <div class="span4 clearfix">
41 <div class="span4 clearfix">
43 <span id="notebook_buttons" class="pull-right">
42 <span id="notebook_buttons" class="pull-right">
44 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
43 <button id="new_notebook" title="Create new notebook" class="btn btn-small">New Notebook</button>
45 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small"><i class="icon-refresh"></i></button>
44 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-small"><i class="icon-refresh"></i></button>
46 </span>
45 </span>
47 </div>
46 </div>
48 </div>
47 </div>
49
48
50 <div id="notebook_list">
49 <div id="notebook_list">
51 <div id="notebook_list_header" class="row-fluid list_header">
50 <div id="notebook_list_header" class="row-fluid list_header">
52 <div id="project_name">
51 <div id="project_name">
53 <ul class="breadcrumb">
52 <ul class="breadcrumb">
54 <li><a href="{{breadcrumbs[0][0]}}"><i class="icon-home"></i></a><span>/</span></li>
53 <li><a href="{{breadcrumbs[0][0]}}"><i class="icon-home"></i></a><span>/</span></li>
55 {% for crumb in breadcrumbs[1:] %}
54 {% for crumb in breadcrumbs[1:] %}
56 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a> <span>/</span></li>
55 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a> <span>/</span></li>
57 {% endfor %}
56 {% endfor %}
58 </ul>
57 </ul>
59 </div>
58 </div>
60 </div>
59 </div>
61 </div>
60 </div>
62 </div>
61 </div>
63
62
64 <div id="clusters" class="tab-pane">
63 <div id="clusters" class="tab-pane">
65
64
66 <div id="cluster_toolbar" class="row-fluid">
65 <div id="cluster_toolbar" class="row-fluid">
67 <div class="span8">
66 <div class="span8">
68 <span id="cluster_list_info">IPython parallel computing clusters</span>
67 <span id="cluster_list_info">IPython parallel computing clusters</span>
69 </div>
68 </div>
70 <div class="span4" class="clearfix">
69 <div class="span4" class="clearfix">
71 <span id="cluster_buttons" class="pull-right">
70 <span id="cluster_buttons" class="pull-right">
72 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small"><i class="icon-refresh"></i></button>
71 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-small"><i class="icon-refresh"></i></button>
73 </span>
72 </span>
74 </div>
73 </div>
75 </div>
74 </div>
76
75
77 <div id="cluster_list">
76 <div id="cluster_list">
78 <div id="cluster_list_header" class="row-fluid list_header">
77 <div id="cluster_list_header" class="row-fluid list_header">
79 <div class="profile_col span4">profile</div>
78 <div class="profile_col span4">profile</div>
80 <div class="status_col span3">status</div>
79 <div class="status_col span3">status</div>
81 <div class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</div>
80 <div class="engines_col span3" title="Enter the number of engines to start or empty for default"># of engines</div>
82 <div class="action_col span2">action</div>
81 <div class="action_col span2">action</div>
83 </div>
82 </div>
84 </div>
83 </div>
85 </div>
84 </div>
86 </div>
85 </div>
87
86
88 </div>
87 </div>
89
88
90 {% endblock %}
89 {% endblock %}
91
90
92 {% block script %}
91 {% block script %}
93 {{super()}}
92 {{super()}}
94 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
93 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
95 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
94 <script src="{{static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
96 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
95 <script src="{{static_url("tree/js/notebooklist.js") }}" type="text/javascript" charset="utf-8"></script>
97 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
96 <script src="{{static_url("tree/js/clusterlist.js") }}" type="text/javascript" charset="utf-8"></script>
98 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
97 <script src="{{static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
99 {% endblock %}
98 {% endblock %}
@@ -1,163 +1,162 b''
1 .. _working_remotely:
1 .. _working_remotely:
2
2
3 Running a notebook server
3 Running a notebook server
4 =========================
4 =========================
5
5
6
6
7 The :ref:`IPython notebook <htmlnotebook>` web-application is based on a
7 The :ref:`IPython notebook <htmlnotebook>` web-application is based on a
8 server-client structure. This server uses a :ref:`two-process kernel
8 server-client structure. This server uses a :ref:`two-process kernel
9 architecture <ipythonzmq>` based on ZeroMQ_, as well as Tornado_ for serving
9 architecture <ipythonzmq>` based on ZeroMQ_, as well as Tornado_ for serving
10 HTTP requests. By default, a notebook server runs on http://127.0.0.1:8888/
10 HTTP requests. By default, a notebook server runs on http://127.0.0.1:8888/
11 and is accessible only from `localhost`. This document describes how you can
11 and is accessible only from `localhost`. This document describes how you can
12 :ref:`secure a notebook server <notebook_security>` and how to :ref:`run it on
12 :ref:`secure a notebook server <notebook_security>` and how to :ref:`run it on
13 a public interface <notebook_public_server>`.
13 a public interface <notebook_public_server>`.
14
14
15 .. _ZeroMQ: http://zeromq.org
15 .. _ZeroMQ: http://zeromq.org
16
16
17 .. _Tornado: http://www.tornadoweb.org
17 .. _Tornado: http://www.tornadoweb.org
18
18
19
19
20 .. _notebook_security:
20 .. _notebook_security:
21
21
22 Notebook security
22 Notebook security
23 -----------------
23 -----------------
24
24
25 You can protect your notebook server with a simple single password by
25 You can protect your notebook server with a simple single password by
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
26 setting the :attr:`NotebookApp.password` configurable. You can prepare a
27 hashed password using the function :func:`IPython.lib.security.passwd`:
27 hashed password using the function :func:`IPython.lib.security.passwd`:
28
28
29 .. sourcecode:: ipython
29 .. sourcecode:: ipython
30
30
31 In [1]: from IPython.lib import passwd
31 In [1]: from IPython.lib import passwd
32 In [2]: passwd()
32 In [2]: passwd()
33 Enter password:
33 Enter password:
34 Verify password:
34 Verify password:
35 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
35 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
36
36
37 .. note::
37 .. note::
38
38
39 :func:`~IPython.lib.security.passwd` can also take the password as a string
39 :func:`~IPython.lib.security.passwd` can also take the password as a string
40 argument. **Do not** pass it as an argument inside an IPython session, as it
40 argument. **Do not** pass it as an argument inside an IPython session, as it
41 will be saved in your input history.
41 will be saved in your input history.
42
42
43 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
43 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
44
44
45 # Password to use for web authentication
45 # Password to use for web authentication
46 c = get_config()
46 c = get_config()
47 c.NotebookApp.password =
47 c.NotebookApp.password =
48 u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
48 u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
49
49
50 When using a password, it is a good idea to also use SSL, so that your
50 When using a password, it is a good idea to also use SSL, so that your
51 password is not sent unencrypted by your browser. You can start the notebook
51 password is not sent unencrypted by your browser. You can start the notebook
52 to communicate via a secure protocol mode using a self-signed certificate with
52 to communicate via a secure protocol mode using a self-signed certificate with
53 the command::
53 the command::
54
54
55 $ ipython notebook --certfile=mycert.pem
55 $ ipython notebook --certfile=mycert.pem
56
56
57 .. note::
57 .. note::
58
58
59 A self-signed certificate can be generated with ``openssl``. For example,
59 A self-signed certificate can be generated with ``openssl``. For example,
60 the following command will create a certificate valid for 365 days with
60 the following command will create a certificate valid for 365 days with
61 both the key and certificate data written to the same file::
61 both the key and certificate data written to the same file::
62
62
63 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
63 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
64
64
65 Your browser will warn you of a dangerous certificate because it is
65 Your browser will warn you of a dangerous certificate because it is
66 self-signed. If you want to have a fully compliant certificate that will not
66 self-signed. If you want to have a fully compliant certificate that will not
67 raise warnings, it is possible (but rather involved) to obtain one,
67 raise warnings, it is possible (but rather involved) to obtain one,
68 as explained in detail in `this tutorial`__.
68 as explained in detail in `this tutorial`__.
69
69
70 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
70 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
71
71
72 Keep in mind that when you enable SSL support, you will need to access the
72 Keep in mind that when you enable SSL support, you will need to access the
73 notebook server over ``https://``, not over plain ``http://``. The startup
73 notebook server over ``https://``, not over plain ``http://``. The startup
74 message from the server prints this, but it is easy to overlook and think the
74 message from the server prints this, but it is easy to overlook and think the
75 server is for some reason non-responsive.
75 server is for some reason non-responsive.
76
76
77
77
78 .. _notebook_public_server:
78 .. _notebook_public_server:
79
79
80 Running a public notebook server
80 Running a public notebook server
81 --------------------------------
81 --------------------------------
82
82
83 If you want to access your notebook server remotely via a web browser,
83 If you want to access your notebook server remotely via a web browser,
84 you can do the following.
84 you can do the following.
85
85
86 Start by creating a certificate file and a hashed password, as explained
86 Start by creating a certificate file and a hashed password, as explained
87 above. Then create a custom profile for the notebook, with the following
87 above. Then create a custom profile for the notebook, with the following
88 command line, type::
88 command line, type::
89
89
90 $ ipython profile create nbserver
90 $ ipython profile create nbserver
91
91
92 In the profile directory just created, edit the file
92 In the profile directory just created, edit the file
93 ``ipython_notebook_config.py``. By default, the file has all fields
93 ``ipython_notebook_config.py``. By default, the file has all fields
94 commented; the minimum set you need to uncomment and edit is the following::
94 commented; the minimum set you need to uncomment and edit is the following::
95
95
96 c = get_config()
96 c = get_config()
97
97
98 # Kernel config
98 # Kernel config
99 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
99 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
100
100
101 # Notebook config
101 # Notebook config
102 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
102 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
103 c.NotebookApp.ip = '*'
103 c.NotebookApp.ip = '*'
104 c.NotebookApp.open_browser = False
104 c.NotebookApp.open_browser = False
105 c.NotebookApp.password = u'sha1:bcd259ccf...[your hashed password here]'
105 c.NotebookApp.password = u'sha1:bcd259ccf...[your hashed password here]'
106 # It is a good idea to put it on a known, fixed port
106 # It is a good idea to put it on a known, fixed port
107 c.NotebookApp.port = 9999
107 c.NotebookApp.port = 9999
108
108
109 You can then start the notebook and access it later by pointing your browser
109 You can then start the notebook and access it later by pointing your browser
110 to ``https://your.host.com:9999`` with ``ipython notebook
110 to ``https://your.host.com:9999`` with ``ipython notebook
111 --profile=nbserver``.
111 --profile=nbserver``.
112
112
113 Running with a different URL prefix
113 Running with a different URL prefix
114 -----------------------------------
114 -----------------------------------
115
115
116 The notebook dashboard (the landing page with an overview
116 The notebook dashboard (the landing page with an overview
117 of the notebooks in your working directory) typically lives at the URL
117 of the notebooks in your working directory) typically lives at the URL
118 ``http://localhost:8888/``. If you prefer that it lives, together with the
118 ``http://localhost:8888/``. If you prefer that it lives, together with the
119 rest of the notebook, under a sub-directory,
119 rest of the notebook, under a sub-directory,
120 e.g. ``http://localhost:8888/ipython/``, you can do so with
120 e.g. ``http://localhost:8888/ipython/``, you can do so with
121 configuration options like the following (see above for instructions about
121 configuration options like the following (see above for instructions about
122 modifying ``ipython_notebook_config.py``)::
122 modifying ``ipython_notebook_config.py``)::
123
123
124 c.NotebookApp.base_project_url = '/ipython/'
124 c.NotebookApp.base_url = '/ipython/'
125 c.NotebookApp.base_kernel_url = '/ipython/'
126 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
125 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
127
126
128 Using a different notebook store
127 Using a different notebook store
129 --------------------------------
128 --------------------------------
130
129
131 By default, the notebook server stores the notebook documents that it saves as
130 By default, the notebook server stores the notebook documents that it saves as
132 files in the working directory of the notebook server, also known as the
131 files in the working directory of the notebook server, also known as the
133 ``notebook_dir``. This logic is implemented in the
132 ``notebook_dir``. This logic is implemented in the
134 :class:`FileNotebookManager` class. However, the server can be configured to
133 :class:`FileNotebookManager` class. However, the server can be configured to
135 use a different notebook manager class, which can
134 use a different notebook manager class, which can
136 store the notebooks in a different format.
135 store the notebooks in a different format.
137
136
138 Currently, we ship a :class:`AzureNotebookManager` class that stores notebooks
137 Currently, we ship a :class:`AzureNotebookManager` class that stores notebooks
139 in Azure blob storage. This can be used by adding the following lines to your
138 in Azure blob storage. This can be used by adding the following lines to your
140 ``ipython_notebook_config.py`` file::
139 ``ipython_notebook_config.py`` file::
141
140
142 c.NotebookApp.notebook_manager_class =
141 c.NotebookApp.notebook_manager_class =
143 'IPython.html.services.notebooks.azurenbmanager.AzureNotebookManager'
142 'IPython.html.services.notebooks.azurenbmanager.AzureNotebookManager'
144 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
143 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
145 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
144 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
146 c.AzureNotebookManager.container = u'notebooks'
145 c.AzureNotebookManager.container = u'notebooks'
147
146
148 In addition to providing your Azure Blob Storage account name and key, you
147 In addition to providing your Azure Blob Storage account name and key, you
149 will have to provide a container name; you can use multiple containers to
148 will have to provide a container name; you can use multiple containers to
150 organize your notebooks.
149 organize your notebooks.
151
150
152
151
153 Known issues
152 Known issues
154 ------------
153 ------------
155
154
156 When behind a proxy, especially if your system or browser is set to autodetect
155 When behind a proxy, especially if your system or browser is set to autodetect
157 the proxy, the notebook web application might fail to connect to the server's
156 the proxy, the notebook web application might fail to connect to the server's
158 websockets, and present you with a warning at startup. In this case, you need
157 websockets, and present you with a warning at startup. In this case, you need
159 to configure your system not to use the proxy for the server's address.
158 to configure your system not to use the proxy for the server's address.
160
159
161 For example, in Firefox, go to the Preferences panel, Advanced section,
160 For example, in Firefox, go to the Preferences panel, Advanced section,
162 Network tab, click 'Settings...', and add the address of the notebook server
161 Network tab, click 'Settings...', and add the address of the notebook server
163 to the 'No proxy for' field.
162 to the 'No proxy for' field.
General Comments 0
You need to be logged in to leave comments. Login now