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