##// END OF EJS Templates
Add logout button.
Stefan van der Walt -
Show More
@@ -0,0 +1,5 b''
1 {% extends layout.html %}
2
3 {% block content_panel %}
4 You've been successfully logged out.
5 {% end %}
@@ -1,569 +1,576 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 import Cookie
20 import Cookie
21 import uuid
21 import uuid
22
22
23 from tornado import web
23 from tornado import web
24 from tornado import websocket
24 from tornado import websocket
25
25
26 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28
28
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.zmq.session import Session
30 from IPython.zmq.session import Session
31 from IPython.lib.security import passwd_check
31 from IPython.lib.security import passwd_check
32
32
33 try:
33 try:
34 from docutils.core import publish_string
34 from docutils.core import publish_string
35 except ImportError:
35 except ImportError:
36 publish_string = None
36 publish_string = None
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
39 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 # Google Chrome, as of release 16, changed its websocket protocol number. The
42 # Google Chrome, as of release 16, changed its websocket protocol number. The
43 # parts tornado cares about haven't really changed, so it's OK to continue
43 # parts tornado cares about haven't really changed, so it's OK to continue
44 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
44 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
45 # version as of Oct 30/2011) the version check fails, see the issue report:
45 # version as of Oct 30/2011) the version check fails, see the issue report:
46
46
47 # https://github.com/facebook/tornado/issues/385
47 # https://github.com/facebook/tornado/issues/385
48
48
49 # This issue has been fixed in Tornado post 2.1.1:
49 # This issue has been fixed in Tornado post 2.1.1:
50
50
51 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
51 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
52
52
53 # Here we manually apply the same patch as above so that users of IPython can
53 # Here we manually apply the same patch as above so that users of IPython can
54 # continue to work with an officially released Tornado. We make the
54 # continue to work with an officially released Tornado. We make the
55 # monkeypatch version check as narrow as possible to limit its effects; once
55 # monkeypatch version check as narrow as possible to limit its effects; once
56 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
56 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
57
57
58 import tornado
58 import tornado
59
59
60 if tornado.version_info <= (2,1,1):
60 if tornado.version_info <= (2,1,1):
61
61
62 def _execute(self, transforms, *args, **kwargs):
62 def _execute(self, transforms, *args, **kwargs):
63 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
63 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
64
64
65 self.open_args = args
65 self.open_args = args
66 self.open_kwargs = kwargs
66 self.open_kwargs = kwargs
67
67
68 # The difference between version 8 and 13 is that in 8 the
68 # The difference between version 8 and 13 is that in 8 the
69 # client sends a "Sec-Websocket-Origin" header and in 13 it's
69 # client sends a "Sec-Websocket-Origin" header and in 13 it's
70 # simply "Origin".
70 # simply "Origin".
71 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
71 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
72 self.ws_connection = WebSocketProtocol8(self)
72 self.ws_connection = WebSocketProtocol8(self)
73 self.ws_connection.accept_connection()
73 self.ws_connection.accept_connection()
74
74
75 elif self.request.headers.get("Sec-WebSocket-Version"):
75 elif self.request.headers.get("Sec-WebSocket-Version"):
76 self.stream.write(tornado.escape.utf8(
76 self.stream.write(tornado.escape.utf8(
77 "HTTP/1.1 426 Upgrade Required\r\n"
77 "HTTP/1.1 426 Upgrade Required\r\n"
78 "Sec-WebSocket-Version: 8\r\n\r\n"))
78 "Sec-WebSocket-Version: 8\r\n\r\n"))
79 self.stream.close()
79 self.stream.close()
80
80
81 else:
81 else:
82 self.ws_connection = WebSocketProtocol76(self)
82 self.ws_connection = WebSocketProtocol76(self)
83 self.ws_connection.accept_connection()
83 self.ws_connection.accept_connection()
84
84
85 websocket.WebSocketHandler._execute = _execute
85 websocket.WebSocketHandler._execute = _execute
86 del _execute
86 del _execute
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Decorator for disabling read-only handlers
89 # Decorator for disabling read-only handlers
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 @decorator
92 @decorator
93 def not_if_readonly(f, self, *args, **kwargs):
93 def not_if_readonly(f, self, *args, **kwargs):
94 if self.application.read_only:
94 if self.application.read_only:
95 raise web.HTTPError(403, "Notebook server is read-only")
95 raise web.HTTPError(403, "Notebook server is read-only")
96 else:
96 else:
97 return f(self, *args, **kwargs)
97 return f(self, *args, **kwargs)
98
98
99 @decorator
99 @decorator
100 def authenticate_unless_readonly(f, self, *args, **kwargs):
100 def authenticate_unless_readonly(f, self, *args, **kwargs):
101 """authenticate this page *unless* readonly view is active.
101 """authenticate this page *unless* readonly view is active.
102
102
103 In read-only mode, the notebook list and print view should
103 In read-only mode, the notebook list and print view should
104 be accessible without authentication.
104 be accessible without authentication.
105 """
105 """
106
106
107 @web.authenticated
107 @web.authenticated
108 def auth_f(self, *args, **kwargs):
108 def auth_f(self, *args, **kwargs):
109 return f(self, *args, **kwargs)
109 return f(self, *args, **kwargs)
110 if self.application.read_only:
110 if self.application.read_only:
111 return f(self, *args, **kwargs)
111 return f(self, *args, **kwargs)
112 else:
112 else:
113 return auth_f(self, *args, **kwargs)
113 return auth_f(self, *args, **kwargs)
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # Top-level handlers
116 # Top-level handlers
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 class RequestHandler(web.RequestHandler):
119 class RequestHandler(web.RequestHandler):
120 """RequestHandler with default variable setting."""
120 """RequestHandler with default variable setting."""
121
121
122 def render(*args, **kwargs):
122 def render(*args, **kwargs):
123 kwargs.setdefault('message', '')
123 kwargs.setdefault('message', '')
124 return web.RequestHandler.render(*args, **kwargs)
124 return web.RequestHandler.render(*args, **kwargs)
125
125
126 class AuthenticatedHandler(RequestHandler):
126 class AuthenticatedHandler(RequestHandler):
127 """A RequestHandler with an authenticated user."""
127 """A RequestHandler with an authenticated user."""
128
128
129 def get_current_user(self):
129 def get_current_user(self):
130 user_id = self.get_secure_cookie("username")
130 user_id = self.get_secure_cookie("username")
131 # For now the user_id should not return empty, but it could eventually
131 # For now the user_id should not return empty, but it could eventually
132 if user_id == '':
132 if user_id == '':
133 user_id = 'anonymous'
133 user_id = 'anonymous'
134 if user_id is None:
134 if user_id is None:
135 # prevent extra Invalid cookie sig warnings:
135 # prevent extra Invalid cookie sig warnings:
136 self.clear_cookie('username')
136 self.clear_cookie('username')
137 if not self.application.password and not self.application.read_only:
137 if not self.application.password and not self.application.read_only:
138 user_id = 'anonymous'
138 user_id = 'anonymous'
139 return user_id
139 return user_id
140
140
141 @property
141 @property
142 def read_only(self):
142 def read_only(self):
143 if self.application.read_only:
143 if self.application.read_only:
144 if self.application.password:
144 if self.application.password:
145 return self.get_current_user() is None
145 return self.get_current_user() is None
146 else:
146 else:
147 return True
147 return True
148 else:
148 else:
149 return False
149 return False
150
150
151 @property
151 @property
152 def ws_url(self):
152 def ws_url(self):
153 """websocket url matching the current request
153 """websocket url matching the current request
154
154
155 turns http[s]://host[:port] into
155 turns http[s]://host[:port] into
156 ws[s]://host[:port]
156 ws[s]://host[:port]
157 """
157 """
158 proto = self.request.protocol.replace('http', 'ws')
158 proto = self.request.protocol.replace('http', 'ws')
159 return "%s://%s" % (proto, self.request.host)
159 return "%s://%s" % (proto, self.request.host)
160
160
161
161
162 class ProjectDashboardHandler(AuthenticatedHandler):
162 class ProjectDashboardHandler(AuthenticatedHandler):
163
163
164 @authenticate_unless_readonly
164 @authenticate_unless_readonly
165 def get(self):
165 def get(self):
166 nbm = self.application.notebook_manager
166 nbm = self.application.notebook_manager
167 project = nbm.notebook_dir
167 project = nbm.notebook_dir
168 self.render(
168 self.render(
169 'projectdashboard.html', project=project,
169 'projectdashboard.html', project=project,
170 base_project_url=u'/', base_kernel_url=u'/',
170 base_project_url=u'/', base_kernel_url=u'/',
171 read_only=self.read_only,
171 read_only=self.read_only,
172 )
172 )
173
173
174
174
175 class LoginHandler(AuthenticatedHandler):
175 class LoginHandler(AuthenticatedHandler):
176
176
177 def _render(self, message=''):
177 def _render(self, message=''):
178 self.render('login.html',
178 self.render('login.html',
179 next=self.get_argument('next', default='/'),
179 next=self.get_argument('next', default='/'),
180 read_only=self.read_only,
180 read_only=self.read_only,
181 message=message
181 message=message
182 )
182 )
183
183
184 def get(self):
184 def get(self):
185 self._render()
185 self._render()
186
186
187 def post(self):
187 def post(self):
188 pwd = self.get_argument('password', default=u'')
188 pwd = self.get_argument('password', default=u'')
189 if self.application.password:
189 if self.application.password:
190 if passwd_check(self.application.password, pwd):
190 if passwd_check(self.application.password, pwd):
191 self.set_secure_cookie('username', str(uuid.uuid4()))
191 self.set_secure_cookie('username', str(uuid.uuid4()))
192 else:
192 else:
193 self._render(message='Invalid password')
193 self._render(message='Invalid password')
194 return
194 return
195
195
196 self.redirect(self.get_argument('next', default='/'))
196 self.redirect(self.get_argument('next', default='/'))
197
197
198
198
199 class LogoutHandler(AuthenticatedHandler):
200
201 def get(self):
202 self.clear_cookie('username')
203 self.render('logout.html')
204
205
199 class NewHandler(AuthenticatedHandler):
206 class NewHandler(AuthenticatedHandler):
200
207
201 @web.authenticated
208 @web.authenticated
202 def get(self):
209 def get(self):
203 nbm = self.application.notebook_manager
210 nbm = self.application.notebook_manager
204 project = nbm.notebook_dir
211 project = nbm.notebook_dir
205 notebook_id = nbm.new_notebook()
212 notebook_id = nbm.new_notebook()
206 self.render(
213 self.render(
207 'notebook.html', project=project,
214 'notebook.html', project=project,
208 notebook_id=notebook_id,
215 notebook_id=notebook_id,
209 base_project_url=u'/', base_kernel_url=u'/',
216 base_project_url=u'/', base_kernel_url=u'/',
210 kill_kernel=False,
217 kill_kernel=False,
211 read_only=False,
218 read_only=False,
212 )
219 )
213
220
214
221
215 class NamedNotebookHandler(AuthenticatedHandler):
222 class NamedNotebookHandler(AuthenticatedHandler):
216
223
217 @authenticate_unless_readonly
224 @authenticate_unless_readonly
218 def get(self, notebook_id):
225 def get(self, notebook_id):
219 nbm = self.application.notebook_manager
226 nbm = self.application.notebook_manager
220 project = nbm.notebook_dir
227 project = nbm.notebook_dir
221 if not nbm.notebook_exists(notebook_id):
228 if not nbm.notebook_exists(notebook_id):
222 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
229 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
223
230
224 self.render(
231 self.render(
225 'notebook.html', project=project,
232 'notebook.html', project=project,
226 notebook_id=notebook_id,
233 notebook_id=notebook_id,
227 base_project_url=u'/', base_kernel_url=u'/',
234 base_project_url=u'/', base_kernel_url=u'/',
228 kill_kernel=False,
235 kill_kernel=False,
229 read_only=self.read_only,
236 read_only=self.read_only,
230 )
237 )
231
238
232
239
233 #-----------------------------------------------------------------------------
240 #-----------------------------------------------------------------------------
234 # Kernel handlers
241 # Kernel handlers
235 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
236
243
237
244
238 class MainKernelHandler(AuthenticatedHandler):
245 class MainKernelHandler(AuthenticatedHandler):
239
246
240 @web.authenticated
247 @web.authenticated
241 def get(self):
248 def get(self):
242 km = self.application.kernel_manager
249 km = self.application.kernel_manager
243 self.finish(jsonapi.dumps(km.kernel_ids))
250 self.finish(jsonapi.dumps(km.kernel_ids))
244
251
245 @web.authenticated
252 @web.authenticated
246 def post(self):
253 def post(self):
247 km = self.application.kernel_manager
254 km = self.application.kernel_manager
248 notebook_id = self.get_argument('notebook', default=None)
255 notebook_id = self.get_argument('notebook', default=None)
249 kernel_id = km.start_kernel(notebook_id)
256 kernel_id = km.start_kernel(notebook_id)
250 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
257 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
251 self.set_header('Location', '/'+kernel_id)
258 self.set_header('Location', '/'+kernel_id)
252 self.finish(jsonapi.dumps(data))
259 self.finish(jsonapi.dumps(data))
253
260
254
261
255 class KernelHandler(AuthenticatedHandler):
262 class KernelHandler(AuthenticatedHandler):
256
263
257 SUPPORTED_METHODS = ('DELETE')
264 SUPPORTED_METHODS = ('DELETE')
258
265
259 @web.authenticated
266 @web.authenticated
260 def delete(self, kernel_id):
267 def delete(self, kernel_id):
261 km = self.application.kernel_manager
268 km = self.application.kernel_manager
262 km.kill_kernel(kernel_id)
269 km.kill_kernel(kernel_id)
263 self.set_status(204)
270 self.set_status(204)
264 self.finish()
271 self.finish()
265
272
266
273
267 class KernelActionHandler(AuthenticatedHandler):
274 class KernelActionHandler(AuthenticatedHandler):
268
275
269 @web.authenticated
276 @web.authenticated
270 def post(self, kernel_id, action):
277 def post(self, kernel_id, action):
271 km = self.application.kernel_manager
278 km = self.application.kernel_manager
272 if action == 'interrupt':
279 if action == 'interrupt':
273 km.interrupt_kernel(kernel_id)
280 km.interrupt_kernel(kernel_id)
274 self.set_status(204)
281 self.set_status(204)
275 if action == 'restart':
282 if action == 'restart':
276 new_kernel_id = km.restart_kernel(kernel_id)
283 new_kernel_id = km.restart_kernel(kernel_id)
277 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
284 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
278 self.set_header('Location', '/'+new_kernel_id)
285 self.set_header('Location', '/'+new_kernel_id)
279 self.write(jsonapi.dumps(data))
286 self.write(jsonapi.dumps(data))
280 self.finish()
287 self.finish()
281
288
282
289
283 class ZMQStreamHandler(websocket.WebSocketHandler):
290 class ZMQStreamHandler(websocket.WebSocketHandler):
284
291
285 def _reserialize_reply(self, msg_list):
292 def _reserialize_reply(self, msg_list):
286 """Reserialize a reply message using JSON.
293 """Reserialize a reply message using JSON.
287
294
288 This takes the msg list from the ZMQ socket, unserializes it using
295 This takes the msg list from the ZMQ socket, unserializes it using
289 self.session and then serializes the result using JSON. This method
296 self.session and then serializes the result using JSON. This method
290 should be used by self._on_zmq_reply to build messages that can
297 should be used by self._on_zmq_reply to build messages that can
291 be sent back to the browser.
298 be sent back to the browser.
292 """
299 """
293 idents, msg_list = self.session.feed_identities(msg_list)
300 idents, msg_list = self.session.feed_identities(msg_list)
294 msg = self.session.unserialize(msg_list)
301 msg = self.session.unserialize(msg_list)
295 try:
302 try:
296 msg['header'].pop('date')
303 msg['header'].pop('date')
297 except KeyError:
304 except KeyError:
298 pass
305 pass
299 try:
306 try:
300 msg['parent_header'].pop('date')
307 msg['parent_header'].pop('date')
301 except KeyError:
308 except KeyError:
302 pass
309 pass
303 msg.pop('buffers')
310 msg.pop('buffers')
304 return jsonapi.dumps(msg)
311 return jsonapi.dumps(msg)
305
312
306 def _on_zmq_reply(self, msg_list):
313 def _on_zmq_reply(self, msg_list):
307 try:
314 try:
308 msg = self._reserialize_reply(msg_list)
315 msg = self._reserialize_reply(msg_list)
309 except:
316 except:
310 self.application.log.critical("Malformed message: %r" % msg_list)
317 self.application.log.critical("Malformed message: %r" % msg_list)
311 else:
318 else:
312 self.write_message(msg)
319 self.write_message(msg)
313
320
314
321
315 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
322 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
316
323
317 def open(self, kernel_id):
324 def open(self, kernel_id):
318 self.kernel_id = kernel_id.decode('ascii')
325 self.kernel_id = kernel_id.decode('ascii')
319 try:
326 try:
320 cfg = self.application.ipython_app.config
327 cfg = self.application.ipython_app.config
321 except AttributeError:
328 except AttributeError:
322 # protect from the case where this is run from something other than
329 # protect from the case where this is run from something other than
323 # the notebook app:
330 # the notebook app:
324 cfg = None
331 cfg = None
325 self.session = Session(config=cfg)
332 self.session = Session(config=cfg)
326 self.save_on_message = self.on_message
333 self.save_on_message = self.on_message
327 self.on_message = self.on_first_message
334 self.on_message = self.on_first_message
328
335
329 def get_current_user(self):
336 def get_current_user(self):
330 user_id = self.get_secure_cookie("username")
337 user_id = self.get_secure_cookie("username")
331 if user_id == '' or (user_id is None and not self.application.password):
338 if user_id == '' or (user_id is None and not self.application.password):
332 user_id = 'anonymous'
339 user_id = 'anonymous'
333 return user_id
340 return user_id
334
341
335 def _inject_cookie_message(self, msg):
342 def _inject_cookie_message(self, msg):
336 """Inject the first message, which is the document cookie,
343 """Inject the first message, which is the document cookie,
337 for authentication."""
344 for authentication."""
338 if isinstance(msg, unicode):
345 if isinstance(msg, unicode):
339 # Cookie can't constructor doesn't accept unicode strings for some reason
346 # Cookie can't constructor doesn't accept unicode strings for some reason
340 msg = msg.encode('utf8', 'replace')
347 msg = msg.encode('utf8', 'replace')
341 try:
348 try:
342 self.request._cookies = Cookie.SimpleCookie(msg)
349 self.request._cookies = Cookie.SimpleCookie(msg)
343 except:
350 except:
344 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
351 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
345
352
346 def on_first_message(self, msg):
353 def on_first_message(self, msg):
347 self._inject_cookie_message(msg)
354 self._inject_cookie_message(msg)
348 if self.get_current_user() is None:
355 if self.get_current_user() is None:
349 logging.warn("Couldn't authenticate WebSocket connection")
356 logging.warn("Couldn't authenticate WebSocket connection")
350 raise web.HTTPError(403)
357 raise web.HTTPError(403)
351 self.on_message = self.save_on_message
358 self.on_message = self.save_on_message
352
359
353
360
354 class IOPubHandler(AuthenticatedZMQStreamHandler):
361 class IOPubHandler(AuthenticatedZMQStreamHandler):
355
362
356 def initialize(self, *args, **kwargs):
363 def initialize(self, *args, **kwargs):
357 self._kernel_alive = True
364 self._kernel_alive = True
358 self._beating = False
365 self._beating = False
359 self.iopub_stream = None
366 self.iopub_stream = None
360 self.hb_stream = None
367 self.hb_stream = None
361
368
362 def on_first_message(self, msg):
369 def on_first_message(self, msg):
363 try:
370 try:
364 super(IOPubHandler, self).on_first_message(msg)
371 super(IOPubHandler, self).on_first_message(msg)
365 except web.HTTPError:
372 except web.HTTPError:
366 self.close()
373 self.close()
367 return
374 return
368 km = self.application.kernel_manager
375 km = self.application.kernel_manager
369 self.time_to_dead = km.time_to_dead
376 self.time_to_dead = km.time_to_dead
370 kernel_id = self.kernel_id
377 kernel_id = self.kernel_id
371 try:
378 try:
372 self.iopub_stream = km.create_iopub_stream(kernel_id)
379 self.iopub_stream = km.create_iopub_stream(kernel_id)
373 self.hb_stream = km.create_hb_stream(kernel_id)
380 self.hb_stream = km.create_hb_stream(kernel_id)
374 except web.HTTPError:
381 except web.HTTPError:
375 # WebSockets don't response to traditional error codes so we
382 # WebSockets don't response to traditional error codes so we
376 # close the connection.
383 # close the connection.
377 if not self.stream.closed():
384 if not self.stream.closed():
378 self.stream.close()
385 self.stream.close()
379 self.close()
386 self.close()
380 else:
387 else:
381 self.iopub_stream.on_recv(self._on_zmq_reply)
388 self.iopub_stream.on_recv(self._on_zmq_reply)
382 self.start_hb(self.kernel_died)
389 self.start_hb(self.kernel_died)
383
390
384 def on_message(self, msg):
391 def on_message(self, msg):
385 pass
392 pass
386
393
387 def on_close(self):
394 def on_close(self):
388 # This method can be called twice, once by self.kernel_died and once
395 # This method can be called twice, once by self.kernel_died and once
389 # from the WebSocket close event. If the WebSocket connection is
396 # from the WebSocket close event. If the WebSocket connection is
390 # closed before the ZMQ streams are setup, they could be None.
397 # closed before the ZMQ streams are setup, they could be None.
391 self.stop_hb()
398 self.stop_hb()
392 if self.iopub_stream is not None and not self.iopub_stream.closed():
399 if self.iopub_stream is not None and not self.iopub_stream.closed():
393 self.iopub_stream.on_recv(None)
400 self.iopub_stream.on_recv(None)
394 self.iopub_stream.close()
401 self.iopub_stream.close()
395 if self.hb_stream is not None and not self.hb_stream.closed():
402 if self.hb_stream is not None and not self.hb_stream.closed():
396 self.hb_stream.close()
403 self.hb_stream.close()
397
404
398 def start_hb(self, callback):
405 def start_hb(self, callback):
399 """Start the heartbeating and call the callback if the kernel dies."""
406 """Start the heartbeating and call the callback if the kernel dies."""
400 if not self._beating:
407 if not self._beating:
401 self._kernel_alive = True
408 self._kernel_alive = True
402
409
403 def ping_or_dead():
410 def ping_or_dead():
404 if self._kernel_alive:
411 if self._kernel_alive:
405 self._kernel_alive = False
412 self._kernel_alive = False
406 self.hb_stream.send(b'ping')
413 self.hb_stream.send(b'ping')
407 else:
414 else:
408 try:
415 try:
409 callback()
416 callback()
410 except:
417 except:
411 pass
418 pass
412 finally:
419 finally:
413 self._hb_periodic_callback.stop()
420 self._hb_periodic_callback.stop()
414
421
415 def beat_received(msg):
422 def beat_received(msg):
416 self._kernel_alive = True
423 self._kernel_alive = True
417
424
418 self.hb_stream.on_recv(beat_received)
425 self.hb_stream.on_recv(beat_received)
419 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
426 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
420 self._hb_periodic_callback.start()
427 self._hb_periodic_callback.start()
421 self._beating= True
428 self._beating= True
422
429
423 def stop_hb(self):
430 def stop_hb(self):
424 """Stop the heartbeating and cancel all related callbacks."""
431 """Stop the heartbeating and cancel all related callbacks."""
425 if self._beating:
432 if self._beating:
426 self._hb_periodic_callback.stop()
433 self._hb_periodic_callback.stop()
427 if not self.hb_stream.closed():
434 if not self.hb_stream.closed():
428 self.hb_stream.on_recv(None)
435 self.hb_stream.on_recv(None)
429
436
430 def kernel_died(self):
437 def kernel_died(self):
431 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
438 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
432 self.write_message(
439 self.write_message(
433 {'header': {'msg_type': 'status'},
440 {'header': {'msg_type': 'status'},
434 'parent_header': {},
441 'parent_header': {},
435 'content': {'execution_state':'dead'}
442 'content': {'execution_state':'dead'}
436 }
443 }
437 )
444 )
438 self.on_close()
445 self.on_close()
439
446
440
447
441 class ShellHandler(AuthenticatedZMQStreamHandler):
448 class ShellHandler(AuthenticatedZMQStreamHandler):
442
449
443 def initialize(self, *args, **kwargs):
450 def initialize(self, *args, **kwargs):
444 self.shell_stream = None
451 self.shell_stream = None
445
452
446 def on_first_message(self, msg):
453 def on_first_message(self, msg):
447 try:
454 try:
448 super(ShellHandler, self).on_first_message(msg)
455 super(ShellHandler, self).on_first_message(msg)
449 except web.HTTPError:
456 except web.HTTPError:
450 self.close()
457 self.close()
451 return
458 return
452 km = self.application.kernel_manager
459 km = self.application.kernel_manager
453 self.max_msg_size = km.max_msg_size
460 self.max_msg_size = km.max_msg_size
454 kernel_id = self.kernel_id
461 kernel_id = self.kernel_id
455 try:
462 try:
456 self.shell_stream = km.create_shell_stream(kernel_id)
463 self.shell_stream = km.create_shell_stream(kernel_id)
457 except web.HTTPError:
464 except web.HTTPError:
458 # WebSockets don't response to traditional error codes so we
465 # WebSockets don't response to traditional error codes so we
459 # close the connection.
466 # close the connection.
460 if not self.stream.closed():
467 if not self.stream.closed():
461 self.stream.close()
468 self.stream.close()
462 self.close()
469 self.close()
463 else:
470 else:
464 self.shell_stream.on_recv(self._on_zmq_reply)
471 self.shell_stream.on_recv(self._on_zmq_reply)
465
472
466 def on_message(self, msg):
473 def on_message(self, msg):
467 if len(msg) < self.max_msg_size:
474 if len(msg) < self.max_msg_size:
468 msg = jsonapi.loads(msg)
475 msg = jsonapi.loads(msg)
469 self.session.send(self.shell_stream, msg)
476 self.session.send(self.shell_stream, msg)
470
477
471 def on_close(self):
478 def on_close(self):
472 # Make sure the stream exists and is not already closed.
479 # Make sure the stream exists and is not already closed.
473 if self.shell_stream is not None and not self.shell_stream.closed():
480 if self.shell_stream is not None and not self.shell_stream.closed():
474 self.shell_stream.close()
481 self.shell_stream.close()
475
482
476
483
477 #-----------------------------------------------------------------------------
484 #-----------------------------------------------------------------------------
478 # Notebook web service handlers
485 # Notebook web service handlers
479 #-----------------------------------------------------------------------------
486 #-----------------------------------------------------------------------------
480
487
481 class NotebookRootHandler(AuthenticatedHandler):
488 class NotebookRootHandler(AuthenticatedHandler):
482
489
483 @authenticate_unless_readonly
490 @authenticate_unless_readonly
484 def get(self):
491 def get(self):
485
492
486 nbm = self.application.notebook_manager
493 nbm = self.application.notebook_manager
487 files = nbm.list_notebooks()
494 files = nbm.list_notebooks()
488 self.finish(jsonapi.dumps(files))
495 self.finish(jsonapi.dumps(files))
489
496
490 @web.authenticated
497 @web.authenticated
491 def post(self):
498 def post(self):
492 nbm = self.application.notebook_manager
499 nbm = self.application.notebook_manager
493 body = self.request.body.strip()
500 body = self.request.body.strip()
494 format = self.get_argument('format', default='json')
501 format = self.get_argument('format', default='json')
495 name = self.get_argument('name', default=None)
502 name = self.get_argument('name', default=None)
496 if body:
503 if body:
497 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
504 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
498 else:
505 else:
499 notebook_id = nbm.new_notebook()
506 notebook_id = nbm.new_notebook()
500 self.set_header('Location', '/'+notebook_id)
507 self.set_header('Location', '/'+notebook_id)
501 self.finish(jsonapi.dumps(notebook_id))
508 self.finish(jsonapi.dumps(notebook_id))
502
509
503
510
504 class NotebookHandler(AuthenticatedHandler):
511 class NotebookHandler(AuthenticatedHandler):
505
512
506 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
513 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
507
514
508 @authenticate_unless_readonly
515 @authenticate_unless_readonly
509 def get(self, notebook_id):
516 def get(self, notebook_id):
510 nbm = self.application.notebook_manager
517 nbm = self.application.notebook_manager
511 format = self.get_argument('format', default='json')
518 format = self.get_argument('format', default='json')
512 last_mod, name, data = nbm.get_notebook(notebook_id, format)
519 last_mod, name, data = nbm.get_notebook(notebook_id, format)
513
520
514 if format == u'json':
521 if format == u'json':
515 self.set_header('Content-Type', 'application/json')
522 self.set_header('Content-Type', 'application/json')
516 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
523 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
517 elif format == u'py':
524 elif format == u'py':
518 self.set_header('Content-Type', 'application/x-python')
525 self.set_header('Content-Type', 'application/x-python')
519 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
526 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
520 self.set_header('Last-Modified', last_mod)
527 self.set_header('Last-Modified', last_mod)
521 self.finish(data)
528 self.finish(data)
522
529
523 @web.authenticated
530 @web.authenticated
524 def put(self, notebook_id):
531 def put(self, notebook_id):
525 nbm = self.application.notebook_manager
532 nbm = self.application.notebook_manager
526 format = self.get_argument('format', default='json')
533 format = self.get_argument('format', default='json')
527 name = self.get_argument('name', default=None)
534 name = self.get_argument('name', default=None)
528 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
535 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
529 self.set_status(204)
536 self.set_status(204)
530 self.finish()
537 self.finish()
531
538
532 @web.authenticated
539 @web.authenticated
533 def delete(self, notebook_id):
540 def delete(self, notebook_id):
534 nbm = self.application.notebook_manager
541 nbm = self.application.notebook_manager
535 nbm.delete_notebook(notebook_id)
542 nbm.delete_notebook(notebook_id)
536 self.set_status(204)
543 self.set_status(204)
537 self.finish()
544 self.finish()
538
545
539 #-----------------------------------------------------------------------------
546 #-----------------------------------------------------------------------------
540 # RST web service handlers
547 # RST web service handlers
541 #-----------------------------------------------------------------------------
548 #-----------------------------------------------------------------------------
542
549
543
550
544 class RSTHandler(AuthenticatedHandler):
551 class RSTHandler(AuthenticatedHandler):
545
552
546 @web.authenticated
553 @web.authenticated
547 def post(self):
554 def post(self):
548 if publish_string is None:
555 if publish_string is None:
549 raise web.HTTPError(503, u'docutils not available')
556 raise web.HTTPError(503, u'docutils not available')
550 body = self.request.body.strip()
557 body = self.request.body.strip()
551 source = body
558 source = body
552 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
559 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
553 defaults = {'file_insertion_enabled': 0,
560 defaults = {'file_insertion_enabled': 0,
554 'raw_enabled': 0,
561 'raw_enabled': 0,
555 '_disable_config': 1,
562 '_disable_config': 1,
556 'stylesheet_path': 0
563 'stylesheet_path': 0
557 # 'template': template_path
564 # 'template': template_path
558 }
565 }
559 try:
566 try:
560 html = publish_string(source, writer_name='html',
567 html = publish_string(source, writer_name='html',
561 settings_overrides=defaults
568 settings_overrides=defaults
562 )
569 )
563 except:
570 except:
564 raise web.HTTPError(400, u'Invalid RST')
571 raise web.HTTPError(400, u'Invalid RST')
565 print html
572 print html
566 self.set_header('Content-Type', 'text/html')
573 self.set_header('Content-Type', 'text/html')
567 self.finish(html)
574 self.finish(html)
568
575
569
576
@@ -1,334 +1,335 b''
1 """A tornado based IPython notebook server.
1 """A tornado based IPython notebook server.
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 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import signal
23 import signal
24 import socket
24 import socket
25 import sys
25 import sys
26 import threading
26 import threading
27 import webbrowser
27 import webbrowser
28
28
29 # Third party
29 # Third party
30 import zmq
30 import zmq
31
31
32 # Install the pyzmq ioloop. This has to be done before anything else from
32 # Install the pyzmq ioloop. This has to be done before anything else from
33 # tornado is imported.
33 # tornado is imported.
34 from zmq.eventloop import ioloop
34 from zmq.eventloop import ioloop
35 import tornado.ioloop
35 import tornado.ioloop
36 tornado.ioloop.IOLoop = ioloop.IOLoop
36 tornado.ioloop.IOLoop = ioloop.IOLoop
37
37
38 from tornado import httpserver
38 from tornado import httpserver
39 from tornado import web
39 from tornado import web
40
40
41 # Our own libraries
41 # Our own libraries
42 from .kernelmanager import MappingKernelManager
42 from .kernelmanager import MappingKernelManager
43 from .handlers import (LoginHandler,
43 from .handlers import (LoginHandler, LogoutHandler,
44 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
44 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
45 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
45 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
46 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
46 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
47 )
47 )
48 from .notebookmanager import NotebookManager
48 from .notebookmanager import NotebookManager
49
49
50 from IPython.config.application import catch_config_error
50 from IPython.config.application import catch_config_error
51 from IPython.core.application import BaseIPythonApplication
51 from IPython.core.application import BaseIPythonApplication
52 from IPython.core.profiledir import ProfileDir
52 from IPython.core.profiledir import ProfileDir
53 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.session import Session, default_secure
54 from IPython.zmq.zmqshell import ZMQInteractiveShell
54 from IPython.zmq.zmqshell import ZMQInteractiveShell
55 from IPython.zmq.ipkernel import (
55 from IPython.zmq.ipkernel import (
56 flags as ipkernel_flags,
56 flags as ipkernel_flags,
57 aliases as ipkernel_aliases,
57 aliases as ipkernel_aliases,
58 IPKernelApp
58 IPKernelApp
59 )
59 )
60 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
60 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Module globals
63 # Module globals
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
66 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
67 _kernel_action_regex = r"(?P<action>restart|interrupt)"
67 _kernel_action_regex = r"(?P<action>restart|interrupt)"
68 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
68 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
69
69
70 LOCALHOST = '127.0.0.1'
70 LOCALHOST = '127.0.0.1'
71
71
72 _examples = """
72 _examples = """
73 ipython notebook # start the notebook
73 ipython notebook # start the notebook
74 ipython notebook --profile=sympy # use the sympy profile
74 ipython notebook --profile=sympy # use the sympy profile
75 ipython notebook --pylab=inline # pylab in inline plotting mode
75 ipython notebook --pylab=inline # pylab in inline plotting mode
76 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
76 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
77 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
77 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
78 """
78 """
79
79
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81 # The Tornado web application
81 # The Tornado web application
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83
83
84 class NotebookWebApplication(web.Application):
84 class NotebookWebApplication(web.Application):
85
85
86 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
86 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
87 handlers = [
87 handlers = [
88 (r"/", ProjectDashboardHandler),
88 (r"/", ProjectDashboardHandler),
89 (r"/login", LoginHandler),
89 (r"/login", LoginHandler),
90 (r"/logout", LogoutHandler),
90 (r"/new", NewHandler),
91 (r"/new", NewHandler),
91 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
92 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
92 (r"/kernels", MainKernelHandler),
93 (r"/kernels", MainKernelHandler),
93 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
94 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
94 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
95 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
95 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
96 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
96 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
97 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
97 (r"/notebooks", NotebookRootHandler),
98 (r"/notebooks", NotebookRootHandler),
98 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
99 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
99 (r"/rstservice/render", RSTHandler)
100 (r"/rstservice/render", RSTHandler)
100 ]
101 ]
101 settings = dict(
102 settings = dict(
102 template_path=os.path.join(os.path.dirname(__file__), "templates"),
103 template_path=os.path.join(os.path.dirname(__file__), "templates"),
103 static_path=os.path.join(os.path.dirname(__file__), "static"),
104 static_path=os.path.join(os.path.dirname(__file__), "static"),
104 cookie_secret=os.urandom(1024),
105 cookie_secret=os.urandom(1024),
105 login_url="/login",
106 login_url="/login",
106 )
107 )
107 web.Application.__init__(self, handlers, **settings)
108 web.Application.__init__(self, handlers, **settings)
108
109
109 self.kernel_manager = kernel_manager
110 self.kernel_manager = kernel_manager
110 self.log = log
111 self.log = log
111 self.notebook_manager = notebook_manager
112 self.notebook_manager = notebook_manager
112 self.ipython_app = ipython_app
113 self.ipython_app = ipython_app
113 self.read_only = self.ipython_app.read_only
114 self.read_only = self.ipython_app.read_only
114
115
115
116
116 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
117 # Aliases and Flags
118 # Aliases and Flags
118 #-----------------------------------------------------------------------------
119 #-----------------------------------------------------------------------------
119
120
120 flags = dict(ipkernel_flags)
121 flags = dict(ipkernel_flags)
121 flags['no-browser']=(
122 flags['no-browser']=(
122 {'NotebookApp' : {'open_browser' : False}},
123 {'NotebookApp' : {'open_browser' : False}},
123 "Don't open the notebook in a browser after startup."
124 "Don't open the notebook in a browser after startup."
124 )
125 )
125 flags['read-only'] = (
126 flags['read-only'] = (
126 {'NotebookApp' : {'read_only' : True}},
127 {'NotebookApp' : {'read_only' : True}},
127 """Allow read-only access to notebooks.
128 """Allow read-only access to notebooks.
128
129
129 When using a password to protect the notebook server, this flag
130 When using a password to protect the notebook server, this flag
130 allows unauthenticated clients to view the notebook list, and
131 allows unauthenticated clients to view the notebook list, and
131 individual notebooks, but not edit them, start kernels, or run
132 individual notebooks, but not edit them, start kernels, or run
132 code.
133 code.
133
134
134 If no password is set, the server will be entirely read-only.
135 If no password is set, the server will be entirely read-only.
135 """
136 """
136 )
137 )
137
138
138 # the flags that are specific to the frontend
139 # the flags that are specific to the frontend
139 # these must be scrubbed before being passed to the kernel,
140 # these must be scrubbed before being passed to the kernel,
140 # or it will raise an error on unrecognized flags
141 # or it will raise an error on unrecognized flags
141 notebook_flags = ['no-browser', 'read-only']
142 notebook_flags = ['no-browser', 'read-only']
142
143
143 aliases = dict(ipkernel_aliases)
144 aliases = dict(ipkernel_aliases)
144
145
145 aliases.update({
146 aliases.update({
146 'ip': 'NotebookApp.ip',
147 'ip': 'NotebookApp.ip',
147 'port': 'NotebookApp.port',
148 'port': 'NotebookApp.port',
148 'keyfile': 'NotebookApp.keyfile',
149 'keyfile': 'NotebookApp.keyfile',
149 'certfile': 'NotebookApp.certfile',
150 'certfile': 'NotebookApp.certfile',
150 'notebook-dir': 'NotebookManager.notebook_dir',
151 'notebook-dir': 'NotebookManager.notebook_dir',
151 })
152 })
152
153
153 # remove ipkernel flags that are singletons, and don't make sense in
154 # remove ipkernel flags that are singletons, and don't make sense in
154 # multi-kernel evironment:
155 # multi-kernel evironment:
155 aliases.pop('f', None)
156 aliases.pop('f', None)
156
157
157 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
158 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
158 u'notebook-dir']
159 u'notebook-dir']
159
160
160 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
161 # NotebookApp
162 # NotebookApp
162 #-----------------------------------------------------------------------------
163 #-----------------------------------------------------------------------------
163
164
164 class NotebookApp(BaseIPythonApplication):
165 class NotebookApp(BaseIPythonApplication):
165
166
166 name = 'ipython-notebook'
167 name = 'ipython-notebook'
167 default_config_file_name='ipython_notebook_config.py'
168 default_config_file_name='ipython_notebook_config.py'
168
169
169 description = """
170 description = """
170 The IPython HTML Notebook.
171 The IPython HTML Notebook.
171
172
172 This launches a Tornado based HTML Notebook Server that serves up an
173 This launches a Tornado based HTML Notebook Server that serves up an
173 HTML5/Javascript Notebook client.
174 HTML5/Javascript Notebook client.
174 """
175 """
175 examples = _examples
176 examples = _examples
176
177
177 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
178 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
178 MappingKernelManager, NotebookManager]
179 MappingKernelManager, NotebookManager]
179 flags = Dict(flags)
180 flags = Dict(flags)
180 aliases = Dict(aliases)
181 aliases = Dict(aliases)
181
182
182 kernel_argv = List(Unicode)
183 kernel_argv = List(Unicode)
183
184
184 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
185 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
185 default_value=logging.INFO,
186 default_value=logging.INFO,
186 config=True,
187 config=True,
187 help="Set the log level by value or name.")
188 help="Set the log level by value or name.")
188
189
189 # Network related information.
190 # Network related information.
190
191
191 ip = Unicode(LOCALHOST, config=True,
192 ip = Unicode(LOCALHOST, config=True,
192 help="The IP address the notebook server will listen on."
193 help="The IP address the notebook server will listen on."
193 )
194 )
194
195
195 def _ip_changed(self, name, old, new):
196 def _ip_changed(self, name, old, new):
196 if new == u'*': self.ip = u''
197 if new == u'*': self.ip = u''
197
198
198 port = Int(8888, config=True,
199 port = Int(8888, config=True,
199 help="The port the notebook server will listen on."
200 help="The port the notebook server will listen on."
200 )
201 )
201
202
202 certfile = Unicode(u'', config=True,
203 certfile = Unicode(u'', config=True,
203 help="""The full path to an SSL/TLS certificate file."""
204 help="""The full path to an SSL/TLS certificate file."""
204 )
205 )
205
206
206 keyfile = Unicode(u'', config=True,
207 keyfile = Unicode(u'', config=True,
207 help="""The full path to a private key file for usage with SSL/TLS."""
208 help="""The full path to a private key file for usage with SSL/TLS."""
208 )
209 )
209
210
210 password = Unicode(u'', config=True,
211 password = Unicode(u'', config=True,
211 help="""Hashed password to use for web authentication.
212 help="""Hashed password to use for web authentication.
212
213
213 To generate, do:
214 To generate, do:
214
215
215 from IPython.lib import passwd
216 from IPython.lib import passwd
216
217
217 passwd('mypassphrase')
218 passwd('mypassphrase')
218
219
219 The string should be of the form type:salt:hashed-password.
220 The string should be of the form type:salt:hashed-password.
220 """
221 """
221 )
222 )
222
223
223 open_browser = Bool(True, config=True,
224 open_browser = Bool(True, config=True,
224 help="Whether to open in a browser after starting.")
225 help="Whether to open in a browser after starting.")
225
226
226 read_only = Bool(False, config=True,
227 read_only = Bool(False, config=True,
227 help="Whether to prevent editing/execution of notebooks."
228 help="Whether to prevent editing/execution of notebooks."
228 )
229 )
229
230
230 def parse_command_line(self, argv=None):
231 def parse_command_line(self, argv=None):
231 super(NotebookApp, self).parse_command_line(argv)
232 super(NotebookApp, self).parse_command_line(argv)
232 if argv is None:
233 if argv is None:
233 argv = sys.argv[1:]
234 argv = sys.argv[1:]
234
235
235 self.kernel_argv = list(argv) # copy
236 self.kernel_argv = list(argv) # copy
236 # Kernel should inherit default config file from frontend
237 # Kernel should inherit default config file from frontend
237 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
238 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
238 # Scrub frontend-specific flags
239 # Scrub frontend-specific flags
239 for a in argv:
240 for a in argv:
240 if a.startswith('-') and a.lstrip('-') in notebook_flags:
241 if a.startswith('-') and a.lstrip('-') in notebook_flags:
241 self.kernel_argv.remove(a)
242 self.kernel_argv.remove(a)
242 swallow_next = False
243 swallow_next = False
243 for a in argv:
244 for a in argv:
244 if swallow_next:
245 if swallow_next:
245 self.kernel_argv.remove(a)
246 self.kernel_argv.remove(a)
246 swallow_next = False
247 swallow_next = False
247 continue
248 continue
248 if a.startswith('-'):
249 if a.startswith('-'):
249 split = a.lstrip('-').split('=')
250 split = a.lstrip('-').split('=')
250 alias = split[0]
251 alias = split[0]
251 if alias in notebook_aliases:
252 if alias in notebook_aliases:
252 self.kernel_argv.remove(a)
253 self.kernel_argv.remove(a)
253 if len(split) == 1:
254 if len(split) == 1:
254 # alias passed with arg via space
255 # alias passed with arg via space
255 swallow_next = True
256 swallow_next = True
256
257
257 def init_configurables(self):
258 def init_configurables(self):
258 # Don't let Qt or ZMQ swallow KeyboardInterupts.
259 # Don't let Qt or ZMQ swallow KeyboardInterupts.
259 signal.signal(signal.SIGINT, signal.SIG_DFL)
260 signal.signal(signal.SIGINT, signal.SIG_DFL)
260
261
261 # force Session default to be secure
262 # force Session default to be secure
262 default_secure(self.config)
263 default_secure(self.config)
263 # Create a KernelManager and start a kernel.
264 # Create a KernelManager and start a kernel.
264 self.kernel_manager = MappingKernelManager(
265 self.kernel_manager = MappingKernelManager(
265 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
266 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
266 connection_dir = self.profile_dir.security_dir,
267 connection_dir = self.profile_dir.security_dir,
267 )
268 )
268 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
269 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
269 self.notebook_manager.list_notebooks()
270 self.notebook_manager.list_notebooks()
270
271
271 def init_logging(self):
272 def init_logging(self):
272 super(NotebookApp, self).init_logging()
273 super(NotebookApp, self).init_logging()
273 # This prevents double log messages because tornado use a root logger that
274 # This prevents double log messages because tornado use a root logger that
274 # self.log is a child of. The logging module dipatches log messages to a log
275 # self.log is a child of. The logging module dipatches log messages to a log
275 # and all of its ancenstors until propagate is set to False.
276 # and all of its ancenstors until propagate is set to False.
276 self.log.propagate = False
277 self.log.propagate = False
277
278
278 @catch_config_error
279 @catch_config_error
279 def initialize(self, argv=None):
280 def initialize(self, argv=None):
280 super(NotebookApp, self).initialize(argv)
281 super(NotebookApp, self).initialize(argv)
281 self.init_configurables()
282 self.init_configurables()
282 self.web_app = NotebookWebApplication(
283 self.web_app = NotebookWebApplication(
283 self, self.kernel_manager, self.notebook_manager, self.log
284 self, self.kernel_manager, self.notebook_manager, self.log
284 )
285 )
285 if self.certfile:
286 if self.certfile:
286 ssl_options = dict(certfile=self.certfile)
287 ssl_options = dict(certfile=self.certfile)
287 if self.keyfile:
288 if self.keyfile:
288 ssl_options['keyfile'] = self.keyfile
289 ssl_options['keyfile'] = self.keyfile
289 else:
290 else:
290 ssl_options = None
291 ssl_options = None
291 self.web_app.password = self.password
292 self.web_app.password = self.password
292 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
293 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
293 if ssl_options is None and not self.ip:
294 if ssl_options is None and not self.ip:
294 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
295 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
295 'but not using any encryption or authentication. This is highly '
296 'but not using any encryption or authentication. This is highly '
296 'insecure and not recommended.')
297 'insecure and not recommended.')
297
298
298 # Try random ports centered around the default.
299 # Try random ports centered around the default.
299 from random import randint
300 from random import randint
300 n = 50 # Max number of attempts, keep reasonably large.
301 n = 50 # Max number of attempts, keep reasonably large.
301 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
302 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
302 try:
303 try:
303 self.http_server.listen(port, self.ip)
304 self.http_server.listen(port, self.ip)
304 except socket.error, e:
305 except socket.error, e:
305 if e.errno != errno.EADDRINUSE:
306 if e.errno != errno.EADDRINUSE:
306 raise
307 raise
307 self.log.info('The port %i is already in use, trying another random port.' % port)
308 self.log.info('The port %i is already in use, trying another random port.' % port)
308 else:
309 else:
309 self.port = port
310 self.port = port
310 break
311 break
311
312
312 def start(self):
313 def start(self):
313 ip = self.ip if self.ip else '[all ip addresses on your system]'
314 ip = self.ip if self.ip else '[all ip addresses on your system]'
314 proto = 'https' if self.certfile else 'http'
315 proto = 'https' if self.certfile else 'http'
315 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
316 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
316 ip,
317 ip,
317 self.port))
318 self.port))
318 if self.open_browser:
319 if self.open_browser:
319 ip = self.ip or '127.0.0.1'
320 ip = self.ip or '127.0.0.1'
320 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
321 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
321 new=2)
322 new=2)
322 threading.Thread(target=b).start()
323 threading.Thread(target=b).start()
323
324
324 ioloop.IOLoop.instance().start()
325 ioloop.IOLoop.instance().start()
325
326
326 #-----------------------------------------------------------------------------
327 #-----------------------------------------------------------------------------
327 # Main entry point
328 # Main entry point
328 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
329
330
330 def launch_new_instance():
331 def launch_new_instance():
331 app = NotebookApp()
332 app = NotebookApp()
332 app.initialize()
333 app.initialize()
333 app.start()
334 app.start()
334
335
@@ -1,38 +1,38 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-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 // Login button
9 // Login button
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var LoginWidget = function (selector) {
14 var LoginWidget = function (selector) {
15 this.selector = selector;
15 this.selector = selector;
16 if (this.selector !== undefined) {
16 if (this.selector !== undefined) {
17 this.element = $(selector);
17 this.element = $(selector);
18 this.style();
18 this.style();
19 this.bind_events();
19 this.bind_events();
20 }
20 }
21 };
21 };
22
22
23 LoginWidget.prototype.style = function () {
23 LoginWidget.prototype.style = function () {
24 this.element.find('button#login').button();
24 this.element.find('button#logout').button();
25 };
25 };
26 LoginWidget.prototype.bind_events = function () {
26 LoginWidget.prototype.bind_events = function () {
27 var that = this;
27 var that = this;
28 this.element.find("button#login").click(function () {
28 this.element.find("button#logout").click(function () {
29 window.location = "/login?next="+location.pathname;
29 window.location = "/logout";
30 });
30 });
31 };
31 };
32
32
33 // Set module variables
33 // Set module variables
34 IPython.LoginWidget = LoginWidget;
34 IPython.LoginWidget = LoginWidget;
35
35
36 return IPython;
36 return IPython;
37
37
38 }(IPython));
38 }(IPython));
@@ -1,71 +1,72 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>{% block title %}IPython Notebook{% end %}</title>
7 <title>{% block title %}IPython Notebook{% end %}</title>
8
8
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css"/>
12 <link rel="stylesheet" href="static/css/base.css" type="text/css"/>
13 {% block stylesheet %}
13 {% block stylesheet %}
14 {% end %}
14 {% end %}
15
15
16 <meta name="read_only" content="{{read_only}}"/>
16 {% block meta %}
17 {% end %}
17
18
18 </head>
19 </head>
19
20
20 <body>
21 <body {% block params %}{% end %}>
21
22
22 <div id="header">
23 <div id="header">
23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
24 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
24 <span id="login_widget" class="hidden">
25 <span id="login_widget">
25 <button id="login">Login</button>
26 <button id="logout">Logout</button>
26 </span>
27 </span>
27 {% block header %}
28 {% block header %}
28 {% end %}
29 {% end %}
29 </div>
30 </div>
30
31
31 <div id="header_border"></div>
32 <div id="header_border"></div>
32
33
33 <div id="main_app">
34 <div id="main_app">
34
35
35 <div id="app_hbox">
36 <div id="app_hbox">
36
37
37 <div id="left_panel">
38 <div id="left_panel">
38 {% block left_panel %}
39 {% block left_panel %}
39 {% end %}
40 {% end %}
40 </div>
41 </div>
41
42
42 <div id="content_panel">
43 <div id="content_panel">
43 {% if message %}
44 {% if message %}
44 <div id="message">
45 <div id="message">
45 {{message}}
46 {{message}}
46 </div>
47 </div>
47 {% end %}
48 {% end %}
48
49
49 {% block content_panel %}
50 {% block content_panel %}
50 {% end %}
51 {% end %}
51 </div>
52 </div>
52 <div id="right_panel">
53 <div id="right_panel">
53 {% block right_panel %}
54 {% block right_panel %}
54 {% end %}
55 {% end %}
55 </div>
56 </div>
56
57
57 </div>
58 </div>
58
59
59 </div>
60 </div>
60
61
61 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
62 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
62 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
63 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
63 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
64 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
64 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
65 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
65 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
66 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
66 {% block script %}
67 {% block script %}
67 {% end %}
68 {% end %}
68
69
69 </body>
70 </body>
70
71
71 </html>
72 </html>
@@ -1,295 +1,295 b''
1 <!DOCTYPE HTML>
1 <!DOCTYPE HTML>
2 <html>
2 <html>
3
3
4 <head>
4 <head>
5 <meta charset="utf-8">
5 <meta charset="utf-8">
6
6
7 <title>IPython Notebook</title>
7 <title>IPython Notebook</title>
8
8
9 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
9 <!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
10 <script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
11 <script type="text/javascript">
11 <script type="text/javascript">
12 function CheckMathJax(){
12 function CheckMathJax(){
13 var div=document.getElementById("MathJaxFetchingWarning")
13 var div=document.getElementById("MathJaxFetchingWarning")
14 if(window.MathJax){
14 if(window.MathJax){
15 document.body.removeChild(div)
15 document.body.removeChild(div)
16 }
16 }
17 else{
17 else{
18 div.style.display = "block";
18 div.style.display = "block";
19 }
19 }
20 }
20 }
21 if (typeof MathJax == 'undefined') {
21 if (typeof MathJax == 'undefined') {
22 console.log("No local MathJax, loading from CDN");
22 console.log("No local MathJax, loading from CDN");
23 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
23 document.write(unescape("%3Cscript type='text/javascript' src='http://cdn.mathjax.org/mathjax/latest/MathJax.js%3Fconfig=TeX-AMS_HTML' charset='utf-8'%3E%3C/script%3E"));
24 }else{
24 }else{
25 console.log("Using local MathJax");
25 console.log("Using local MathJax");
26 }
26 }
27 </script>
27 </script>
28
28
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
29 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
30 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
30 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
31 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
32 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
33 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
34 <link rel="stylesheet" href="static/codemirror/theme/default.css">
35
35
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
36 <link rel="stylesheet" href="static/prettify/prettify.css"/>
37
37
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
38 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
39 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43
43
44 <meta name="read_only" content="{{read_only}}"/>
44 <meta name="read_only" content="{{read_only}}"/>
45
45
46 </head>
46 </head>
47
47
48 <body onload='CheckMathJax();'
48 <body onload='CheckMathJax();'
49 data-project={{project}} data-notebook-id={{notebook_id}}
49 data-project={{project}} data-notebook-id={{notebook_id}}
50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
51 >
51 >
52
52
53 <div id="header">
53 <div id="header">
54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
55 <span id="save_widget">
55 <span id="save_widget">
56 <input type="text" id="notebook_name" size="20"></textarea>
56 <input type="text" id="notebook_name" size="20"></textarea>
57 <button id="save_notebook"><u>S</u>ave</button>
57 <button id="save_notebook"><u>S</u>ave</button>
58 </span>
58 </span>
59 <span id="quick_help_area">
59 <span id="quick_help_area">
60 <button id="quick_help">Quick<u>H</u>elp</button>
60 <button id="quick_help">Quick<u>H</u>elp</button>
61 </span>
61 </span>
62 <span id="login_widget" class="hidden">
62 <span id="login_widget">
63 <button id="login">Login</button>
63 <button id="logout">Logout</button>
64 </span>
64 </span>
65 <span id="kernel_status">Idle</span>
65 <span id="kernel_status">Idle</span>
66 </div>
66 </div>
67
67
68 <div id="MathJaxFetchingWarning"
68 <div id="MathJaxFetchingWarning"
69 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
69 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
70 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
70 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
71 from the internet.</p>
71 from the internet.</p>
72
72
73 <p style="padding:0.2em"> With a working internet connection, you can run
73 <p style="padding:0.2em"> With a working internet connection, you can run
74 the following at a Python or IPython prompt, which will install a local
74 the following at a Python or IPython prompt, which will install a local
75 copy of MathJax:</p>
75 copy of MathJax:</p>
76
76
77 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
77 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
78 from IPython.external import mathjax; mathjax.install_mathjax()
78 from IPython.external import mathjax; mathjax.install_mathjax()
79 </pre>
79 </pre>
80 This will try to install MathJax into the directory where you installed
80 This will try to install MathJax into the directory where you installed
81 IPython. If you installed IPython to a location that requires
81 IPython. If you installed IPython to a location that requires
82 administrative privileges to write, you will need to make this call as
82 administrative privileges to write, you will need to make this call as
83 an administrator. On OSX/Linux/Unix, this can be done at the
83 an administrator. On OSX/Linux/Unix, this can be done at the
84 command-line via:
84 command-line via:
85 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
85 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
86 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
86 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
87 </pre>
87 </pre>
88 </p>
88 </p>
89 </div>
89 </div>
90
90
91 <div id="main_app">
91 <div id="main_app">
92
92
93 <div id="left_panel">
93 <div id="left_panel">
94
94
95 <div id="notebook_section">
95 <div id="notebook_section">
96 <div class="section_header">
96 <div class="section_header">
97 <h3>Notebook</h3>
97 <h3>Notebook</h3>
98 </div>
98 </div>
99 <div class="section_content">
99 <div class="section_content">
100 <div class="section_row">
100 <div class="section_row">
101 <span id="new_open" class="section_row_buttons">
101 <span id="new_open" class="section_row_buttons">
102 <button id="new_notebook">New</button>
102 <button id="new_notebook">New</button>
103 <button id="open_notebook">Open</button>
103 <button id="open_notebook">Open</button>
104 </span>
104 </span>
105 <span class="section_row_header">Actions</span>
105 <span class="section_row_header">Actions</span>
106 </div>
106 </div>
107 <div class="section_row">
107 <div class="section_row">
108 <span>
108 <span>
109 <select id="download_format">
109 <select id="download_format">
110 <option value="json">ipynb</option>
110 <option value="json">ipynb</option>
111 <option value="py">py</option>
111 <option value="py">py</option>
112 </select>
112 </select>
113 </span>
113 </span>
114 <span class="section_row_buttons">
114 <span class="section_row_buttons">
115 <button id="download_notebook">Download</button>
115 <button id="download_notebook">Download</button>
116 </span>
116 </span>
117 </div>
117 </div>
118 <div class="section_row">
118 <div class="section_row">
119 <span class="section_row_buttons">
119 <span class="section_row_buttons">
120 <span id="print_widget">
120 <span id="print_widget">
121 <button id="print_notebook">Print</button>
121 <button id="print_notebook">Print</button>
122 </span>
122 </span>
123 </span>
123 </span>
124 </div>
124 </div>
125 </div>
125 </div>
126 </div>
126 </div>
127
127
128 <div id="cell_section">
128 <div id="cell_section">
129 <div class="section_header">
129 <div class="section_header">
130 <h3>Cell</h3>
130 <h3>Cell</h3>
131 </div>
131 </div>
132 <div class="section_content">
132 <div class="section_content">
133 <div class="section_row">
133 <div class="section_row">
134 <span class="section_row_buttons">
134 <span class="section_row_buttons">
135 <button id="delete_cell"><u>D</u>elete</button>
135 <button id="delete_cell"><u>D</u>elete</button>
136 </span>
136 </span>
137 <span class="section_row_header">Actions</span>
137 <span class="section_row_header">Actions</span>
138 </div>
138 </div>
139 <div class="section_row">
139 <div class="section_row">
140 <span id="cell_type" class="section_row_buttons">
140 <span id="cell_type" class="section_row_buttons">
141 <button id="to_code"><u>C</u>ode</button>
141 <button id="to_code"><u>C</u>ode</button>
142 <!-- <button id="to_html">HTML</button>-->
142 <!-- <button id="to_html">HTML</button>-->
143 <button id="to_markdown"><u>M</u>arkdown</button>
143 <button id="to_markdown"><u>M</u>arkdown</button>
144 </span>
144 </span>
145 <span class="button_label">Format</span>
145 <span class="button_label">Format</span>
146 </div>
146 </div>
147 <div class="section_row">
147 <div class="section_row">
148 <span id="cell_output" class="section_row_buttons">
148 <span id="cell_output" class="section_row_buttons">
149 <button id="toggle_output"><u>T</u>oggle</button>
149 <button id="toggle_output"><u>T</u>oggle</button>
150 <button id="clear_all_output">ClearAll</button>
150 <button id="clear_all_output">ClearAll</button>
151 </span>
151 </span>
152 <span class="button_label">Output</span>
152 <span class="button_label">Output</span>
153 </div>
153 </div>
154 <div class="section_row">
154 <div class="section_row">
155 <span id="insert" class="section_row_buttons">
155 <span id="insert" class="section_row_buttons">
156 <button id="insert_cell_above"><u>A</u>bove</button>
156 <button id="insert_cell_above"><u>A</u>bove</button>
157 <button id="insert_cell_below"><u>B</u>elow</button>
157 <button id="insert_cell_below"><u>B</u>elow</button>
158 </span>
158 </span>
159 <span class="button_label">Insert</span>
159 <span class="button_label">Insert</span>
160 </div>
160 </div>
161 <div class="section_row">
161 <div class="section_row">
162 <span id="move" class="section_row_buttons">
162 <span id="move" class="section_row_buttons">
163 <button id="move_cell_up">Up</button>
163 <button id="move_cell_up">Up</button>
164 <button id="move_cell_down">Down</button>
164 <button id="move_cell_down">Down</button>
165 </span>
165 </span>
166 <span class="button_label">Move</span>
166 <span class="button_label">Move</span>
167 </div>
167 </div>
168 <div class="section_row">
168 <div class="section_row">
169 <span id="run_cells" class="section_row_buttons">
169 <span id="run_cells" class="section_row_buttons">
170 <button id="run_selected_cell">Selected</button>
170 <button id="run_selected_cell">Selected</button>
171 <button id="run_all_cells">All</button>
171 <button id="run_all_cells">All</button>
172 </span>
172 </span>
173 <span class="button_label">Run</span>
173 <span class="button_label">Run</span>
174 </div>
174 </div>
175 <div class="section_row">
175 <div class="section_row">
176 <span id="autoindent_span">
176 <span id="autoindent_span">
177 <input type="checkbox" id="autoindent" checked="true"></input>
177 <input type="checkbox" id="autoindent" checked="true"></input>
178 </span>
178 </span>
179 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
179 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
180 </div>
180 </div>
181 </div>
181 </div>
182 </div>
182 </div>
183
183
184 <div id="kernel_section">
184 <div id="kernel_section">
185 <div class="section_header">
185 <div class="section_header">
186 <h3>Kernel</h3>
186 <h3>Kernel</h3>
187 </div>
187 </div>
188 <div class="section_content">
188 <div class="section_content">
189 <div class="section_row">
189 <div class="section_row">
190 <span id="int_restart" class="section_row_buttons">
190 <span id="int_restart" class="section_row_buttons">
191 <button id="int_kernel"><u>I</u>nterrupt</button>
191 <button id="int_kernel"><u>I</u>nterrupt</button>
192 <button id="restart_kernel">Restart</button>
192 <button id="restart_kernel">Restart</button>
193 </span>
193 </span>
194 <span class="section_row_header">Actions</span>
194 <span class="section_row_header">Actions</span>
195 </div>
195 </div>
196 <div class="section_row">
196 <div class="section_row">
197 <span id="kernel_persist">
197 <span id="kernel_persist">
198 {% if kill_kernel %}
198 {% if kill_kernel %}
199 <input type="checkbox" id="kill_kernel" checked="true"></input>
199 <input type="checkbox" id="kill_kernel" checked="true"></input>
200 {% else %}
200 {% else %}
201 <input type="checkbox" id="kill_kernel"></input>
201 <input type="checkbox" id="kill_kernel"></input>
202 {% end %}
202 {% end %}
203 </span>
203 </span>
204 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
204 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
205 </div>
205 </div>
206 </div>
206 </div>
207 </div>
207 </div>
208
208
209 <div id="help_section">
209 <div id="help_section">
210 <div class="section_header">
210 <div class="section_header">
211 <h3>Help</h3>
211 <h3>Help</h3>
212 </div>
212 </div>
213 <div class="section_content">
213 <div class="section_content">
214 <div class="section_row">
214 <div class="section_row">
215 <span id="help_buttons0" class="section_row_buttons">
215 <span id="help_buttons0" class="section_row_buttons">
216 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
216 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
217 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
217 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
218 </span>
218 </span>
219 <span class="section_row_header">Links</span>
219 <span class="section_row_header">Links</span>
220 </div>
220 </div>
221 <div class="section_row">
221 <div class="section_row">
222 <span id="help_buttons1" class="section_row_buttons">
222 <span id="help_buttons1" class="section_row_buttons">
223 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
223 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
224 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
224 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
225 </span>
225 </span>
226 </div>
226 </div>
227 <div class="section_row">
227 <div class="section_row">
228 <span id="help_buttons2" class="section_row_buttons">
228 <span id="help_buttons2" class="section_row_buttons">
229 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
229 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
230 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
230 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
231 </span>
231 </span>
232 </div>
232 </div>
233 <div class="section_row">
233 <div class="section_row">
234 <span class="help_string">run selected cell</span>
234 <span class="help_string">run selected cell</span>
235 <span class="help_string_label">Shift-Enter :</span>
235 <span class="help_string_label">Shift-Enter :</span>
236 </div>
236 </div>
237 <div class="section_row">
237 <div class="section_row">
238 <span class="help_string">run selected cell in-place</span>
238 <span class="help_string">run selected cell in-place</span>
239 <span class="help_string_label">Ctrl-Enter :</span>
239 <span class="help_string_label">Ctrl-Enter :</span>
240 </div>
240 </div>
241 <div class="section_row">
241 <div class="section_row">
242 <span class="help_string">show keyboard shortcuts</span>
242 <span class="help_string">show keyboard shortcuts</span>
243 <span class="help_string_label">Ctrl-m h :</span>
243 <span class="help_string_label">Ctrl-m h :</span>
244 </div>
244 </div>
245 </div>
245 </div>
246 </div>
246 </div>
247
247
248 </div>
248 </div>
249 <div id="left_panel_splitter"></div>
249 <div id="left_panel_splitter"></div>
250 <div id="notebook_panel">
250 <div id="notebook_panel">
251 <div id="notebook"></div>
251 <div id="notebook"></div>
252 <div id="pager_splitter"></div>
252 <div id="pager_splitter"></div>
253 <div id="pager"></div>
253 <div id="pager"></div>
254 </div>
254 </div>
255
255
256 </div>
256 </div>
257
257
258 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
258 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
259 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
259 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
260 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
260 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
261
261
262 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
262 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
263 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
263 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
264 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
264 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
265 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
265 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
266 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
266 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
267 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
267 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
268 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
268 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
269 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
269 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
270
270
271 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
271 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
272
272
273 <script src="static/prettify/prettify.js" charset="utf-8"></script>
273 <script src="static/prettify/prettify.js" charset="utf-8"></script>
274
274
275 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
275 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
276 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
276 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
277 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
277 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
278 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
278 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
289 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
290 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
291 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
291 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
292
292
293 </body>
293 </body>
294
294
295 </html>
295 </html>
@@ -1,26 +1,36 b''
1 {% extends layout.html %}
1 {% extends layout.html %}
2
2
3 {% block title %}
3 {% block title %}
4 IPython Dashboard
4 IPython Dashboard
5 {% end %}
5 {% end %}
6
6
7 {% block stylesheet %}
7 {% block stylesheet %}
8 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
8 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
9 {% end %}
10
11 {% block meta %}
12 <meta name="read_only" content="{{read_only}}"/>
13 {% end %}
14
15 {% block params %}
16 data-project={{project}}
17 data-base-project-url={{base_project_url}}
18 data-base-kernel-url={{base_kernel_url}}
9 {% end %}
19 {% end %}
10
20
11 {% block content_panel %}
21 {% block content_panel %}
12 <div id="content_toolbar">
22 <div id="content_toolbar">
13 <span id="drag_info">Drag files onto the list to import notebooks.</span>
23 <span id="drag_info">Drag files onto the list to import notebooks.</span>
14 <span id="notebooks_buttons">
24 <span id="notebooks_buttons">
15 <button id="new_notebook">New Notebook</button>
25 <button id="new_notebook">New Notebook</button>
16 </span>
26 </span>
17 </div>
27 </div>
18 <div id="notebook_list">
28 <div id="notebook_list">
19 <div id="project_name"><h2>{{project}}</h2></div>
29 <div id="project_name"><h2>{{project}}</h2></div>
20 </div>
30 </div>
21 {% end %}
31 {% end %}
22
32
23 {% block script %}
33 {% block script %}
24 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
34 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
25 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
35 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
26 {% end %}
36 {% end %}
General Comments 0
You need to be logged in to leave comments. Login now