##// END OF EJS Templates
Split read-only logic into three functions: read_only, logged_in, and login_available. Move display logic from javascript into templates.
Stefan van der Walt -
Show More
@@ -1,589 +1,614
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.external.decorator import decorator
30 30 from IPython.zmq.session import Session
31 31 from IPython.lib.security import passwd_check
32 32
33 33 try:
34 34 from docutils.core import publish_string
35 35 except ImportError:
36 36 publish_string = None
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
40 40 #-----------------------------------------------------------------------------
41 41
42 42 # Google Chrome, as of release 16, changed its websocket protocol number. The
43 43 # parts tornado cares about haven't really changed, so it's OK to continue
44 44 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
45 45 # version as of Oct 30/2011) the version check fails, see the issue report:
46 46
47 47 # https://github.com/facebook/tornado/issues/385
48 48
49 49 # This issue has been fixed in Tornado post 2.1.1:
50 50
51 51 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
52 52
53 53 # Here we manually apply the same patch as above so that users of IPython can
54 54 # continue to work with an officially released Tornado. We make the
55 55 # monkeypatch version check as narrow as possible to limit its effects; once
56 56 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
57 57
58 58 import tornado
59 59
60 60 if tornado.version_info <= (2,1,1):
61 61
62 62 def _execute(self, transforms, *args, **kwargs):
63 63 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
64 64
65 65 self.open_args = args
66 66 self.open_kwargs = kwargs
67 67
68 68 # The difference between version 8 and 13 is that in 8 the
69 69 # client sends a "Sec-Websocket-Origin" header and in 13 it's
70 70 # simply "Origin".
71 71 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
72 72 self.ws_connection = WebSocketProtocol8(self)
73 73 self.ws_connection.accept_connection()
74 74
75 75 elif self.request.headers.get("Sec-WebSocket-Version"):
76 76 self.stream.write(tornado.escape.utf8(
77 77 "HTTP/1.1 426 Upgrade Required\r\n"
78 78 "Sec-WebSocket-Version: 8\r\n\r\n"))
79 79 self.stream.close()
80 80
81 81 else:
82 82 self.ws_connection = WebSocketProtocol76(self)
83 83 self.ws_connection.accept_connection()
84 84
85 85 websocket.WebSocketHandler._execute = _execute
86 86 del _execute
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Decorator for disabling read-only handlers
90 90 #-----------------------------------------------------------------------------
91 91
92 92 @decorator
93 93 def not_if_readonly(f, self, *args, **kwargs):
94 94 if self.application.read_only:
95 95 raise web.HTTPError(403, "Notebook server is read-only")
96 96 else:
97 97 return f(self, *args, **kwargs)
98 98
99 99 @decorator
100 100 def authenticate_unless_readonly(f, self, *args, **kwargs):
101 101 """authenticate this page *unless* readonly view is active.
102 102
103 103 In read-only mode, the notebook list and print view should
104 104 be accessible without authentication.
105 105 """
106 106
107 107 @web.authenticated
108 108 def auth_f(self, *args, **kwargs):
109 109 return f(self, *args, **kwargs)
110 110 if self.application.read_only:
111 111 return f(self, *args, **kwargs)
112 112 else:
113 113 return auth_f(self, *args, **kwargs)
114 114
115 115 #-----------------------------------------------------------------------------
116 116 # Top-level handlers
117 117 #-----------------------------------------------------------------------------
118 118
119 119 class RequestHandler(web.RequestHandler):
120 120 """RequestHandler with default variable setting."""
121 121
122 122 def render(*args, **kwargs):
123 123 kwargs.setdefault('message', '')
124 124 return web.RequestHandler.render(*args, **kwargs)
125 125
126 126 class AuthenticatedHandler(RequestHandler):
127 127 """A RequestHandler with an authenticated user."""
128 128
129 129 def get_current_user(self):
130 130 user_id = self.get_secure_cookie("username")
131 131 # For now the user_id should not return empty, but it could eventually
132 132 if user_id == '':
133 133 user_id = 'anonymous'
134 134 if user_id is None:
135 135 # prevent extra Invalid cookie sig warnings:
136 136 self.clear_cookie('username')
137 137 if not self.application.password and not self.application.read_only:
138 138 user_id = 'anonymous'
139 139 return user_id
140 140
141 141 @property
142 def logged_in(self):
143 """Is a user currently logged in?
144
145 """
146 user = self.get_current_user()
147 return (user and not user == 'anonymous')
148
149 @property
150 def login_available(self):
151 """May a user proceed to log in?
152
153 This returns True if login capability is available, irrespective of
154 whether the user is already logged in or not.
155
156 """
157 return bool(self.application.password)
158
159 @property
142 160 def read_only(self):
143 161 """Is the notebook read-only?
144 162
145 None -- notebook is read-only, but the user can log-in to edit
146 True -- notebook is read-only, no log-in available
147 False -- no read-only mode available, user must log in
148
149 163 """
150 user = self.get_current_user()
151 if user and user != 'anonymous':
152 return False
153 elif self.application.read_only:
154 if self.application.password:
155 return None
156 else:
157 return True
164 return self.application.read_only
158 165
159 166 @property
160 167 def ws_url(self):
161 168 """websocket url matching the current request
162 169
163 170 turns http[s]://host[:port] into
164 171 ws[s]://host[:port]
165 172 """
166 173 proto = self.request.protocol.replace('http', 'ws')
167 174 return "%s://%s" % (proto, self.request.host)
168 175
169 176
170 177 class ProjectDashboardHandler(AuthenticatedHandler):
171 178
172 179 @authenticate_unless_readonly
173 180 def get(self):
174 181 nbm = self.application.notebook_manager
175 182 project = nbm.notebook_dir
176 183 self.render(
177 184 'projectdashboard.html', project=project,
178 185 base_project_url=u'/', base_kernel_url=u'/',
179 186 read_only=self.read_only,
187 logged_in=self.logged_in,
188 login_available=self.login_available
180 189 )
181 190
182 191
183 192 class LoginHandler(AuthenticatedHandler):
184 193
185 194 def _render(self, message=None):
186 195 self.render('login.html',
187 196 next=self.get_argument('next', default='/'),
188 197 read_only=self.read_only,
198 logged_in=self.logged_in,
199 login_available=self.login_available,
189 200 message=message
190 201 )
191 202
192 203 def get(self):
193 204 if self.current_user:
194 205 self.redirect(self.get_argument('next', default='/'))
195 206 else:
196 207 self._render()
197 208
198 209 def post(self):
199 210 pwd = self.get_argument('password', default=u'')
200 211 if self.application.password:
201 212 if passwd_check(self.application.password, pwd):
202 213 self.set_secure_cookie('username', str(uuid.uuid4()))
203 214 else:
204 215 self._render(message={'error': 'Invalid password'})
205 216 return
206 217
207 218 self.redirect(self.get_argument('next', default='/'))
208 219
209 220
210 221 class LogoutHandler(AuthenticatedHandler):
211 222
212 223 def get(self):
213 224 self.clear_cookie('username')
214 self.render('logout.html', message={'info': 'Successfully logged out.'})
225 if self.login_available:
226 message = {'info': 'Successfully logged out.'}
227 else:
228 message = {'warning': 'Cannot log out. Notebook authentication '
229 'is disabled.'}
230
231 self.render('logout.html',
232 read_only=self.read_only,
233 logged_in=self.logged_in,
234 login_available=self.login_available,
235 message=message)
215 236
216 237
217 238 class NewHandler(AuthenticatedHandler):
218 239
219 240 @web.authenticated
220 241 def get(self):
221 242 nbm = self.application.notebook_manager
222 243 project = nbm.notebook_dir
223 244 notebook_id = nbm.new_notebook()
224 245 self.render(
225 246 'notebook.html', project=project,
226 247 notebook_id=notebook_id,
227 248 base_project_url=u'/', base_kernel_url=u'/',
228 249 kill_kernel=False,
229 250 read_only=False,
251 logged_in=self.logged_in,
252 login_available=self.login_available,
230 253 mathjax_url=self.application.ipython_app.mathjax_url,
231 254 )
232 255
233 256
234 257 class NamedNotebookHandler(AuthenticatedHandler):
235 258
236 259 @authenticate_unless_readonly
237 260 def get(self, notebook_id):
238 261 nbm = self.application.notebook_manager
239 262 project = nbm.notebook_dir
240 263 if not nbm.notebook_exists(notebook_id):
241 264 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
242 265
243 266 self.render(
244 267 'notebook.html', project=project,
245 268 notebook_id=notebook_id,
246 269 base_project_url=u'/', base_kernel_url=u'/',
247 270 kill_kernel=False,
248 271 read_only=self.read_only,
272 logged_in=self.logged_in,
273 login_available=self.login_available,
249 274 mathjax_url=self.application.ipython_app.mathjax_url,
250 275 )
251 276
252 277
253 278 #-----------------------------------------------------------------------------
254 279 # Kernel handlers
255 280 #-----------------------------------------------------------------------------
256 281
257 282
258 283 class MainKernelHandler(AuthenticatedHandler):
259 284
260 285 @web.authenticated
261 286 def get(self):
262 287 km = self.application.kernel_manager
263 288 self.finish(jsonapi.dumps(km.kernel_ids))
264 289
265 290 @web.authenticated
266 291 def post(self):
267 292 km = self.application.kernel_manager
268 293 notebook_id = self.get_argument('notebook', default=None)
269 294 kernel_id = km.start_kernel(notebook_id)
270 295 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
271 296 self.set_header('Location', '/'+kernel_id)
272 297 self.finish(jsonapi.dumps(data))
273 298
274 299
275 300 class KernelHandler(AuthenticatedHandler):
276 301
277 302 SUPPORTED_METHODS = ('DELETE')
278 303
279 304 @web.authenticated
280 305 def delete(self, kernel_id):
281 306 km = self.application.kernel_manager
282 307 km.kill_kernel(kernel_id)
283 308 self.set_status(204)
284 309 self.finish()
285 310
286 311
287 312 class KernelActionHandler(AuthenticatedHandler):
288 313
289 314 @web.authenticated
290 315 def post(self, kernel_id, action):
291 316 km = self.application.kernel_manager
292 317 if action == 'interrupt':
293 318 km.interrupt_kernel(kernel_id)
294 319 self.set_status(204)
295 320 if action == 'restart':
296 321 new_kernel_id = km.restart_kernel(kernel_id)
297 322 data = {'ws_url':self.ws_url,'kernel_id':new_kernel_id}
298 323 self.set_header('Location', '/'+new_kernel_id)
299 324 self.write(jsonapi.dumps(data))
300 325 self.finish()
301 326
302 327
303 328 class ZMQStreamHandler(websocket.WebSocketHandler):
304 329
305 330 def _reserialize_reply(self, msg_list):
306 331 """Reserialize a reply message using JSON.
307 332
308 333 This takes the msg list from the ZMQ socket, unserializes it using
309 334 self.session and then serializes the result using JSON. This method
310 335 should be used by self._on_zmq_reply to build messages that can
311 336 be sent back to the browser.
312 337 """
313 338 idents, msg_list = self.session.feed_identities(msg_list)
314 339 msg = self.session.unserialize(msg_list)
315 340 try:
316 341 msg['header'].pop('date')
317 342 except KeyError:
318 343 pass
319 344 try:
320 345 msg['parent_header'].pop('date')
321 346 except KeyError:
322 347 pass
323 348 msg.pop('buffers')
324 349 return jsonapi.dumps(msg)
325 350
326 351 def _on_zmq_reply(self, msg_list):
327 352 try:
328 353 msg = self._reserialize_reply(msg_list)
329 354 except:
330 355 self.application.log.critical("Malformed message: %r" % msg_list)
331 356 else:
332 357 self.write_message(msg)
333 358
334 359
335 360 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
336 361
337 362 def open(self, kernel_id):
338 363 self.kernel_id = kernel_id.decode('ascii')
339 364 try:
340 365 cfg = self.application.ipython_app.config
341 366 except AttributeError:
342 367 # protect from the case where this is run from something other than
343 368 # the notebook app:
344 369 cfg = None
345 370 self.session = Session(config=cfg)
346 371 self.save_on_message = self.on_message
347 372 self.on_message = self.on_first_message
348 373
349 374 def get_current_user(self):
350 375 user_id = self.get_secure_cookie("username")
351 376 if user_id == '' or (user_id is None and not self.application.password):
352 377 user_id = 'anonymous'
353 378 return user_id
354 379
355 380 def _inject_cookie_message(self, msg):
356 381 """Inject the first message, which is the document cookie,
357 382 for authentication."""
358 383 if isinstance(msg, unicode):
359 384 # Cookie can't constructor doesn't accept unicode strings for some reason
360 385 msg = msg.encode('utf8', 'replace')
361 386 try:
362 387 self.request._cookies = Cookie.SimpleCookie(msg)
363 388 except:
364 389 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
365 390
366 391 def on_first_message(self, msg):
367 392 self._inject_cookie_message(msg)
368 393 if self.get_current_user() is None:
369 394 logging.warn("Couldn't authenticate WebSocket connection")
370 395 raise web.HTTPError(403)
371 396 self.on_message = self.save_on_message
372 397
373 398
374 399 class IOPubHandler(AuthenticatedZMQStreamHandler):
375 400
376 401 def initialize(self, *args, **kwargs):
377 402 self._kernel_alive = True
378 403 self._beating = False
379 404 self.iopub_stream = None
380 405 self.hb_stream = None
381 406
382 407 def on_first_message(self, msg):
383 408 try:
384 409 super(IOPubHandler, self).on_first_message(msg)
385 410 except web.HTTPError:
386 411 self.close()
387 412 return
388 413 km = self.application.kernel_manager
389 414 self.time_to_dead = km.time_to_dead
390 415 kernel_id = self.kernel_id
391 416 try:
392 417 self.iopub_stream = km.create_iopub_stream(kernel_id)
393 418 self.hb_stream = km.create_hb_stream(kernel_id)
394 419 except web.HTTPError:
395 420 # WebSockets don't response to traditional error codes so we
396 421 # close the connection.
397 422 if not self.stream.closed():
398 423 self.stream.close()
399 424 self.close()
400 425 else:
401 426 self.iopub_stream.on_recv(self._on_zmq_reply)
402 427 self.start_hb(self.kernel_died)
403 428
404 429 def on_message(self, msg):
405 430 pass
406 431
407 432 def on_close(self):
408 433 # This method can be called twice, once by self.kernel_died and once
409 434 # from the WebSocket close event. If the WebSocket connection is
410 435 # closed before the ZMQ streams are setup, they could be None.
411 436 self.stop_hb()
412 437 if self.iopub_stream is not None and not self.iopub_stream.closed():
413 438 self.iopub_stream.on_recv(None)
414 439 self.iopub_stream.close()
415 440 if self.hb_stream is not None and not self.hb_stream.closed():
416 441 self.hb_stream.close()
417 442
418 443 def start_hb(self, callback):
419 444 """Start the heartbeating and call the callback if the kernel dies."""
420 445 if not self._beating:
421 446 self._kernel_alive = True
422 447
423 448 def ping_or_dead():
424 449 if self._kernel_alive:
425 450 self._kernel_alive = False
426 451 self.hb_stream.send(b'ping')
427 452 else:
428 453 try:
429 454 callback()
430 455 except:
431 456 pass
432 457 finally:
433 458 self._hb_periodic_callback.stop()
434 459
435 460 def beat_received(msg):
436 461 self._kernel_alive = True
437 462
438 463 self.hb_stream.on_recv(beat_received)
439 464 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
440 465 self._hb_periodic_callback.start()
441 466 self._beating= True
442 467
443 468 def stop_hb(self):
444 469 """Stop the heartbeating and cancel all related callbacks."""
445 470 if self._beating:
446 471 self._hb_periodic_callback.stop()
447 472 if not self.hb_stream.closed():
448 473 self.hb_stream.on_recv(None)
449 474
450 475 def kernel_died(self):
451 476 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
452 477 self.write_message(
453 478 {'header': {'msg_type': 'status'},
454 479 'parent_header': {},
455 480 'content': {'execution_state':'dead'}
456 481 }
457 482 )
458 483 self.on_close()
459 484
460 485
461 486 class ShellHandler(AuthenticatedZMQStreamHandler):
462 487
463 488 def initialize(self, *args, **kwargs):
464 489 self.shell_stream = None
465 490
466 491 def on_first_message(self, msg):
467 492 try:
468 493 super(ShellHandler, self).on_first_message(msg)
469 494 except web.HTTPError:
470 495 self.close()
471 496 return
472 497 km = self.application.kernel_manager
473 498 self.max_msg_size = km.max_msg_size
474 499 kernel_id = self.kernel_id
475 500 try:
476 501 self.shell_stream = km.create_shell_stream(kernel_id)
477 502 except web.HTTPError:
478 503 # WebSockets don't response to traditional error codes so we
479 504 # close the connection.
480 505 if not self.stream.closed():
481 506 self.stream.close()
482 507 self.close()
483 508 else:
484 509 self.shell_stream.on_recv(self._on_zmq_reply)
485 510
486 511 def on_message(self, msg):
487 512 if len(msg) < self.max_msg_size:
488 513 msg = jsonapi.loads(msg)
489 514 self.session.send(self.shell_stream, msg)
490 515
491 516 def on_close(self):
492 517 # Make sure the stream exists and is not already closed.
493 518 if self.shell_stream is not None and not self.shell_stream.closed():
494 519 self.shell_stream.close()
495 520
496 521
497 522 #-----------------------------------------------------------------------------
498 523 # Notebook web service handlers
499 524 #-----------------------------------------------------------------------------
500 525
501 526 class NotebookRootHandler(AuthenticatedHandler):
502 527
503 528 @authenticate_unless_readonly
504 529 def get(self):
505 530
506 531 nbm = self.application.notebook_manager
507 532 files = nbm.list_notebooks()
508 533 self.finish(jsonapi.dumps(files))
509 534
510 535 @web.authenticated
511 536 def post(self):
512 537 nbm = self.application.notebook_manager
513 538 body = self.request.body.strip()
514 539 format = self.get_argument('format', default='json')
515 540 name = self.get_argument('name', default=None)
516 541 if body:
517 542 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
518 543 else:
519 544 notebook_id = nbm.new_notebook()
520 545 self.set_header('Location', '/'+notebook_id)
521 546 self.finish(jsonapi.dumps(notebook_id))
522 547
523 548
524 549 class NotebookHandler(AuthenticatedHandler):
525 550
526 551 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
527 552
528 553 @authenticate_unless_readonly
529 554 def get(self, notebook_id):
530 555 nbm = self.application.notebook_manager
531 556 format = self.get_argument('format', default='json')
532 557 last_mod, name, data = nbm.get_notebook(notebook_id, format)
533 558
534 559 if format == u'json':
535 560 self.set_header('Content-Type', 'application/json')
536 561 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
537 562 elif format == u'py':
538 563 self.set_header('Content-Type', 'application/x-python')
539 564 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
540 565 self.set_header('Last-Modified', last_mod)
541 566 self.finish(data)
542 567
543 568 @web.authenticated
544 569 def put(self, notebook_id):
545 570 nbm = self.application.notebook_manager
546 571 format = self.get_argument('format', default='json')
547 572 name = self.get_argument('name', default=None)
548 573 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
549 574 self.set_status(204)
550 575 self.finish()
551 576
552 577 @web.authenticated
553 578 def delete(self, notebook_id):
554 579 nbm = self.application.notebook_manager
555 580 nbm.delete_notebook(notebook_id)
556 581 self.set_status(204)
557 582 self.finish()
558 583
559 584 #-----------------------------------------------------------------------------
560 585 # RST web service handlers
561 586 #-----------------------------------------------------------------------------
562 587
563 588
564 589 class RSTHandler(AuthenticatedHandler):
565 590
566 591 @web.authenticated
567 592 def post(self):
568 593 if publish_string is None:
569 594 raise web.HTTPError(503, u'docutils not available')
570 595 body = self.request.body.strip()
571 596 source = body
572 597 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
573 598 defaults = {'file_insertion_enabled': 0,
574 599 'raw_enabled': 0,
575 600 '_disable_config': 1,
576 601 'stylesheet_path': 0
577 602 # 'template': template_path
578 603 }
579 604 try:
580 605 html = publish_string(source, writer_name='html',
581 606 settings_overrides=defaults
582 607 )
583 608 except:
584 609 raise web.HTTPError(400, u'Invalid RST')
585 610 print html
586 611 self.set_header('Content-Type', 'text/html')
587 612 self.finish(html)
588 613
589 614
@@ -1,51 +1,42
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 14
15 15 $('div#header').addClass('border-box-sizing');
16 16 $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
17 17
18 18 $('div#main_app').addClass('border-box-sizing ui-widget');
19 19 $('div#app_hbox').addClass('hbox');
20 20
21 21 $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix');
22 22
23 23 $('#new_notebook').button().click(function (e) {
24 24 window.open($('body').data('baseProjectUrl')+'new');
25 25 });
26 26
27 27 $('div#left_panel').addClass('box-flex');
28 28 $('div#right_panel').addClass('box-flex');
29 29
30 30 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
31
32 31 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
33 32 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
34
35 if (IPython.read_only){
36 // unhide login button if it's relevant
37 $('span#login_widget').removeClass('hidden');
38 $('#drag_info').remove();
39 } else {
40 $('#new_notebook').removeClass('hidden');
41 $('#drag_info').removeClass('hidden');
42 }
33
43 34 IPython.notebook_list.load_list();
44 35
45 36 // These have display: none in the css file and are made visible here to prevent FLOUC.
46 37 $('div#header').css('display','block');
47 38 $('div#main_app').css('display','block');
48 39
49 40
50 41 });
51 42
@@ -1,79 +1,79
1 1 <!DOCTYPE HTML>
2 2 <html>
3 3
4 4 <head>
5 5 <meta charset="utf-8">
6 6
7 7 <title>{% block title %}IPython Notebook{% end %}</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 13 {% block stylesheet %}
14 14 {% end %}
15 15
16 16 {% block meta %}
17 17 {% end %}
18 18
19 19 </head>
20 20
21 21 <body {% block params %}{% end %}>
22 22
23 23 <div id="header">
24 24 <span id="ipython_notebook"><h1><img src='static/ipynblogo.png' alt='IPython Notebook'/></h1></span>
25 25 <span id="login_widget">
26 {% if current_user and current_user != 'anonymous' %}
26 {% if logged_in %}
27 27 <button id="logout">Logout</button>
28 {% elif read_only is None %}
28 {% elif login_available and not logged_in %}
29 29 <button id="login">Login</button>
30 30 {% end %}
31 31 </span>
32 32 {% block header %}
33 33 {% end %}
34 34 </div>
35 35
36 36 <div id="header_border"></div>
37 37
38 38 <div id="main_app">
39 39
40 40 <div id="app_hbox">
41 41
42 42 <div id="left_panel">
43 43 {% block left_panel %}
44 44 {% end %}
45 45 </div>
46 46
47 47 <div id="content_panel">
48 48 {% if message %}
49 49
50 50 {% for key in message %}
51 51 <div class="message {{key}}">
52 52 {{message[key]}}
53 53 </div>
54 54 {% end %}
55 55 {% end %}
56 56
57 57 {% block content_panel %}
58 58 {% end %}
59 59 </div>
60 60 <div id="right_panel">
61 61 {% block right_panel %}
62 62 {% end %}
63 63 </div>
64 64
65 65 </div>
66 66
67 67 </div>
68 68
69 69 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
70 70 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
71 71 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
72 72 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
73 73 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
74 74 {% block script %}
75 75 {% end %}
76 76
77 77 </body>
78 78
79 79 </html>
@@ -1,17 +1,23
1 1 {% extends layout.html %}
2 2
3 3 {% block content_panel %}
4
5 {% if login_available %}
6
4 7 <form action="/login?next={{url_escape(next)}}" method="post">
5 8 Password: <input type="password" name="password" id="focus">
6 9 <input type="submit" value="Sign in" id="signin">
7 10 </form>
11
12 {% end %}
13
8 14 {% end %}
9 15
10 16 {% block script %}
11 17 <script type="text/javascript">
12 18 $(document).ready(function() {
13 19 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
14 20 $('#focus').focus();
15 21 });
16 22 </script>
17 23 {% end %}
@@ -1,17 +1,25
1 1 {% extends layout.html %}
2 2
3 3 {% block content_panel %}
4 {% if current_user and current_user != 'anonymous' %}
5 Proceed to the <a href="/">front page</a>.
6 {% else %}
7 Proceed to the <a href="/login">login page</a>.
8 {% end %}
4 <ul>
5 {% if read_only or not login_available %}
6
7 Proceed to the <a href="/">list of notebooks</a>.</li>
8
9 {% else %}
10
11 Proceed to the <a href="/login">login page</a>.</li>
12
13 {% end %}
14
15 </ul>
16
9 17 {% end %}
10 18
11 19 {% block script %}
12 20 <script type="text/javascript">
13 21 $(document).ready(function() {
14 22 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
15 23 });
16 24 </script>
17 25 {% end %}
@@ -1,296 +1,296
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 {% if mathjax_url %}
10 10 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML" charset="utf-8"></script>
11 11 {% end %}
12 12 <script type="text/javascript">
13 13 // MathJax disabled, set as null to distingish from *missing* MathJax,
14 14 // where it will be undefined, and should prompt a dialog later.
15 15 window.mathjax_url = "{{mathjax_url}}";
16 16 </script>
17 17
18 18 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
19 19 <link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
20 20 <link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
21 21 <link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
22 22 <link rel="stylesheet" href="static/codemirror/theme/ipython.css">
23 23 <link rel="stylesheet" href="static/codemirror/theme/default.css">
24 24
25 25 <link rel="stylesheet" href="static/prettify/prettify.css"/>
26 26
27 27 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
28 28 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
29 29 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
30 30 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
31 31 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
32 32
33 33 <meta name="read_only" content="{{read_only}}"/>
34 34
35 35 </head>
36 36
37 37 <body
38 38 data-project={{project}} data-notebook-id={{notebook_id}}
39 39 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
40 40 >
41 41
42 42 <div id="header">
43 43 <span id="ipython_notebook"><h1><a href='..' alt='dashboard'><img src='static/ipynblogo.png' alt='IPython Notebook'/></a></h1></span>
44 44 <span id="save_widget">
45 45 <input type="text" id="notebook_name" size="20"></textarea>
46 46 <button id="save_notebook"><u>S</u>ave</button>
47 47 </span>
48 48 <span id="quick_help_area">
49 49 <button id="quick_help">Quick<u>H</u>elp</button>
50 50 </span>
51 51
52 52 <span id="login_widget">
53 53 {% comment This is a temporary workaround to hide the logout button %}
54 54 {% comment when appropriate until notebook.html is templated %}
55 {% if current_user and current_user != 'anonymous' %}
55 {% if logged_in %}
56 56 <button id="logout">Logout</button>
57 {% elif read_only is None %}
57 {% elif not logged_in and login_available %}
58 58 <button id="login">Login</button>
59 59 {% end %}
60 60 </span>
61 61
62 62 <span id="kernel_status">Idle</span>
63 63 </div>
64 64
65 65 <div id="main_app">
66 66
67 67 <div id="left_panel">
68 68
69 69 <div id="notebook_section">
70 70 <div class="section_header">
71 71 <h3>Notebook</h3>
72 72 </div>
73 73 <div class="section_content">
74 74 <div class="section_row">
75 75 <span id="new_open" class="section_row_buttons">
76 76 <button id="new_notebook">New</button>
77 77 <button id="open_notebook">Open</button>
78 78 </span>
79 79 <span class="section_row_header">Actions</span>
80 80 </div>
81 81 <div class="section_row">
82 82 <span>
83 83 <select id="download_format">
84 84 <option value="json">ipynb</option>
85 85 <option value="py">py</option>
86 86 </select>
87 87 </span>
88 88 <span class="section_row_buttons">
89 89 <button id="download_notebook">Download</button>
90 90 </span>
91 91 </div>
92 92 <div class="section_row">
93 93 <span class="section_row_buttons">
94 94 <span id="print_widget">
95 95 <button id="print_notebook">Print</button>
96 96 </span>
97 97 </span>
98 98 </div>
99 99 </div>
100 100 </div>
101 101
102 102 <div id="cell_section">
103 103 <div class="section_header">
104 104 <h3>Cell</h3>
105 105 </div>
106 106 <div class="section_content">
107 107 <div class="section_row">
108 108 <span class="section_row_buttons">
109 109 <button id="delete_cell"><u>D</u>elete</button>
110 110 </span>
111 111 <span class="section_row_header">Actions</span>
112 112 </div>
113 113 <div class="section_row">
114 114 <span id="cell_type" class="section_row_buttons">
115 115 <button id="to_code"><u>C</u>ode</button>
116 116 <!-- <button id="to_html">HTML</button>-->
117 117 <button id="to_markdown"><u>M</u>arkdown</button>
118 118 </span>
119 119 <span class="button_label">Format</span>
120 120 </div>
121 121 <div class="section_row">
122 122 <span id="cell_output" class="section_row_buttons">
123 123 <button id="toggle_output"><u>T</u>oggle</button>
124 124 <button id="clear_all_output">ClearAll</button>
125 125 </span>
126 126 <span class="button_label">Output</span>
127 127 </div>
128 128 <div class="section_row">
129 129 <span id="insert" class="section_row_buttons">
130 130 <button id="insert_cell_above"><u>A</u>bove</button>
131 131 <button id="insert_cell_below"><u>B</u>elow</button>
132 132 </span>
133 133 <span class="button_label">Insert</span>
134 134 </div>
135 135 <div class="section_row">
136 136 <span id="move" class="section_row_buttons">
137 137 <button id="move_cell_up">Up</button>
138 138 <button id="move_cell_down">Down</button>
139 139 </span>
140 140 <span class="button_label">Move</span>
141 141 </div>
142 142 <div class="section_row">
143 143 <span id="run_cells" class="section_row_buttons">
144 144 <button id="run_selected_cell">Selected</button>
145 145 <button id="run_all_cells">All</button>
146 146 </span>
147 147 <span class="button_label">Run</span>
148 148 </div>
149 149 <div class="section_row">
150 150 <span id="autoindent_span">
151 151 <input type="checkbox" id="autoindent" checked="true"></input>
152 152 </span>
153 153 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
154 154 </div>
155 155 </div>
156 156 </div>
157 157
158 158 <div id="kernel_section">
159 159 <div class="section_header">
160 160 <h3>Kernel</h3>
161 161 </div>
162 162 <div class="section_content">
163 163 <div class="section_row">
164 164 <span id="int_restart" class="section_row_buttons">
165 165 <button id="int_kernel"><u>I</u>nterrupt</button>
166 166 <button id="restart_kernel">Restart</button>
167 167 </span>
168 168 <span class="section_row_header">Actions</span>
169 169 </div>
170 170 <div class="section_row">
171 171 <span id="kernel_persist">
172 172 {% if kill_kernel %}
173 173 <input type="checkbox" id="kill_kernel" checked="true"></input>
174 174 {% else %}
175 175 <input type="checkbox" id="kill_kernel"></input>
176 176 {% end %}
177 177 </span>
178 178 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
179 179 </div>
180 180 </div>
181 181 </div>
182 182
183 183 <div id="help_section">
184 184 <div class="section_header">
185 185 <h3>Help</h3>
186 186 </div>
187 187 <div class="section_content">
188 188 <div class="section_row">
189 189 <span id="help_buttons0" class="section_row_buttons">
190 190 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
191 191 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
192 192 </span>
193 193 <span class="section_row_header">Links</span>
194 194 </div>
195 195 <div class="section_row">
196 196 <span id="help_buttons1" class="section_row_buttons">
197 197 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
198 198 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
199 199 </span>
200 200 </div>
201 201 <div class="section_row">
202 202 <span id="help_buttons2" class="section_row_buttons">
203 203 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
204 204 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
205 205 </span>
206 206 </div>
207 207 <div class="section_row">
208 208 <span class="help_string">run selected cell</span>
209 209 <span class="help_string_label">Shift-Enter :</span>
210 210 </div>
211 211 <div class="section_row">
212 212 <span class="help_string">run selected cell in-place</span>
213 213 <span class="help_string_label">Ctrl-Enter :</span>
214 214 </div>
215 215 <div class="section_row">
216 216 <span class="help_string">show keyboard shortcuts</span>
217 217 <span class="help_string_label">Ctrl-m h :</span>
218 218 </div>
219 219 </div>
220 220 </div>
221 221
222 222 <div id="config_section">
223 223 <div class="section_header">
224 224 <h3>Configuration</h3>
225 225 </div>
226 226 <div class="section_content">
227 227 <div class="section_row">
228 228 <span id="tooltipontab_span">
229 229 <input type="checkbox" id="tooltipontab" checked="true"></input>
230 230 </span>
231 231 <span class="checkbox_label" id="tooltipontab_label">Tooltip on tab:</span>
232 232 </div>
233 233 <div class="section_row">
234 234 <span id="smartcompleter_span">
235 235 <input type="checkbox" id="smartcompleter" checked="true"></input>
236 236 </span>
237 237 <span class="checkbox_label" id="smartcompleter_label">Smart completer:</span>
238 238 </div>
239 239 <div class="section_row">
240 240 <span id="timebeforetooltip_span">
241 241 <input type="text" id="timebeforetooltip" value="1200" size='6'></input>
242 242 <span class="numeric_input_label" id="timebeforetooltip_unit">milliseconds</span>
243 243 </span>
244 244 <span class="numeric_input_label" id="timebeforetooltip_label">Time before tooltip : </span>
245 245 </div>
246 246 </div>
247 247 </div>
248 248
249 249 </div>
250 250 <div id="left_panel_splitter"></div>
251 251 <div id="notebook_panel">
252 252 <div id="notebook"></div>
253 253 <div id="pager_splitter"></div>
254 254 <div id="pager"></div>
255 255 </div>
256 256
257 257 </div>
258 258
259 259 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
260 260 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
261 261 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
262 262
263 263 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
264 264 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
265 265 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
266 266 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
267 267 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
268 268 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
269 269 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
270 270 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
271 271
272 272 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
273 273
274 274 <script src="static/prettify/prettify.js" charset="utf-8"></script>
275 275
276 276 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
277 277 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
278 278 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
279 279 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
280 280 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
281 281 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
282 282 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
283 283 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
284 284 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
285 285 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
286 286 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
287 287 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
288 288 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
289 289 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
290 290 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
291 291 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
292 292 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
293 293
294 294 </body>
295 295
296 296 </html>
@@ -1,41 +1,43
1 1 {% extends layout.html %}
2 2
3 3 {% block title %}
4 4 IPython Dashboard
5 5 {% end %}
6 6
7 7 {% block stylesheet %}
8 8 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
9 9 {% end %}
10 10
11 11 {% block meta %}
12 12 <meta name="read_only" content="{{read_only}}"/>
13 13 {% end %}
14 14
15 15 {% block params %}
16 16 data-project={{project}}
17 17 data-base-project-url={{base_project_url}}
18 18 data-base-kernel-url={{base_kernel_url}}
19 19 {% end %}
20 20
21 21 {% block content_panel %}
22 {% if logged_in or not read_only %}
23
22 24 <div id="content_toolbar">
23 <span id="drag_info" class="hidden">Drag files onto the list to import
25 <span id="drag_info">Drag files onto the list to import
24 26 notebooks.</span>
25 27
26 {% if read_only == False %}
27 <span id="notebooks_buttons">
28 <button id="new_notebook" class="hidden">New Notebook</button>
29 </span>
30 {% end %}
31
28 <span id="notebooks_buttons">
29 <button id="new_notebook">New Notebook</button>
30 </span>
32 31 </div>
32
33 {% end %}
34
33 35 <div id="notebook_list">
34 36 <div id="project_name"><h2>{{project}}</h2></div>
35 37 </div>
36 38 {% end %}
37 39
38 40 {% block script %}
39 41 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
40 42 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
41 43 {% end %}
General Comments 0
You need to be logged in to leave comments. Login now