##// END OF EJS Templates
Use template inheritance.
Stefan van der Walt -
Show More
@@ -0,0 +1,71 b''
1 <!DOCTYPE HTML>
2 <html>
3
4 <head>
5 <meta charset="utf-8">
6
7 <title>{% block title %}IPython Notebook{% end %}</title>
8
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" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css"/>
13 {% block stylesheet %}
14 {% end %}
15
16 <meta name="read_only" content="{{read_only}}"/>
17
18 </head>
19
20 <body>
21
22 <div id="header">
23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
24 <span id="login_widget" class="hidden">
25 <button id="login">Login</button>
26 </span>
27 {% block header %}
28 {% end %}
29 </div>
30
31 <div id="header_border"></div>
32
33 <div id="main_app">
34
35 <div id="app_hbox">
36
37 <div id="left_panel">
38 {% block left_panel %}
39 {% end %}
40 </div>
41
42 <div id="content_panel">
43 {% if message %}
44 <div id="message">
45 {{message}}
46 </div>
47 {% end %}
48
49 {% block content_panel %}
50 {% end %}
51 </div>
52 <div id="right_panel">
53 {% block right_panel %}
54 {% end %}
55 </div>
56
57 </div>
58
59 </div>
60
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-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/loginmain.js" type="text/javascript" charset="utf-8"></script>
65 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
66 {% block script %}
67 {% end %}
68
69 </body>
70
71 </html>
@@ -1,562 +1,569 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 AuthenticatedHandler(web.RequestHandler):
119 class RequestHandler(web.RequestHandler):
120 """RequestHandler with default variable setting."""
121
122 def render(*args, **kwargs):
123 kwargs.setdefault('message', '')
124 return web.RequestHandler.render(*args, **kwargs)
125
126 class AuthenticatedHandler(RequestHandler):
120 """A RequestHandler with an authenticated user."""
127 """A RequestHandler with an authenticated user."""
121
128
122 def get_current_user(self):
129 def get_current_user(self):
123 user_id = self.get_secure_cookie("username")
130 user_id = self.get_secure_cookie("username")
124 # 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
125 if user_id == '':
132 if user_id == '':
126 user_id = 'anonymous'
133 user_id = 'anonymous'
127 if user_id is None:
134 if user_id is None:
128 # prevent extra Invalid cookie sig warnings:
135 # prevent extra Invalid cookie sig warnings:
129 self.clear_cookie('username')
136 self.clear_cookie('username')
130 if not self.application.password and not self.application.read_only:
137 if not self.application.password and not self.application.read_only:
131 user_id = 'anonymous'
138 user_id = 'anonymous'
132 return user_id
139 return user_id
133
140
134 @property
141 @property
135 def read_only(self):
142 def read_only(self):
136 if self.application.read_only:
143 if self.application.read_only:
137 if self.application.password:
144 if self.application.password:
138 return self.get_current_user() is None
145 return self.get_current_user() is None
139 else:
146 else:
140 return True
147 return True
141 else:
148 else:
142 return False
149 return False
143
150
144 @property
151 @property
145 def ws_url(self):
152 def ws_url(self):
146 """websocket url matching the current request
153 """websocket url matching the current request
147
154
148 turns http[s]://host[:port] into
155 turns http[s]://host[:port] into
149 ws[s]://host[:port]
156 ws[s]://host[:port]
150 """
157 """
151 proto = self.request.protocol.replace('http', 'ws')
158 proto = self.request.protocol.replace('http', 'ws')
152 return "%s://%s" % (proto, self.request.host)
159 return "%s://%s" % (proto, self.request.host)
153
160
154
161
155 class ProjectDashboardHandler(AuthenticatedHandler):
162 class ProjectDashboardHandler(AuthenticatedHandler):
156
163
157 @authenticate_unless_readonly
164 @authenticate_unless_readonly
158 def get(self):
165 def get(self):
159 nbm = self.application.notebook_manager
166 nbm = self.application.notebook_manager
160 project = nbm.notebook_dir
167 project = nbm.notebook_dir
161 self.render(
168 self.render(
162 'projectdashboard.html', project=project,
169 'projectdashboard.html', project=project,
163 base_project_url=u'/', base_kernel_url=u'/',
170 base_project_url=u'/', base_kernel_url=u'/',
164 read_only=self.read_only,
171 read_only=self.read_only,
165 )
172 )
166
173
167
174
168 class LoginHandler(AuthenticatedHandler):
175 class LoginHandler(AuthenticatedHandler):
169
176
170 def _render(self, message=''):
177 def _render(self, message=''):
171 self.render('login.html',
178 self.render('login.html',
172 next=self.get_argument('next', default='/'),
179 next=self.get_argument('next', default='/'),
173 read_only=self.read_only,
180 read_only=self.read_only,
174 message=message
181 message=message
175 )
182 )
176
183
177 def get(self):
184 def get(self):
178 self._render()
185 self._render()
179
186
180 def post(self):
187 def post(self):
181 pwd = self.get_argument('password', default=u'')
188 pwd = self.get_argument('password', default=u'')
182 if self.application.password:
189 if self.application.password:
183 if passwd_check(self.application.password, pwd):
190 if passwd_check(self.application.password, pwd):
184 self.set_secure_cookie('username', str(uuid.uuid4()))
191 self.set_secure_cookie('username', str(uuid.uuid4()))
185 else:
192 else:
186 self._render(message='Invalid password')
193 self._render(message='Invalid password')
187 return
194 return
188
195
189 self.redirect(self.get_argument('next', default='/'))
196 self.redirect(self.get_argument('next', default='/'))
190
197
191
198
192 class NewHandler(AuthenticatedHandler):
199 class NewHandler(AuthenticatedHandler):
193
200
194 @web.authenticated
201 @web.authenticated
195 def get(self):
202 def get(self):
196 nbm = self.application.notebook_manager
203 nbm = self.application.notebook_manager
197 project = nbm.notebook_dir
204 project = nbm.notebook_dir
198 notebook_id = nbm.new_notebook()
205 notebook_id = nbm.new_notebook()
199 self.render(
206 self.render(
200 'notebook.html', project=project,
207 'notebook.html', project=project,
201 notebook_id=notebook_id,
208 notebook_id=notebook_id,
202 base_project_url=u'/', base_kernel_url=u'/',
209 base_project_url=u'/', base_kernel_url=u'/',
203 kill_kernel=False,
210 kill_kernel=False,
204 read_only=False,
211 read_only=False,
205 )
212 )
206
213
207
214
208 class NamedNotebookHandler(AuthenticatedHandler):
215 class NamedNotebookHandler(AuthenticatedHandler):
209
216
210 @authenticate_unless_readonly
217 @authenticate_unless_readonly
211 def get(self, notebook_id):
218 def get(self, notebook_id):
212 nbm = self.application.notebook_manager
219 nbm = self.application.notebook_manager
213 project = nbm.notebook_dir
220 project = nbm.notebook_dir
214 if not nbm.notebook_exists(notebook_id):
221 if not nbm.notebook_exists(notebook_id):
215 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
222 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
216
223
217 self.render(
224 self.render(
218 'notebook.html', project=project,
225 'notebook.html', project=project,
219 notebook_id=notebook_id,
226 notebook_id=notebook_id,
220 base_project_url=u'/', base_kernel_url=u'/',
227 base_project_url=u'/', base_kernel_url=u'/',
221 kill_kernel=False,
228 kill_kernel=False,
222 read_only=self.read_only,
229 read_only=self.read_only,
223 )
230 )
224
231
225
232
226 #-----------------------------------------------------------------------------
233 #-----------------------------------------------------------------------------
227 # Kernel handlers
234 # Kernel handlers
228 #-----------------------------------------------------------------------------
235 #-----------------------------------------------------------------------------
229
236
230
237
231 class MainKernelHandler(AuthenticatedHandler):
238 class MainKernelHandler(AuthenticatedHandler):
232
239
233 @web.authenticated
240 @web.authenticated
234 def get(self):
241 def get(self):
235 km = self.application.kernel_manager
242 km = self.application.kernel_manager
236 self.finish(jsonapi.dumps(km.kernel_ids))
243 self.finish(jsonapi.dumps(km.kernel_ids))
237
244
238 @web.authenticated
245 @web.authenticated
239 def post(self):
246 def post(self):
240 km = self.application.kernel_manager
247 km = self.application.kernel_manager
241 notebook_id = self.get_argument('notebook', default=None)
248 notebook_id = self.get_argument('notebook', default=None)
242 kernel_id = km.start_kernel(notebook_id)
249 kernel_id = km.start_kernel(notebook_id)
243 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
250 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
244 self.set_header('Location', '/'+kernel_id)
251 self.set_header('Location', '/'+kernel_id)
245 self.finish(jsonapi.dumps(data))
252 self.finish(jsonapi.dumps(data))
246
253
247
254
248 class KernelHandler(AuthenticatedHandler):
255 class KernelHandler(AuthenticatedHandler):
249
256
250 SUPPORTED_METHODS = ('DELETE')
257 SUPPORTED_METHODS = ('DELETE')
251
258
252 @web.authenticated
259 @web.authenticated
253 def delete(self, kernel_id):
260 def delete(self, kernel_id):
254 km = self.application.kernel_manager
261 km = self.application.kernel_manager
255 km.kill_kernel(kernel_id)
262 km.kill_kernel(kernel_id)
256 self.set_status(204)
263 self.set_status(204)
257 self.finish()
264 self.finish()
258
265
259
266
260 class KernelActionHandler(AuthenticatedHandler):
267 class KernelActionHandler(AuthenticatedHandler):
261
268
262 @web.authenticated
269 @web.authenticated
263 def post(self, kernel_id, action):
270 def post(self, kernel_id, action):
264 km = self.application.kernel_manager
271 km = self.application.kernel_manager
265 if action == 'interrupt':
272 if action == 'interrupt':
266 km.interrupt_kernel(kernel_id)
273 km.interrupt_kernel(kernel_id)
267 self.set_status(204)
274 self.set_status(204)
268 if action == 'restart':
275 if action == 'restart':
269 new_kernel_id = km.restart_kernel(kernel_id)
276 new_kernel_id = km.restart_kernel(kernel_id)
270 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
277 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
271 self.set_header('Location', '/'+new_kernel_id)
278 self.set_header('Location', '/'+new_kernel_id)
272 self.write(jsonapi.dumps(data))
279 self.write(jsonapi.dumps(data))
273 self.finish()
280 self.finish()
274
281
275
282
276 class ZMQStreamHandler(websocket.WebSocketHandler):
283 class ZMQStreamHandler(websocket.WebSocketHandler):
277
284
278 def _reserialize_reply(self, msg_list):
285 def _reserialize_reply(self, msg_list):
279 """Reserialize a reply message using JSON.
286 """Reserialize a reply message using JSON.
280
287
281 This takes the msg list from the ZMQ socket, unserializes it using
288 This takes the msg list from the ZMQ socket, unserializes it using
282 self.session and then serializes the result using JSON. This method
289 self.session and then serializes the result using JSON. This method
283 should be used by self._on_zmq_reply to build messages that can
290 should be used by self._on_zmq_reply to build messages that can
284 be sent back to the browser.
291 be sent back to the browser.
285 """
292 """
286 idents, msg_list = self.session.feed_identities(msg_list)
293 idents, msg_list = self.session.feed_identities(msg_list)
287 msg = self.session.unserialize(msg_list)
294 msg = self.session.unserialize(msg_list)
288 try:
295 try:
289 msg['header'].pop('date')
296 msg['header'].pop('date')
290 except KeyError:
297 except KeyError:
291 pass
298 pass
292 try:
299 try:
293 msg['parent_header'].pop('date')
300 msg['parent_header'].pop('date')
294 except KeyError:
301 except KeyError:
295 pass
302 pass
296 msg.pop('buffers')
303 msg.pop('buffers')
297 return jsonapi.dumps(msg)
304 return jsonapi.dumps(msg)
298
305
299 def _on_zmq_reply(self, msg_list):
306 def _on_zmq_reply(self, msg_list):
300 try:
307 try:
301 msg = self._reserialize_reply(msg_list)
308 msg = self._reserialize_reply(msg_list)
302 except:
309 except:
303 self.application.log.critical("Malformed message: %r" % msg_list)
310 self.application.log.critical("Malformed message: %r" % msg_list)
304 else:
311 else:
305 self.write_message(msg)
312 self.write_message(msg)
306
313
307
314
308 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
315 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
309
316
310 def open(self, kernel_id):
317 def open(self, kernel_id):
311 self.kernel_id = kernel_id.decode('ascii')
318 self.kernel_id = kernel_id.decode('ascii')
312 try:
319 try:
313 cfg = self.application.ipython_app.config
320 cfg = self.application.ipython_app.config
314 except AttributeError:
321 except AttributeError:
315 # protect from the case where this is run from something other than
322 # protect from the case where this is run from something other than
316 # the notebook app:
323 # the notebook app:
317 cfg = None
324 cfg = None
318 self.session = Session(config=cfg)
325 self.session = Session(config=cfg)
319 self.save_on_message = self.on_message
326 self.save_on_message = self.on_message
320 self.on_message = self.on_first_message
327 self.on_message = self.on_first_message
321
328
322 def get_current_user(self):
329 def get_current_user(self):
323 user_id = self.get_secure_cookie("username")
330 user_id = self.get_secure_cookie("username")
324 if user_id == '' or (user_id is None and not self.application.password):
331 if user_id == '' or (user_id is None and not self.application.password):
325 user_id = 'anonymous'
332 user_id = 'anonymous'
326 return user_id
333 return user_id
327
334
328 def _inject_cookie_message(self, msg):
335 def _inject_cookie_message(self, msg):
329 """Inject the first message, which is the document cookie,
336 """Inject the first message, which is the document cookie,
330 for authentication."""
337 for authentication."""
331 if isinstance(msg, unicode):
338 if isinstance(msg, unicode):
332 # Cookie can't constructor doesn't accept unicode strings for some reason
339 # Cookie can't constructor doesn't accept unicode strings for some reason
333 msg = msg.encode('utf8', 'replace')
340 msg = msg.encode('utf8', 'replace')
334 try:
341 try:
335 self.request._cookies = Cookie.SimpleCookie(msg)
342 self.request._cookies = Cookie.SimpleCookie(msg)
336 except:
343 except:
337 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
344 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
338
345
339 def on_first_message(self, msg):
346 def on_first_message(self, msg):
340 self._inject_cookie_message(msg)
347 self._inject_cookie_message(msg)
341 if self.get_current_user() is None:
348 if self.get_current_user() is None:
342 logging.warn("Couldn't authenticate WebSocket connection")
349 logging.warn("Couldn't authenticate WebSocket connection")
343 raise web.HTTPError(403)
350 raise web.HTTPError(403)
344 self.on_message = self.save_on_message
351 self.on_message = self.save_on_message
345
352
346
353
347 class IOPubHandler(AuthenticatedZMQStreamHandler):
354 class IOPubHandler(AuthenticatedZMQStreamHandler):
348
355
349 def initialize(self, *args, **kwargs):
356 def initialize(self, *args, **kwargs):
350 self._kernel_alive = True
357 self._kernel_alive = True
351 self._beating = False
358 self._beating = False
352 self.iopub_stream = None
359 self.iopub_stream = None
353 self.hb_stream = None
360 self.hb_stream = None
354
361
355 def on_first_message(self, msg):
362 def on_first_message(self, msg):
356 try:
363 try:
357 super(IOPubHandler, self).on_first_message(msg)
364 super(IOPubHandler, self).on_first_message(msg)
358 except web.HTTPError:
365 except web.HTTPError:
359 self.close()
366 self.close()
360 return
367 return
361 km = self.application.kernel_manager
368 km = self.application.kernel_manager
362 self.time_to_dead = km.time_to_dead
369 self.time_to_dead = km.time_to_dead
363 kernel_id = self.kernel_id
370 kernel_id = self.kernel_id
364 try:
371 try:
365 self.iopub_stream = km.create_iopub_stream(kernel_id)
372 self.iopub_stream = km.create_iopub_stream(kernel_id)
366 self.hb_stream = km.create_hb_stream(kernel_id)
373 self.hb_stream = km.create_hb_stream(kernel_id)
367 except web.HTTPError:
374 except web.HTTPError:
368 # WebSockets don't response to traditional error codes so we
375 # WebSockets don't response to traditional error codes so we
369 # close the connection.
376 # close the connection.
370 if not self.stream.closed():
377 if not self.stream.closed():
371 self.stream.close()
378 self.stream.close()
372 self.close()
379 self.close()
373 else:
380 else:
374 self.iopub_stream.on_recv(self._on_zmq_reply)
381 self.iopub_stream.on_recv(self._on_zmq_reply)
375 self.start_hb(self.kernel_died)
382 self.start_hb(self.kernel_died)
376
383
377 def on_message(self, msg):
384 def on_message(self, msg):
378 pass
385 pass
379
386
380 def on_close(self):
387 def on_close(self):
381 # This method can be called twice, once by self.kernel_died and once
388 # This method can be called twice, once by self.kernel_died and once
382 # from the WebSocket close event. If the WebSocket connection is
389 # from the WebSocket close event. If the WebSocket connection is
383 # closed before the ZMQ streams are setup, they could be None.
390 # closed before the ZMQ streams are setup, they could be None.
384 self.stop_hb()
391 self.stop_hb()
385 if self.iopub_stream is not None and not self.iopub_stream.closed():
392 if self.iopub_stream is not None and not self.iopub_stream.closed():
386 self.iopub_stream.on_recv(None)
393 self.iopub_stream.on_recv(None)
387 self.iopub_stream.close()
394 self.iopub_stream.close()
388 if self.hb_stream is not None and not self.hb_stream.closed():
395 if self.hb_stream is not None and not self.hb_stream.closed():
389 self.hb_stream.close()
396 self.hb_stream.close()
390
397
391 def start_hb(self, callback):
398 def start_hb(self, callback):
392 """Start the heartbeating and call the callback if the kernel dies."""
399 """Start the heartbeating and call the callback if the kernel dies."""
393 if not self._beating:
400 if not self._beating:
394 self._kernel_alive = True
401 self._kernel_alive = True
395
402
396 def ping_or_dead():
403 def ping_or_dead():
397 if self._kernel_alive:
404 if self._kernel_alive:
398 self._kernel_alive = False
405 self._kernel_alive = False
399 self.hb_stream.send(b'ping')
406 self.hb_stream.send(b'ping')
400 else:
407 else:
401 try:
408 try:
402 callback()
409 callback()
403 except:
410 except:
404 pass
411 pass
405 finally:
412 finally:
406 self._hb_periodic_callback.stop()
413 self._hb_periodic_callback.stop()
407
414
408 def beat_received(msg):
415 def beat_received(msg):
409 self._kernel_alive = True
416 self._kernel_alive = True
410
417
411 self.hb_stream.on_recv(beat_received)
418 self.hb_stream.on_recv(beat_received)
412 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
419 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
413 self._hb_periodic_callback.start()
420 self._hb_periodic_callback.start()
414 self._beating= True
421 self._beating= True
415
422
416 def stop_hb(self):
423 def stop_hb(self):
417 """Stop the heartbeating and cancel all related callbacks."""
424 """Stop the heartbeating and cancel all related callbacks."""
418 if self._beating:
425 if self._beating:
419 self._hb_periodic_callback.stop()
426 self._hb_periodic_callback.stop()
420 if not self.hb_stream.closed():
427 if not self.hb_stream.closed():
421 self.hb_stream.on_recv(None)
428 self.hb_stream.on_recv(None)
422
429
423 def kernel_died(self):
430 def kernel_died(self):
424 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
431 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
425 self.write_message(
432 self.write_message(
426 {'header': {'msg_type': 'status'},
433 {'header': {'msg_type': 'status'},
427 'parent_header': {},
434 'parent_header': {},
428 'content': {'execution_state':'dead'}
435 'content': {'execution_state':'dead'}
429 }
436 }
430 )
437 )
431 self.on_close()
438 self.on_close()
432
439
433
440
434 class ShellHandler(AuthenticatedZMQStreamHandler):
441 class ShellHandler(AuthenticatedZMQStreamHandler):
435
442
436 def initialize(self, *args, **kwargs):
443 def initialize(self, *args, **kwargs):
437 self.shell_stream = None
444 self.shell_stream = None
438
445
439 def on_first_message(self, msg):
446 def on_first_message(self, msg):
440 try:
447 try:
441 super(ShellHandler, self).on_first_message(msg)
448 super(ShellHandler, self).on_first_message(msg)
442 except web.HTTPError:
449 except web.HTTPError:
443 self.close()
450 self.close()
444 return
451 return
445 km = self.application.kernel_manager
452 km = self.application.kernel_manager
446 self.max_msg_size = km.max_msg_size
453 self.max_msg_size = km.max_msg_size
447 kernel_id = self.kernel_id
454 kernel_id = self.kernel_id
448 try:
455 try:
449 self.shell_stream = km.create_shell_stream(kernel_id)
456 self.shell_stream = km.create_shell_stream(kernel_id)
450 except web.HTTPError:
457 except web.HTTPError:
451 # WebSockets don't response to traditional error codes so we
458 # WebSockets don't response to traditional error codes so we
452 # close the connection.
459 # close the connection.
453 if not self.stream.closed():
460 if not self.stream.closed():
454 self.stream.close()
461 self.stream.close()
455 self.close()
462 self.close()
456 else:
463 else:
457 self.shell_stream.on_recv(self._on_zmq_reply)
464 self.shell_stream.on_recv(self._on_zmq_reply)
458
465
459 def on_message(self, msg):
466 def on_message(self, msg):
460 if len(msg) < self.max_msg_size:
467 if len(msg) < self.max_msg_size:
461 msg = jsonapi.loads(msg)
468 msg = jsonapi.loads(msg)
462 self.session.send(self.shell_stream, msg)
469 self.session.send(self.shell_stream, msg)
463
470
464 def on_close(self):
471 def on_close(self):
465 # Make sure the stream exists and is not already closed.
472 # Make sure the stream exists and is not already closed.
466 if self.shell_stream is not None and not self.shell_stream.closed():
473 if self.shell_stream is not None and not self.shell_stream.closed():
467 self.shell_stream.close()
474 self.shell_stream.close()
468
475
469
476
470 #-----------------------------------------------------------------------------
477 #-----------------------------------------------------------------------------
471 # Notebook web service handlers
478 # Notebook web service handlers
472 #-----------------------------------------------------------------------------
479 #-----------------------------------------------------------------------------
473
480
474 class NotebookRootHandler(AuthenticatedHandler):
481 class NotebookRootHandler(AuthenticatedHandler):
475
482
476 @authenticate_unless_readonly
483 @authenticate_unless_readonly
477 def get(self):
484 def get(self):
478
485
479 nbm = self.application.notebook_manager
486 nbm = self.application.notebook_manager
480 files = nbm.list_notebooks()
487 files = nbm.list_notebooks()
481 self.finish(jsonapi.dumps(files))
488 self.finish(jsonapi.dumps(files))
482
489
483 @web.authenticated
490 @web.authenticated
484 def post(self):
491 def post(self):
485 nbm = self.application.notebook_manager
492 nbm = self.application.notebook_manager
486 body = self.request.body.strip()
493 body = self.request.body.strip()
487 format = self.get_argument('format', default='json')
494 format = self.get_argument('format', default='json')
488 name = self.get_argument('name', default=None)
495 name = self.get_argument('name', default=None)
489 if body:
496 if body:
490 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
497 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
491 else:
498 else:
492 notebook_id = nbm.new_notebook()
499 notebook_id = nbm.new_notebook()
493 self.set_header('Location', '/'+notebook_id)
500 self.set_header('Location', '/'+notebook_id)
494 self.finish(jsonapi.dumps(notebook_id))
501 self.finish(jsonapi.dumps(notebook_id))
495
502
496
503
497 class NotebookHandler(AuthenticatedHandler):
504 class NotebookHandler(AuthenticatedHandler):
498
505
499 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
506 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
500
507
501 @authenticate_unless_readonly
508 @authenticate_unless_readonly
502 def get(self, notebook_id):
509 def get(self, notebook_id):
503 nbm = self.application.notebook_manager
510 nbm = self.application.notebook_manager
504 format = self.get_argument('format', default='json')
511 format = self.get_argument('format', default='json')
505 last_mod, name, data = nbm.get_notebook(notebook_id, format)
512 last_mod, name, data = nbm.get_notebook(notebook_id, format)
506
513
507 if format == u'json':
514 if format == u'json':
508 self.set_header('Content-Type', 'application/json')
515 self.set_header('Content-Type', 'application/json')
509 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
516 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
510 elif format == u'py':
517 elif format == u'py':
511 self.set_header('Content-Type', 'application/x-python')
518 self.set_header('Content-Type', 'application/x-python')
512 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
519 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
513 self.set_header('Last-Modified', last_mod)
520 self.set_header('Last-Modified', last_mod)
514 self.finish(data)
521 self.finish(data)
515
522
516 @web.authenticated
523 @web.authenticated
517 def put(self, notebook_id):
524 def put(self, notebook_id):
518 nbm = self.application.notebook_manager
525 nbm = self.application.notebook_manager
519 format = self.get_argument('format', default='json')
526 format = self.get_argument('format', default='json')
520 name = self.get_argument('name', default=None)
527 name = self.get_argument('name', default=None)
521 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
528 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
522 self.set_status(204)
529 self.set_status(204)
523 self.finish()
530 self.finish()
524
531
525 @web.authenticated
532 @web.authenticated
526 def delete(self, notebook_id):
533 def delete(self, notebook_id):
527 nbm = self.application.notebook_manager
534 nbm = self.application.notebook_manager
528 nbm.delete_notebook(notebook_id)
535 nbm.delete_notebook(notebook_id)
529 self.set_status(204)
536 self.set_status(204)
530 self.finish()
537 self.finish()
531
538
532 #-----------------------------------------------------------------------------
539 #-----------------------------------------------------------------------------
533 # RST web service handlers
540 # RST web service handlers
534 #-----------------------------------------------------------------------------
541 #-----------------------------------------------------------------------------
535
542
536
543
537 class RSTHandler(AuthenticatedHandler):
544 class RSTHandler(AuthenticatedHandler):
538
545
539 @web.authenticated
546 @web.authenticated
540 def post(self):
547 def post(self):
541 if publish_string is None:
548 if publish_string is None:
542 raise web.HTTPError(503, u'docutils not available')
549 raise web.HTTPError(503, u'docutils not available')
543 body = self.request.body.strip()
550 body = self.request.body.strip()
544 source = body
551 source = body
545 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
552 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
546 defaults = {'file_insertion_enabled': 0,
553 defaults = {'file_insertion_enabled': 0,
547 'raw_enabled': 0,
554 'raw_enabled': 0,
548 '_disable_config': 1,
555 '_disable_config': 1,
549 'stylesheet_path': 0
556 'stylesheet_path': 0
550 # 'template': template_path
557 # 'template': template_path
551 }
558 }
552 try:
559 try:
553 html = publish_string(source, writer_name='html',
560 html = publish_string(source, writer_name='html',
554 settings_overrides=defaults
561 settings_overrides=defaults
555 )
562 )
556 except:
563 except:
557 raise web.HTTPError(400, u'Invalid RST')
564 raise web.HTTPError(400, u'Invalid RST')
558 print html
565 print html
559 self.set_header('Content-Type', 'text/html')
566 self.set_header('Content-Type', 'text/html')
560 self.finish(html)
567 self.finish(html)
561
568
562
569
@@ -1,61 +1,8 b''
1 <!DOCTYPE HTML>
1 {% extends layout.html %}
2 <html>
2
3
3 {% block content_panel %}
4 <head>
4 <form action="/login?next={{url_escape(next)}}" method="post">
5 <meta charset="utf-8">
5 Password: <input type="password" name="password">
6
6 <input type="submit" value="Sign in" id="signin">
7 <title>IPython Notebook</title>
7 </form>
8
8 {% end %}
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" />
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
13
14 <meta name="read_only" content="{{read_only}}"/>
15
16 </head>
17
18 <body>
19
20 <div id="header">
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
22 </div>
23
24 <div id="header_border"></div>
25
26 <div id="main_app">
27
28 <div id="app_hbox">
29
30 <div id="left_panel">
31 </div>
32
33 <div id="content_panel">
34 {% if message %}
35 <div id="message">
36 {{message}}
37 </div>
38 {% end %}
39
40 <form action="/login?next={{url_escape(next)}}" method="post">
41 Password: <input type="password" name="password">
42 <input type="submit" value="Sign in" id="signin">
43 </form>
44 </div>
45 <div id="right_panel">
46 </div>
47
48 </div>
49
50 </div>
51
52 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
53 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
54 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
55 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
56
57 </body>
58
59 </html>
60
61
@@ -1,69 +1,26 b''
1 <!DOCTYPE HTML>
1 {% extends layout.html %}
2 <html>
2
3
3 {% block title %}
4 <head>
4 IPython Dashboard
5 <meta charset="utf-8">
5 {% end %}
6
6
7 <title>IPython Dashboard</title>
7 {% block stylesheet %}
8
8 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
9 {% end %}
10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
10
11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
11 {% block content_panel %}
12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
12 <div id="content_toolbar">
13 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
13 <span id="drag_info">Drag files onto the list to import notebooks.</span>
14
14 <span id="notebooks_buttons">
15 <meta name="read_only" content="{{read_only}}"/>
15 <button id="new_notebook">New Notebook</button>
16
16 </span>
17 </head>
18
19 <body data-project={{project}} data-base-project-url={{base_project_url}}
20 data-base-kernel-url={{base_kernel_url}}>
21
22 <div id="header">
23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
24 <span id="login_widget" class="hidden">
25 <button id="login">Login</button>
26 </span>
27 </div>
28
29 <div id="header_border"></div>
30
31 <div id="main_app">
32
33 <div id="app_hbox">
34
35 <div id="left_panel">
36 </div>
17 </div>
37
18 <div id="notebook_list">
38 <div id="content_panel">
19 <div id="project_name"><h2>{{project}}</h2></div>
39 <div id="content_toolbar">
40 <span id="drag_info">Drag files onto the list to import notebooks.</span>
41 <span id="notebooks_buttons">
42 <button id="new_notebook">New Notebook</button>
43 </span>
44 </div>
45 <div id="notebook_list">
46 <div id="project_name"><h2>{{project}}</h2></div>
47 </div>
48
49 </div>
50
51 <div id="right_panel">
52 </div>
53
54 </div>
20 </div>
21 {% end %}
55
22
56 </div>
23 {% block script %}
57
58 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
61 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
24 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
62 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
63 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
25 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
64
26 {% end %}
65 </body>
66
67 </html>
68
69
General Comments 0
You need to be logged in to leave comments. Login now