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