##// END OF EJS Templates
Simplifying logic on login page.
Brian E. Granger -
Show More
@@ -0,0 +1,29 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // On document ready
10 //============================================================================
11
12
13 $(document).ready(function () {
14
15 $('div#header').addClass('border-box-sizing');
16 $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
17
18 $('div#main_app').addClass('border-box-sizing ui-widget');
19 $('div#app_hbox').addClass('hbox');
20
21 $('div#left_panel').addClass('box-flex');
22 $('div#right_panel').addClass('box-flex');
23
24 // These have display: none in the css file and are made visible here to prevent FLOUC.
25 $('div#header').css('display','block');
26 $('div#main_app').css('display','block');
27
28 });
29
@@ -1,446 +1,446 b''
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import logging
20 20 import Cookie
21 21 import uuid
22 22
23 23 from tornado import web
24 24 from tornado import websocket
25 25
26 26 from zmq.eventloop import ioloop
27 27 from zmq.utils import jsonapi
28 28
29 29 from IPython.zmq.session import Session
30 30
31 31 try:
32 32 from docutils.core import publish_string
33 33 except ImportError:
34 34 publish_string = None
35 35
36 36
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Top-level handlers
40 40 #-----------------------------------------------------------------------------
41 41
42 42 class AuthenticatedHandler(web.RequestHandler):
43 43 """A RequestHandler with an authenticated user."""
44 44
45 45 def get_current_user(self):
46 46 user_id = self.get_secure_cookie("user")
47 47 # For now the user_id should not return empty, but it could eventually
48 48 if user_id == '':
49 49 user_id = 'anonymous'
50 50 if user_id is None:
51 51 # prevent extra Invalid cookie sig warnings:
52 52 self.clear_cookie('user')
53 53 if not self.application.password:
54 54 user_id = 'anonymous'
55 55 return user_id
56 56
57 57
58 58 class NBBrowserHandler(AuthenticatedHandler):
59 59
60 60 @web.authenticated
61 61 def get(self):
62 62 nbm = self.application.notebook_manager
63 63 project = nbm.notebook_dir
64 64 self.render(
65 65 'nbbrowser.html', project=project,
66 66 base_project_url=u'/', base_kernel_url=u'/'
67 67 )
68 68
69 69
70 70 class LoginHandler(AuthenticatedHandler):
71 71
72 72 def get(self):
73 self.render('login.html')
73 self.render('login.html', next='/')
74 74
75 75 def post(self):
76 pwd = self.get_argument("password", default=u'')
76 pwd = self.get_argument('password', default=u'')
77 77 if self.application.password and pwd == self.application.password:
78 self.set_secure_cookie("user", str(uuid.uuid4()))
79 url = self.get_argument("next", default="/")
78 self.set_secure_cookie('user', str(uuid.uuid4()))
79 url = self.get_argument('next', default='/')
80 80 self.redirect(url)
81 81
82 82
83 83 class NewHandler(AuthenticatedHandler):
84 84
85 85 @web.authenticated
86 86 def get(self):
87 87 nbm = self.application.notebook_manager
88 88 project = nbm.notebook_dir
89 89 notebook_id = nbm.new_notebook()
90 90 self.render(
91 91 'notebook.html', project=project,
92 92 notebook_id=notebook_id,
93 93 base_project_url=u'/', base_kernel_url=u'/'
94 94 )
95 95
96 96
97 97 class NamedNotebookHandler(AuthenticatedHandler):
98 98
99 99 @web.authenticated
100 100 def get(self, notebook_id):
101 101 nbm = self.application.notebook_manager
102 102 project = nbm.notebook_dir
103 103 if not nbm.notebook_exists(notebook_id):
104 104 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
105 105 self.render(
106 106 'notebook.html', project=project,
107 107 notebook_id=notebook_id,
108 108 base_project_url=u'/', base_kernel_url=u'/'
109 109 )
110 110
111 111
112 112 #-----------------------------------------------------------------------------
113 113 # Kernel handlers
114 114 #-----------------------------------------------------------------------------
115 115
116 116
117 117 class MainKernelHandler(AuthenticatedHandler):
118 118
119 119 @web.authenticated
120 120 def get(self):
121 121 km = self.application.kernel_manager
122 122 self.finish(jsonapi.dumps(km.kernel_ids))
123 123
124 124 @web.authenticated
125 125 def post(self):
126 126 km = self.application.kernel_manager
127 127 notebook_id = self.get_argument('notebook', default=None)
128 128 kernel_id = km.start_kernel(notebook_id)
129 129 ws_url = self.application.ipython_app.get_ws_url()
130 130 data = {'ws_url':ws_url,'kernel_id':kernel_id}
131 131 self.set_header('Location', '/'+kernel_id)
132 132 self.finish(jsonapi.dumps(data))
133 133
134 134
135 135 class KernelHandler(AuthenticatedHandler):
136 136
137 137 SUPPORTED_METHODS = ('DELETE')
138 138
139 139 @web.authenticated
140 140 def delete(self, kernel_id):
141 141 km = self.application.kernel_manager
142 142 km.kill_kernel(kernel_id)
143 143 self.set_status(204)
144 144 self.finish()
145 145
146 146
147 147 class KernelActionHandler(AuthenticatedHandler):
148 148
149 149 @web.authenticated
150 150 def post(self, kernel_id, action):
151 151 km = self.application.kernel_manager
152 152 if action == 'interrupt':
153 153 km.interrupt_kernel(kernel_id)
154 154 self.set_status(204)
155 155 if action == 'restart':
156 156 new_kernel_id = km.restart_kernel(kernel_id)
157 157 ws_url = self.application.ipython_app.get_ws_url()
158 158 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
159 159 self.set_header('Location', '/'+new_kernel_id)
160 160 self.write(jsonapi.dumps(data))
161 161 self.finish()
162 162
163 163
164 164 class ZMQStreamHandler(websocket.WebSocketHandler):
165 165
166 166 def _reserialize_reply(self, msg_list):
167 167 """Reserialize a reply message using JSON.
168 168
169 169 This takes the msg list from the ZMQ socket, unserializes it using
170 170 self.session and then serializes the result using JSON. This method
171 171 should be used by self._on_zmq_reply to build messages that can
172 172 be sent back to the browser.
173 173 """
174 174 idents, msg_list = self.session.feed_identities(msg_list)
175 175 msg = self.session.unserialize(msg_list)
176 176 try:
177 177 msg['header'].pop('date')
178 178 except KeyError:
179 179 pass
180 180 try:
181 181 msg['parent_header'].pop('date')
182 182 except KeyError:
183 183 pass
184 184 msg.pop('buffers')
185 185 return jsonapi.dumps(msg)
186 186
187 187 def _on_zmq_reply(self, msg_list):
188 188 try:
189 189 msg = self._reserialize_reply(msg_list)
190 190 except:
191 191 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
192 192 else:
193 193 self.write_message(msg)
194 194
195 195 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
196 196 def open(self, kernel_id):
197 197 self.kernel_id = kernel_id.decode('ascii')
198 198 try:
199 199 cfg = self.application.ipython_app.config
200 200 except AttributeError:
201 201 # protect from the case where this is run from something other than
202 202 # the notebook app:
203 203 cfg = None
204 204 self.session = Session(config=cfg)
205 205 self.save_on_message = self.on_message
206 206 self.on_message = self.on_first_message
207 207
208 208 def get_current_user(self):
209 209 user_id = self.get_secure_cookie("user")
210 210 if user_id == '' or (user_id is None and not self.application.password):
211 211 user_id = 'anonymous'
212 212 return user_id
213 213
214 214 def _inject_cookie_message(self, msg):
215 215 """Inject the first message, which is the document cookie,
216 216 for authentication."""
217 217 if isinstance(msg, unicode):
218 218 # Cookie can't constructor doesn't accept unicode strings for some reason
219 219 msg = msg.encode('utf8', 'replace')
220 220 try:
221 221 self._cookies = Cookie.SimpleCookie(msg)
222 222 except:
223 223 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
224 224
225 225 def on_first_message(self, msg):
226 226 self._inject_cookie_message(msg)
227 227 if self.get_current_user() is None:
228 228 logging.warn("Couldn't authenticate WebSocket connection")
229 229 raise web.HTTPError(403)
230 230 self.on_message = self.save_on_message
231 231
232 232
233 233 class IOPubHandler(AuthenticatedZMQStreamHandler):
234 234
235 235 def initialize(self, *args, **kwargs):
236 236 self._kernel_alive = True
237 237 self._beating = False
238 238 self.iopub_stream = None
239 239 self.hb_stream = None
240 240
241 241 def on_first_message(self, msg):
242 242 try:
243 243 super(IOPubHandler, self).on_first_message(msg)
244 244 except web.HTTPError:
245 245 self.close()
246 246 return
247 247 km = self.application.kernel_manager
248 248 self.time_to_dead = km.time_to_dead
249 249 kernel_id = self.kernel_id
250 250 try:
251 251 self.iopub_stream = km.create_iopub_stream(kernel_id)
252 252 self.hb_stream = km.create_hb_stream(kernel_id)
253 253 except web.HTTPError:
254 254 # WebSockets don't response to traditional error codes so we
255 255 # close the connection.
256 256 if not self.stream.closed():
257 257 self.stream.close()
258 258 self.close()
259 259 else:
260 260 self.iopub_stream.on_recv(self._on_zmq_reply)
261 261 self.start_hb(self.kernel_died)
262 262
263 263 def on_message(self, msg):
264 264 pass
265 265
266 266 def on_close(self):
267 267 # This method can be called twice, once by self.kernel_died and once
268 268 # from the WebSocket close event. If the WebSocket connection is
269 269 # closed before the ZMQ streams are setup, they could be None.
270 270 self.stop_hb()
271 271 if self.iopub_stream is not None and not self.iopub_stream.closed():
272 272 self.iopub_stream.on_recv(None)
273 273 self.iopub_stream.close()
274 274 if self.hb_stream is not None and not self.hb_stream.closed():
275 275 self.hb_stream.close()
276 276
277 277 def start_hb(self, callback):
278 278 """Start the heartbeating and call the callback if the kernel dies."""
279 279 if not self._beating:
280 280 self._kernel_alive = True
281 281
282 282 def ping_or_dead():
283 283 if self._kernel_alive:
284 284 self._kernel_alive = False
285 285 self.hb_stream.send(b'ping')
286 286 else:
287 287 try:
288 288 callback()
289 289 except:
290 290 pass
291 291 finally:
292 292 self._hb_periodic_callback.stop()
293 293
294 294 def beat_received(msg):
295 295 self._kernel_alive = True
296 296
297 297 self.hb_stream.on_recv(beat_received)
298 298 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
299 299 self._hb_periodic_callback.start()
300 300 self._beating= True
301 301
302 302 def stop_hb(self):
303 303 """Stop the heartbeating and cancel all related callbacks."""
304 304 if self._beating:
305 305 self._hb_periodic_callback.stop()
306 306 if not self.hb_stream.closed():
307 307 self.hb_stream.on_recv(None)
308 308
309 309 def kernel_died(self):
310 310 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
311 311 self.write_message(
312 312 {'header': {'msg_type': 'status'},
313 313 'parent_header': {},
314 314 'content': {'execution_state':'dead'}
315 315 }
316 316 )
317 317 self.on_close()
318 318
319 319
320 320 class ShellHandler(AuthenticatedZMQStreamHandler):
321 321
322 322 def initialize(self, *args, **kwargs):
323 323 self.shell_stream = None
324 324
325 325 def on_first_message(self, msg):
326 326 try:
327 327 super(ShellHandler, self).on_first_message(msg)
328 328 except web.HTTPError:
329 329 self.close()
330 330 return
331 331 km = self.application.kernel_manager
332 332 self.max_msg_size = km.max_msg_size
333 333 kernel_id = self.kernel_id
334 334 try:
335 335 self.shell_stream = km.create_shell_stream(kernel_id)
336 336 except web.HTTPError:
337 337 # WebSockets don't response to traditional error codes so we
338 338 # close the connection.
339 339 if not self.stream.closed():
340 340 self.stream.close()
341 341 self.close()
342 342 else:
343 343 self.shell_stream.on_recv(self._on_zmq_reply)
344 344
345 345 def on_message(self, msg):
346 346 if len(msg) < self.max_msg_size:
347 347 msg = jsonapi.loads(msg)
348 348 self.session.send(self.shell_stream, msg)
349 349
350 350 def on_close(self):
351 351 # Make sure the stream exists and is not already closed.
352 352 if self.shell_stream is not None and not self.shell_stream.closed():
353 353 self.shell_stream.close()
354 354
355 355
356 356 #-----------------------------------------------------------------------------
357 357 # Notebook web service handlers
358 358 #-----------------------------------------------------------------------------
359 359
360 360 class NotebookRootHandler(AuthenticatedHandler):
361 361
362 362 @web.authenticated
363 363 def get(self):
364 364 nbm = self.application.notebook_manager
365 365 files = nbm.list_notebooks()
366 366 self.finish(jsonapi.dumps(files))
367 367
368 368 @web.authenticated
369 369 def post(self):
370 370 nbm = self.application.notebook_manager
371 371 body = self.request.body.strip()
372 372 format = self.get_argument('format', default='json')
373 373 name = self.get_argument('name', default=None)
374 374 if body:
375 375 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
376 376 else:
377 377 notebook_id = nbm.new_notebook()
378 378 self.set_header('Location', '/'+notebook_id)
379 379 self.finish(jsonapi.dumps(notebook_id))
380 380
381 381
382 382 class NotebookHandler(AuthenticatedHandler):
383 383
384 384 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
385 385
386 386 @web.authenticated
387 387 def get(self, notebook_id):
388 388 nbm = self.application.notebook_manager
389 389 format = self.get_argument('format', default='json')
390 390 last_mod, name, data = nbm.get_notebook(notebook_id, format)
391 391 if format == u'json':
392 392 self.set_header('Content-Type', 'application/json')
393 393 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
394 394 elif format == u'py':
395 395 self.set_header('Content-Type', 'application/x-python')
396 396 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
397 397 self.set_header('Last-Modified', last_mod)
398 398 self.finish(data)
399 399
400 400 @web.authenticated
401 401 def put(self, notebook_id):
402 402 nbm = self.application.notebook_manager
403 403 format = self.get_argument('format', default='json')
404 404 name = self.get_argument('name', default=None)
405 405 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
406 406 self.set_status(204)
407 407 self.finish()
408 408
409 409 @web.authenticated
410 410 def delete(self, notebook_id):
411 411 nbm = self.application.notebook_manager
412 412 nbm.delete_notebook(notebook_id)
413 413 self.set_status(204)
414 414 self.finish()
415 415
416 416 #-----------------------------------------------------------------------------
417 417 # RST web service handlers
418 418 #-----------------------------------------------------------------------------
419 419
420 420
421 421 class RSTHandler(AuthenticatedHandler):
422 422
423 423 @web.authenticated
424 424 def post(self):
425 425 if publish_string is None:
426 426 raise web.HTTPError(503, u'docutils not available')
427 427 body = self.request.body.strip()
428 428 source = body
429 429 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
430 430 defaults = {'file_insertion_enabled': 0,
431 431 'raw_enabled': 0,
432 432 '_disable_config': 1,
433 433 'stylesheet_path': 0
434 434 # 'template': template_path
435 435 }
436 436 try:
437 437 html = publish_string(source, writer_name='html',
438 438 settings_overrides=defaults
439 439 )
440 440 except:
441 441 raise web.HTTPError(400, u'Invalid RST')
442 442 print html
443 443 self.set_header('Content-Type', 'text/html')
444 444 self.finish(html)
445 445
446 446
@@ -1,62 +1,53 b''
1 1 <!DOCTYPE HTML>
2 2 <html>
3 3
4 4 <head>
5 5 <meta charset="utf-8">
6 6
7 7 <title>IPython Notebook</title>
8 8
9 9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
11 11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
13 <script type="text/javascript" charset="utf-8">
14 function add_next_to_action(){
15 // add 'next' argument to action url, to preserve redirect
16 var query = location.search.substring(1);
17 var form = document.forms[0];
18 var action = form.getAttribute("action");
19 form.setAttribute("action", action + '?' + query);
20 }
21 </script>
13
22 14 </head>
23 15
24 <body onload="add_next_to_action()">
16 <body>
25 17
26 18 <div id="header">
27 19 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
28 20 </div>
29 21
30 22 <div id="header_border"></div>
31 23
32 24 <div id="main_app">
33 25
34 26 <div id="app_hbox">
35 27
36 28 <div id="left_panel">
37 29 </div>
38 30
39 31 <div id="content_panel">
40 <form action="/login" method="post">
32 <form action="/login?next={{url_escape(next)}}" method="post">
41 33 Password: <input type="password" name="password">
42 34 <input type="submit" value="Sign in">
43 35 </form>
44 36 </div>
45 37 <div id="right_panel">
46 38 </div>
47 39
48 40 </div>
49 41
50 42 </div>
51 43
52 44 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
53 45 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
54 46 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
55 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
56 <script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
47 <script src="static/js/login_main.js" type="text/javascript" charset="utf-8"></script>
57 48
58 49 </body>
59 50
60 51 </html>
61 52
62 53
General Comments 0
You need to be logged in to leave comments. Login now