##// END OF EJS Templates
move read_only flag to page-level...
MinRK -
Show More
@@ -1,491 +1,496 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
32 32 try:
33 33 from docutils.core import publish_string
34 34 except ImportError:
35 35 publish_string = None
36 36
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Decorator for disabling read-only handlers
40 40 #-----------------------------------------------------------------------------
41 41
42 42 @decorator
43 43 def not_if_readonly(f, self, *args, **kwargs):
44 if self.application.ipython_app.read_only:
44 if self.application.read_only:
45 45 raise web.HTTPError(403, "Notebook server is read-only")
46 46 else:
47 47 return f(self, *args, **kwargs)
48 48
49 49 @decorator
50 50 def authenticate_unless_readonly(f, self, *args, **kwargs):
51 51 """authenticate this page *unless* readonly view is active.
52 52
53 53 In read-only mode, the notebook list and print view should
54 54 be accessible without authentication.
55 55 """
56 56
57 57 @web.authenticated
58 58 def auth_f(self, *args, **kwargs):
59 59 return f(self, *args, **kwargs)
60 if self.application.ipython_app.read_only:
60 if self.application.read_only:
61 61 return f(self, *args, **kwargs)
62 62 else:
63 63 return auth_f(self, *args, **kwargs)
64 64
65 65 #-----------------------------------------------------------------------------
66 66 # Top-level handlers
67 67 #-----------------------------------------------------------------------------
68 68
69 69 class AuthenticatedHandler(web.RequestHandler):
70 70 """A RequestHandler with an authenticated user."""
71 71
72 72 def get_current_user(self):
73 73 user_id = self.get_secure_cookie("username")
74 74 # For now the user_id should not return empty, but it could eventually
75 75 if user_id == '':
76 76 user_id = 'anonymous'
77 77 if user_id is None:
78 78 # prevent extra Invalid cookie sig warnings:
79 79 self.clear_cookie('username')
80 if not self.application.password and not self.application.ipython_app.read_only:
80 if not self.application.password and not self.application.read_only:
81 81 user_id = 'anonymous'
82 82 return user_id
83
84 @property
85 def read_only(self):
86 if self.application.read_only:
87 if self.application.password:
88 return self.get_current_user() is None
89 else:
90 return True
91 else:
92 return False
93
83 94
84 95
85 96 class ProjectDashboardHandler(AuthenticatedHandler):
86 97
87 98 @authenticate_unless_readonly
88 99 def get(self):
89 100 nbm = self.application.notebook_manager
90 101 project = nbm.notebook_dir
91 102 self.render(
92 103 'projectdashboard.html', project=project,
93 base_project_url=u'/', base_kernel_url=u'/'
104 base_project_url=u'/', base_kernel_url=u'/',
105 read_only=self.read_only,
94 106 )
95 107
96 108
97 109 class LoginHandler(AuthenticatedHandler):
98 110
99 111 def get(self):
100 self.render('login.html', next=self.get_argument('next', default='/'))
112 self.render('login.html',
113 next=self.get_argument('next', default='/'),
114 read_only=self.read_only,
115 )
101 116
102 117 def post(self):
103 118 pwd = self.get_argument('password', default=u'')
104 119 if self.application.password and pwd == self.application.password:
105 120 self.set_secure_cookie('username', str(uuid.uuid4()))
106 url = self.get_argument('next', default='/')
107 self.redirect(url)
121 self.redirect(self.get_argument('next', default='/'))
108 122
109 123
110 124 class NewHandler(AuthenticatedHandler):
111 125
112 126 @web.authenticated
113 127 def get(self):
114 128 nbm = self.application.notebook_manager
115 129 project = nbm.notebook_dir
116 130 notebook_id = nbm.new_notebook()
117 131 self.render(
118 132 'notebook.html', project=project,
119 133 notebook_id=notebook_id,
120 134 base_project_url=u'/', base_kernel_url=u'/',
121 kill_kernel=False
135 kill_kernel=False,
136 read_only=False,
122 137 )
123 138
124 139
125 140 class NamedNotebookHandler(AuthenticatedHandler):
126 141
127 142 @authenticate_unless_readonly
128 143 def get(self, notebook_id):
129 144 nbm = self.application.notebook_manager
130 145 project = nbm.notebook_dir
131 146 if not nbm.notebook_exists(notebook_id):
132 147 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
148
133 149 self.render(
134 150 'notebook.html', project=project,
135 151 notebook_id=notebook_id,
136 152 base_project_url=u'/', base_kernel_url=u'/',
137 kill_kernel=False
153 kill_kernel=False,
154 read_only=self.read_only,
138 155 )
139 156
140 157
141 158 #-----------------------------------------------------------------------------
142 159 # Kernel handlers
143 160 #-----------------------------------------------------------------------------
144 161
145 162
146 163 class MainKernelHandler(AuthenticatedHandler):
147 164
148 165 @web.authenticated
149 166 def get(self):
150 167 km = self.application.kernel_manager
151 168 self.finish(jsonapi.dumps(km.kernel_ids))
152 169
153 170 @web.authenticated
154 171 def post(self):
155 172 km = self.application.kernel_manager
156 173 notebook_id = self.get_argument('notebook', default=None)
157 174 kernel_id = km.start_kernel(notebook_id)
158 175 ws_url = self.application.ipython_app.get_ws_url()
159 176 data = {'ws_url':ws_url,'kernel_id':kernel_id}
160 177 self.set_header('Location', '/'+kernel_id)
161 178 self.finish(jsonapi.dumps(data))
162 179
163 180
164 181 class KernelHandler(AuthenticatedHandler):
165 182
166 183 SUPPORTED_METHODS = ('DELETE')
167 184
168 185 @web.authenticated
169 186 def delete(self, kernel_id):
170 187 km = self.application.kernel_manager
171 188 km.kill_kernel(kernel_id)
172 189 self.set_status(204)
173 190 self.finish()
174 191
175 192
176 193 class KernelActionHandler(AuthenticatedHandler):
177 194
178 195 @web.authenticated
179 196 def post(self, kernel_id, action):
180 197 km = self.application.kernel_manager
181 198 if action == 'interrupt':
182 199 km.interrupt_kernel(kernel_id)
183 200 self.set_status(204)
184 201 if action == 'restart':
185 202 new_kernel_id = km.restart_kernel(kernel_id)
186 203 ws_url = self.application.ipython_app.get_ws_url()
187 204 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
188 205 self.set_header('Location', '/'+new_kernel_id)
189 206 self.write(jsonapi.dumps(data))
190 207 self.finish()
191 208
192 209
193 210 class ZMQStreamHandler(websocket.WebSocketHandler):
194 211
195 212 def _reserialize_reply(self, msg_list):
196 213 """Reserialize a reply message using JSON.
197 214
198 215 This takes the msg list from the ZMQ socket, unserializes it using
199 216 self.session and then serializes the result using JSON. This method
200 217 should be used by self._on_zmq_reply to build messages that can
201 218 be sent back to the browser.
202 219 """
203 220 idents, msg_list = self.session.feed_identities(msg_list)
204 221 msg = self.session.unserialize(msg_list)
205 222 try:
206 223 msg['header'].pop('date')
207 224 except KeyError:
208 225 pass
209 226 try:
210 227 msg['parent_header'].pop('date')
211 228 except KeyError:
212 229 pass
213 230 msg.pop('buffers')
214 231 return jsonapi.dumps(msg)
215 232
216 233 def _on_zmq_reply(self, msg_list):
217 234 try:
218 235 msg = self._reserialize_reply(msg_list)
219 236 except:
220 237 self.application.log.critical("Malformed message: %r" % msg_list)
221 238 else:
222 239 self.write_message(msg)
223 240
224 241
225 242 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
226 243
227 244 def open(self, kernel_id):
228 245 self.kernel_id = kernel_id.decode('ascii')
229 246 try:
230 247 cfg = self.application.ipython_app.config
231 248 except AttributeError:
232 249 # protect from the case where this is run from something other than
233 250 # the notebook app:
234 251 cfg = None
235 252 self.session = Session(config=cfg)
236 253 self.save_on_message = self.on_message
237 254 self.on_message = self.on_first_message
238 255
239 256 def get_current_user(self):
240 257 user_id = self.get_secure_cookie("username")
241 258 if user_id == '' or (user_id is None and not self.application.password):
242 259 user_id = 'anonymous'
243 260 return user_id
244 261
245 262 def _inject_cookie_message(self, msg):
246 263 """Inject the first message, which is the document cookie,
247 264 for authentication."""
248 265 if isinstance(msg, unicode):
249 266 # Cookie can't constructor doesn't accept unicode strings for some reason
250 267 msg = msg.encode('utf8', 'replace')
251 268 try:
252 269 self.request._cookies = Cookie.SimpleCookie(msg)
253 270 except:
254 271 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
255 272
256 273 def on_first_message(self, msg):
257 274 self._inject_cookie_message(msg)
258 275 if self.get_current_user() is None:
259 276 logging.warn("Couldn't authenticate WebSocket connection")
260 277 raise web.HTTPError(403)
261 278 self.on_message = self.save_on_message
262 279
263 280
264 281 class IOPubHandler(AuthenticatedZMQStreamHandler):
265 282
266 283 def initialize(self, *args, **kwargs):
267 284 self._kernel_alive = True
268 285 self._beating = False
269 286 self.iopub_stream = None
270 287 self.hb_stream = None
271 288
272 289 def on_first_message(self, msg):
273 290 try:
274 291 super(IOPubHandler, self).on_first_message(msg)
275 292 except web.HTTPError:
276 293 self.close()
277 294 return
278 295 km = self.application.kernel_manager
279 296 self.time_to_dead = km.time_to_dead
280 297 kernel_id = self.kernel_id
281 298 try:
282 299 self.iopub_stream = km.create_iopub_stream(kernel_id)
283 300 self.hb_stream = km.create_hb_stream(kernel_id)
284 301 except web.HTTPError:
285 302 # WebSockets don't response to traditional error codes so we
286 303 # close the connection.
287 304 if not self.stream.closed():
288 305 self.stream.close()
289 306 self.close()
290 307 else:
291 308 self.iopub_stream.on_recv(self._on_zmq_reply)
292 309 self.start_hb(self.kernel_died)
293 310
294 311 def on_message(self, msg):
295 312 pass
296 313
297 314 def on_close(self):
298 315 # This method can be called twice, once by self.kernel_died and once
299 316 # from the WebSocket close event. If the WebSocket connection is
300 317 # closed before the ZMQ streams are setup, they could be None.
301 318 self.stop_hb()
302 319 if self.iopub_stream is not None and not self.iopub_stream.closed():
303 320 self.iopub_stream.on_recv(None)
304 321 self.iopub_stream.close()
305 322 if self.hb_stream is not None and not self.hb_stream.closed():
306 323 self.hb_stream.close()
307 324
308 325 def start_hb(self, callback):
309 326 """Start the heartbeating and call the callback if the kernel dies."""
310 327 if not self._beating:
311 328 self._kernel_alive = True
312 329
313 330 def ping_or_dead():
314 331 if self._kernel_alive:
315 332 self._kernel_alive = False
316 333 self.hb_stream.send(b'ping')
317 334 else:
318 335 try:
319 336 callback()
320 337 except:
321 338 pass
322 339 finally:
323 340 self._hb_periodic_callback.stop()
324 341
325 342 def beat_received(msg):
326 343 self._kernel_alive = True
327 344
328 345 self.hb_stream.on_recv(beat_received)
329 346 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
330 347 self._hb_periodic_callback.start()
331 348 self._beating= True
332 349
333 350 def stop_hb(self):
334 351 """Stop the heartbeating and cancel all related callbacks."""
335 352 if self._beating:
336 353 self._hb_periodic_callback.stop()
337 354 if not self.hb_stream.closed():
338 355 self.hb_stream.on_recv(None)
339 356
340 357 def kernel_died(self):
341 358 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
342 359 self.write_message(
343 360 {'header': {'msg_type': 'status'},
344 361 'parent_header': {},
345 362 'content': {'execution_state':'dead'}
346 363 }
347 364 )
348 365 self.on_close()
349 366
350 367
351 368 class ShellHandler(AuthenticatedZMQStreamHandler):
352 369
353 370 def initialize(self, *args, **kwargs):
354 371 self.shell_stream = None
355 372
356 373 def on_first_message(self, msg):
357 374 try:
358 375 super(ShellHandler, self).on_first_message(msg)
359 376 except web.HTTPError:
360 377 self.close()
361 378 return
362 379 km = self.application.kernel_manager
363 380 self.max_msg_size = km.max_msg_size
364 381 kernel_id = self.kernel_id
365 382 try:
366 383 self.shell_stream = km.create_shell_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.shell_stream.on_recv(self._on_zmq_reply)
375 392
376 393 def on_message(self, msg):
377 394 if len(msg) < self.max_msg_size:
378 395 msg = jsonapi.loads(msg)
379 396 self.session.send(self.shell_stream, msg)
380 397
381 398 def on_close(self):
382 399 # Make sure the stream exists and is not already closed.
383 400 if self.shell_stream is not None and not self.shell_stream.closed():
384 401 self.shell_stream.close()
385 402
386 403
387 404 #-----------------------------------------------------------------------------
388 405 # Notebook web service handlers
389 406 #-----------------------------------------------------------------------------
390 407
391 408 class NotebookRootHandler(AuthenticatedHandler):
392 409
393 410 @authenticate_unless_readonly
394 411 def get(self):
395 412
396 # communicate read-only via Allow header
397 if self.application.ipython_app.read_only and not self.get_current_user():
398 self.set_header('Allow', 'GET')
399 else:
400 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
401
402 413 nbm = self.application.notebook_manager
403 414 files = nbm.list_notebooks()
404 415 self.finish(jsonapi.dumps(files))
405 416
406 417 @web.authenticated
407 418 def post(self):
408 419 nbm = self.application.notebook_manager
409 420 body = self.request.body.strip()
410 421 format = self.get_argument('format', default='json')
411 422 name = self.get_argument('name', default=None)
412 423 if body:
413 424 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
414 425 else:
415 426 notebook_id = nbm.new_notebook()
416 427 self.set_header('Location', '/'+notebook_id)
417 428 self.finish(jsonapi.dumps(notebook_id))
418 429
419 430
420 431 class NotebookHandler(AuthenticatedHandler):
421 432
422 433 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
423 434
424 435 @authenticate_unless_readonly
425 436 def get(self, notebook_id):
426 437 nbm = self.application.notebook_manager
427 438 format = self.get_argument('format', default='json')
428 439 last_mod, name, data = nbm.get_notebook(notebook_id, format)
429 440
430 # communicate read-only via Allow header
431 if self.application.ipython_app.read_only and not self.get_current_user():
432 self.set_header('Allow', 'GET')
433 else:
434 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
435
436 441 if format == u'json':
437 442 self.set_header('Content-Type', 'application/json')
438 443 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
439 444 elif format == u'py':
440 445 self.set_header('Content-Type', 'application/x-python')
441 446 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
442 447 self.set_header('Last-Modified', last_mod)
443 448 self.finish(data)
444 449
445 450 @web.authenticated
446 451 def put(self, notebook_id):
447 452 nbm = self.application.notebook_manager
448 453 format = self.get_argument('format', default='json')
449 454 name = self.get_argument('name', default=None)
450 455 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
451 456 self.set_status(204)
452 457 self.finish()
453 458
454 459 @web.authenticated
455 460 def delete(self, notebook_id):
456 461 nbm = self.application.notebook_manager
457 462 nbm.delete_notebook(notebook_id)
458 463 self.set_status(204)
459 464 self.finish()
460 465
461 466 #-----------------------------------------------------------------------------
462 467 # RST web service handlers
463 468 #-----------------------------------------------------------------------------
464 469
465 470
466 471 class RSTHandler(AuthenticatedHandler):
467 472
468 473 @web.authenticated
469 474 def post(self):
470 475 if publish_string is None:
471 476 raise web.HTTPError(503, u'docutils not available')
472 477 body = self.request.body.strip()
473 478 source = body
474 479 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
475 480 defaults = {'file_insertion_enabled': 0,
476 481 'raw_enabled': 0,
477 482 '_disable_config': 1,
478 483 'stylesheet_path': 0
479 484 # 'template': template_path
480 485 }
481 486 try:
482 487 html = publish_string(source, writer_name='html',
483 488 settings_overrides=defaults
484 489 )
485 490 except:
486 491 raise web.HTTPError(400, u'Invalid RST')
487 492 print html
488 493 self.set_header('Content-Type', 'text/html')
489 494 self.finish(html)
490 495
491 496
@@ -1,331 +1,332 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 import errno
20 20 import logging
21 21 import os
22 22 import signal
23 23 import socket
24 24 import sys
25 25 import webbrowser
26 26
27 27 import zmq
28 28
29 29 # Install the pyzmq ioloop. This has to be done before anything else from
30 30 # tornado is imported.
31 31 from zmq.eventloop import ioloop
32 32 import tornado.ioloop
33 33 tornado.ioloop.IOLoop = ioloop.IOLoop
34 34
35 35 from tornado import httpserver
36 36 from tornado import web
37 37
38 38 from .kernelmanager import MappingKernelManager
39 39 from .handlers import (LoginHandler,
40 40 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
41 41 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
42 42 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
43 43 )
44 44 from .notebookmanager import NotebookManager
45 45
46 46 from IPython.core.application import BaseIPythonApplication
47 47 from IPython.core.profiledir import ProfileDir
48 48 from IPython.zmq.session import Session, default_secure
49 49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 50 from IPython.zmq.ipkernel import (
51 51 flags as ipkernel_flags,
52 52 aliases as ipkernel_aliases,
53 53 IPKernelApp
54 54 )
55 55 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Module globals
59 59 #-----------------------------------------------------------------------------
60 60
61 61 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
62 62 _kernel_action_regex = r"(?P<action>restart|interrupt)"
63 63 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
64 64
65 65 LOCALHOST = '127.0.0.1'
66 66
67 67 _examples = """
68 68 ipython notebook # start the notebook
69 69 ipython notebook --profile=sympy # use the sympy profile
70 70 ipython notebook --pylab=inline # pylab in inline plotting mode
71 71 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
72 72 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
73 73 """
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # The Tornado web application
77 77 #-----------------------------------------------------------------------------
78 78
79 79 class NotebookWebApplication(web.Application):
80 80
81 81 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
82 82 handlers = [
83 83 (r"/", ProjectDashboardHandler),
84 84 (r"/login", LoginHandler),
85 85 (r"/new", NewHandler),
86 86 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
87 87 (r"/kernels", MainKernelHandler),
88 88 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
89 89 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
90 90 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
91 91 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
92 92 (r"/notebooks", NotebookRootHandler),
93 93 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
94 94 (r"/rstservice/render", RSTHandler)
95 95 ]
96 96 settings = dict(
97 97 template_path=os.path.join(os.path.dirname(__file__), "templates"),
98 98 static_path=os.path.join(os.path.dirname(__file__), "static"),
99 99 cookie_secret=os.urandom(1024),
100 100 login_url="/login",
101 101 )
102 102 web.Application.__init__(self, handlers, **settings)
103 103
104 104 self.kernel_manager = kernel_manager
105 105 self.log = log
106 106 self.notebook_manager = notebook_manager
107 107 self.ipython_app = ipython_app
108 self.read_only = self.ipython_app.read_only
108 109
109 110
110 111 #-----------------------------------------------------------------------------
111 112 # Aliases and Flags
112 113 #-----------------------------------------------------------------------------
113 114
114 115 flags = dict(ipkernel_flags)
115 116 flags['no-browser']=(
116 117 {'NotebookApp' : {'open_browser' : False}},
117 118 "Don't open the notebook in a browser after startup."
118 119 )
119 120 flags['read-only'] = (
120 121 {'NotebookApp' : {'read_only' : True}},
121 122 """Allow read-only access to notebooks.
122 123
123 124 When using a password to protect the notebook server, this flag
124 125 allows unauthenticated clients to view the notebook list, and
125 126 individual notebooks, but not edit them, start kernels, or run
126 127 code.
127 128
128 129 If no password is set, the server will be entirely read-only.
129 130 """
130 131 )
131 132
132 133 # the flags that are specific to the frontend
133 134 # these must be scrubbed before being passed to the kernel,
134 135 # or it will raise an error on unrecognized flags
135 136 notebook_flags = ['no-browser', 'read-only']
136 137
137 138 aliases = dict(ipkernel_aliases)
138 139
139 140 aliases.update({
140 141 'ip': 'NotebookApp.ip',
141 142 'port': 'NotebookApp.port',
142 143 'keyfile': 'NotebookApp.keyfile',
143 144 'certfile': 'NotebookApp.certfile',
144 145 'ws-hostname': 'NotebookApp.ws_hostname',
145 146 'notebook-dir': 'NotebookManager.notebook_dir',
146 147 })
147 148
148 149 # remove ipkernel flags that are singletons, and don't make sense in
149 150 # multi-kernel evironment:
150 151 aliases.pop('f', None)
151 152
152 153 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
153 154 u'notebook-dir']
154 155
155 156 #-----------------------------------------------------------------------------
156 157 # NotebookApp
157 158 #-----------------------------------------------------------------------------
158 159
159 160 class NotebookApp(BaseIPythonApplication):
160 161
161 162 name = 'ipython-notebook'
162 163 default_config_file_name='ipython_notebook_config.py'
163 164
164 165 description = """
165 166 The IPython HTML Notebook.
166 167
167 168 This launches a Tornado based HTML Notebook Server that serves up an
168 169 HTML5/Javascript Notebook client.
169 170 """
170 171 examples = _examples
171 172
172 173 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
173 174 MappingKernelManager, NotebookManager]
174 175 flags = Dict(flags)
175 176 aliases = Dict(aliases)
176 177
177 178 kernel_argv = List(Unicode)
178 179
179 180 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
180 181 default_value=logging.INFO,
181 182 config=True,
182 183 help="Set the log level by value or name.")
183 184
184 185 # Network related information.
185 186
186 187 ip = Unicode(LOCALHOST, config=True,
187 188 help="The IP address the notebook server will listen on."
188 189 )
189 190
190 191 def _ip_changed(self, name, old, new):
191 192 if new == u'*': self.ip = u''
192 193
193 194 port = Int(8888, config=True,
194 195 help="The port the notebook server will listen on."
195 196 )
196 197
197 198 ws_hostname = Unicode(LOCALHOST, config=True,
198 199 help="""The FQDN or IP for WebSocket connections. The default will work
199 200 fine when the server is listening on localhost, but this needs to
200 201 be set if the ip option is used. It will be used as the hostname part
201 202 of the WebSocket url: ws://hostname/path."""
202 203 )
203 204
204 205 certfile = Unicode(u'', config=True,
205 206 help="""The full path to an SSL/TLS certificate file."""
206 207 )
207 208
208 209 keyfile = Unicode(u'', config=True,
209 210 help="""The full path to a private key file for usage with SSL/TLS."""
210 211 )
211 212
212 213 password = Unicode(u'', config=True,
213 214 help="""Password to use for web authentication"""
214 215 )
215 216
216 217 open_browser = Bool(True, config=True,
217 218 help="Whether to open in a browser after starting.")
218 219
219 220 read_only = Bool(False, config=True,
220 221 help="Whether to prevent editing/execution of notebooks."
221 222 )
222 223
223 224 def get_ws_url(self):
224 225 """Return the WebSocket URL for this server."""
225 226 if self.certfile:
226 227 prefix = u'wss://'
227 228 else:
228 229 prefix = u'ws://'
229 230 return prefix + self.ws_hostname + u':' + unicode(self.port)
230 231
231 232 def parse_command_line(self, argv=None):
232 233 super(NotebookApp, self).parse_command_line(argv)
233 234 if argv is None:
234 235 argv = sys.argv[1:]
235 236
236 237 self.kernel_argv = list(argv) # copy
237 238 # Kernel should inherit default config file from frontend
238 239 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
239 240 # Scrub frontend-specific flags
240 241 for a in argv:
241 242 if a.startswith('-') and a.lstrip('-') in notebook_flags:
242 243 self.kernel_argv.remove(a)
243 244 swallow_next = False
244 245 for a in argv:
245 246 if swallow_next:
246 247 self.kernel_argv.remove(a)
247 248 swallow_next = False
248 249 continue
249 250 if a.startswith('-'):
250 251 split = a.lstrip('-').split('=')
251 252 alias = split[0]
252 253 if alias in notebook_aliases:
253 254 self.kernel_argv.remove(a)
254 255 if len(split) == 1:
255 256 # alias passed with arg via space
256 257 swallow_next = True
257 258
258 259 def init_configurables(self):
259 260 # Don't let Qt or ZMQ swallow KeyboardInterupts.
260 261 signal.signal(signal.SIGINT, signal.SIG_DFL)
261 262
262 263 # force Session default to be secure
263 264 default_secure(self.config)
264 265 # Create a KernelManager and start a kernel.
265 266 self.kernel_manager = MappingKernelManager(
266 267 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
267 268 connection_dir = self.profile_dir.security_dir,
268 269 )
269 270 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
270 271 self.notebook_manager.list_notebooks()
271 272
272 273 def init_logging(self):
273 274 super(NotebookApp, self).init_logging()
274 275 # This prevents double log messages because tornado use a root logger that
275 276 # self.log is a child of. The logging module dipatches log messages to a log
276 277 # and all of its ancenstors until propagate is set to False.
277 278 self.log.propagate = False
278 279
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 webbrowser.open("%s://%s:%i" % (proto, ip, self.port), new=2)
321 322 ioloop.IOLoop.instance().start()
322 323
323 324 #-----------------------------------------------------------------------------
324 325 # Main entry point
325 326 #-----------------------------------------------------------------------------
326 327
327 328 def launch_new_instance():
328 329 app = NotebookApp()
329 330 app.initialize()
330 331 app.start()
331 332
@@ -1,102 +1,104 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 // LeftPanel
10 10 //============================================================================
11 11
12 12
13 13 var IPython = (function (IPython) {
14 14
15 15 var utils = IPython.utils;
16 16
17 17 var LeftPanel = function (left_panel_selector, left_panel_splitter_selector) {
18 18 this.left_panel_element = $(left_panel_selector);
19 19 this.left_panel_splitter_element = $(left_panel_splitter_selector);
20 20 this.expanded = true;
21 21 this.width = 300;
22 22 this.style();
23 23 this.bind_events();
24 24 this.create_children();
25 25 };
26 26
27 27
28 28 LeftPanel.prototype.style = function () {
29 29 this.left_panel_splitter_element.addClass('border-box-sizing ui-widget ui-state-default');
30 30 this.left_panel_element.addClass('border-box-sizing ui-widget');
31 31 this.left_panel_element.width(this.width);
32 32 this.left_panel_splitter_element.css({left : this.width});
33 33 this.left_panel_splitter_element.attr('title', 'Click to Show/Hide left panel');
34 34 };
35 35
36 36
37 37 LeftPanel.prototype.bind_events = function () {
38 38 var that = this;
39 39
40 40 this.left_panel_element.bind('collapse_left_panel', function () {
41 41 that.left_panel_element.hide('fast');
42 42 that.left_panel_splitter_element.animate({left : 0}, 'fast');
43 43 });
44 44
45 45 this.left_panel_element.bind('expand_left_panel', function () {
46 46 that.left_panel_element.show('fast');
47 47 that.left_panel_splitter_element.animate({left : that.width}, 'fast');
48 48 });
49 49
50 50 this.left_panel_splitter_element.hover(
51 51 function () {
52 52 that.left_panel_splitter_element.addClass('ui-state-hover');
53 53 },
54 54 function () {
55 55 that.left_panel_splitter_element.removeClass('ui-state-hover');
56 56 }
57 57 );
58 58
59 59 this.left_panel_splitter_element.click(function () {
60 60 that.toggle();
61 61 });
62 62
63 63 };
64 64
65 65
66 66 LeftPanel.prototype.create_children = function () {
67 67 this.notebook_section = new IPython.NotebookSection('div#notebook_section');
68 this.cell_section = new IPython.CellSection('div#cell_section');
69 this.kernel_section = new IPython.KernelSection('div#kernel_section');
68 if (! IPython.read_only){
69 this.cell_section = new IPython.CellSection('div#cell_section');
70 this.kernel_section = new IPython.KernelSection('div#kernel_section');
71 }
70 72 this.help_section = new IPython.HelpSection('div#help_section');
71 73 }
72 74
73 75 LeftPanel.prototype.collapse = function () {
74 76 if (this.expanded === true) {
75 77 this.left_panel_element.add($('div#notebook')).trigger('collapse_left_panel');
76 78 this.expanded = false;
77 79 };
78 80 };
79 81
80 82
81 83 LeftPanel.prototype.expand = function () {
82 84 if (this.expanded !== true) {
83 85 this.left_panel_element.add($('div#notebook')).trigger('expand_left_panel');
84 86 this.expanded = true;
85 87 };
86 88 };
87 89
88 90
89 91 LeftPanel.prototype.toggle = function () {
90 92 if (this.expanded === true) {
91 93 this.collapse();
92 94 } else {
93 95 this.expand();
94 96 };
95 97 };
96 98
97 99 IPython.LeftPanel = LeftPanel;
98 100
99 101 return IPython;
100 102
101 103 }(IPython));
102 104
@@ -1,1025 +1,1006 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 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 var Notebook = function (selector) {
17 this.read_only = false;
17 this.read_only = IPython.read_only;
18 18 this.element = $(selector);
19 19 this.element.scroll();
20 20 this.element.data("notebook", this);
21 21 this.next_prompt_number = 1;
22 22 this.kernel = null;
23 23 this.dirty = false;
24 24 this.msg_cell_map = {};
25 25 this.metadata = {};
26 26 this.control_key_active = false;
27 27 this.style();
28 28 this.create_elements();
29 29 this.bind_events();
30 30 };
31 31
32 32
33 33 Notebook.prototype.style = function () {
34 34 $('div#notebook').addClass('border-box-sizing');
35 35 };
36 36
37 37
38 38 Notebook.prototype.create_elements = function () {
39 39 // We add this end_space div to the end of the notebook div to:
40 40 // i) provide a margin between the last cell and the end of the notebook
41 41 // ii) to prevent the div from scrolling up when the last cell is being
42 42 // edited, but is too low on the page, which browsers will do automatically.
43 43 var that = this;
44 44 var end_space = $('<div class="end_space"></div>').height(150);
45 45 end_space.dblclick(function (e) {
46 46 if (that.read_only) return;
47 47 var ncells = that.ncells();
48 48 that.insert_code_cell_below(ncells-1);
49 49 });
50 50 this.element.append(end_space);
51 51 $('div#notebook').addClass('border-box-sizing');
52 52 };
53 53
54 54
55 55 Notebook.prototype.bind_events = function () {
56 56 var that = this;
57 57 $(document).keydown(function (event) {
58 58 // console.log(event);
59 59 if (that.read_only) return;
60 60 if (event.which === 38) {
61 61 var cell = that.selected_cell();
62 62 if (cell.at_top()) {
63 63 event.preventDefault();
64 64 that.select_prev();
65 65 };
66 66 } else if (event.which === 40) {
67 67 var cell = that.selected_cell();
68 68 if (cell.at_bottom()) {
69 69 event.preventDefault();
70 70 that.select_next();
71 71 };
72 72 } else if (event.which === 13 && event.shiftKey) {
73 73 that.execute_selected_cell();
74 74 return false;
75 75 } else if (event.which === 13 && event.ctrlKey) {
76 76 that.execute_selected_cell({terminal:true});
77 77 return false;
78 78 } else if (event.which === 77 && event.ctrlKey) {
79 79 that.control_key_active = true;
80 80 return false;
81 81 } else if (event.which === 68 && that.control_key_active) {
82 82 // Delete selected cell = d
83 83 that.delete_cell();
84 84 that.control_key_active = false;
85 85 return false;
86 86 } else if (event.which === 65 && that.control_key_active) {
87 87 // Insert code cell above selected = a
88 88 that.insert_code_cell_above();
89 89 that.control_key_active = false;
90 90 return false;
91 91 } else if (event.which === 66 && that.control_key_active) {
92 92 // Insert code cell below selected = b
93 93 that.insert_code_cell_below();
94 94 that.control_key_active = false;
95 95 return false;
96 96 } else if (event.which === 67 && that.control_key_active) {
97 97 // To code = c
98 98 that.to_code();
99 99 that.control_key_active = false;
100 100 return false;
101 101 } else if (event.which === 77 && that.control_key_active) {
102 102 // To markdown = m
103 103 that.to_markdown();
104 104 that.control_key_active = false;
105 105 return false;
106 106 } else if (event.which === 84 && that.control_key_active) {
107 107 // Toggle output = t
108 108 that.toggle_output();
109 109 that.control_key_active = false;
110 110 return false;
111 111 } else if (event.which === 83 && that.control_key_active) {
112 112 // Save notebook = s
113 113 IPython.save_widget.save_notebook();
114 114 that.control_key_active = false;
115 115 return false;
116 116 } else if (event.which === 74 && that.control_key_active) {
117 117 // Move cell down = j
118 118 that.move_cell_down();
119 119 that.control_key_active = false;
120 120 return false;
121 121 } else if (event.which === 75 && that.control_key_active) {
122 122 // Move cell up = k
123 123 that.move_cell_up();
124 124 that.control_key_active = false;
125 125 return false;
126 126 } else if (event.which === 80 && that.control_key_active) {
127 127 // Select previous = p
128 128 that.select_prev();
129 129 that.control_key_active = false;
130 130 return false;
131 131 } else if (event.which === 78 && that.control_key_active) {
132 132 // Select next = n
133 133 that.select_next();
134 134 that.control_key_active = false;
135 135 return false;
136 136 } else if (event.which === 76 && that.control_key_active) {
137 137 // Toggle line numbers = l
138 138 that.cell_toggle_line_numbers();
139 139 that.control_key_active = false;
140 140 return false;
141 141 } else if (event.which === 73 && that.control_key_active) {
142 142 // Interrupt kernel = i
143 143 IPython.notebook.kernel.interrupt();
144 144 that.control_key_active = false;
145 145 return false;
146 146 } else if (event.which === 190 && that.control_key_active) {
147 147 // Restart kernel = . # matches qt console
148 148 IPython.notebook.restart_kernel();
149 149 that.control_key_active = false;
150 150 return false;
151 151 } else if (event.which === 72 && that.control_key_active) {
152 152 // Show keyboard shortcuts = h
153 153 that.toggle_keyboard_shortcuts();
154 154 that.control_key_active = false;
155 155 return false;
156 156 } else if (that.control_key_active) {
157 157 that.control_key_active = false;
158 158 return true;
159 159 };
160 160 });
161 161
162 162 this.element.bind('collapse_pager', function () {
163 163 var app_height = $('div#main_app').height(); // content height
164 164 var splitter_height = $('div#pager_splitter').outerHeight(true);
165 165 var new_height = app_height - splitter_height;
166 166 that.element.animate({height : new_height + 'px'}, 'fast');
167 167 });
168 168
169 169 this.element.bind('expand_pager', function () {
170 170 var app_height = $('div#main_app').height(); // content height
171 171 var splitter_height = $('div#pager_splitter').outerHeight(true);
172 172 var pager_height = $('div#pager').outerHeight(true);
173 173 var new_height = app_height - pager_height - splitter_height;
174 174 that.element.animate({height : new_height + 'px'}, 'fast');
175 175 });
176 176
177 177 this.element.bind('collapse_left_panel', function () {
178 178 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
179 179 var new_margin = splitter_width;
180 180 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
181 181 });
182 182
183 183 this.element.bind('expand_left_panel', function () {
184 184 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
185 185 var left_panel_width = IPython.left_panel.width;
186 186 var new_margin = splitter_width + left_panel_width;
187 187 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
188 188 });
189 189
190 190 $(window).bind('beforeunload', function () {
191 191 var kill_kernel = $('#kill_kernel').prop('checked');
192 192 if (kill_kernel) {
193 193 that.kernel.kill();
194 194 }
195 195 if (that.dirty && ! that.read_only) {
196 196 return "You have unsaved changes that will be lost if you leave this page.";
197 197 };
198 198 });
199 199 };
200 200
201 201
202 202 Notebook.prototype.toggle_keyboard_shortcuts = function () {
203 203 // toggles display of keyboard shortcut dialog
204 204 var that = this;
205 205 if ( this.shortcut_dialog ){
206 206 // if dialog is already shown, close it
207 207 this.shortcut_dialog.dialog("close");
208 208 this.shortcut_dialog = null;
209 209 return;
210 210 }
211 211 var dialog = $('<div/>');
212 212 this.shortcut_dialog = dialog;
213 213 var shortcuts = [
214 214 {key: 'Shift-Enter', help: 'run cell'},
215 215 {key: 'Ctrl-Enter', help: 'run cell in-place'},
216 216 {key: 'Ctrl-m d', help: 'delete cell'},
217 217 {key: 'Ctrl-m a', help: 'insert cell above'},
218 218 {key: 'Ctrl-m b', help: 'insert cell below'},
219 219 {key: 'Ctrl-m t', help: 'toggle output'},
220 220 {key: 'Ctrl-m l', help: 'toggle line numbers'},
221 221 {key: 'Ctrl-m s', help: 'save notebook'},
222 222 {key: 'Ctrl-m j', help: 'move cell down'},
223 223 {key: 'Ctrl-m k', help: 'move cell up'},
224 224 {key: 'Ctrl-m c', help: 'code cell'},
225 225 {key: 'Ctrl-m m', help: 'markdown cell'},
226 226 {key: 'Ctrl-m p', help: 'select previous'},
227 227 {key: 'Ctrl-m n', help: 'select next'},
228 228 {key: 'Ctrl-m i', help: 'interrupt kernel'},
229 229 {key: 'Ctrl-m .', help: 'restart kernel'},
230 230 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
231 231 ];
232 232 for (var i=0; i<shortcuts.length; i++) {
233 233 dialog.append($('<div>').
234 234 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
235 235 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
236 236 );
237 237 };
238 238 dialog.bind('dialogclose', function(event) {
239 239 // dialog has been closed, allow it to be drawn again.
240 240 that.shortcut_dialog = null;
241 241 });
242 242 dialog.dialog({title: 'Keyboard shortcuts'});
243 243 };
244 244
245 245
246 246 Notebook.prototype.scroll_to_bottom = function () {
247 247 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
248 248 };
249 249
250 250
251 251 Notebook.prototype.scroll_to_top = function () {
252 252 this.element.animate({scrollTop:0}, 0);
253 253 };
254 254
255 255
256 256 // Cell indexing, retrieval, etc.
257 257
258 258
259 259 Notebook.prototype.cell_elements = function () {
260 260 return this.element.children("div.cell");
261 261 }
262 262
263 263
264 264 Notebook.prototype.ncells = function (cell) {
265 265 return this.cell_elements().length;
266 266 }
267 267
268 268
269 269 // TODO: we are often calling cells as cells()[i], which we should optimize
270 270 // to cells(i) or a new method.
271 271 Notebook.prototype.cells = function () {
272 272 return this.cell_elements().toArray().map(function (e) {
273 273 return $(e).data("cell");
274 274 });
275 275 }
276 276
277 277
278 278 Notebook.prototype.find_cell_index = function (cell) {
279 279 var result = null;
280 280 this.cell_elements().filter(function (index) {
281 281 if ($(this).data("cell") === cell) {
282 282 result = index;
283 283 };
284 284 });
285 285 return result;
286 286 };
287 287
288 288
289 289 Notebook.prototype.index_or_selected = function (index) {
290 290 return index || this.selected_index() || 0;
291 291 }
292 292
293 293
294 294 Notebook.prototype.select = function (index) {
295 295 if (index !== undefined && index >= 0 && index < this.ncells()) {
296 296 if (this.selected_index() !== null) {
297 297 this.selected_cell().unselect();
298 298 };
299 299 this.cells()[index].select();
300 300 };
301 301 return this;
302 302 };
303 303
304 304
305 305 Notebook.prototype.select_next = function () {
306 306 var index = this.selected_index();
307 307 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
308 308 this.select(index+1);
309 309 };
310 310 return this;
311 311 };
312 312
313 313
314 314 Notebook.prototype.select_prev = function () {
315 315 var index = this.selected_index();
316 316 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
317 317 this.select(index-1);
318 318 };
319 319 return this;
320 320 };
321 321
322 322
323 323 Notebook.prototype.selected_index = function () {
324 324 var result = null;
325 325 this.cell_elements().filter(function (index) {
326 326 if ($(this).data("cell").selected === true) {
327 327 result = index;
328 328 };
329 329 });
330 330 return result;
331 331 };
332 332
333 333
334 334 Notebook.prototype.cell_for_msg = function (msg_id) {
335 335 var cell_id = this.msg_cell_map[msg_id];
336 336 var result = null;
337 337 this.cell_elements().filter(function (index) {
338 338 cell = $(this).data("cell");
339 339 if (cell.cell_id === cell_id) {
340 340 result = cell;
341 341 };
342 342 });
343 343 return result;
344 344 };
345 345
346 346
347 347 Notebook.prototype.selected_cell = function () {
348 348 return this.cell_elements().eq(this.selected_index()).data("cell");
349 349 }
350 350
351 351
352 352 // Cell insertion, deletion and moving.
353 353
354 354
355 355 Notebook.prototype.delete_cell = function (index) {
356 356 var i = index || this.selected_index();
357 357 if (i !== null && i >= 0 && i < this.ncells()) {
358 358 this.cell_elements().eq(i).remove();
359 359 if (i === (this.ncells())) {
360 360 this.select(i-1);
361 361 } else {
362 362 this.select(i);
363 363 };
364 364 };
365 365 this.dirty = true;
366 366 return this;
367 367 };
368 368
369 369
370 370 Notebook.prototype.append_cell = function (cell) {
371 371 this.element.find('div.end_space').before(cell.element);
372 372 this.dirty = true;
373 373 return this;
374 374 };
375 375
376 376
377 377 Notebook.prototype.insert_cell_below = function (cell, index) {
378 378 var ncells = this.ncells();
379 379 if (ncells === 0) {
380 380 this.append_cell(cell);
381 381 return this;
382 382 };
383 383 if (index >= 0 && index < ncells) {
384 384 this.cell_elements().eq(index).after(cell.element);
385 385 };
386 386 this.dirty = true;
387 387 return this
388 388 };
389 389
390 390
391 391 Notebook.prototype.insert_cell_above = function (cell, index) {
392 392 var ncells = this.ncells();
393 393 if (ncells === 0) {
394 394 this.append_cell(cell);
395 395 return this;
396 396 };
397 397 if (index >= 0 && index < ncells) {
398 398 this.cell_elements().eq(index).before(cell.element);
399 399 };
400 400 this.dirty = true;
401 401 return this;
402 402 };
403 403
404 404
405 405 Notebook.prototype.move_cell_up = function (index) {
406 406 var i = index || this.selected_index();
407 407 if (i !== null && i < this.ncells() && i > 0) {
408 408 var pivot = this.cell_elements().eq(i-1);
409 409 var tomove = this.cell_elements().eq(i);
410 410 if (pivot !== null && tomove !== null) {
411 411 tomove.detach();
412 412 pivot.before(tomove);
413 413 this.select(i-1);
414 414 };
415 415 };
416 416 this.dirty = true;
417 417 return this;
418 418 }
419 419
420 420
421 421 Notebook.prototype.move_cell_down = function (index) {
422 422 var i = index || this.selected_index();
423 423 if (i !== null && i < (this.ncells()-1) && i >= 0) {
424 424 var pivot = this.cell_elements().eq(i+1)
425 425 var tomove = this.cell_elements().eq(i)
426 426 if (pivot !== null && tomove !== null) {
427 427 tomove.detach();
428 428 pivot.after(tomove);
429 429 this.select(i+1);
430 430 };
431 431 };
432 432 this.dirty = true;
433 433 return this;
434 434 }
435 435
436 436
437 437 Notebook.prototype.sort_cells = function () {
438 438 var ncells = this.ncells();
439 439 var sindex = this.selected_index();
440 440 var swapped;
441 441 do {
442 442 swapped = false
443 443 for (var i=1; i<ncells; i++) {
444 444 current = this.cell_elements().eq(i).data("cell");
445 445 previous = this.cell_elements().eq(i-1).data("cell");
446 446 if (previous.input_prompt_number > current.input_prompt_number) {
447 447 this.move_cell_up(i);
448 448 swapped = true;
449 449 };
450 450 };
451 451 } while (swapped);
452 452 this.select(sindex);
453 453 return this;
454 454 };
455 455
456 456
457 457 Notebook.prototype.insert_code_cell_above = function (index) {
458 458 // TODO: Bounds check for i
459 459 var i = this.index_or_selected(index);
460 460 var cell = new IPython.CodeCell(this);
461 461 cell.set_input_prompt();
462 462 this.insert_cell_above(cell, i);
463 463 this.select(this.find_cell_index(cell));
464 464 return cell;
465 465 }
466 466
467 467
468 468 Notebook.prototype.insert_code_cell_below = function (index) {
469 469 // TODO: Bounds check for i
470 470 var i = this.index_or_selected(index);
471 471 var cell = new IPython.CodeCell(this);
472 472 cell.set_input_prompt();
473 473 this.insert_cell_below(cell, i);
474 474 this.select(this.find_cell_index(cell));
475 475 return cell;
476 476 }
477 477
478 478
479 479 Notebook.prototype.insert_html_cell_above = function (index) {
480 480 // TODO: Bounds check for i
481 481 var i = this.index_or_selected(index);
482 482 var cell = new IPython.HTMLCell(this);
483 483 cell.config_mathjax();
484 484 this.insert_cell_above(cell, i);
485 485 this.select(this.find_cell_index(cell));
486 486 return cell;
487 487 }
488 488
489 489
490 490 Notebook.prototype.insert_html_cell_below = function (index) {
491 491 // TODO: Bounds check for i
492 492 var i = this.index_or_selected(index);
493 493 var cell = new IPython.HTMLCell(this);
494 494 cell.config_mathjax();
495 495 this.insert_cell_below(cell, i);
496 496 this.select(this.find_cell_index(cell));
497 497 return cell;
498 498 }
499 499
500 500
501 501 Notebook.prototype.insert_markdown_cell_above = function (index) {
502 502 // TODO: Bounds check for i
503 503 var i = this.index_or_selected(index);
504 504 var cell = new IPython.MarkdownCell(this);
505 505 cell.config_mathjax();
506 506 this.insert_cell_above(cell, i);
507 507 this.select(this.find_cell_index(cell));
508 508 return cell;
509 509 }
510 510
511 511
512 512 Notebook.prototype.insert_markdown_cell_below = function (index) {
513 513 // TODO: Bounds check for i
514 514 var i = this.index_or_selected(index);
515 515 var cell = new IPython.MarkdownCell(this);
516 516 cell.config_mathjax();
517 517 this.insert_cell_below(cell, i);
518 518 this.select(this.find_cell_index(cell));
519 519 return cell;
520 520 }
521 521
522 522
523 523 Notebook.prototype.to_code = function (index) {
524 524 // TODO: Bounds check for i
525 525 var i = this.index_or_selected(index);
526 526 var source_element = this.cell_elements().eq(i);
527 527 var source_cell = source_element.data("cell");
528 528 if (source_cell instanceof IPython.HTMLCell ||
529 529 source_cell instanceof IPython.MarkdownCell) {
530 530 this.insert_code_cell_below(i);
531 531 var target_cell = this.cells()[i+1];
532 532 target_cell.set_code(source_cell.get_source());
533 533 source_element.remove();
534 534 target_cell.select();
535 535 };
536 536 this.dirty = true;
537 537 };
538 538
539 539
540 540 Notebook.prototype.to_markdown = function (index) {
541 541 // TODO: Bounds check for i
542 542 var i = this.index_or_selected(index);
543 543 var source_element = this.cell_elements().eq(i);
544 544 var source_cell = source_element.data("cell");
545 545 var target_cell = null;
546 546 if (source_cell instanceof IPython.CodeCell) {
547 547 this.insert_markdown_cell_below(i);
548 548 var target_cell = this.cells()[i+1];
549 549 var text = source_cell.get_code();
550 550 } else if (source_cell instanceof IPython.HTMLCell) {
551 551 this.insert_markdown_cell_below(i);
552 552 var target_cell = this.cells()[i+1];
553 553 var text = source_cell.get_source();
554 554 if (text === source_cell.placeholder) {
555 555 text = target_cell.placeholder;
556 556 }
557 557 }
558 558 if (target_cell !== null) {
559 559 if (text === "") {text = target_cell.placeholder;};
560 560 target_cell.set_source(text);
561 561 source_element.remove();
562 562 target_cell.edit();
563 563 }
564 564 this.dirty = true;
565 565 };
566 566
567 567
568 568 Notebook.prototype.to_html = function (index) {
569 569 // TODO: Bounds check for i
570 570 var i = this.index_or_selected(index);
571 571 var source_element = this.cell_elements().eq(i);
572 572 var source_cell = source_element.data("cell");
573 573 var target_cell = null;
574 574 if (source_cell instanceof IPython.CodeCell) {
575 575 this.insert_html_cell_below(i);
576 576 var target_cell = this.cells()[i+1];
577 577 var text = source_cell.get_code();
578 578 } else if (source_cell instanceof IPython.MarkdownCell) {
579 579 this.insert_html_cell_below(i);
580 580 var target_cell = this.cells()[i+1];
581 581 var text = source_cell.get_source();
582 582 if (text === source_cell.placeholder) {
583 583 text = target_cell.placeholder;
584 584 }
585 585 }
586 586 if (target_cell !== null) {
587 587 if (text === "") {text = target_cell.placeholder;};
588 588 target_cell.set_source(text);
589 589 source_element.remove();
590 590 target_cell.edit();
591 591 }
592 592 this.dirty = true;
593 593 };
594 594
595 595
596 596 // Cell collapsing and output clearing
597 597
598 598 Notebook.prototype.collapse = function (index) {
599 599 var i = this.index_or_selected(index);
600 600 this.cells()[i].collapse();
601 601 this.dirty = true;
602 602 };
603 603
604 604
605 605 Notebook.prototype.expand = function (index) {
606 606 var i = this.index_or_selected(index);
607 607 this.cells()[i].expand();
608 608 this.dirty = true;
609 609 };
610 610
611 611
612 612 Notebook.prototype.toggle_output = function (index) {
613 613 var i = this.index_or_selected(index);
614 614 this.cells()[i].toggle_output();
615 615 this.dirty = true;
616 616 };
617 617
618 618
619 619 Notebook.prototype.set_autoindent = function (state) {
620 620 var cells = this.cells();
621 621 len = cells.length;
622 622 for (var i=0; i<len; i++) {
623 623 cells[i].set_autoindent(state)
624 624 };
625 625 };
626 626
627 627
628 628 Notebook.prototype.clear_all_output = function () {
629 629 var ncells = this.ncells();
630 630 var cells = this.cells();
631 631 for (var i=0; i<ncells; i++) {
632 632 if (cells[i] instanceof IPython.CodeCell) {
633 633 cells[i].clear_output(true,true,true);
634 634 }
635 635 };
636 636 this.dirty = true;
637 637 };
638 638
639 639 // Other cell functions: line numbers, ...
640 640
641 641 Notebook.prototype.cell_toggle_line_numbers = function() {
642 642 this.selected_cell().toggle_line_numbers()
643 643 };
644 644
645 645 // Kernel related things
646 646
647 647 Notebook.prototype.start_kernel = function () {
648 648 this.kernel = new IPython.Kernel();
649 649 var notebook_id = IPython.save_widget.get_notebook_id();
650 650 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
651 651 };
652 652
653 653
654 654 Notebook.prototype.restart_kernel = function () {
655 655 var that = this;
656 656 var notebook_id = IPython.save_widget.get_notebook_id();
657 657
658 658 var dialog = $('<div/>');
659 659 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
660 660 $(document).append(dialog);
661 661 dialog.dialog({
662 662 resizable: false,
663 663 modal: true,
664 664 title: "Restart kernel or continue running?",
665 665 buttons : {
666 666 "Restart": function () {
667 667 that.kernel.restart($.proxy(that.kernel_started, that));
668 668 $(this).dialog('close');
669 669 },
670 670 "Continue running": function () {
671 671 $(this).dialog('close');
672 672 }
673 673 }
674 674 });
675 675 };
676 676
677 677
678 678 Notebook.prototype.kernel_started = function () {
679 679 console.log("Kernel started: ", this.kernel.kernel_id);
680 680 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
681 681 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
682 682 };
683 683
684 684
685 685 Notebook.prototype.handle_shell_reply = function (e) {
686 686 reply = $.parseJSON(e.data);
687 687 var header = reply.header;
688 688 var content = reply.content;
689 689 var msg_type = header.msg_type;
690 690 // console.log(reply);
691 691 var cell = this.cell_for_msg(reply.parent_header.msg_id);
692 692 if (msg_type === "execute_reply") {
693 693 cell.set_input_prompt(content.execution_count);
694 694 this.dirty = true;
695 695 } else if (msg_type === "complete_reply") {
696 696 cell.finish_completing(content.matched_text, content.matches);
697 697 };
698 698 var payload = content.payload || [];
699 699 this.handle_payload(cell, payload);
700 700 };
701 701
702 702
703 703 Notebook.prototype.handle_payload = function (cell, payload) {
704 704 var l = payload.length;
705 705 for (var i=0; i<l; i++) {
706 706 if (payload[i].source === 'IPython.zmq.page.page') {
707 707 if (payload[i].text.trim() !== '') {
708 708 IPython.pager.clear();
709 709 IPython.pager.expand();
710 710 IPython.pager.append_text(payload[i].text);
711 711 }
712 712 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
713 713 var index = this.find_cell_index(cell);
714 714 var new_cell = this.insert_code_cell_below(index);
715 715 new_cell.set_code(payload[i].text);
716 716 this.dirty = true;
717 717 }
718 718 };
719 719 };
720 720
721 721
722 722 Notebook.prototype.handle_iopub_reply = function (e) {
723 723 reply = $.parseJSON(e.data);
724 724 var content = reply.content;
725 725 // console.log(reply);
726 726 var msg_type = reply.header.msg_type;
727 727 var cell = this.cell_for_msg(reply.parent_header.msg_id);
728 728 var output_types = ['stream','display_data','pyout','pyerr'];
729 729 if (output_types.indexOf(msg_type) >= 0) {
730 730 this.handle_output(cell, msg_type, content);
731 731 } else if (msg_type === 'status') {
732 732 if (content.execution_state === 'busy') {
733 733 IPython.kernel_status_widget.status_busy();
734 734 } else if (content.execution_state === 'idle') {
735 735 IPython.kernel_status_widget.status_idle();
736 736 } else if (content.execution_state === 'dead') {
737 737 this.handle_status_dead();
738 738 };
739 739 } else if (msg_type === 'clear_output') {
740 740 cell.clear_output(content.stdout, content.stderr, content.other);
741 741 };
742 742 };
743 743
744 744
745 745 Notebook.prototype.handle_status_dead = function () {
746 746 var that = this;
747 747 this.kernel.stop_channels();
748 748 var dialog = $('<div/>');
749 749 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
750 750 $(document).append(dialog);
751 751 dialog.dialog({
752 752 resizable: false,
753 753 modal: true,
754 754 title: "Dead kernel",
755 755 buttons : {
756 756 "Restart": function () {
757 757 that.start_kernel();
758 758 $(this).dialog('close');
759 759 },
760 760 "Continue running": function () {
761 761 $(this).dialog('close');
762 762 }
763 763 }
764 764 });
765 765 };
766 766
767 767
768 768 Notebook.prototype.handle_output = function (cell, msg_type, content) {
769 769 var json = {};
770 770 json.output_type = msg_type;
771 771 if (msg_type === "stream") {
772 772 json.text = utils.fixConsole(content.data);
773 773 json.stream = content.name;
774 774 } else if (msg_type === "display_data") {
775 775 json = this.convert_mime_types(json, content.data);
776 776 } else if (msg_type === "pyout") {
777 777 json.prompt_number = content.execution_count;
778 778 json = this.convert_mime_types(json, content.data);
779 779 } else if (msg_type === "pyerr") {
780 780 json.ename = content.ename;
781 781 json.evalue = content.evalue;
782 782 var traceback = [];
783 783 for (var i=0; i<content.traceback.length; i++) {
784 784 traceback.push(utils.fixConsole(content.traceback[i]));
785 785 }
786 786 json.traceback = traceback;
787 787 };
788 788 cell.append_output(json);
789 789 this.dirty = true;
790 790 };
791 791
792 792
793 793 Notebook.prototype.convert_mime_types = function (json, data) {
794 794 if (data['text/plain'] !== undefined) {
795 795 json.text = utils.fixConsole(data['text/plain']);
796 796 };
797 797 if (data['text/html'] !== undefined) {
798 798 json.html = data['text/html'];
799 799 };
800 800 if (data['image/svg+xml'] !== undefined) {
801 801 json.svg = data['image/svg+xml'];
802 802 };
803 803 if (data['image/png'] !== undefined) {
804 804 json.png = data['image/png'];
805 805 };
806 806 if (data['image/jpeg'] !== undefined) {
807 807 json.jpeg = data['image/jpeg'];
808 808 };
809 809 if (data['text/latex'] !== undefined) {
810 810 json.latex = data['text/latex'];
811 811 };
812 812 if (data['application/json'] !== undefined) {
813 813 json.json = data['application/json'];
814 814 };
815 815 if (data['application/javascript'] !== undefined) {
816 816 json.javascript = data['application/javascript'];
817 817 }
818 818 return json;
819 819 };
820 820
821 821
822 822 Notebook.prototype.execute_selected_cell = function (options) {
823 823 // add_new: should a new cell be added if we are at the end of the nb
824 824 // terminal: execute in terminal mode, which stays in the current cell
825 825 default_options = {terminal: false, add_new: true}
826 826 $.extend(default_options, options)
827 827 var that = this;
828 828 var cell = that.selected_cell();
829 829 var cell_index = that.find_cell_index(cell);
830 830 if (cell instanceof IPython.CodeCell) {
831 831 cell.clear_output(true, true, true);
832 832 var code = cell.get_code();
833 833 var msg_id = that.kernel.execute(cell.get_code());
834 834 that.msg_cell_map[msg_id] = cell.cell_id;
835 835 } else if (cell instanceof IPython.HTMLCell) {
836 836 cell.render();
837 837 }
838 838 if (default_options.terminal) {
839 839 cell.select_all();
840 840 } else {
841 841 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
842 842 that.insert_code_cell_below();
843 843 // If we are adding a new cell at the end, scroll down to show it.
844 844 that.scroll_to_bottom();
845 845 } else {
846 846 that.select(cell_index+1);
847 847 };
848 848 };
849 849 this.dirty = true;
850 850 };
851 851
852 852
853 853 Notebook.prototype.execute_all_cells = function () {
854 854 var ncells = this.ncells();
855 855 for (var i=0; i<ncells; i++) {
856 856 this.select(i);
857 857 this.execute_selected_cell({add_new:false});
858 858 };
859 859 this.scroll_to_bottom();
860 860 };
861 861
862 862
863 863 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
864 864 var msg_id = this.kernel.complete(line, cursor_pos);
865 865 this.msg_cell_map[msg_id] = cell.cell_id;
866 866 };
867 867
868 868 // Persistance and loading
869 869
870 870
871 871 Notebook.prototype.fromJSON = function (data) {
872 872 var ncells = this.ncells();
873 873 for (var i=0; i<ncells; i++) {
874 874 // Always delete cell 0 as they get renumbered as they are deleted.
875 875 this.delete_cell(0);
876 876 };
877 877 // Save the metadata
878 878 this.metadata = data.metadata;
879 879 // Only handle 1 worksheet for now.
880 880 var worksheet = data.worksheets[0];
881 881 if (worksheet !== undefined) {
882 882 var new_cells = worksheet.cells;
883 883 ncells = new_cells.length;
884 884 var cell_data = null;
885 885 var new_cell = null;
886 886 for (var i=0; i<ncells; i++) {
887 887 cell_data = new_cells[i];
888 888 if (cell_data.cell_type == 'code') {
889 889 new_cell = this.insert_code_cell_below();
890 890 new_cell.fromJSON(cell_data);
891 891 } else if (cell_data.cell_type === 'html') {
892 892 new_cell = this.insert_html_cell_below();
893 893 new_cell.fromJSON(cell_data);
894 894 } else if (cell_data.cell_type === 'markdown') {
895 895 new_cell = this.insert_markdown_cell_below();
896 896 new_cell.fromJSON(cell_data);
897 897 };
898 898 };
899 899 };
900 900 };
901 901
902 902
903 903 Notebook.prototype.toJSON = function () {
904 904 var cells = this.cells();
905 905 var ncells = cells.length;
906 906 cell_array = new Array(ncells);
907 907 for (var i=0; i<ncells; i++) {
908 908 cell_array[i] = cells[i].toJSON();
909 909 };
910 910 data = {
911 911 // Only handle 1 worksheet for now.
912 912 worksheets : [{cells:cell_array}],
913 913 metadata : this.metadata
914 914 }
915 915 return data
916 916 };
917 917
918 918 Notebook.prototype.save_notebook = function () {
919 919 if (IPython.save_widget.test_notebook_name()) {
920 920 var notebook_id = IPython.save_widget.get_notebook_id();
921 921 var nbname = IPython.save_widget.get_notebook_name();
922 922 // We may want to move the name/id/nbformat logic inside toJSON?
923 923 var data = this.toJSON();
924 924 data.metadata.name = nbname;
925 925 data.nbformat = 2;
926 926 // We do the call with settings so we can set cache to false.
927 927 var settings = {
928 928 processData : false,
929 929 cache : false,
930 930 type : "PUT",
931 931 data : JSON.stringify(data),
932 932 headers : {'Content-Type': 'application/json'},
933 933 success : $.proxy(this.notebook_saved,this),
934 934 error : $.proxy(this.notebook_save_failed,this)
935 935 };
936 936 IPython.save_widget.status_saving();
937 937 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
938 938 $.ajax(url, settings);
939 939 };
940 940 };
941 941
942 942
943 943 Notebook.prototype.notebook_saved = function (data, status, xhr) {
944 944 this.dirty = false;
945 945 IPython.save_widget.notebook_saved();
946 946 IPython.save_widget.status_save();
947 947 }
948 948
949 949
950 950 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
951 951 // Notify the user and reset the save button
952 952 // TODO: Handle different types of errors (timeout etc.)
953 953 alert('An unexpected error occured while saving the notebook.');
954 954 IPython.save_widget.reset_status();
955 955 }
956 956
957 957
958 958 Notebook.prototype.load_notebook = function (callback) {
959 959 var that = this;
960 960 var notebook_id = IPython.save_widget.get_notebook_id();
961 961 // We do the call with settings so we can set cache to false.
962 962 var settings = {
963 963 processData : false,
964 964 cache : false,
965 965 type : "GET",
966 966 dataType : "json",
967 967 success : function (data, status, xhr) {
968 968 that.notebook_loaded(data, status, xhr);
969 969 if (callback !== undefined) {
970 970 callback();
971 971 };
972 972 }
973 973 };
974 974 IPython.save_widget.status_loading();
975 975 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
976 976 $.ajax(url, settings);
977 977 }
978 978
979 979
980 980 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
981 981 var allowed = xhr.getResponseHeader('Allow');
982 if (allowed && allowed.indexOf('PUT') == -1){
983 this.read_only = true;
984 // unhide login button if it's relevant
985 $('span#login_widget').removeClass('hidden');
986 }else{
987 this.read_only = false;
988 }
989 982 this.fromJSON(data);
990 983 if (this.ncells() === 0) {
991 984 this.insert_code_cell_below();
992 985 };
993 986 IPython.save_widget.status_save();
994 987 IPython.save_widget.set_notebook_name(data.metadata.name);
995 988 this.dirty = false;
996 if (this.read_only) {
997 this.handle_read_only();
998 }else{
989 if (! this.read_only) {
999 990 this.start_kernel();
1000 991 }
1001 992 // fromJSON always selects the last cell inserted. We need to wait
1002 993 // until that is done before scrolling to the top.
1003 994 setTimeout(function () {
1004 995 IPython.notebook.select(0);
1005 996 IPython.notebook.scroll_to_top();
1006 997 }, 50);
1007 998 };
1008 999
1009
1010 Notebook.prototype.handle_read_only = function(){
1011 IPython.left_panel.collapse();
1012 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
1013 $('button#new_notebook').addClass('hidden');
1014 $('div#cell_section').addClass('hidden');
1015 $('div#kernel_section').addClass('hidden');
1016 }
1017
1018
1019 1000 IPython.Notebook = Notebook;
1020 1001
1021 1002
1022 1003 return IPython;
1023 1004
1024 1005 }(IPython));
1025 1006
@@ -1,257 +1,248 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 // NotebookList
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var NotebookList = 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 NotebookList.prototype.style = function () {
24 24 this.element.addClass('ui-widget ui-widget-content');
25 25 $('div#project_name').addClass('ui-widget ui-widget-header');
26 26 };
27 27
28 28
29 29 NotebookList.prototype.bind_events = function () {
30 30 var that = this;
31 31 this.element.bind('dragover', function () {
32 32 return false;
33 33 });
34 34 this.element.bind('drop', function (event) {
35 35 var files = event.originalEvent.dataTransfer.files;
36 36 for (var i = 0, f; f = files[i]; i++) {
37 37 var reader = new FileReader();
38 38 reader.readAsText(f);
39 39 var fname = f.name.split('.');
40 40 var nbname = fname.slice(0,-1).join('.');
41 41 var nbformat = fname.slice(-1)[0];
42 42 if (nbformat === 'ipynb') {nbformat = 'json';};
43 43 if (nbformat === 'py' || nbformat === 'json') {
44 44 var item = that.new_notebook_item(0);
45 45 that.add_name_input(nbname, item);
46 46 item.data('nbformat', nbformat);
47 47 // Store the notebook item in the reader so we can use it later
48 48 // to know which item it belongs to.
49 49 $(reader).data('item', item);
50 50 reader.onload = function (event) {
51 51 var nbitem = $(event.target).data('item');
52 52 that.add_notebook_data(event.target.result, nbitem);
53 53 that.add_upload_button(nbitem);
54 54 };
55 55 };
56 56 }
57 57 return false;
58 58 });
59 59 };
60 60
61 61
62 62 NotebookList.prototype.load_list = function () {
63 63 var settings = {
64 64 processData : false,
65 65 cache : false,
66 66 type : "GET",
67 67 dataType : "json",
68 68 success : $.proxy(this.list_loaded, this)
69 69 };
70 70 var url = $('body').data('baseProjectUrl') + 'notebooks'
71 71 $.ajax(url, settings);
72 72 };
73 73
74 74
75 75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
76 var allowed = xhr.getResponseHeader('Allow');
77 if (allowed && allowed.indexOf('PUT') == -1){
78 this.read_only = true;
79 $('#new_notebook').addClass('hidden');
80 // unhide login button if it's relevant
81 $('span#login_widget').removeClass('hidden');
82 }else{
83 this.read_only = false;
84 }
85 76 var len = data.length;
86 77 // Todo: remove old children
87 78 for (var i=0; i<len; i++) {
88 79 var notebook_id = data[i].notebook_id;
89 80 var nbname = data[i].name;
90 81 var item = this.new_notebook_item(i);
91 82 this.add_link(notebook_id, nbname, item);
92 if (!this.read_only){
83 if (!IPython.read_only){
93 84 // hide delete buttons when readonly
94 85 this.add_delete_button(item);
95 86 }
96 87 };
97 88 };
98 89
99 90
100 91 NotebookList.prototype.new_notebook_item = function (index) {
101 92 var item = $('<div/>');
102 93 item.addClass('notebook_item ui-widget ui-widget-content ui-helper-clearfix');
103 94 var item_name = $('<span/>').addClass('item_name');
104 95
105 96 item.append(item_name);
106 97 if (index === -1) {
107 98 this.element.append(item);
108 99 } else {
109 100 this.element.children().eq(index).after(item);
110 101 }
111 102 return item;
112 103 };
113 104
114 105
115 106 NotebookList.prototype.add_link = function (notebook_id, nbname, item) {
116 107 item.data('nbname', nbname);
117 108 item.data('notebook_id', notebook_id);
118 109 var new_item_name = $('<span/>').addClass('item_name');
119 110 new_item_name.append(
120 111 $('<a/>').
121 112 attr('href', $('body').data('baseProjectURL')+notebook_id).
122 113 attr('target','_blank').
123 114 text(nbname)
124 115 );
125 116 var e = item.find('.item_name');
126 117 if (e.length === 0) {
127 118 item.append(new_item_name);
128 119 } else {
129 120 e.replaceWith(new_item_name);
130 121 };
131 122 };
132 123
133 124
134 125 NotebookList.prototype.add_name_input = function (nbname, item) {
135 126 item.data('nbname', nbname);
136 127 var new_item_name = $('<span/>').addClass('item_name');
137 128 new_item_name.append(
138 129 $('<input/>').addClass('ui-widget ui-widget-content').
139 130 attr('value', nbname).
140 131 attr('size', '30').
141 132 attr('type', 'text')
142 133 );
143 134 var e = item.find('.item_name');
144 135 if (e.length === 0) {
145 136 item.append(new_item_name);
146 137 } else {
147 138 e.replaceWith(new_item_name);
148 139 };
149 140 };
150 141
151 142
152 143 NotebookList.prototype.add_notebook_data = function (data, item) {
153 144 item.data('nbdata',data);
154 145 };
155 146
156 147
157 148 NotebookList.prototype.add_delete_button = function (item) {
158 149 var new_buttons = $('<span/>').addClass('item_buttons');
159 150 var delete_button = $('<button>Delete</button>').button().
160 151 click(function (e) {
161 152 // $(this) is the button that was clicked.
162 153 var that = $(this);
163 154 // We use the nbname and notebook_id from the parent notebook_item element's
164 155 // data because the outer scopes values change as we iterate through the loop.
165 156 var parent_item = that.parents('div.notebook_item');
166 157 var nbname = parent_item.data('nbname');
167 158 var notebook_id = parent_item.data('notebook_id');
168 159 var dialog = $('<div/>');
169 160 dialog.html('Are you sure you want to permanently delete the notebook: ' + nbname + '?');
170 161 parent_item.append(dialog);
171 162 dialog.dialog({
172 163 resizable: false,
173 164 modal: true,
174 165 title: "Delete notebook",
175 166 buttons : {
176 167 "Delete": function () {
177 168 var settings = {
178 169 processData : false,
179 170 cache : false,
180 171 type : "DELETE",
181 172 dataType : "json",
182 173 success : function (data, status, xhr) {
183 174 parent_item.remove();
184 175 }
185 176 };
186 177 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
187 178 $.ajax(url, settings);
188 179 $(this).dialog('close');
189 180 },
190 181 "Cancel": function () {
191 182 $(this).dialog('close');
192 183 }
193 184 }
194 185 });
195 186 });
196 187 new_buttons.append(delete_button);
197 188 var e = item.find('.item_buttons');
198 189 if (e.length === 0) {
199 190 item.append(new_buttons);
200 191 } else {
201 192 e.replaceWith(new_buttons);
202 193 };
203 194 };
204 195
205 196
206 197 NotebookList.prototype.add_upload_button = function (item) {
207 198 var that = this;
208 199 var new_buttons = $('<span/>').addClass('item_buttons');
209 200 var upload_button = $('<button>Upload</button>').button().
210 201 click(function (e) {
211 202 var nbname = item.find('.item_name > input').attr('value');
212 203 var nbformat = item.data('nbformat');
213 204 var nbdata = item.data('nbdata');
214 205 var content_type = 'text/plain';
215 206 if (nbformat === 'json') {
216 207 content_type = 'application/json';
217 208 } else if (nbformat === 'py') {
218 209 content_type = 'application/x-python';
219 210 };
220 211 var settings = {
221 212 processData : false,
222 213 cache : false,
223 214 type : 'POST',
224 215 dataType : 'json',
225 216 data : nbdata,
226 217 headers : {'Content-Type': content_type},
227 218 success : function (data, status, xhr) {
228 219 that.add_link(data, nbname, item);
229 220 that.add_delete_button(item);
230 221 }
231 222 };
232 223
233 224 var qs = $.param({name:nbname, format:nbformat});
234 225 var url = $('body').data('baseProjectUrl') + 'notebooks?' + qs
235 226 $.ajax(url, settings);
236 227 });
237 228 var cancel_button = $('<button>Cancel</button>').button().
238 229 click(function (e) {
239 230 item.remove();
240 231 });
241 232 upload_button.addClass('upload_button');
242 233 new_buttons.append(upload_button).append(cancel_button);
243 234 var e = item.find('.item_buttons');
244 235 if (e.length === 0) {
245 236 item.append(new_buttons);
246 237 } else {
247 238 e.replaceWith(new_buttons);
248 239 };
249 240 };
250 241
251 242
252 243 IPython.NotebookList = NotebookList;
253 244
254 245 return IPython;
255 246
256 247 }(IPython));
257 248
@@ -1,60 +1,84 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 14
15 15 MathJax.Hub.Config({
16 16 tex2jax: {
17 17 inlineMath: [ ['$','$'], ["\\(","\\)"] ],
18 18 displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
19 19 },
20 20 displayAlign: 'left', // Change this to 'center' to center equations.
21 21 "HTML-CSS": {
22 22 styles: {'.MathJax_Display': {"margin": 0}}
23 23 }
24 24 });
25 25 IPython.markdown_converter = new Markdown.Converter();
26 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
26 27
27 28 $('div#header').addClass('border-box-sizing');
28 29 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
29 30 $('div#notebook_panel').addClass('border-box-sizing ui-widget');
30 31
31 32 IPython.layout_manager = new IPython.LayoutManager();
32 33 IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
33 34 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 35 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 36 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 37 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
37 38 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
38 39 IPython.notebook = new IPython.Notebook('div#notebook');
39 40 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
40 41 IPython.kernel_status_widget.status_idle();
41 42
42 43 IPython.layout_manager.do_resize();
43 44
44 45 // These have display: none in the css file and are made visible here to prevent FLOUC.
45 46 $('div#header').css('display','block');
47
48 if(IPython.read_only){
49 // hide various elements from read-only view
50 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 $('button#new_notebook').addClass('hidden');
53 $('div#cell_section').addClass('hidden');
54 $('div#kernel_section').addClass('hidden');
55 $('span#login_widget').removeClass('hidden');
56 // left panel starts collapsed, but the collapse must happen after
57 // elements start drawing. Don't draw contents of the panel until
58 // after they are collapsed
59 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
60 }
61
46 62 $('div#main_app').css('display','block');
47 63
48 64 // Perform these actions after the notebook has been loaded.
49 65 // We wait 100 milliseconds because the notebook scrolls to the top after a load
50 66 // is completed and we need to wait for that to mostly finish.
51 67 IPython.notebook.load_notebook(function () {
52 68 setTimeout(function () {
53 69 IPython.save_widget.update_url();
54 70 IPython.layout_manager.do_resize();
55 71 IPython.pager.collapse();
72 if(IPython.read_only){
73 // collapse the left panel on read-only
74 IPython.left_panel.collapse();
75 // and finally unhide the panel contents after collapse
76 setTimeout(function(){
77 IPython.left_panel.left_panel_element.css('visibility', 'visible');
78 }, 200)
79 }
56 80 },100);
57 81 });
58 82
59 83 });
60 84
@@ -1,40 +1,48 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // On document ready
10 10 //============================================================================
11 11
12 12
13 13 $(document).ready(function () {
14 14
15 15 $('div#header').addClass('border-box-sizing');
16 16 $('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
17 17
18 18 $('div#main_app').addClass('border-box-sizing ui-widget');
19 19 $('div#app_hbox').addClass('hbox');
20 20
21 21 $('div#content_toolbar').addClass('ui-widget ui-helper-clearfix');
22 22
23 23 $('#new_notebook').button().click(function (e) {
24 24 window.open($('body').data('baseProjectUrl')+'new');
25 25 });
26 26
27 27 $('div#left_panel').addClass('box-flex');
28 28 $('div#right_panel').addClass('box-flex');
29 29
30 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
31
30 32 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
31 33 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
34
35 if (IPython.read_only){
36 $('#new_notebook').addClass('hidden');
37 // unhide login button if it's relevant
38 $('span#login_widget').removeClass('hidden');
39 }
32 40 IPython.notebook_list.load_list();
33 41
34 42 // These have display: none in the css file and are made visible here to prevent FLOUC.
35 43 $('div#header').css('display','block');
36 44 $('div#main_app').css('display','block');
37 45
38 46
39 47 });
40 48
@@ -1,53 +1,55 b''
1 1 <!DOCTYPE HTML>
2 2 <html>
3 3
4 4 <head>
5 5 <meta charset="utf-8">
6 6
7 7 <title>IPython Notebook</title>
8 8
9 9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 10 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
11 11 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
12 12 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
13 13
14 <meta name="read_only" content="{{read_only}}"/>
15
14 16 </head>
15 17
16 18 <body>
17 19
18 20 <div id="header">
19 21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
20 22 </div>
21 23
22 24 <div id="header_border"></div>
23 25
24 26 <div id="main_app">
25 27
26 28 <div id="app_hbox">
27 29
28 30 <div id="left_panel">
29 31 </div>
30 32
31 33 <div id="content_panel">
32 34 <form action="/login?next={{url_escape(next)}}" method="post">
33 35 Password: <input type="password" name="password">
34 36 <input type="submit" value="Sign in" id="signin">
35 37 </form>
36 38 </div>
37 39 <div id="right_panel">
38 40 </div>
39 41
40 42 </div>
41 43
42 44 </div>
43 45
44 46 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
45 47 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
46 48 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
47 49 <script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
48 50
49 51 </body>
50 52
51 53 </html>
52 54
53 55
@@ -1,294 +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 <meta name="read_only" content="{{read_only}}"/>
44 45
45 46 </head>
46 47
47 48 <body onload='CheckMathJax();'
48 49 data-project={{project}} data-notebook-id={{notebook_id}}
49 50 data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
50 51 >
51 52
52 53 <div id="header">
53 54 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
54 55 <span id="save_widget">
55 56 <input type="text" id="notebook_name" size="20"></textarea>
56 57 <button id="save_notebook"><u>S</u>ave</button>
57 58 </span>
58 59 <span id="quick_help_area">
59 60 <button id="quick_help">Quick<u>H</u>elp</button>
60 61 </span>
61 62 <span id="login_widget" class="hidden">
62 63 <button id="login">Login</button>
63 64 </span>
64 65 <span id="kernel_status">Idle</span>
65 66 </div>
66 67
67 68 <div id="MathJaxFetchingWarning"
68 69 style="width:80%; margin:auto;padding-top:20%;text-align: justify; display:none">
69 70 <p style="font-size:26px;">There was an issue trying to fetch MathJax.js
70 71 from the internet.</p>
71 72
72 73 <p style="padding:0.2em"> With a working internet connection, you can run
73 74 the following at a Python or IPython prompt, which will install a local
74 75 copy of MathJax:</p>
75 76
76 77 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
77 78 from IPython.external import mathjax; mathjax.install_mathjax()
78 79 </pre>
79 80 This will try to install MathJax into the directory where you installed
80 81 IPython. If you installed IPython to a location that requires
81 82 administrative privileges to write, you will need to make this call as
82 83 an administrator. On OSX/Linux/Unix, this can be done at the
83 84 command-line via:
84 85 <pre style="background-color:lightblue;border:thin silver solid;padding:0.4em">
85 86 sudo python -c "from IPython.external import mathjax; mathjax.install_mathjax()"
86 87 </pre>
87 88 </p>
88 89 </div>
89 90
90 91 <div id="main_app">
91 92
92 93 <div id="left_panel">
93 94
94 95 <div id="notebook_section">
95 96 <div class="section_header">
96 97 <h3>Notebook</h3>
97 98 </div>
98 99 <div class="section_content">
99 100 <div class="section_row">
100 101 <span id="new_open" class="section_row_buttons">
101 102 <button id="new_notebook">New</button>
102 103 <button id="open_notebook">Open</button>
103 104 </span>
104 105 <span class="section_row_header">Actions</span>
105 106 </div>
106 107 <div class="section_row">
107 108 <span>
108 109 <select id="download_format">
109 110 <option value="json">ipynb</option>
110 111 <option value="py">py</option>
111 112 </select>
112 113 </span>
113 114 <span class="section_row_buttons">
114 115 <button id="download_notebook">Download</button>
115 116 </span>
116 117 </div>
117 118 <div class="section_row">
118 119 <span class="section_row_buttons">
119 120 <span id="print_widget">
120 121 <button id="print_notebook">Print</button>
121 122 </span>
122 123 </span>
123 124 </div>
124 125 </div>
125 126 </div>
126 127
127 128 <div id="cell_section">
128 129 <div class="section_header">
129 130 <h3>Cell</h3>
130 131 </div>
131 132 <div class="section_content">
132 133 <div class="section_row">
133 134 <span class="section_row_buttons">
134 135 <button id="delete_cell"><u>D</u>elete</button>
135 136 </span>
136 137 <span class="section_row_header">Actions</span>
137 138 </div>
138 139 <div class="section_row">
139 140 <span id="cell_type" class="section_row_buttons">
140 141 <button id="to_code"><u>C</u>ode</button>
141 142 <!-- <button id="to_html">HTML</button>-->
142 143 <button id="to_markdown"><u>M</u>arkdown</button>
143 144 </span>
144 145 <span class="button_label">Format</span>
145 146 </div>
146 147 <div class="section_row">
147 148 <span id="cell_output" class="section_row_buttons">
148 149 <button id="toggle_output"><u>T</u>oggle</button>
149 150 <button id="clear_all_output">ClearAll</button>
150 151 </span>
151 152 <span class="button_label">Output</span>
152 153 </div>
153 154 <div class="section_row">
154 155 <span id="insert" class="section_row_buttons">
155 156 <button id="insert_cell_above"><u>A</u>bove</button>
156 157 <button id="insert_cell_below"><u>B</u>elow</button>
157 158 </span>
158 159 <span class="button_label">Insert</span>
159 160 </div>
160 161 <div class="section_row">
161 162 <span id="move" class="section_row_buttons">
162 163 <button id="move_cell_up">Up</button>
163 164 <button id="move_cell_down">Down</button>
164 165 </span>
165 166 <span class="button_label">Move</span>
166 167 </div>
167 168 <div class="section_row">
168 169 <span id="run_cells" class="section_row_buttons">
169 170 <button id="run_selected_cell">Selected</button>
170 171 <button id="run_all_cells">All</button>
171 172 </span>
172 173 <span class="button_label">Run</span>
173 174 </div>
174 175 <div class="section_row">
175 176 <span id="autoindent_span">
176 177 <input type="checkbox" id="autoindent" checked="true"></input>
177 178 </span>
178 179 <span class="checkbox_label" id="autoindent_label">Autoindent:</span>
179 180 </div>
180 181 </div>
181 182 </div>
182 183
183 184 <div id="kernel_section">
184 185 <div class="section_header">
185 186 <h3>Kernel</h3>
186 187 </div>
187 188 <div class="section_content">
188 189 <div class="section_row">
189 190 <span id="int_restart" class="section_row_buttons">
190 191 <button id="int_kernel"><u>I</u>nterrupt</button>
191 192 <button id="restart_kernel">Restart</button>
192 193 </span>
193 194 <span class="section_row_header">Actions</span>
194 195 </div>
195 196 <div class="section_row">
196 197 <span id="kernel_persist">
197 198 {% if kill_kernel %}
198 199 <input type="checkbox" id="kill_kernel" checked="true"></input>
199 200 {% else %}
200 201 <input type="checkbox" id="kill_kernel"></input>
201 202 {% end %}
202 203 </span>
203 204 <span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
204 205 </div>
205 206 </div>
206 207 </div>
207 208
208 209 <div id="help_section">
209 210 <div class="section_header">
210 211 <h3>Help</h3>
211 212 </div>
212 213 <div class="section_content">
213 214 <div class="section_row">
214 215 <span id="help_buttons0" class="section_row_buttons">
215 216 <a id="python_help" href="http://docs.python.org" target="_blank">Python</a>
216 217 <a id="ipython_help" href="http://ipython.org/documentation.html" target="_blank">IPython</a>
217 218 </span>
218 219 <span class="section_row_header">Links</span>
219 220 </div>
220 221 <div class="section_row">
221 222 <span id="help_buttons1" class="section_row_buttons">
222 223 <a id="numpy_help" href="http://docs.scipy.org/doc/numpy/reference/" target="_blank">NumPy</a>
223 224 <a id="scipy_help" href="http://docs.scipy.org/doc/scipy/reference/" target="_blank">SciPy</a>
224 225 </span>
225 226 </div>
226 227 <div class="section_row">
227 228 <span id="help_buttons2" class="section_row_buttons">
228 229 <a id="matplotlib_help" href="http://matplotlib.sourceforge.net/" target="_blank">MPL</a>
229 230 <a id="sympy_help" href="http://docs.sympy.org/dev/index.html" target="_blank">SymPy</a>
230 231 </span>
231 232 </div>
232 233 <div class="section_row">
233 234 <span class="help_string">run selected cell</span>
234 235 <span class="help_string_label">Shift-Enter :</span>
235 236 </div>
236 237 <div class="section_row">
237 238 <span class="help_string">run selected cell in-place</span>
238 239 <span class="help_string_label">Ctrl-Enter :</span>
239 240 </div>
240 241 <div class="section_row">
241 242 <span class="help_string">show keyboard shortcuts</span>
242 243 <span class="help_string_label">Ctrl-m h :</span>
243 244 </div>
244 245 </div>
245 246 </div>
246 247
247 248 </div>
248 249 <div id="left_panel_splitter"></div>
249 250 <div id="notebook_panel">
250 251 <div id="notebook"></div>
251 252 <div id="pager_splitter"></div>
252 253 <div id="pager"></div>
253 254 </div>
254 255
255 256 </div>
256 257
257 258 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
258 259 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
259 260 <script src="static/jquery/js/jquery.autogrow.js" type="text/javascript" charset="utf-8"></script>
260 261
261 262 <script src="static/codemirror/lib/codemirror.js" charset="utf-8"></script>
262 263 <script src="static/codemirror/mode/python/python.js" charset="utf-8"></script>
263 264 <script src="static/codemirror/mode/htmlmixed/htmlmixed.js" charset="utf-8"></script>
264 265 <script src="static/codemirror/mode/xml/xml.js" charset="utf-8"></script>
265 266 <script src="static/codemirror/mode/javascript/javascript.js" charset="utf-8"></script>
266 267 <script src="static/codemirror/mode/css/css.js" charset="utf-8"></script>
267 268 <script src="static/codemirror/mode/rst/rst.js" charset="utf-8"></script>
268 269 <script src="static/codemirror/mode/markdown/markdown.js" charset="utf-8"></script>
269 270
270 271 <script src="static/pagedown/Markdown.Converter.js" charset="utf-8"></script>
271 272
272 273 <script src="static/prettify/prettify.js" charset="utf-8"></script>
273 274
274 275 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
275 276 <script src="static/js/utils.js" type="text/javascript" charset="utf-8"></script>
276 277 <script src="static/js/cell.js" type="text/javascript" charset="utf-8"></script>
277 278 <script src="static/js/codecell.js" type="text/javascript" charset="utf-8"></script>
278 279 <script src="static/js/textcell.js" type="text/javascript" charset="utf-8"></script>
279 280 <script src="static/js/kernel.js" type="text/javascript" charset="utf-8"></script>
280 281 <script src="static/js/kernelstatus.js" type="text/javascript" charset="utf-8"></script>
281 282 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
282 283 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
283 284 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 285 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
285 286 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
286 287 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
287 288 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
288 289 <script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
289 290 <script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
290 291 <script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
291 292
292 293 </body>
293 294
294 295 </html>
@@ -1,67 +1,69 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 Dashboard</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 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
14 14
15 <meta name="read_only" content="{{read_only}}"/>
16
15 17 </head>
16 18
17 19 <body data-project={{project}} data-base-project-url={{base_project_url}}
18 20 data-base-kernel-url={{base_kernel_url}}>
19 21
20 22 <div id="header">
21 23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
22 24 <span id="login_widget" class="hidden">
23 25 <button id="login">Login</button>
24 26 </span>
25 27 </div>
26 28
27 29 <div id="header_border"></div>
28 30
29 31 <div id="main_app">
30 32
31 33 <div id="app_hbox">
32 34
33 35 <div id="left_panel">
34 36 </div>
35 37
36 38 <div id="content_panel">
37 39 <div id="content_toolbar">
38 40 <span id="drag_info">Drag files onto the list to import notebooks.</span>
39 41 <span id="notebooks_buttons">
40 42 <button id="new_notebook">New Notebook</button>
41 43 </span>
42 44 </div>
43 45 <div id="notebook_list">
44 46 <div id="project_name"><h2>{{project}}</h2></div>
45 47 </div>
46 48
47 49 </div>
48 50
49 51 <div id="right_panel">
50 52 </div>
51 53
52 54 </div>
53 55
54 56 </div>
55 57
56 58 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
57 59 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
58 60 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
59 61 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 62 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
61 63 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
62 64
63 65 </body>
64 66
65 67 </html>
66 68
67 69
General Comments 0
You need to be logged in to leave comments. Login now