##// END OF EJS Templates
add read-only view for notebooks...
MinRK -
Show More
@@ -46,6 +46,22 b' def not_if_readonly(f, self, *args, **kwargs):'
46 else:
46 else:
47 return f(self, *args, **kwargs)
47 return f(self, *args, **kwargs)
48
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.ipython_app.read_only:
61 return f(self, *args, **kwargs)
62 else:
63 return auth_f(self, *args, **kwargs)
64
49 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
50 # Top-level handlers
66 # Top-level handlers
51 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
@@ -68,7 +84,7 b' class AuthenticatedHandler(web.RequestHandler):'
68
84
69 class ProjectDashboardHandler(AuthenticatedHandler):
85 class ProjectDashboardHandler(AuthenticatedHandler):
70
86
71 @web.authenticated
87 @authenticate_unless_readonly
72 def get(self):
88 def get(self):
73 nbm = self.application.notebook_manager
89 nbm = self.application.notebook_manager
74 project = nbm.notebook_dir
90 project = nbm.notebook_dir
@@ -81,7 +97,7 b' class ProjectDashboardHandler(AuthenticatedHandler):'
81 class LoginHandler(AuthenticatedHandler):
97 class LoginHandler(AuthenticatedHandler):
82
98
83 def get(self):
99 def get(self):
84 self.render('login.html', next='/')
100 self.render('login.html', next=self.get_argument('next', default='/'))
85
101
86 def post(self):
102 def post(self):
87 pwd = self.get_argument('password', default=u'')
103 pwd = self.get_argument('password', default=u'')
@@ -93,7 +109,6 b' class LoginHandler(AuthenticatedHandler):'
93
109
94 class NewHandler(AuthenticatedHandler):
110 class NewHandler(AuthenticatedHandler):
95
111
96 @not_if_readonly
97 @web.authenticated
112 @web.authenticated
98 def get(self):
113 def get(self):
99 nbm = self.application.notebook_manager
114 nbm = self.application.notebook_manager
@@ -109,7 +124,7 b' class NewHandler(AuthenticatedHandler):'
109
124
110 class NamedNotebookHandler(AuthenticatedHandler):
125 class NamedNotebookHandler(AuthenticatedHandler):
111
126
112 @web.authenticated
127 @authenticate_unless_readonly
113 def get(self, notebook_id):
128 def get(self, notebook_id):
114 nbm = self.application.notebook_manager
129 nbm = self.application.notebook_manager
115 project = nbm.notebook_dir
130 project = nbm.notebook_dir
@@ -130,13 +145,11 b' class NamedNotebookHandler(AuthenticatedHandler):'
130
145
131 class MainKernelHandler(AuthenticatedHandler):
146 class MainKernelHandler(AuthenticatedHandler):
132
147
133 @not_if_readonly
134 @web.authenticated
148 @web.authenticated
135 def get(self):
149 def get(self):
136 km = self.application.kernel_manager
150 km = self.application.kernel_manager
137 self.finish(jsonapi.dumps(km.kernel_ids))
151 self.finish(jsonapi.dumps(km.kernel_ids))
138
152
139 @not_if_readonly
140 @web.authenticated
153 @web.authenticated
141 def post(self):
154 def post(self):
142 km = self.application.kernel_manager
155 km = self.application.kernel_manager
@@ -152,7 +165,6 b' class KernelHandler(AuthenticatedHandler):'
152
165
153 SUPPORTED_METHODS = ('DELETE')
166 SUPPORTED_METHODS = ('DELETE')
154
167
155 @not_if_readonly
156 @web.authenticated
168 @web.authenticated
157 def delete(self, kernel_id):
169 def delete(self, kernel_id):
158 km = self.application.kernel_manager
170 km = self.application.kernel_manager
@@ -163,7 +175,6 b' class KernelHandler(AuthenticatedHandler):'
163
175
164 class KernelActionHandler(AuthenticatedHandler):
176 class KernelActionHandler(AuthenticatedHandler):
165
177
166 @not_if_readonly
167 @web.authenticated
178 @web.authenticated
168 def post(self, kernel_id, action):
179 def post(self, kernel_id, action):
169 km = self.application.kernel_manager
180 km = self.application.kernel_manager
@@ -242,7 +253,6 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler):'
242 except:
253 except:
243 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
254 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
244
255
245 @not_if_readonly
246 def on_first_message(self, msg):
256 def on_first_message(self, msg):
247 self._inject_cookie_message(msg)
257 self._inject_cookie_message(msg)
248 if self.get_current_user() is None:
258 if self.get_current_user() is None:
@@ -380,13 +390,19 b' class ShellHandler(AuthenticatedZMQStreamHandler):'
380
390
381 class NotebookRootHandler(AuthenticatedHandler):
391 class NotebookRootHandler(AuthenticatedHandler):
382
392
383 @web.authenticated
393 @authenticate_unless_readonly
384 def get(self):
394 def get(self):
395
396 # communicate read-only via Allow header
397 if self.application.ipython_app.read_only and not self.get_current_user():
398 self.set_header('Allow', 'GET')
399 else:
400 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
401
385 nbm = self.application.notebook_manager
402 nbm = self.application.notebook_manager
386 files = nbm.list_notebooks()
403 files = nbm.list_notebooks()
387 self.finish(jsonapi.dumps(files))
404 self.finish(jsonapi.dumps(files))
388
405
389 @not_if_readonly
390 @web.authenticated
406 @web.authenticated
391 def post(self):
407 def post(self):
392 nbm = self.application.notebook_manager
408 nbm = self.application.notebook_manager
@@ -405,11 +421,18 b' class NotebookHandler(AuthenticatedHandler):'
405
421
406 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
422 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
407
423
408 @web.authenticated
424 @authenticate_unless_readonly
409 def get(self, notebook_id):
425 def get(self, notebook_id):
410 nbm = self.application.notebook_manager
426 nbm = self.application.notebook_manager
411 format = self.get_argument('format', default='json')
427 format = self.get_argument('format', default='json')
412 last_mod, name, data = nbm.get_notebook(notebook_id, format)
428 last_mod, name, data = nbm.get_notebook(notebook_id, format)
429
430 # communicate read-only via Allow header
431 if self.application.ipython_app.read_only and not self.get_current_user():
432 self.set_header('Allow', 'GET')
433 else:
434 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
435
413 if format == u'json':
436 if format == u'json':
414 self.set_header('Content-Type', 'application/json')
437 self.set_header('Content-Type', 'application/json')
415 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
438 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
@@ -419,7 +442,6 b' class NotebookHandler(AuthenticatedHandler):'
419 self.set_header('Last-Modified', last_mod)
442 self.set_header('Last-Modified', last_mod)
420 self.finish(data)
443 self.finish(data)
421
444
422 @not_if_readonly
423 @web.authenticated
445 @web.authenticated
424 def put(self, notebook_id):
446 def put(self, notebook_id):
425 nbm = self.application.notebook_manager
447 nbm = self.application.notebook_manager
@@ -429,7 +451,6 b' class NotebookHandler(AuthenticatedHandler):'
429 self.set_status(204)
451 self.set_status(204)
430 self.finish()
452 self.finish()
431
453
432 @not_if_readonly
433 @web.authenticated
454 @web.authenticated
434 def delete(self, notebook_id):
455 def delete(self, notebook_id):
435 nbm = self.application.notebook_manager
456 nbm = self.application.notebook_manager
@@ -118,13 +118,22 b" flags['no-browser']=("
118 )
118 )
119 flags['read-only'] = (
119 flags['read-only'] = (
120 {'NotebookApp' : {'read_only' : True}},
120 {'NotebookApp' : {'read_only' : True}},
121 "Launch the Notebook server in read-only mode, not allowing execution or editing"
121 """Allow read-only access to notebooks.
122
123 When using a password to protect the notebook server, this flag
124 allows unauthenticated clients to view the notebook list, and
125 individual notebooks, but not edit them, start kernels, or run
126 code.
127
128 This flag only makes sense in conjunction with setting a password,
129 via the ``NotebookApp.password`` configurable.
130 """
122 )
131 )
123
132
124 # the flags that are specific to the frontend
133 # the flags that are specific to the frontend
125 # these must be scrubbed before being passed to the kernel,
134 # these must be scrubbed before being passed to the kernel,
126 # or it will raise an error on unrecognized flags
135 # or it will raise an error on unrecognized flags
127 notebook_flags = ['no-browser']
136 notebook_flags = ['no-browser', 'read-only']
128
137
129 aliases = dict(ipkernel_aliases)
138 aliases = dict(ipkernel_aliases)
130
139
@@ -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);
@@ -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 = false;
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,26 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');
982 if (allowed && allowed.indexOf('PUT') == -1){
983 this.read_only = true;
984 // unhide login button if it's relevant
985 $('span#login_widget').removeClass('hidden');
986 }else{
987 this.read_only = false;
988 }
978 this.fromJSON(data);
989 this.fromJSON(data);
979 if (this.ncells() === 0) {
990 if (this.ncells() === 0) {
980 this.insert_code_cell_below();
991 this.insert_code_cell_below();
981 };
992 };
982 IPython.save_widget.status_save();
993 IPython.save_widget.status_save();
983 IPython.save_widget.set_notebook_name(data.metadata.name);
994 IPython.save_widget.set_notebook_name(data.metadata.name);
984 this.start_kernel();
985 this.dirty = false;
995 this.dirty = false;
996 if (this.read_only) {
997 this.handle_read_only();
998 }else{
999 this.start_kernel();
1000 }
986 // fromJSON always selects the last cell inserted. We need to wait
1001 // fromJSON always selects the last cell inserted. We need to wait
987 // until that is done before scrolling to the top.
1002 // until that is done before scrolling to the top.
988 setTimeout(function () {
1003 setTimeout(function () {
@@ -992,6 +1007,15 b' var IPython = (function (IPython) {'
992 };
1007 };
993
1008
994
1009
1010 Notebook.prototype.handle_read_only = function(){
1011 IPython.left_panel.collapse();
1012 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
1013 $('button#new_notebook').addClass('hidden');
1014 $('div#cell_section').addClass('hidden');
1015 $('div#kernel_section').addClass('hidden');
1016 }
1017
1018
995 IPython.Notebook = Notebook;
1019 IPython.Notebook = Notebook;
996
1020
997
1021
@@ -73,6 +73,15 b' var IPython = (function (IPython) {'
73
73
74
74
75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
76 var allowed = xhr.getResponseHeader('Allow');
77 if (allowed && allowed.indexOf('PUT') == -1){
78 this.read_only = true;
79 $('#new_notebook').addClass('hidden');
80 // unhide login button if it's relevant
81 $('span#login_widget').removeClass('hidden');
82 }else{
83 this.read_only = false;
84 }
76 var len = data.length;
85 var len = data.length;
77 // Todo: remove old children
86 // Todo: remove old children
78 for (var i=0; i<len; i++) {
87 for (var i=0; i<len; i++) {
@@ -80,7 +89,10 b' var IPython = (function (IPython) {'
80 var nbname = data[i].name;
89 var nbname = data[i].name;
81 var item = this.new_notebook_item(i);
90 var item = this.new_notebook_item(i);
82 this.add_link(notebook_id, nbname, item);
91 this.add_link(notebook_id, nbname, item);
83 this.add_delete_button(item);
92 if (!this.read_only){
93 // hide delete buttons when readonly
94 this.add_delete_button(item);
95 }
84 };
96 };
85 };
97 };
86
98
@@ -33,6 +33,7 b' $(document).ready(function () {'
33 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
33 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
34 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
35 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
36 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
37 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
37 IPython.notebook = new IPython.Notebook('div#notebook');
38 IPython.notebook = new IPython.Notebook('div#notebook');
38 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
39 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
@@ -28,6 +28,7 b' $(document).ready(function () {'
28 $('div#right_panel').addClass('box-flex');
28 $('div#right_panel').addClass('box-flex');
29
29
30 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
30 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
31 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
31 IPython.notebook_list.load_list();
32 IPython.notebook_list.load_list();
32
33
33 // These have display: none in the css file and are made visible here to prevent FLOUC.
34 // 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");
@@ -57,7 +57,10 b''
57 </span>
57 </span>
58 <span id="quick_help_area">
58 <span id="quick_help_area">
59 <button id="quick_help">Quick<u>H</u>elp</button>
59 <button id="quick_help">Quick<u>H</u>elp</button>
60 </span>
60 </span>
61 <span id="login_widget" class="hidden">
62 <button id="login">Login</button>
63 </span>
61 <span id="kernel_status">Idle</span>
64 <span id="kernel_status">Idle</span>
62 </div>
65 </div>
63
66
@@ -278,6 +281,7 b''
278 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
281 <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>
282 <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>
283 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
281 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
285 <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>
286 <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>
287 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
@@ -19,6 +19,9 b''
19
19
20 <div id="header">
20 <div id="header">
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
22 <span id="login_widget" class="hidden">
23 <button id="login">Login</button>
24 </span>
22 </div>
25 </div>
23
26
24 <div id="header_border"></div>
27 <div id="header_border"></div>
@@ -54,6 +57,7 b''
54 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
57 <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>
58 <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>
59 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 <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>
61 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
58
62
59 </body>
63 </body>
General Comments 0
You need to be logged in to leave comments. Login now