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