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