##// END OF EJS Templates
Merge pull request #931 from minrk/readonly...
Fernando Perez -
r5219:80e60eb2 merge
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Login button
10 //============================================================================
11
12 var IPython = (function (IPython) {
13
14 var LoginWidget = function (selector) {
15 this.selector = selector;
16 if (this.selector !== undefined) {
17 this.element = $(selector);
18 this.style();
19 this.bind_events();
20 }
21 };
22
23 LoginWidget.prototype.style = function () {
24 this.element.find('button#login').button();
25 };
26 LoginWidget.prototype.bind_events = function () {
27 var that = this;
28 this.element.find("button#login").click(function () {
29 window.location = "/login?next="+location.pathname;
30 });
31 };
32
33 // Set module variables
34 IPython.LoginWidget = LoginWidget;
35
36 return IPython;
37
38 }(IPython));
@@ -26,6 +26,7 b' from tornado import websocket'
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.zmq.session import Session
30 from IPython.zmq.session import Session
30
31
31 try:
32 try:
@@ -34,6 +35,32 b' except ImportError:'
34 publish_string = None
35 publish_string = None
35
36
36
37
38 #-----------------------------------------------------------------------------
39 # Decorator for disabling read-only handlers
40 #-----------------------------------------------------------------------------
41
42 @decorator
43 def not_if_readonly(f, self, *args, **kwargs):
44 if self.application.read_only:
45 raise web.HTTPError(403, "Notebook server is read-only")
46 else:
47 return f(self, *args, **kwargs)
48
49 @decorator
50 def authenticate_unless_readonly(f, self, *args, **kwargs):
51 """authenticate this page *unless* readonly view is active.
52
53 In read-only mode, the notebook list and print view should
54 be accessible without authentication.
55 """
56
57 @web.authenticated
58 def auth_f(self, *args, **kwargs):
59 return f(self, *args, **kwargs)
60 if self.application.read_only:
61 return f(self, *args, **kwargs)
62 else:
63 return auth_f(self, *args, **kwargs)
37
64
38 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
39 # Top-level handlers
66 # Top-level handlers
@@ -50,34 +77,48 b' class AuthenticatedHandler(web.RequestHandler):'
50 if user_id is None:
77 if user_id is None:
51 # prevent extra Invalid cookie sig warnings:
78 # prevent extra Invalid cookie sig warnings:
52 self.clear_cookie('username')
79 self.clear_cookie('username')
53 if not self.application.password:
80 if not self.application.password and not self.application.read_only:
54 user_id = 'anonymous'
81 user_id = 'anonymous'
55 return user_id
82 return user_id
83
84 @property
85 def read_only(self):
86 if self.application.read_only:
87 if self.application.password:
88 return self.get_current_user() is None
89 else:
90 return True
91 else:
92 return False
93
56
94
57
95
58 class ProjectDashboardHandler(AuthenticatedHandler):
96 class ProjectDashboardHandler(AuthenticatedHandler):
59
97
60 @web.authenticated
98 @authenticate_unless_readonly
61 def get(self):
99 def get(self):
62 nbm = self.application.notebook_manager
100 nbm = self.application.notebook_manager
63 project = nbm.notebook_dir
101 project = nbm.notebook_dir
64 self.render(
102 self.render(
65 'projectdashboard.html', project=project,
103 'projectdashboard.html', project=project,
66 base_project_url=u'/', base_kernel_url=u'/'
104 base_project_url=u'/', base_kernel_url=u'/',
105 read_only=self.read_only,
67 )
106 )
68
107
69
108
70 class LoginHandler(AuthenticatedHandler):
109 class LoginHandler(AuthenticatedHandler):
71
110
72 def get(self):
111 def get(self):
73 self.render('login.html', next='/')
112 self.render('login.html',
113 next=self.get_argument('next', default='/'),
114 read_only=self.read_only,
115 )
74
116
75 def post(self):
117 def post(self):
76 pwd = self.get_argument('password', default=u'')
118 pwd = self.get_argument('password', default=u'')
77 if self.application.password and pwd == self.application.password:
119 if self.application.password and pwd == self.application.password:
78 self.set_secure_cookie('username', str(uuid.uuid4()))
120 self.set_secure_cookie('username', str(uuid.uuid4()))
79 url = self.get_argument('next', default='/')
121 self.redirect(self.get_argument('next', default='/'))
80 self.redirect(url)
81
122
82
123
83 class NewHandler(AuthenticatedHandler):
124 class NewHandler(AuthenticatedHandler):
@@ -91,23 +132,26 b' class NewHandler(AuthenticatedHandler):'
91 'notebook.html', project=project,
132 'notebook.html', project=project,
92 notebook_id=notebook_id,
133 notebook_id=notebook_id,
93 base_project_url=u'/', base_kernel_url=u'/',
134 base_project_url=u'/', base_kernel_url=u'/',
94 kill_kernel=False
135 kill_kernel=False,
136 read_only=False,
95 )
137 )
96
138
97
139
98 class NamedNotebookHandler(AuthenticatedHandler):
140 class NamedNotebookHandler(AuthenticatedHandler):
99
141
100 @web.authenticated
142 @authenticate_unless_readonly
101 def get(self, notebook_id):
143 def get(self, notebook_id):
102 nbm = self.application.notebook_manager
144 nbm = self.application.notebook_manager
103 project = nbm.notebook_dir
145 project = nbm.notebook_dir
104 if not nbm.notebook_exists(notebook_id):
146 if not nbm.notebook_exists(notebook_id):
105 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
147 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
148
106 self.render(
149 self.render(
107 'notebook.html', project=project,
150 'notebook.html', project=project,
108 notebook_id=notebook_id,
151 notebook_id=notebook_id,
109 base_project_url=u'/', base_kernel_url=u'/',
152 base_project_url=u'/', base_kernel_url=u'/',
110 kill_kernel=False
153 kill_kernel=False,
154 read_only=self.read_only,
111 )
155 )
112
156
113
157
@@ -363,8 +407,9 b' class ShellHandler(AuthenticatedZMQStreamHandler):'
363
407
364 class NotebookRootHandler(AuthenticatedHandler):
408 class NotebookRootHandler(AuthenticatedHandler):
365
409
366 @web.authenticated
410 @authenticate_unless_readonly
367 def get(self):
411 def get(self):
412
368 nbm = self.application.notebook_manager
413 nbm = self.application.notebook_manager
369 files = nbm.list_notebooks()
414 files = nbm.list_notebooks()
370 self.finish(jsonapi.dumps(files))
415 self.finish(jsonapi.dumps(files))
@@ -387,11 +432,12 b' class NotebookHandler(AuthenticatedHandler):'
387
432
388 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
433 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
389
434
390 @web.authenticated
435 @authenticate_unless_readonly
391 def get(self, notebook_id):
436 def get(self, notebook_id):
392 nbm = self.application.notebook_manager
437 nbm = self.application.notebook_manager
393 format = self.get_argument('format', default='json')
438 format = self.get_argument('format', default='json')
394 last_mod, name, data = nbm.get_notebook(notebook_id, format)
439 last_mod, name, data = nbm.get_notebook(notebook_id, format)
440
395 if format == u'json':
441 if format == u'json':
396 self.set_header('Content-Type', 'application/json')
442 self.set_header('Content-Type', 'application/json')
397 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
443 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
@@ -110,6 +110,7 b' class NotebookWebApplication(web.Application):'
110 self.log = log
110 self.log = log
111 self.notebook_manager = notebook_manager
111 self.notebook_manager = notebook_manager
112 self.ipython_app = ipython_app
112 self.ipython_app = ipython_app
113 self.read_only = self.ipython_app.read_only
113
114
114
115
115 #-----------------------------------------------------------------------------
116 #-----------------------------------------------------------------------------
@@ -121,11 +122,23 b" flags['no-browser']=("
121 {'NotebookApp' : {'open_browser' : False}},
122 {'NotebookApp' : {'open_browser' : False}},
122 "Don't open the notebook in a browser after startup."
123 "Don't open the notebook in a browser after startup."
123 )
124 )
125 flags['read-only'] = (
126 {'NotebookApp' : {'read_only' : True}},
127 """Allow read-only access to notebooks.
128
129 When using a password to protect the notebook server, this flag
130 allows unauthenticated clients to view the notebook list, and
131 individual notebooks, but not edit them, start kernels, or run
132 code.
133
134 If no password is set, the server will be entirely read-only.
135 """
136 )
124
137
125 # the flags that are specific to the frontend
138 # the flags that are specific to the frontend
126 # these must be scrubbed before being passed to the kernel,
139 # these must be scrubbed before being passed to the kernel,
127 # or it will raise an error on unrecognized flags
140 # or it will raise an error on unrecognized flags
128 notebook_flags = ['no-browser']
141 notebook_flags = ['no-browser', 'read-only']
129
142
130 aliases = dict(ipkernel_aliases)
143 aliases = dict(ipkernel_aliases)
131
144
@@ -208,6 +221,10 b' class NotebookApp(BaseIPythonApplication):'
208
221
209 open_browser = Bool(True, config=True,
222 open_browser = Bool(True, config=True,
210 help="Whether to open in a browser after starting.")
223 help="Whether to open in a browser after starting.")
224
225 read_only = Bool(False, config=True,
226 help="Whether to prevent editing/execution of notebooks."
227 )
211
228
212 def get_ws_url(self):
229 def get_ws_url(self):
213 """Return the WebSocket URL for this server."""
230 """Return the WebSocket URL for this server."""
@@ -288,7 +305,7 b' class NotebookApp(BaseIPythonApplication):'
288 # Try random ports centered around the default.
305 # Try random ports centered around the default.
289 from random import randint
306 from random import randint
290 n = 50 # Max number of attempts, keep reasonably large.
307 n = 50 # Max number of attempts, keep reasonably large.
291 for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
308 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
292 try:
309 try:
293 self.http_server.listen(port, self.ip)
310 self.http_server.listen(port, self.ip)
294 except socket.error, e:
311 except socket.error, e:
@@ -51,3 +51,12 b' div#main_app {'
51 padding: 0.2em 0.8em;
51 padding: 0.2em 0.8em;
52 font-size: 77%;
52 font-size: 77%;
53 }
53 }
54
55 span#login_widget {
56 float: right;
57 }
58
59 /* generic class for hidden objects */
60 .hidden {
61 display: none;
62 } No newline at end of file
@@ -15,6 +15,10 b' var IPython = (function (IPython) {'
15
15
16 var Cell = function (notebook) {
16 var Cell = function (notebook) {
17 this.notebook = notebook;
17 this.notebook = notebook;
18 this.read_only = false;
19 if (notebook){
20 this.read_only = notebook.read_only;
21 }
18 this.selected = false;
22 this.selected = false;
19 this.element = null;
23 this.element = null;
20 this.create_element();
24 this.create_element();
@@ -37,6 +37,7 b' var IPython = (function (IPython) {'
37 indentUnit : 4,
37 indentUnit : 4,
38 mode: 'python',
38 mode: 'python',
39 theme: 'ipython',
39 theme: 'ipython',
40 readOnly: this.read_only,
40 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
41 });
42 });
42 input.append(input_area);
43 input.append(input_area);
@@ -65,8 +65,10 b' var IPython = (function (IPython) {'
65
65
66 LeftPanel.prototype.create_children = function () {
66 LeftPanel.prototype.create_children = function () {
67 this.notebook_section = new IPython.NotebookSection('div#notebook_section');
67 this.notebook_section = new IPython.NotebookSection('div#notebook_section');
68 this.cell_section = new IPython.CellSection('div#cell_section');
68 if (! IPython.read_only){
69 this.kernel_section = new IPython.KernelSection('div#kernel_section');
69 this.cell_section = new IPython.CellSection('div#cell_section');
70 this.kernel_section = new IPython.KernelSection('div#kernel_section');
71 }
70 this.help_section = new IPython.HelpSection('div#help_section');
72 this.help_section = new IPython.HelpSection('div#help_section');
71 }
73 }
72
74
@@ -14,6 +14,7 b' var IPython = (function (IPython) {'
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var Notebook = function (selector) {
16 var Notebook = function (selector) {
17 this.read_only = IPython.read_only;
17 this.element = $(selector);
18 this.element = $(selector);
18 this.element.scroll();
19 this.element.scroll();
19 this.element.data("notebook", this);
20 this.element.data("notebook", this);
@@ -42,6 +43,7 b' var IPython = (function (IPython) {'
42 var that = this;
43 var that = this;
43 var end_space = $('<div class="end_space"></div>').height(150);
44 var end_space = $('<div class="end_space"></div>').height(150);
44 end_space.dblclick(function (e) {
45 end_space.dblclick(function (e) {
46 if (that.read_only) return;
45 var ncells = that.ncells();
47 var ncells = that.ncells();
46 that.insert_code_cell_below(ncells-1);
48 that.insert_code_cell_below(ncells-1);
47 });
49 });
@@ -54,6 +56,7 b' var IPython = (function (IPython) {'
54 var that = this;
56 var that = this;
55 $(document).keydown(function (event) {
57 $(document).keydown(function (event) {
56 // console.log(event);
58 // console.log(event);
59 if (that.read_only) return;
57 if (event.which === 38) {
60 if (event.which === 38) {
58 var cell = that.selected_cell();
61 var cell = that.selected_cell();
59 if (cell.at_top()) {
62 if (cell.at_top()) {
@@ -185,11 +188,11 b' var IPython = (function (IPython) {'
185 });
188 });
186
189
187 $(window).bind('beforeunload', function () {
190 $(window).bind('beforeunload', function () {
188 var kill_kernel = $('#kill_kernel').prop('checked');
191 var kill_kernel = $('#kill_kernel').prop('checked');
189 if (kill_kernel) {
192 if (kill_kernel) {
190 that.kernel.kill();
193 that.kernel.kill();
191 }
194 }
192 if (that.dirty) {
195 if (that.dirty && ! that.read_only) {
193 return "You have unsaved changes that will be lost if you leave this page.";
196 return "You have unsaved changes that will be lost if you leave this page.";
194 };
197 };
195 });
198 });
@@ -975,14 +978,17 b' var IPython = (function (IPython) {'
975
978
976
979
977 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
980 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
981 var allowed = xhr.getResponseHeader('Allow');
978 this.fromJSON(data);
982 this.fromJSON(data);
979 if (this.ncells() === 0) {
983 if (this.ncells() === 0) {
980 this.insert_code_cell_below();
984 this.insert_code_cell_below();
981 };
985 };
982 IPython.save_widget.status_save();
986 IPython.save_widget.status_save();
983 IPython.save_widget.set_notebook_name(data.metadata.name);
987 IPython.save_widget.set_notebook_name(data.metadata.name);
984 this.start_kernel();
985 this.dirty = false;
988 this.dirty = false;
989 if (! this.read_only) {
990 this.start_kernel();
991 }
986 // fromJSON always selects the last cell inserted. We need to wait
992 // fromJSON always selects the last cell inserted. We need to wait
987 // until that is done before scrolling to the top.
993 // until that is done before scrolling to the top.
988 setTimeout(function () {
994 setTimeout(function () {
@@ -991,7 +997,6 b' var IPython = (function (IPython) {'
991 }, 50);
997 }, 50);
992 };
998 };
993
999
994
995 IPython.Notebook = Notebook;
1000 IPython.Notebook = Notebook;
996
1001
997
1002
@@ -80,7 +80,10 b' var IPython = (function (IPython) {'
80 var nbname = data[i].name;
80 var nbname = data[i].name;
81 var item = this.new_notebook_item(i);
81 var item = this.new_notebook_item(i);
82 this.add_link(notebook_id, nbname, item);
82 this.add_link(notebook_id, nbname, item);
83 this.add_delete_button(item);
83 if (!IPython.read_only){
84 // hide delete buttons when readonly
85 this.add_delete_button(item);
86 }
84 };
87 };
85 };
88 };
86
89
@@ -23,6 +23,7 b' $(document).ready(function () {'
23 }
23 }
24 });
24 });
25 IPython.markdown_converter = new Markdown.Converter();
25 IPython.markdown_converter = new Markdown.Converter();
26 IPython.read_only = $('meta[name=read_only]').attr("content") == 'True';
26
27
27 $('div#header').addClass('border-box-sizing');
28 $('div#header').addClass('border-box-sizing');
28 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
29 $('div#main_app').addClass('border-box-sizing ui-widget ui-widget-content');
@@ -33,6 +34,7 b' $(document).ready(function () {'
33 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
37 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
36 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
38 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
37 IPython.notebook = new IPython.Notebook('div#notebook');
39 IPython.notebook = new IPython.Notebook('div#notebook');
38 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
40 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
@@ -42,6 +44,21 b' $(document).ready(function () {'
42
44
43 // These have display: none in the css file and are made visible here to prevent FLOUC.
45 // These have display: none in the css file and are made visible here to prevent FLOUC.
44 $('div#header').css('display','block');
46 $('div#header').css('display','block');
47
48 if(IPython.read_only){
49 // hide various elements from read-only view
50 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
51 IPython.quick_help.element.addClass('hidden'); // shortcuts are disabled in read_only
52 $('button#new_notebook').addClass('hidden');
53 $('div#cell_section').addClass('hidden');
54 $('div#kernel_section').addClass('hidden');
55 $('span#login_widget').removeClass('hidden');
56 // left panel starts collapsed, but the collapse must happen after
57 // elements start drawing. Don't draw contents of the panel until
58 // after they are collapsed
59 IPython.left_panel.left_panel_element.css('visibility', 'hidden');
60 }
61
45 $('div#main_app').css('display','block');
62 $('div#main_app').css('display','block');
46
63
47 // Perform these actions after the notebook has been loaded.
64 // Perform these actions after the notebook has been loaded.
@@ -52,6 +69,14 b' $(document).ready(function () {'
52 IPython.save_widget.update_url();
69 IPython.save_widget.update_url();
53 IPython.layout_manager.do_resize();
70 IPython.layout_manager.do_resize();
54 IPython.pager.collapse();
71 IPython.pager.collapse();
72 if(IPython.read_only){
73 // collapse the left panel on read-only
74 IPython.left_panel.collapse();
75 // and finally unhide the panel contents after collapse
76 setTimeout(function(){
77 IPython.left_panel.left_panel_element.css('visibility', 'visible');
78 }, 200)
79 }
55 },100);
80 },100);
56 });
81 });
57
82
@@ -27,7 +27,16 b' $(document).ready(function () {'
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';
31
30 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
32 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
33 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
34
35 if (IPython.read_only){
36 $('#new_notebook').addClass('hidden');
37 // unhide login button if it's relevant
38 $('span#login_widget').removeClass('hidden');
39 }
31 IPython.notebook_list.load_list();
40 IPython.notebook_list.load_list();
32
41
33 // These have display: none in the css file and are made visible here to prevent FLOUC.
42 // These have display: none in the css file and are made visible here to prevent FLOUC.
@@ -33,7 +33,8 b' var IPython = (function (IPython) {'
33 indentUnit : 4,
33 indentUnit : 4,
34 mode: this.code_mirror_mode,
34 mode: this.code_mirror_mode,
35 theme: 'default',
35 theme: 'default',
36 value: this.placeholder
36 value: this.placeholder,
37 readOnly: this.read_only,
37 });
38 });
38 // The tabindex=-1 makes this div focusable.
39 // The tabindex=-1 makes this div focusable.
39 var render_area = $('<div/>').addClass('text_cell_render').
40 var render_area = $('<div/>').addClass('text_cell_render').
@@ -65,6 +66,7 b' var IPython = (function (IPython) {'
65
66
66
67
67 TextCell.prototype.edit = function () {
68 TextCell.prototype.edit = function () {
69 if ( this.read_only ) return;
68 if (this.rendered === true) {
70 if (this.rendered === true) {
69 var text_cell = this.element;
71 var text_cell = this.element;
70 var output = text_cell.find("div.text_cell_render");
72 var output = text_cell.find("div.text_cell_render");
@@ -11,6 +11,8 b''
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
13
14 <meta name="read_only" content="{{read_only}}"/>
15
14 </head>
16 </head>
15
17
16 <body>
18 <body>
@@ -40,7 +40,8 b''
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
40 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
41 <link rel="stylesheet" href="static/css/notebook.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
42 <link rel="stylesheet" href="static/css/renderedhtml.css" type="text/css" />
43
43
44 <meta name="read_only" content="{{read_only}}"/>
44
45
45 </head>
46 </head>
46
47
@@ -57,7 +58,10 b''
57 </span>
58 </span>
58 <span id="quick_help_area">
59 <span id="quick_help_area">
59 <button id="quick_help">Quick<u>H</u>elp</button>
60 <button id="quick_help">Quick<u>H</u>elp</button>
60 </span>
61 </span>
62 <span id="login_widget" class="hidden">
63 <button id="login">Login</button>
64 </span>
61 <span id="kernel_status">Idle</span>
65 <span id="kernel_status">Idle</span>
62 </div>
66 </div>
63
67
@@ -278,6 +282,7 b''
278 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
279 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
280 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
285 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
286 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
282 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
287 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
283 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
288 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
@@ -12,6 +12,8 b''
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 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
13 <link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
14
14
15 <meta name="read_only" content="{{read_only}}"/>
16
15 </head>
17 </head>
16
18
17 <body data-project={{project}} data-base-project-url={{base_project_url}}
19 <body data-project={{project}} data-base-project-url={{base_project_url}}
@@ -19,6 +21,9 b''
19
21
20 <div id="header">
22 <div id="header">
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
23 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
24 <span id="login_widget" class="hidden">
25 <button id="login">Login</button>
26 </span>
22 </div>
27 </div>
23
28
24 <div id="header_border"></div>
29 <div id="header_border"></div>
@@ -54,6 +59,7 b''
54 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
55 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
56 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
61 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
62 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
57 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
63 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
58
64
59 </body>
65 </body>
General Comments 0
You need to be logged in to leave comments. Login now