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