##// END OF EJS Templates
Adds configuration options to use Google Drive content manager...
KesterTong -
Show More
@@ -0,0 +1,43 b''
1 """A notebook manager for when the logic is done client side (in JavaScript)."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 from .nbmanager import NotebookManager
15
16 from fnmatch import fnmatch
17 import itertools
18 import os
19
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.nbformat import current, sign
22 from IPython.utils.traitlets import Instance, Unicode, List
23
24 #-----------------------------------------------------------------------------
25 # Classes
26 #-----------------------------------------------------------------------------
27
28 class ClientSideNotebookManager(NotebookManager):
29 # The notebook directory is meaningless since we are not using
30 # the local filesystem.
31 notebook_dir = ''
32
33 def path_exists(self, path):
34 # Always return true, because this check is now done client side.
35 return True
36
37 def is_hidden(self, path):
38 # Always return false, because this check is now done client side.
39 return False
40
41 def notebook_exists(self, name, path=''):
42 # Always return true, because this check is now done client side.
43 return True
@@ -1,474 +1,482 b''
1 """Base Tornado handlers for the notebook server."""
1 """Base Tornado handlers for the notebook server."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import functools
6 import functools
7 import json
7 import json
8 import logging
8 import logging
9 import os
9 import os
10 import re
10 import re
11 import sys
11 import sys
12 import traceback
12 import traceback
13 try:
13 try:
14 # py3
14 # py3
15 from http.client import responses
15 from http.client import responses
16 except ImportError:
16 except ImportError:
17 from httplib import responses
17 from httplib import responses
18
18
19 from jinja2 import TemplateNotFound
19 from jinja2 import TemplateNotFound
20 from tornado import web
20 from tornado import web
21
21
22 try:
22 try:
23 from tornado.log import app_log
23 from tornado.log import app_log
24 except ImportError:
24 except ImportError:
25 app_log = logging.getLogger()
25 app_log = logging.getLogger()
26
26
27 import IPython
27 import IPython
28 from IPython.utils.sysinfo import get_sys_info
28 from IPython.utils.sysinfo import get_sys_info
29
29
30 from IPython.config import Application
30 from IPython.config import Application
31 from IPython.utils.path import filefind
31 from IPython.utils.path import filefind
32 from IPython.utils.py3compat import string_types
32 from IPython.utils.py3compat import string_types
33 from IPython.html.utils import is_hidden, url_path_join, url_escape
33 from IPython.html.utils import is_hidden, url_path_join, url_escape
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Top-level handlers
36 # Top-level handlers
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 non_alphanum = re.compile(r'[^A-Za-z0-9]')
38 non_alphanum = re.compile(r'[^A-Za-z0-9]')
39
39
40 sys_info = json.dumps(get_sys_info())
40 sys_info = json.dumps(get_sys_info())
41
41
42 class AuthenticatedHandler(web.RequestHandler):
42 class AuthenticatedHandler(web.RequestHandler):
43 """A RequestHandler with an authenticated user."""
43 """A RequestHandler with an authenticated user."""
44
44
45 def set_default_headers(self):
45 def set_default_headers(self):
46 headers = self.settings.get('headers', {})
46 headers = self.settings.get('headers', {})
47
47
48 if "X-Frame-Options" not in headers:
48 if "X-Frame-Options" not in headers:
49 headers["X-Frame-Options"] = "SAMEORIGIN"
49 headers["X-Frame-Options"] = "SAMEORIGIN"
50
50
51 for header_name,value in headers.items() :
51 for header_name,value in headers.items() :
52 try:
52 try:
53 self.set_header(header_name, value)
53 self.set_header(header_name, value)
54 except Exception:
54 except Exception:
55 # tornado raise Exception (not a subclass)
55 # tornado raise Exception (not a subclass)
56 # if method is unsupported (websocket and Access-Control-Allow-Origin
56 # if method is unsupported (websocket and Access-Control-Allow-Origin
57 # for example, so just ignore)
57 # for example, so just ignore)
58 pass
58 pass
59
59
60 def clear_login_cookie(self):
60 def clear_login_cookie(self):
61 self.clear_cookie(self.cookie_name)
61 self.clear_cookie(self.cookie_name)
62
62
63 def get_current_user(self):
63 def get_current_user(self):
64 user_id = self.get_secure_cookie(self.cookie_name)
64 user_id = self.get_secure_cookie(self.cookie_name)
65 # For now the user_id should not return empty, but it could eventually
65 # For now the user_id should not return empty, but it could eventually
66 if user_id == '':
66 if user_id == '':
67 user_id = 'anonymous'
67 user_id = 'anonymous'
68 if user_id is None:
68 if user_id is None:
69 # prevent extra Invalid cookie sig warnings:
69 # prevent extra Invalid cookie sig warnings:
70 self.clear_login_cookie()
70 self.clear_login_cookie()
71 if not self.login_available:
71 if not self.login_available:
72 user_id = 'anonymous'
72 user_id = 'anonymous'
73 return user_id
73 return user_id
74
74
75 @property
75 @property
76 def cookie_name(self):
76 def cookie_name(self):
77 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
77 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
78 self.request.host
78 self.request.host
79 ))
79 ))
80 return self.settings.get('cookie_name', default_cookie_name)
80 return self.settings.get('cookie_name', default_cookie_name)
81
81
82 @property
82 @property
83 def password(self):
83 def password(self):
84 """our password"""
84 """our password"""
85 return self.settings.get('password', '')
85 return self.settings.get('password', '')
86
86
87 @property
87 @property
88 def logged_in(self):
88 def logged_in(self):
89 """Is a user currently logged in?
89 """Is a user currently logged in?
90
90
91 """
91 """
92 user = self.get_current_user()
92 user = self.get_current_user()
93 return (user and not user == 'anonymous')
93 return (user and not user == 'anonymous')
94
94
95 @property
95 @property
96 def login_available(self):
96 def login_available(self):
97 """May a user proceed to log in?
97 """May a user proceed to log in?
98
98
99 This returns True if login capability is available, irrespective of
99 This returns True if login capability is available, irrespective of
100 whether the user is already logged in or not.
100 whether the user is already logged in or not.
101
101
102 """
102 """
103 return bool(self.settings.get('password', ''))
103 return bool(self.settings.get('password', ''))
104
104
105
105
106 class IPythonHandler(AuthenticatedHandler):
106 class IPythonHandler(AuthenticatedHandler):
107 """IPython-specific extensions to authenticated handling
107 """IPython-specific extensions to authenticated handling
108
108
109 Mostly property shortcuts to IPython-specific settings.
109 Mostly property shortcuts to IPython-specific settings.
110 """
110 """
111
111
112 @property
112 @property
113 def config(self):
113 def config(self):
114 return self.settings.get('config', None)
114 return self.settings.get('config', None)
115
115
116 @property
116 @property
117 def log(self):
117 def log(self):
118 """use the IPython log by default, falling back on tornado's logger"""
118 """use the IPython log by default, falling back on tornado's logger"""
119 if Application.initialized():
119 if Application.initialized():
120 return Application.instance().log
120 return Application.instance().log
121 else:
121 else:
122 return app_log
122 return app_log
123
123
124 #---------------------------------------------------------------
124 #---------------------------------------------------------------
125 # URLs
125 # URLs
126 #---------------------------------------------------------------
126 #---------------------------------------------------------------
127
127
128 @property
128 @property
129 def mathjax_url(self):
129 def mathjax_url(self):
130 return self.settings.get('mathjax_url', '')
130 return self.settings.get('mathjax_url', '')
131
131
132 @property
132 @property
133 def base_url(self):
133 def base_url(self):
134 return self.settings.get('base_url', '/')
134 return self.settings.get('base_url', '/')
135
135
136 @property
136 @property
137 def ws_url(self):
137 def ws_url(self):
138 return self.settings.get('websocket_url', '')
138 return self.settings.get('websocket_url', '')
139
139
140 @property
141 def contentmanager_js_source(self):
142 self.log.debug("Using contentmanager: %s", self.settings.get('contentmanager_js_source',
143 'base/js/contentmanager'))
144 return self.settings.get('contentmanager_js_source',
145 'base/js/contentmanager')
146
140 #---------------------------------------------------------------
147 #---------------------------------------------------------------
141 # Manager objects
148 # Manager objects
142 #---------------------------------------------------------------
149 #---------------------------------------------------------------
143
150
144 @property
151 @property
145 def kernel_manager(self):
152 def kernel_manager(self):
146 return self.settings['kernel_manager']
153 return self.settings['kernel_manager']
147
154
148 @property
155 @property
149 def contents_manager(self):
156 def contents_manager(self):
150 return self.settings['contents_manager']
157 return self.settings['contents_manager']
151
158
152 @property
159 @property
153 def cluster_manager(self):
160 def cluster_manager(self):
154 return self.settings['cluster_manager']
161 return self.settings['cluster_manager']
155
162
156 @property
163 @property
157 def session_manager(self):
164 def session_manager(self):
158 return self.settings['session_manager']
165 return self.settings['session_manager']
159
166
160 @property
167 @property
161 def kernel_spec_manager(self):
168 def kernel_spec_manager(self):
162 return self.settings['kernel_spec_manager']
169 return self.settings['kernel_spec_manager']
163
170
164 #---------------------------------------------------------------
171 #---------------------------------------------------------------
165 # CORS
172 # CORS
166 #---------------------------------------------------------------
173 #---------------------------------------------------------------
167
174
168 @property
175 @property
169 def allow_origin(self):
176 def allow_origin(self):
170 """Normal Access-Control-Allow-Origin"""
177 """Normal Access-Control-Allow-Origin"""
171 return self.settings.get('allow_origin', '')
178 return self.settings.get('allow_origin', '')
172
179
173 @property
180 @property
174 def allow_origin_pat(self):
181 def allow_origin_pat(self):
175 """Regular expression version of allow_origin"""
182 """Regular expression version of allow_origin"""
176 return self.settings.get('allow_origin_pat', None)
183 return self.settings.get('allow_origin_pat', None)
177
184
178 @property
185 @property
179 def allow_credentials(self):
186 def allow_credentials(self):
180 """Whether to set Access-Control-Allow-Credentials"""
187 """Whether to set Access-Control-Allow-Credentials"""
181 return self.settings.get('allow_credentials', False)
188 return self.settings.get('allow_credentials', False)
182
189
183 def set_default_headers(self):
190 def set_default_headers(self):
184 """Add CORS headers, if defined"""
191 """Add CORS headers, if defined"""
185 super(IPythonHandler, self).set_default_headers()
192 super(IPythonHandler, self).set_default_headers()
186 if self.allow_origin:
193 if self.allow_origin:
187 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
194 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
188 elif self.allow_origin_pat:
195 elif self.allow_origin_pat:
189 origin = self.get_origin()
196 origin = self.get_origin()
190 if origin and self.allow_origin_pat.match(origin):
197 if origin and self.allow_origin_pat.match(origin):
191 self.set_header("Access-Control-Allow-Origin", origin)
198 self.set_header("Access-Control-Allow-Origin", origin)
192 if self.allow_credentials:
199 if self.allow_credentials:
193 self.set_header("Access-Control-Allow-Credentials", 'true')
200 self.set_header("Access-Control-Allow-Credentials", 'true')
194
201
195 def get_origin(self):
202 def get_origin(self):
196 # Handle WebSocket Origin naming convention differences
203 # Handle WebSocket Origin naming convention differences
197 # The difference between version 8 and 13 is that in 8 the
204 # The difference between version 8 and 13 is that in 8 the
198 # client sends a "Sec-Websocket-Origin" header and in 13 it's
205 # client sends a "Sec-Websocket-Origin" header and in 13 it's
199 # simply "Origin".
206 # simply "Origin".
200 if "Origin" in self.request.headers:
207 if "Origin" in self.request.headers:
201 origin = self.request.headers.get("Origin")
208 origin = self.request.headers.get("Origin")
202 else:
209 else:
203 origin = self.request.headers.get("Sec-Websocket-Origin", None)
210 origin = self.request.headers.get("Sec-Websocket-Origin", None)
204 return origin
211 return origin
205
212
206 #---------------------------------------------------------------
213 #---------------------------------------------------------------
207 # template rendering
214 # template rendering
208 #---------------------------------------------------------------
215 #---------------------------------------------------------------
209
216
210 def get_template(self, name):
217 def get_template(self, name):
211 """Return the jinja template object for a given name"""
218 """Return the jinja template object for a given name"""
212 return self.settings['jinja2_env'].get_template(name)
219 return self.settings['jinja2_env'].get_template(name)
213
220
214 def render_template(self, name, **ns):
221 def render_template(self, name, **ns):
215 ns.update(self.template_namespace)
222 ns.update(self.template_namespace)
216 template = self.get_template(name)
223 template = self.get_template(name)
217 return template.render(**ns)
224 return template.render(**ns)
218
225
219 @property
226 @property
220 def template_namespace(self):
227 def template_namespace(self):
221 return dict(
228 return dict(
222 base_url=self.base_url,
229 base_url=self.base_url,
223 ws_url=self.ws_url,
230 ws_url=self.ws_url,
224 logged_in=self.logged_in,
231 logged_in=self.logged_in,
225 login_available=self.login_available,
232 login_available=self.login_available,
226 static_url=self.static_url,
233 static_url=self.static_url,
227 sys_info=sys_info
234 sys_info=sys_info,
235 contentmanager_js_source=self.contentmanager_js_source,
228 )
236 )
229
237
230 def get_json_body(self):
238 def get_json_body(self):
231 """Return the body of the request as JSON data."""
239 """Return the body of the request as JSON data."""
232 if not self.request.body:
240 if not self.request.body:
233 return None
241 return None
234 # Do we need to call body.decode('utf-8') here?
242 # Do we need to call body.decode('utf-8') here?
235 body = self.request.body.strip().decode(u'utf-8')
243 body = self.request.body.strip().decode(u'utf-8')
236 try:
244 try:
237 model = json.loads(body)
245 model = json.loads(body)
238 except Exception:
246 except Exception:
239 self.log.debug("Bad JSON: %r", body)
247 self.log.debug("Bad JSON: %r", body)
240 self.log.error("Couldn't parse JSON", exc_info=True)
248 self.log.error("Couldn't parse JSON", exc_info=True)
241 raise web.HTTPError(400, u'Invalid JSON in body of request')
249 raise web.HTTPError(400, u'Invalid JSON in body of request')
242 return model
250 return model
243
251
244 def write_error(self, status_code, **kwargs):
252 def write_error(self, status_code, **kwargs):
245 """render custom error pages"""
253 """render custom error pages"""
246 exc_info = kwargs.get('exc_info')
254 exc_info = kwargs.get('exc_info')
247 message = ''
255 message = ''
248 status_message = responses.get(status_code, 'Unknown HTTP Error')
256 status_message = responses.get(status_code, 'Unknown HTTP Error')
249 if exc_info:
257 if exc_info:
250 exception = exc_info[1]
258 exception = exc_info[1]
251 # get the custom message, if defined
259 # get the custom message, if defined
252 try:
260 try:
253 message = exception.log_message % exception.args
261 message = exception.log_message % exception.args
254 except Exception:
262 except Exception:
255 pass
263 pass
256
264
257 # construct the custom reason, if defined
265 # construct the custom reason, if defined
258 reason = getattr(exception, 'reason', '')
266 reason = getattr(exception, 'reason', '')
259 if reason:
267 if reason:
260 status_message = reason
268 status_message = reason
261
269
262 # build template namespace
270 # build template namespace
263 ns = dict(
271 ns = dict(
264 status_code=status_code,
272 status_code=status_code,
265 status_message=status_message,
273 status_message=status_message,
266 message=message,
274 message=message,
267 exception=exception,
275 exception=exception,
268 )
276 )
269
277
270 self.set_header('Content-Type', 'text/html')
278 self.set_header('Content-Type', 'text/html')
271 # render the template
279 # render the template
272 try:
280 try:
273 html = self.render_template('%s.html' % status_code, **ns)
281 html = self.render_template('%s.html' % status_code, **ns)
274 except TemplateNotFound:
282 except TemplateNotFound:
275 self.log.debug("No template for %d", status_code)
283 self.log.debug("No template for %d", status_code)
276 html = self.render_template('error.html', **ns)
284 html = self.render_template('error.html', **ns)
277
285
278 self.write(html)
286 self.write(html)
279
287
280
288
281
289
282 class Template404(IPythonHandler):
290 class Template404(IPythonHandler):
283 """Render our 404 template"""
291 """Render our 404 template"""
284 def prepare(self):
292 def prepare(self):
285 raise web.HTTPError(404)
293 raise web.HTTPError(404)
286
294
287
295
288 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
296 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
289 """static files should only be accessible when logged in"""
297 """static files should only be accessible when logged in"""
290
298
291 @web.authenticated
299 @web.authenticated
292 def get(self, path):
300 def get(self, path):
293 if os.path.splitext(path)[1] == '.ipynb':
301 if os.path.splitext(path)[1] == '.ipynb':
294 name = os.path.basename(path)
302 name = os.path.basename(path)
295 self.set_header('Content-Type', 'application/json')
303 self.set_header('Content-Type', 'application/json')
296 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
304 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
297
305
298 return web.StaticFileHandler.get(self, path)
306 return web.StaticFileHandler.get(self, path)
299
307
300 def compute_etag(self):
308 def compute_etag(self):
301 return None
309 return None
302
310
303 def validate_absolute_path(self, root, absolute_path):
311 def validate_absolute_path(self, root, absolute_path):
304 """Validate and return the absolute path.
312 """Validate and return the absolute path.
305
313
306 Requires tornado 3.1
314 Requires tornado 3.1
307
315
308 Adding to tornado's own handling, forbids the serving of hidden files.
316 Adding to tornado's own handling, forbids the serving of hidden files.
309 """
317 """
310 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
318 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
311 abs_root = os.path.abspath(root)
319 abs_root = os.path.abspath(root)
312 if is_hidden(abs_path, abs_root):
320 if is_hidden(abs_path, abs_root):
313 self.log.info("Refusing to serve hidden file, via 404 Error")
321 self.log.info("Refusing to serve hidden file, via 404 Error")
314 raise web.HTTPError(404)
322 raise web.HTTPError(404)
315 return abs_path
323 return abs_path
316
324
317
325
318 def json_errors(method):
326 def json_errors(method):
319 """Decorate methods with this to return GitHub style JSON errors.
327 """Decorate methods with this to return GitHub style JSON errors.
320
328
321 This should be used on any JSON API on any handler method that can raise HTTPErrors.
329 This should be used on any JSON API on any handler method that can raise HTTPErrors.
322
330
323 This will grab the latest HTTPError exception using sys.exc_info
331 This will grab the latest HTTPError exception using sys.exc_info
324 and then:
332 and then:
325
333
326 1. Set the HTTP status code based on the HTTPError
334 1. Set the HTTP status code based on the HTTPError
327 2. Create and return a JSON body with a message field describing
335 2. Create and return a JSON body with a message field describing
328 the error in a human readable form.
336 the error in a human readable form.
329 """
337 """
330 @functools.wraps(method)
338 @functools.wraps(method)
331 def wrapper(self, *args, **kwargs):
339 def wrapper(self, *args, **kwargs):
332 try:
340 try:
333 result = method(self, *args, **kwargs)
341 result = method(self, *args, **kwargs)
334 except web.HTTPError as e:
342 except web.HTTPError as e:
335 status = e.status_code
343 status = e.status_code
336 message = e.log_message
344 message = e.log_message
337 self.log.warn(message)
345 self.log.warn(message)
338 self.set_status(e.status_code)
346 self.set_status(e.status_code)
339 self.finish(json.dumps(dict(message=message)))
347 self.finish(json.dumps(dict(message=message)))
340 except Exception:
348 except Exception:
341 self.log.error("Unhandled error in API request", exc_info=True)
349 self.log.error("Unhandled error in API request", exc_info=True)
342 status = 500
350 status = 500
343 message = "Unknown server error"
351 message = "Unknown server error"
344 t, value, tb = sys.exc_info()
352 t, value, tb = sys.exc_info()
345 self.set_status(status)
353 self.set_status(status)
346 tb_text = ''.join(traceback.format_exception(t, value, tb))
354 tb_text = ''.join(traceback.format_exception(t, value, tb))
347 reply = dict(message=message, traceback=tb_text)
355 reply = dict(message=message, traceback=tb_text)
348 self.finish(json.dumps(reply))
356 self.finish(json.dumps(reply))
349 else:
357 else:
350 return result
358 return result
351 return wrapper
359 return wrapper
352
360
353
361
354
362
355 #-----------------------------------------------------------------------------
363 #-----------------------------------------------------------------------------
356 # File handler
364 # File handler
357 #-----------------------------------------------------------------------------
365 #-----------------------------------------------------------------------------
358
366
359 # to minimize subclass changes:
367 # to minimize subclass changes:
360 HTTPError = web.HTTPError
368 HTTPError = web.HTTPError
361
369
362 class FileFindHandler(web.StaticFileHandler):
370 class FileFindHandler(web.StaticFileHandler):
363 """subclass of StaticFileHandler for serving files from a search path"""
371 """subclass of StaticFileHandler for serving files from a search path"""
364
372
365 # cache search results, don't search for files more than once
373 # cache search results, don't search for files more than once
366 _static_paths = {}
374 _static_paths = {}
367
375
368 def initialize(self, path, default_filename=None):
376 def initialize(self, path, default_filename=None):
369 if isinstance(path, string_types):
377 if isinstance(path, string_types):
370 path = [path]
378 path = [path]
371
379
372 self.root = tuple(
380 self.root = tuple(
373 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
381 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
374 )
382 )
375 self.default_filename = default_filename
383 self.default_filename = default_filename
376
384
377 def compute_etag(self):
385 def compute_etag(self):
378 return None
386 return None
379
387
380 @classmethod
388 @classmethod
381 def get_absolute_path(cls, roots, path):
389 def get_absolute_path(cls, roots, path):
382 """locate a file to serve on our static file search path"""
390 """locate a file to serve on our static file search path"""
383 with cls._lock:
391 with cls._lock:
384 if path in cls._static_paths:
392 if path in cls._static_paths:
385 return cls._static_paths[path]
393 return cls._static_paths[path]
386 try:
394 try:
387 abspath = os.path.abspath(filefind(path, roots))
395 abspath = os.path.abspath(filefind(path, roots))
388 except IOError:
396 except IOError:
389 # IOError means not found
397 # IOError means not found
390 return ''
398 return ''
391
399
392 cls._static_paths[path] = abspath
400 cls._static_paths[path] = abspath
393 return abspath
401 return abspath
394
402
395 def validate_absolute_path(self, root, absolute_path):
403 def validate_absolute_path(self, root, absolute_path):
396 """check if the file should be served (raises 404, 403, etc.)"""
404 """check if the file should be served (raises 404, 403, etc.)"""
397 if absolute_path == '':
405 if absolute_path == '':
398 raise web.HTTPError(404)
406 raise web.HTTPError(404)
399
407
400 for root in self.root:
408 for root in self.root:
401 if (absolute_path + os.sep).startswith(root):
409 if (absolute_path + os.sep).startswith(root):
402 break
410 break
403
411
404 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
412 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
405
413
406
414
407 class ApiVersionHandler(IPythonHandler):
415 class ApiVersionHandler(IPythonHandler):
408
416
409 @json_errors
417 @json_errors
410 def get(self):
418 def get(self):
411 # not authenticated, so give as few info as possible
419 # not authenticated, so give as few info as possible
412 self.finish(json.dumps({"version":IPython.__version__}))
420 self.finish(json.dumps({"version":IPython.__version__}))
413
421
414 class TrailingSlashHandler(web.RequestHandler):
422 class TrailingSlashHandler(web.RequestHandler):
415 """Simple redirect handler that strips trailing slashes
423 """Simple redirect handler that strips trailing slashes
416
424
417 This should be the first, highest priority handler.
425 This should be the first, highest priority handler.
418 """
426 """
419
427
420 SUPPORTED_METHODS = ['GET']
428 SUPPORTED_METHODS = ['GET']
421
429
422 def get(self):
430 def get(self):
423 self.redirect(self.request.uri.rstrip('/'))
431 self.redirect(self.request.uri.rstrip('/'))
424
432
425
433
426 class FilesRedirectHandler(IPythonHandler):
434 class FilesRedirectHandler(IPythonHandler):
427 """Handler for redirecting relative URLs to the /files/ handler"""
435 """Handler for redirecting relative URLs to the /files/ handler"""
428 def get(self, path=''):
436 def get(self, path=''):
429 cm = self.contents_manager
437 cm = self.contents_manager
430 if cm.path_exists(path):
438 if cm.path_exists(path):
431 # it's a *directory*, redirect to /tree
439 # it's a *directory*, redirect to /tree
432 url = url_path_join(self.base_url, 'tree', path)
440 url = url_path_join(self.base_url, 'tree', path)
433 else:
441 else:
434 orig_path = path
442 orig_path = path
435 # otherwise, redirect to /files
443 # otherwise, redirect to /files
436 parts = path.split('/')
444 parts = path.split('/')
437 path = '/'.join(parts[:-1])
445 path = '/'.join(parts[:-1])
438 name = parts[-1]
446 name = parts[-1]
439
447
440 if not cm.file_exists(name=name, path=path) and 'files' in parts:
448 if not cm.file_exists(name=name, path=path) and 'files' in parts:
441 # redirect without files/ iff it would 404
449 # redirect without files/ iff it would 404
442 # this preserves pre-2.0-style 'files/' links
450 # this preserves pre-2.0-style 'files/' links
443 self.log.warn("Deprecated files/ URL: %s", orig_path)
451 self.log.warn("Deprecated files/ URL: %s", orig_path)
444 parts.remove('files')
452 parts.remove('files')
445 path = '/'.join(parts[:-1])
453 path = '/'.join(parts[:-1])
446
454
447 if not cm.file_exists(name=name, path=path):
455 if not cm.file_exists(name=name, path=path):
448 raise web.HTTPError(404)
456 raise web.HTTPError(404)
449
457
450 url = url_path_join(self.base_url, 'files', path, name)
458 url = url_path_join(self.base_url, 'files', path, name)
451 url = url_escape(url)
459 url = url_escape(url)
452 self.log.debug("Redirecting %s to %s", self.request.path, url)
460 self.log.debug("Redirecting %s to %s", self.request.path, url)
453 self.redirect(url)
461 self.redirect(url)
454
462
455
463
456 #-----------------------------------------------------------------------------
464 #-----------------------------------------------------------------------------
457 # URL pattern fragments for re-use
465 # URL pattern fragments for re-use
458 #-----------------------------------------------------------------------------
466 #-----------------------------------------------------------------------------
459
467
460 path_regex = r"(?P<path>(?:/.*)*)"
468 path_regex = r"(?P<path>(?:/.*)*)"
461 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
469 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
462 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
470 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
463 file_name_regex = r"(?P<name>[^/]+)"
471 file_name_regex = r"(?P<name>[^/]+)"
464 file_path_regex = "%s/%s" % (path_regex, file_name_regex)
472 file_path_regex = "%s/%s" % (path_regex, file_name_regex)
465
473
466 #-----------------------------------------------------------------------------
474 #-----------------------------------------------------------------------------
467 # URL to handler mappings
475 # URL to handler mappings
468 #-----------------------------------------------------------------------------
476 #-----------------------------------------------------------------------------
469
477
470
478
471 default_handlers = [
479 default_handlers = [
472 (r".*/", TrailingSlashHandler),
480 (r".*/", TrailingSlashHandler),
473 (r"api", ApiVersionHandler)
481 (r"api", ApiVersionHandler)
474 ]
482 ]
@@ -1,156 +1,156 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'notebook/js/notebook',
7 'notebook/js/notebook',
8 'base/js/contentmanager',
8 'contentmanager',
9 'base/js/utils',
9 'base/js/utils',
10 'base/js/page',
10 'base/js/page',
11 'notebook/js/layoutmanager',
11 'notebook/js/layoutmanager',
12 'base/js/events',
12 'base/js/events',
13 'auth/js/loginwidget',
13 'auth/js/loginwidget',
14 'notebook/js/maintoolbar',
14 'notebook/js/maintoolbar',
15 'notebook/js/pager',
15 'notebook/js/pager',
16 'notebook/js/quickhelp',
16 'notebook/js/quickhelp',
17 'notebook/js/menubar',
17 'notebook/js/menubar',
18 'notebook/js/notificationarea',
18 'notebook/js/notificationarea',
19 'notebook/js/savewidget',
19 'notebook/js/savewidget',
20 'notebook/js/keyboardmanager',
20 'notebook/js/keyboardmanager',
21 'notebook/js/config',
21 'notebook/js/config',
22 'notebook/js/kernelselector',
22 'notebook/js/kernelselector',
23 'codemirror/lib/codemirror',
23 'codemirror/lib/codemirror',
24 'notebook/js/about',
24 'notebook/js/about',
25 // only loaded, not used, please keep sure this is loaded last
25 // only loaded, not used, please keep sure this is loaded last
26 'custom/custom'
26 'custom/custom'
27 ], function(
27 ], function(
28 IPython,
28 IPython,
29 $,
29 $,
30 notebook,
30 notebook,
31 contentmanager,
31 contentmanager,
32 utils,
32 utils,
33 page,
33 page,
34 layoutmanager,
34 layoutmanager,
35 events,
35 events,
36 loginwidget,
36 loginwidget,
37 maintoolbar,
37 maintoolbar,
38 pager,
38 pager,
39 quickhelp,
39 quickhelp,
40 menubar,
40 menubar,
41 notificationarea,
41 notificationarea,
42 savewidget,
42 savewidget,
43 keyboardmanager,
43 keyboardmanager,
44 config,
44 config,
45 kernelselector,
45 kernelselector,
46 CodeMirror,
46 CodeMirror,
47 about,
47 about,
48 // please keep sure that even if not used, this is loaded last
48 // please keep sure that even if not used, this is loaded last
49 custom
49 custom
50 ) {
50 ) {
51 "use strict";
51 "use strict";
52
52
53 // compat with old IPython, remove for IPython > 3.0
53 // compat with old IPython, remove for IPython > 3.0
54 window.CodeMirror = CodeMirror;
54 window.CodeMirror = CodeMirror;
55
55
56 var common_options = {
56 var common_options = {
57 ws_url : utils.get_body_data("wsUrl"),
57 ws_url : utils.get_body_data("wsUrl"),
58 base_url : utils.get_body_data("baseUrl"),
58 base_url : utils.get_body_data("baseUrl"),
59 notebook_path : utils.get_body_data("notebookPath"),
59 notebook_path : utils.get_body_data("notebookPath"),
60 notebook_name : utils.get_body_data('notebookName')
60 notebook_name : utils.get_body_data('notebookName')
61 };
61 };
62
62
63 var user_config = $.extend({}, config.default_config);
63 var user_config = $.extend({}, config.default_config);
64 var page = new page.Page();
64 var page = new page.Page();
65 var layout_manager = new layoutmanager.LayoutManager();
65 var layout_manager = new layoutmanager.LayoutManager();
66 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
66 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
67 layout_manager: layout_manager,
67 layout_manager: layout_manager,
68 events: events});
68 events: events});
69 var keyboard_manager = new keyboardmanager.KeyboardManager({
69 var keyboard_manager = new keyboardmanager.KeyboardManager({
70 pager: pager,
70 pager: pager,
71 events: events});
71 events: events});
72 var save_widget = new savewidget.SaveWidget('span#save_widget', {
72 var save_widget = new savewidget.SaveWidget('span#save_widget', {
73 events: events,
73 events: events,
74 keyboard_manager: keyboard_manager});
74 keyboard_manager: keyboard_manager});
75 var content_manager = new contentmanager.ContentManager($.extend({
75 var content_manager = new contentmanager.ContentManager($.extend({
76 events: events},
76 events: events},
77 common_options));
77 common_options));
78 var notebook = new notebook.Notebook('div#notebook', $.extend({
78 var notebook = new notebook.Notebook('div#notebook', $.extend({
79 events: events,
79 events: events,
80 keyboard_manager: keyboard_manager,
80 keyboard_manager: keyboard_manager,
81 save_widget: save_widget,
81 save_widget: save_widget,
82 content_manager: content_manager,
82 content_manager: content_manager,
83 config: user_config},
83 config: user_config},
84 common_options));
84 common_options));
85 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
85 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
86 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
86 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
87 notebook: notebook,
87 notebook: notebook,
88 events: events});
88 events: events});
89 var quick_help = new quickhelp.QuickHelp({
89 var quick_help = new quickhelp.QuickHelp({
90 keyboard_manager: keyboard_manager,
90 keyboard_manager: keyboard_manager,
91 events: events,
91 events: events,
92 notebook: notebook});
92 notebook: notebook});
93 var menubar = new menubar.MenuBar('#menubar', $.extend({
93 var menubar = new menubar.MenuBar('#menubar', $.extend({
94 notebook: notebook,
94 notebook: notebook,
95 content_manager: content_manager,
95 content_manager: content_manager,
96 layout_manager: layout_manager,
96 layout_manager: layout_manager,
97 events: events,
97 events: events,
98 save_widget: save_widget,
98 save_widget: save_widget,
99 quick_help: quick_help},
99 quick_help: quick_help},
100 common_options));
100 common_options));
101 var notification_area = new notificationarea.NotificationArea(
101 var notification_area = new notificationarea.NotificationArea(
102 '#notification_area', {
102 '#notification_area', {
103 events: events,
103 events: events,
104 save_widget: save_widget,
104 save_widget: save_widget,
105 notebook: notebook,
105 notebook: notebook,
106 keyboard_manager: keyboard_manager});
106 keyboard_manager: keyboard_manager});
107 notification_area.init_notification_widgets();
107 notification_area.init_notification_widgets();
108 var kernel_selector = new kernelselector.KernelSelector(
108 var kernel_selector = new kernelselector.KernelSelector(
109 '#kernel_selector_widget', notebook);
109 '#kernel_selector_widget', notebook);
110
110
111 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
111 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
112 '<span id="test2" style="font-weight: bold;">x</span>'+
112 '<span id="test2" style="font-weight: bold;">x</span>'+
113 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
113 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
114 var nh = $('#test1').innerHeight();
114 var nh = $('#test1').innerHeight();
115 var bh = $('#test2').innerHeight();
115 var bh = $('#test2').innerHeight();
116 var ih = $('#test3').innerHeight();
116 var ih = $('#test3').innerHeight();
117 if(nh != bh || nh != ih) {
117 if(nh != bh || nh != ih) {
118 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
118 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
119 }
119 }
120 $('#fonttest').remove();
120 $('#fonttest').remove();
121
121
122 page.show();
122 page.show();
123
123
124 layout_manager.do_resize();
124 layout_manager.do_resize();
125 var first_load = function () {
125 var first_load = function () {
126 layout_manager.do_resize();
126 layout_manager.do_resize();
127 var hash = document.location.hash;
127 var hash = document.location.hash;
128 if (hash) {
128 if (hash) {
129 document.location.hash = '';
129 document.location.hash = '';
130 document.location.hash = hash;
130 document.location.hash = hash;
131 }
131 }
132 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
132 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
133 // only do this once
133 // only do this once
134 events.off('notebook_loaded.Notebook', first_load);
134 events.off('notebook_loaded.Notebook', first_load);
135 };
135 };
136 events.on('notebook_loaded.Notebook', first_load);
136 events.on('notebook_loaded.Notebook', first_load);
137
137
138 IPython.page = page;
138 IPython.page = page;
139 IPython.layout_manager = layout_manager;
139 IPython.layout_manager = layout_manager;
140 IPython.notebook = notebook;
140 IPython.notebook = notebook;
141 IPython.content_manager = content_manager;
141 IPython.content_manager = content_manager;
142 IPython.pager = pager;
142 IPython.pager = pager;
143 IPython.quick_help = quick_help;
143 IPython.quick_help = quick_help;
144 IPython.login_widget = login_widget;
144 IPython.login_widget = login_widget;
145 IPython.menubar = menubar;
145 IPython.menubar = menubar;
146 IPython.toolbar = toolbar;
146 IPython.toolbar = toolbar;
147 IPython.notification_area = notification_area;
147 IPython.notification_area = notification_area;
148 IPython.keyboard_manager = keyboard_manager;
148 IPython.keyboard_manager = keyboard_manager;
149 IPython.save_widget = save_widget;
149 IPython.save_widget = save_widget;
150 IPython.config = user_config;
150 IPython.config = user_config;
151 IPython.tooltip = notebook.tooltip;
151 IPython.tooltip = notebook.tooltip;
152
152
153 events.trigger('app_initialized.NotebookApp');
153 events.trigger('app_initialized.NotebookApp');
154 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
154 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
155
155
156 });
156 });
@@ -1,137 +1,137 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 require([
4 require([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/events',
7 'base/js/events',
8 'base/js/page',
8 'base/js/page',
9 'base/js/utils',
9 'base/js/utils',
10 'base/js/contentmanager',
10 'contentmanager',
11 'tree/js/notebooklist',
11 'tree/js/notebooklist',
12 'tree/js/clusterlist',
12 'tree/js/clusterlist',
13 'tree/js/sessionlist',
13 'tree/js/sessionlist',
14 'tree/js/kernellist',
14 'tree/js/kernellist',
15 'tree/js/terminallist',
15 'tree/js/terminallist',
16 'auth/js/loginwidget',
16 'auth/js/loginwidget',
17 // only loaded, not used:
17 // only loaded, not used:
18 'jqueryui',
18 'jqueryui',
19 'bootstrap',
19 'bootstrap',
20 'custom/custom',
20 'custom/custom',
21 ], function(
21 ], function(
22 IPython,
22 IPython,
23 $,
23 $,
24 events,
24 events,
25 page,
25 page,
26 utils,
26 utils,
27 contentmanager,
27 contentmanager,
28 notebooklist,
28 notebooklist,
29 clusterlist,
29 clusterlist,
30 sesssionlist,
30 sesssionlist,
31 kernellist,
31 kernellist,
32 terminallist,
32 terminallist,
33 loginwidget){
33 loginwidget){
34
34
35 page = new page.Page();
35 page = new page.Page();
36
36
37 var common_options = {
37 var common_options = {
38 base_url: utils.get_body_data("baseUrl"),
38 base_url: utils.get_body_data("baseUrl"),
39 notebook_path: utils.get_body_data("notebookPath"),
39 notebook_path: utils.get_body_data("notebookPath"),
40 };
40 };
41 session_list = new sesssionlist.SesssionList($.extend({
41 session_list = new sesssionlist.SesssionList($.extend({
42 events: events},
42 events: events},
43 common_options));
43 common_options));
44 content_manager = new contentmanager.ContentManager($.extend({
44 content_manager = new contentmanager.ContentManager($.extend({
45 events: events},
45 events: events},
46 common_options));
46 common_options));
47 notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
47 notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({
48 content_manager: content_manager,
48 content_manager: content_manager,
49 session_list: session_list},
49 session_list: session_list},
50 common_options));
50 common_options));
51 cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
51 cluster_list = new clusterlist.ClusterList('#cluster_list', common_options);
52 kernel_list = new kernellist.KernelList('#running_list', $.extend({
52 kernel_list = new kernellist.KernelList('#running_list', $.extend({
53 session_list: session_list},
53 session_list: session_list},
54 common_options));
54 common_options));
55
55
56 if (utils.get_body_data("terminalsAvailable") === "True") {
56 if (utils.get_body_data("terminalsAvailable") === "True") {
57 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
57 terminal_list = new terminallist.TerminalList('#terminal_list', common_options);
58 }
58 }
59
59
60 login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
60 login_widget = new loginwidget.LoginWidget('#login_widget', common_options);
61
61
62 $('#new_notebook').button().click(function (e) {
62 $('#new_notebook').button().click(function (e) {
63 content_manager.new_notebook(common_options.notebook_path);
63 content_manager.new_notebook(common_options.notebook_path);
64 });
64 });
65
65
66 var interval_id=0;
66 var interval_id=0;
67 // auto refresh every xx secondes, no need to be fast,
67 // auto refresh every xx secondes, no need to be fast,
68 // update is done at least when page get focus
68 // update is done at least when page get focus
69 var time_refresh = 60; // in sec
69 var time_refresh = 60; // in sec
70
70
71 var enable_autorefresh = function(){
71 var enable_autorefresh = function(){
72 //refresh immediately , then start interval
72 //refresh immediately , then start interval
73 session_list.load_sessions();
73 session_list.load_sessions();
74 cluster_list.load_list();
74 cluster_list.load_list();
75 if (!interval_id){
75 if (!interval_id){
76 interval_id = setInterval(function(){
76 interval_id = setInterval(function(){
77 session_list.load_sessions();
77 session_list.load_sessions();
78 cluster_list.load_list();
78 cluster_list.load_list();
79 }, time_refresh*1000);
79 }, time_refresh*1000);
80 }
80 }
81 };
81 };
82
82
83 var disable_autorefresh = function(){
83 var disable_autorefresh = function(){
84 clearInterval(interval_id);
84 clearInterval(interval_id);
85 interval_id = 0;
85 interval_id = 0;
86 };
86 };
87
87
88 // stop autorefresh when page lose focus
88 // stop autorefresh when page lose focus
89 $(window).blur(function() {
89 $(window).blur(function() {
90 disable_autorefresh();
90 disable_autorefresh();
91 });
91 });
92
92
93 //re-enable when page get focus back
93 //re-enable when page get focus back
94 $(window).focus(function() {
94 $(window).focus(function() {
95 enable_autorefresh();
95 enable_autorefresh();
96 });
96 });
97
97
98 // finally start it, it will refresh immediately
98 // finally start it, it will refresh immediately
99 enable_autorefresh();
99 enable_autorefresh();
100
100
101 page.show();
101 page.show();
102
102
103 // For backwards compatability.
103 // For backwards compatability.
104 IPython.page = page;
104 IPython.page = page;
105 IPython.notebook_list = notebook_list;
105 IPython.notebook_list = notebook_list;
106 IPython.cluster_list = cluster_list;
106 IPython.cluster_list = cluster_list;
107 IPython.session_list = session_list;
107 IPython.session_list = session_list;
108 IPython.kernel_list = kernel_list;
108 IPython.kernel_list = kernel_list;
109 IPython.login_widget = login_widget;
109 IPython.login_widget = login_widget;
110
110
111 events.trigger('app_initialized.DashboardApp');
111 events.trigger('app_initialized.DashboardApp');
112
112
113 // bound the upload method to the on change of the file select list
113 // bound the upload method to the on change of the file select list
114 $("#alternate_upload").change(function (event){
114 $("#alternate_upload").change(function (event){
115 notebook_list.handleFilesUpload(event,'form');
115 notebook_list.handleFilesUpload(event,'form');
116 });
116 });
117
117
118 // set hash on tab click
118 // set hash on tab click
119 $("#tabs").find("a").click(function() {
119 $("#tabs").find("a").click(function() {
120 window.location.hash = $(this).attr("href");
120 window.location.hash = $(this).attr("href");
121 });
121 });
122
122
123 // load tab if url hash
123 // load tab if url hash
124 if (window.location.hash) {
124 if (window.location.hash) {
125 $("#tabs").find("a[href=" + window.location.hash + "]").click();
125 $("#tabs").find("a[href=" + window.location.hash + "]").click();
126 }
126 }
127
127
128 // For backwards compatability.
128 // For backwards compatability.
129 IPython.page = page;
129 IPython.page = page;
130 IPython.content_manager = content_manager;
130 IPython.content_manager = content_manager;
131 IPython.notebook_list = notebook_list;
131 IPython.notebook_list = notebook_list;
132 IPython.cluster_list = cluster_list;
132 IPython.cluster_list = cluster_list;
133 IPython.session_list = session_list;
133 IPython.session_list = session_list;
134 IPython.kernel_list = kernel_list;
134 IPython.kernel_list = kernel_list;
135 IPython.login_widget = login_widget;
135 IPython.login_widget = login_widget;
136 IPython.events = events;
136 IPython.events = events;
137 });
137 });
@@ -1,106 +1,107 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{% endblock %}</title>
7 <title>{% block title %}IPython Notebook{% endblock %}</title>
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
8 <link rel="shortcut icon" type="image/x-icon" href="{{static_url("base/images/favicon.ico") }}">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
9 <meta http-equiv="X-UA-Compatible" content="chrome=1">
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
10 <link rel="stylesheet" href="{{static_url("components/jquery-ui/themes/smoothness/jquery-ui.min.css") }}" type="text/css" />
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
12
13 {% block stylesheet %}
13 {% block stylesheet %}
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
14 <link rel="stylesheet" href="{{ static_url("style/style.min.css") }}" type="text/css"/>
15 {% endblock %}
15 {% endblock %}
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
16 <link rel="stylesheet" href="{{ static_url("custom/custom.css") }}" type="text/css" />
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
17 <script src="{{static_url("components/requirejs/require.js") }}" type="text/javascript" charset="utf-8"></script>
18 <script>
18 <script>
19 require.config({
19 require.config({
20 baseUrl: '{{static_url("", include_version=False)}}',
20 baseUrl: '{{static_url("", include_version=False)}}',
21 paths: {
21 paths: {
22 nbextensions : '{{ base_url }}nbextensions',
22 nbextensions : '{{ base_url }}nbextensions',
23 underscore : 'components/underscore/underscore-min',
23 underscore : 'components/underscore/underscore-min',
24 backbone : 'components/backbone/backbone-min',
24 backbone : 'components/backbone/backbone-min',
25 jquery: 'components/jquery/jquery.min',
25 jquery: 'components/jquery/jquery.min',
26 bootstrap: 'components/bootstrap/js/bootstrap.min',
26 bootstrap: 'components/bootstrap/js/bootstrap.min',
27 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
27 bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
28 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
28 jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
29 highlight: 'components/highlight.js/build/highlight.pack',
29 highlight: 'components/highlight.js/build/highlight.pack',
30 moment: "components/moment/moment",
30 moment: "components/moment/moment",
31 codemirror: 'components/codemirror',
31 codemirror: 'components/codemirror',
32 termjs: "components/term.js/src/term"
32 termjs: "components/term.js/src/term"
33 contentmanager: '{{ contentmanager_js_source }}',
33 },
34 },
34 shim: {
35 shim: {
35 underscore: {
36 underscore: {
36 exports: '_'
37 exports: '_'
37 },
38 },
38 backbone: {
39 backbone: {
39 deps: ["underscore", "jquery"],
40 deps: ["underscore", "jquery"],
40 exports: "Backbone"
41 exports: "Backbone"
41 },
42 },
42 bootstrap: {
43 bootstrap: {
43 deps: ["jquery"],
44 deps: ["jquery"],
44 exports: "bootstrap"
45 exports: "bootstrap"
45 },
46 },
46 bootstraptour: {
47 bootstraptour: {
47 deps: ["bootstrap"],
48 deps: ["bootstrap"],
48 exports: "Tour"
49 exports: "Tour"
49 },
50 },
50 jqueryui: {
51 jqueryui: {
51 deps: ["jquery"],
52 deps: ["jquery"],
52 exports: "$"
53 exports: "$"
53 },
54 },
54 highlight: {
55 highlight: {
55 exports: "hljs"
56 exports: "hljs"
56 },
57 },
57 }
58 }
58 });
59 });
59 </script>
60 </script>
60
61
61 {% block meta %}
62 {% block meta %}
62 {% endblock %}
63 {% endblock %}
63
64
64 </head>
65 </head>
65
66
66 <body {% block params %}{% endblock %}>
67 <body {% block params %}{% endblock %}>
67
68
68 <noscript>
69 <noscript>
69 <div id='noscript'>
70 <div id='noscript'>
70 IPython Notebook requires JavaScript.<br>
71 IPython Notebook requires JavaScript.<br>
71 Please enable it to proceed.
72 Please enable it to proceed.
72 </div>
73 </div>
73 </noscript>
74 </noscript>
74
75
75 <div id="header" class="navbar navbar-static-top">
76 <div id="header" class="navbar navbar-static-top">
76 <div class="container">
77 <div class="container">
77 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
78 <div id="ipython_notebook" class="nav navbar-brand pull-left"><a href="{{base_url}}tree/{{notebook_path}}" alt='dashboard'><img src='{{static_url("base/images/ipynblogo.png") }}' alt='IPython Notebook'/></a></div>
78
79
79 {% block login_widget %}
80 {% block login_widget %}
80
81
81 <span id="login_widget">
82 <span id="login_widget">
82 {% if logged_in %}
83 {% if logged_in %}
83 <button id="logout">Logout</button>
84 <button id="logout">Logout</button>
84 {% elif login_available and not logged_in %}
85 {% elif login_available and not logged_in %}
85 <button id="login">Login</button>
86 <button id="login">Login</button>
86 {% endif %}
87 {% endif %}
87 </span>
88 </span>
88
89
89 {% endblock %}
90 {% endblock %}
90
91
91 {% block header %}
92 {% block header %}
92 {% endblock %}
93 {% endblock %}
93 </div>
94 </div>
94 </div>
95 </div>
95
96
96 <div id="site">
97 <div id="site">
97 {% block site %}
98 {% block site %}
98 {% endblock %}
99 {% endblock %}
99 </div>
100 </div>
100
101
101 {% block script %}
102 {% block script %}
102 {% endblock %}
103 {% endblock %}
103
104
104 </body>
105 </body>
105
106
106 </html>
107 </html>
General Comments 0
You need to be logged in to leave comments. Login now