##// END OF EJS Templates
Add selector button to dashboard
Jonathan Frederic -
Show More
@@ -1,771 +1,876 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 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'base/js/events',
9 'base/js/events',
10 'base/js/keyboard',
10 'base/js/keyboard',
11 ], function(IPython, $, utils, dialog, events, keyboard) {
11 ], function(IPython, $, utils, dialog, events, keyboard) {
12 "use strict";
12 "use strict";
13
13
14 var NotebookList = function (selector, options) {
14 var NotebookList = function (selector, options) {
15 /**
15 /**
16 * Constructor
16 * Constructor
17 *
17 *
18 * Parameters:
18 * Parameters:
19 * selector: string
19 * selector: string
20 * options: dictionary
20 * options: dictionary
21 * Dictionary of keyword arguments.
21 * Dictionary of keyword arguments.
22 * session_list: SessionList instance
22 * session_list: SessionList instance
23 * element_name: string
23 * element_name: string
24 * base_url: string
24 * base_url: string
25 * notebook_path: string
25 * notebook_path: string
26 * contents: Contents instance
26 * contents: Contents instance
27 */
27 */
28 var that = this;
28 var that = this;
29 this.session_list = options.session_list;
29 this.session_list = options.session_list;
30 // allow code re-use by just changing element_name in kernellist.js
30 // allow code re-use by just changing element_name in kernellist.js
31 this.element_name = options.element_name || 'notebook';
31 this.element_name = options.element_name || 'notebook';
32 this.selector = selector;
32 this.selector = selector;
33 if (this.selector !== undefined) {
33 if (this.selector !== undefined) {
34 this.element = $(selector);
34 this.element = $(selector);
35 this.style();
35 this.style();
36 this.bind_events();
36 this.bind_events();
37 }
37 }
38 this.notebooks_list = [];
38 this.notebooks_list = [];
39 this.sessions = {};
39 this.sessions = {};
40 this.base_url = options.base_url || utils.get_body_data("baseUrl");
40 this.base_url = options.base_url || utils.get_body_data("baseUrl");
41 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
41 this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath");
42 this.contents = options.contents;
42 this.contents = options.contents;
43 if (this.session_list && this.session_list.events) {
43 if (this.session_list && this.session_list.events) {
44 this.session_list.events.on('sessions_loaded.Dashboard',
44 this.session_list.events.on('sessions_loaded.Dashboard',
45 function(e, d) { that.sessions_loaded(d); });
45 function(e, d) { that.sessions_loaded(d); });
46 }
46 }
47 };
47 };
48
48
49 NotebookList.prototype.style = function () {
49 NotebookList.prototype.style = function () {
50 var prefix = '#' + this.element_name;
50 var prefix = '#' + this.element_name;
51 $(prefix + '_toolbar').addClass('list_toolbar');
51 $(prefix + '_toolbar').addClass('list_toolbar');
52 $(prefix + '_list_info').addClass('toolbar_info');
52 $(prefix + '_list_info').addClass('toolbar_info');
53 $(prefix + '_buttons').addClass('toolbar_buttons');
53 $(prefix + '_buttons').addClass('toolbar_buttons');
54 $(prefix + '_list_header').addClass('list_header');
54 $(prefix + '_list_header').addClass('list_header');
55 this.element.addClass("list_container");
55 this.element.addClass("list_container");
56 };
56 };
57
57
58 NotebookList.prototype.bind_events = function () {
58 NotebookList.prototype.bind_events = function () {
59 var that = this;
59 var that = this;
60 $('#refresh_' + this.element_name + '_list').click(function () {
60 $('#refresh_' + this.element_name + '_list').click(function () {
61 that.load_sessions();
61 that.load_sessions();
62 });
62 });
63 this.element.bind('dragover', function () {
63 this.element.bind('dragover', function () {
64 return false;
64 return false;
65 });
65 });
66 this.element.bind('drop', function(event){
66 this.element.bind('drop', function(event){
67 that.handleFilesUpload(event,'drop');
67 that.handleFilesUpload(event,'drop');
68 return false;
68 return false;
69 });
69 });
70
70
71 // Bind events for singleton controls.
71 // Bind events for singleton controls.
72 if (!NotebookList._bound_singletons) {
72 if (!NotebookList._bound_singletons) {
73 NotebookList._bound_singletons = true;
73 NotebookList._bound_singletons = true;
74 $('#new-file').click(function(e) {
74 $('#new-file').click(function(e) {
75 var w = window.open();
75 var w = window.open();
76 that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) {
76 that.contents.new_untitled(that.notebook_path || '', {type: 'file', ext: '.txt'}).then(function(data) {
77 var url = utils.url_join_encode(
77 var url = utils.url_join_encode(
78 that.base_url, 'edit', data.path
78 that.base_url, 'edit', data.path
79 );
79 );
80 w.location = url;
80 w.location = url;
81 }).catch(function (e) {
81 }).catch(function (e) {
82 w.close();
82 w.close();
83 dialog.modal({
83 dialog.modal({
84 title: 'Creating File Failed',
84 title: 'Creating File Failed',
85 body: $('<div/>')
85 body: $('<div/>')
86 .text("An error occurred while creating a new file.")
86 .text("An error occurred while creating a new file.")
87 .append($('<div/>')
87 .append($('<div/>')
88 .addClass('alert alert-danger')
88 .addClass('alert alert-danger')
89 .text(e.message || e)),
89 .text(e.message || e)),
90 buttons: {
90 buttons: {
91 OK: {'class': 'btn-primary'}
91 OK: {'class': 'btn-primary'}
92 }
92 }
93 });
93 });
94 });
94 });
95 that.load_sessions();
95 that.load_sessions();
96 });
96 });
97 $('#new-folder').click(function(e) {
97 $('#new-folder').click(function(e) {
98 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'})
98 that.contents.new_untitled(that.notebook_path || '', {type: 'directory'})
99 .then(function(){
99 .then(function(){
100 that.load_list();
100 that.load_list();
101 }).catch(function (e) {
101 }).catch(function (e) {
102 dialog.modal({
102 dialog.modal({
103 title: 'Creating Folder Failed',
103 title: 'Creating Folder Failed',
104 body: $('<div/>')
104 body: $('<div/>')
105 .text("An error occurred while creating a new folder.")
105 .text("An error occurred while creating a new folder.")
106 .append($('<div/>')
106 .append($('<div/>')
107 .addClass('alert alert-danger')
107 .addClass('alert alert-danger')
108 .text(e.message || e)),
108 .text(e.message || e)),
109 buttons: {
109 buttons: {
110 OK: {'class': 'btn-primary'}
110 OK: {'class': 'btn-primary'}
111 }
111 }
112 });
112 });
113 });
113 });
114 that.load_sessions();
114 that.load_sessions();
115 });
115 });
116
116
117 // Bind events for action buttons.
117 $('.rename-button').click($.proxy(this.rename_selected, this));
118 $('.rename-button').click($.proxy(this.rename_selected, this));
118 $('.shutdown-button').click($.proxy(this.shutdown_selected, this));
119 $('.shutdown-button').click($.proxy(this.shutdown_selected, this));
119 $('.duplicate-button').click($.proxy(this.duplicate_selected, this));
120 $('.duplicate-button').click($.proxy(this.duplicate_selected, this));
120 $('.delete-button').click($.proxy(this.delete_selected, this));
121 $('.delete-button').click($.proxy(this.delete_selected, this));
122
123 // Bind events for selection menu buttons.
124 $('#tree-selector .select-all').click($.proxy(this.select_all, this));
125 $('#tree-selector .select-notebooks').click($.proxy(this.select_notebooks, this));
126 $('#tree-selector .select-running-notebooks').click($.proxy(this.select_running_notebooks, this));
127 $('#tree-selector .select-files').click($.proxy(this.select_files, this));
128 $('#tree-selector .select-directories').click($.proxy(this.select_directories, this));
129 $('#tree-selector .select-inverse').click($.proxy(this.select_inverse, this));
130 $('#tree-selector .unselect-all').click($.proxy(this.unselect_all, this));
121 }
131 }
122 };
132 };
123
133
124 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
134 NotebookList.prototype.handleFilesUpload = function(event, dropOrForm) {
125 var that = this;
135 var that = this;
126 var files;
136 var files;
127 if(dropOrForm =='drop'){
137 if(dropOrForm =='drop'){
128 files = event.originalEvent.dataTransfer.files;
138 files = event.originalEvent.dataTransfer.files;
129 } else
139 } else
130 {
140 {
131 files = event.originalEvent.target.files;
141 files = event.originalEvent.target.files;
132 }
142 }
133 for (var i = 0; i < files.length; i++) {
143 for (var i = 0; i < files.length; i++) {
134 var f = files[i];
144 var f = files[i];
135 var name_and_ext = utils.splitext(f.name);
145 var name_and_ext = utils.splitext(f.name);
136 var file_ext = name_and_ext[1];
146 var file_ext = name_and_ext[1];
137
147
138 var reader = new FileReader();
148 var reader = new FileReader();
139 if (file_ext === '.ipynb') {
149 if (file_ext === '.ipynb') {
140 reader.readAsText(f);
150 reader.readAsText(f);
141 } else {
151 } else {
142 // read non-notebook files as binary
152 // read non-notebook files as binary
143 reader.readAsArrayBuffer(f);
153 reader.readAsArrayBuffer(f);
144 }
154 }
145 var item = that.new_item(0, true);
155 var item = that.new_item(0, true);
146 item.addClass('new-file');
156 item.addClass('new-file');
147 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
157 that.add_name_input(f.name, item, file_ext == '.ipynb' ? 'notebook' : 'file');
148 // Store the list item in the reader so we can use it later
158 // Store the list item in the reader so we can use it later
149 // to know which item it belongs to.
159 // to know which item it belongs to.
150 $(reader).data('item', item);
160 $(reader).data('item', item);
151 reader.onload = function (event) {
161 reader.onload = function (event) {
152 var item = $(event.target).data('item');
162 var item = $(event.target).data('item');
153 that.add_file_data(event.target.result, item);
163 that.add_file_data(event.target.result, item);
154 that.add_upload_button(item);
164 that.add_upload_button(item);
155 };
165 };
156 reader.onerror = function (event) {
166 reader.onerror = function (event) {
157 var item = $(event.target).data('item');
167 var item = $(event.target).data('item');
158 var name = item.data('name');
168 var name = item.data('name');
159 item.remove();
169 item.remove();
160 dialog.modal({
170 dialog.modal({
161 title : 'Failed to read file',
171 title : 'Failed to read file',
162 body : "Failed to read file '" + name + "'",
172 body : "Failed to read file '" + name + "'",
163 buttons : {'OK' : { 'class' : 'btn-primary' }}
173 buttons : {'OK' : { 'class' : 'btn-primary' }}
164 });
174 });
165 };
175 };
166 }
176 }
167 // Replace the file input form wth a clone of itself. This is required to
177 // Replace the file input form wth a clone of itself. This is required to
168 // reset the form. Otherwise, if you upload a file, delete it and try to
178 // reset the form. Otherwise, if you upload a file, delete it and try to
169 // upload it again, the changed event won't fire.
179 // upload it again, the changed event won't fire.
170 var form = $('input.fileinput');
180 var form = $('input.fileinput');
171 form.replaceWith(form.clone(true));
181 form.replaceWith(form.clone(true));
172 return false;
182 return false;
173 };
183 };
174
184
175 NotebookList.prototype.clear_list = function (remove_uploads) {
185 NotebookList.prototype.clear_list = function (remove_uploads) {
176 /**
186 /**
177 * Clears the navigation tree.
187 * Clears the navigation tree.
178 *
188 *
179 * Parameters
189 * Parameters
180 * remove_uploads: bool=False
190 * remove_uploads: bool=False
181 * Should upload prompts also be removed from the tree.
191 * Should upload prompts also be removed from the tree.
182 */
192 */
183 if (remove_uploads) {
193 if (remove_uploads) {
184 this.element.children('.list_item').remove();
194 this.element.children('.list_item').remove();
185 } else {
195 } else {
186 this.element.children('.list_item:not(.new-file)').remove();
196 this.element.children('.list_item:not(.new-file)').remove();
187 }
197 }
188 };
198 };
189
199
190 NotebookList.prototype.load_sessions = function(){
200 NotebookList.prototype.load_sessions = function(){
191 this.session_list.load_sessions();
201 this.session_list.load_sessions();
192 };
202 };
193
203
194
204
195 NotebookList.prototype.sessions_loaded = function(data){
205 NotebookList.prototype.sessions_loaded = function(data){
196 this.sessions = data;
206 this.sessions = data;
197 this.load_list();
207 this.load_list();
198 };
208 };
199
209
200 NotebookList.prototype.load_list = function () {
210 NotebookList.prototype.load_list = function () {
201 var that = this;
211 var that = this;
202 this.contents.list_contents(that.notebook_path).then(
212 this.contents.list_contents(that.notebook_path).then(
203 $.proxy(this.draw_notebook_list, this),
213 $.proxy(this.draw_notebook_list, this),
204 function(error) {
214 function(error) {
205 that.draw_notebook_list({content: []}, "Server error: " + error.message);
215 that.draw_notebook_list({content: []}, "Server error: " + error.message);
206 }
216 }
207 );
217 );
208 };
218 };
209
219
210 /**
220 /**
211 * Draw the list of notebooks
221 * Draw the list of notebooks
212 * @method draw_notebook_list
222 * @method draw_notebook_list
213 * @param {Array} list An array of dictionaries representing files or
223 * @param {Array} list An array of dictionaries representing files or
214 * directories.
224 * directories.
215 * @param {String} error_msg An error message
225 * @param {String} error_msg An error message
216 */
226 */
217
227
218
228
219 var type_order = {'directory':0,'notebook':1,'file':2};
229 var type_order = {'directory':0,'notebook':1,'file':2};
220
230
221 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
231 NotebookList.prototype.draw_notebook_list = function (list, error_msg) {
222 list.content.sort(function(a, b) {
232 list.content.sort(function(a, b) {
223 if (type_order[a['type']] < type_order[b['type']]) {
233 if (type_order[a['type']] < type_order[b['type']]) {
224 return -1;
234 return -1;
225 }
235 }
226 if (type_order[a['type']] > type_order[b['type']]) {
236 if (type_order[a['type']] > type_order[b['type']]) {
227 return 1;
237 return 1;
228 }
238 }
229 if (a['name'] < b['name']) {
239 if (a['name'] < b['name']) {
230 return -1;
240 return -1;
231 }
241 }
232 if (a['name'] > b['name']) {
242 if (a['name'] > b['name']) {
233 return 1;
243 return 1;
234 }
244 }
235 return 0;
245 return 0;
236 });
246 });
237 var message = error_msg || 'Notebook list empty.';
247 var message = error_msg || 'Notebook list empty.';
238 var item = null;
248 var item = null;
239 var model = null;
249 var model = null;
240 var len = list.content.length;
250 var len = list.content.length;
241 this.clear_list();
251 this.clear_list();
242 var n_uploads = this.element.children('.list_item').length;
252 var n_uploads = this.element.children('.list_item').length;
243 if (len === 0) {
253 if (len === 0) {
244 item = this.new_item(0);
254 item = this.new_item(0);
245 var span12 = item.children().first();
255 var span12 = item.children().first();
246 span12.empty();
256 span12.empty();
247 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
257 span12.append($('<div style="margin:auto;text-align:center;color:grey"/>').text(message));
248 }
258 }
249 var path = this.notebook_path;
259 var path = this.notebook_path;
250 var offset = n_uploads;
260 var offset = n_uploads;
251 if (path !== '') {
261 if (path !== '') {
252 item = this.new_item(offset, false);
262 item = this.new_item(offset, false);
253 model = {
263 model = {
254 type: 'directory',
264 type: 'directory',
255 name: '..',
265 name: '..',
256 path: utils.url_path_split(path)[0],
266 path: utils.url_path_split(path)[0],
257 };
267 };
258 this.add_link(model, item);
268 this.add_link(model, item);
259 offset += 1;
269 offset += 1;
260 }
270 }
261 for (var i=0; i<len; i++) {
271 for (var i=0; i<len; i++) {
262 model = list.content[i];
272 model = list.content[i];
263 item = this.new_item(i+offset, true);
273 item = this.new_item(i+offset, true);
264 this.add_link(model, item);
274 this.add_link(model, item);
265 }
275 }
266 // Trigger an event when we've finished drawing the notebook list.
276 // Trigger an event when we've finished drawing the notebook list.
267 events.trigger('draw_notebook_list.NotebookList');
277 events.trigger('draw_notebook_list.NotebookList');
268 this._selection_changed();
278 this._selection_changed();
269 };
279 };
270
280
271
281
272 /**
282 /**
273 * Creates a new item.
283 * Creates a new item.
274 * @param {integer} index
284 * @param {integer} index
275 * @param {boolean} [selectable] - tristate, undefined: don't draw checkbox,
285 * @param {boolean} [selectable] - tristate, undefined: don't draw checkbox,
276 * false: don't draw checkbox but pad
286 * false: don't draw checkbox but pad
277 * where it should be, true: draw checkbox.
287 * where it should be, true: draw checkbox.
278 * @return {JQuery} row
288 * @return {JQuery} row
279 */
289 */
280 NotebookList.prototype.new_item = function (index, selectable) {
290 NotebookList.prototype.new_item = function (index, selectable) {
281 var row = $('<div/>')
291 var row = $('<div/>')
282 .addClass("list_item")
292 .addClass("list_item")
283 .addClass("row");
293 .addClass("row");
284
294
285 var item = $("<div/>")
295 var item = $("<div/>")
286 .addClass("col-md-12")
296 .addClass("col-md-12")
287 .appendTo(row);
297 .appendTo(row);
288
298
289 var checkbox;
299 var checkbox;
290 if (selectable !== undefined) {
300 if (selectable !== undefined) {
291 checkbox = $('<input/>')
301 checkbox = $('<input/>')
292 .attr('type', 'checkbox')
302 .attr('type', 'checkbox')
293 .attr('title', 'Click here to rename, delete, etc.')
303 .attr('title', 'Click here to rename, delete, etc.')
294 .appendTo(item);
304 .appendTo(item);
295 }
305 }
296
306
297 $('<i/>')
307 $('<i/>')
298 .addClass('item_icon')
308 .addClass('item_icon')
299 .appendTo(item);
309 .appendTo(item);
300
310
301 var link = $("<a/>")
311 var link = $("<a/>")
302 .addClass("item_link")
312 .addClass("item_link")
303 .appendTo(item);
313 .appendTo(item);
304
314
305 $("<span/>")
315 $("<span/>")
306 .addClass("item_name")
316 .addClass("item_name")
307 .appendTo(link);
317 .appendTo(link);
308
318
309 if (selectable === false) {
319 if (selectable === false) {
310 checkbox.css('visibility', 'hidden');
320 checkbox.css('visibility', 'hidden');
311 } else if (selectable === true) {
321 } else if (selectable === true) {
312 var that = this;
322 var that = this;
313 link.click(function(e) {
323 link.click(function(e) {
314 e.stopPropagation();
324 e.stopPropagation();
315 });
325 });
316 checkbox.click(function(e) {
326 checkbox.click(function(e) {
317 e.stopPropagation();
327 e.stopPropagation();
318 that._selection_changed();
328 that._selection_changed();
319 });
329 });
320 row.click(function(e) {
330 row.click(function(e) {
321 e.stopPropagation();
331 e.stopPropagation();
322 checkbox.prop('checked', !checkbox.prop('checked'));
332 checkbox.prop('checked', !checkbox.prop('checked'));
323 that._selection_changed();
333 that._selection_changed();
324 });
334 });
325 }
335 }
326
336
327 var buttons = $('<div/>')
337 var buttons = $('<div/>')
328 .addClass("item_buttons pull-right")
338 .addClass("item_buttons pull-right")
329 .appendTo(item);
339 .appendTo(item);
330
340
331 $('<div/>')
341 $('<div/>')
332 .addClass('running-indicator')
342 .addClass('running-indicator')
333 .text('Running')
343 .text('Running')
334 .css('visibility', 'hidden')
344 .css('visibility', 'hidden')
335 .appendTo(buttons);
345 .appendTo(buttons);
336
346
337 if (index === -1) {
347 if (index === -1) {
338 this.element.append(row);
348 this.element.append(row);
339 } else {
349 } else {
340 this.element.children().eq(index).after(row);
350 this.element.children().eq(index).after(row);
341 }
351 }
342 return row;
352 return row;
343 };
353 };
344
354
345
355
346 NotebookList.icons = {
356 NotebookList.icons = {
347 directory: 'folder_icon',
357 directory: 'folder_icon',
348 notebook: 'notebook_icon',
358 notebook: 'notebook_icon',
349 file: 'file_icon',
359 file: 'file_icon',
350 };
360 };
351
361
352 NotebookList.uri_prefixes = {
362 NotebookList.uri_prefixes = {
353 directory: 'tree',
363 directory: 'tree',
354 notebook: 'notebooks',
364 notebook: 'notebooks',
355 file: 'edit',
365 file: 'edit',
356 };
366 };
357
367
358 /**
368 /**
369 * Select all of the items in the tree.
370 */
371 NotebookList.prototype.select_all = function() {
372 $('.list_item input[type=checkbox]').each(function(index, item) {
373 $(item).prop('checked', true);
374 });
375 this._selection_changed();
376 };
377
378 /**
379 * Select all of the notebooks in the tree.
380 */
381 NotebookList.prototype.select_notebooks = function() {
382 this.unselect_all();
383 $('.list_item').each(function(index, item) {
384 if ($(item).data('type') === 'notebook') {
385 $(item).find('input[type=checkbox]').prop('checked', true);
386 }
387 });
388 this._selection_changed();
389 };
390
391 /**
392 * Select all of the running notebooks in the tree.
393 */
394 NotebookList.prototype.select_running_notebooks = function() {
395 this.unselect_all();
396 var that = this;
397 $('.list_item').each(function(index, item) {
398 if ($(item).data('type') === 'notebook' && that.sessions[$(item).data('path')] !== undefined) {
399 $(item).find('input[type=checkbox]').prop('checked', true);
400 }
401 });
402 this._selection_changed();
403 };
404
405 /**
406 * Select all of the files in the tree.
407 */
408 NotebookList.prototype.select_files = function() {
409 this.unselect_all();
410 $('.list_item').each(function(index, item) {
411 if ($(item).data('type') === 'file') {
412 $(item).find('input[type=checkbox]').prop('checked', true);
413 }
414 });
415 this._selection_changed();
416 };
417
418 /**
419 * Select all of the directories in the tree.
420 */
421 NotebookList.prototype.select_directories = function() {
422 this.unselect_all();
423 $('.list_item').each(function(index, item) {
424 if ($(item).data('type') === 'directory') {
425 $(item).find('input[type=checkbox]').prop('checked', true);
426 }
427 });
428 this._selection_changed();
429 };
430
431 /**
432 * Select the inverse of the current selection.
433 */
434 NotebookList.prototype.select_inverse = function() {
435 $('.list_item input[type=checkbox]').each(function(index, item) {
436 $(item).prop('checked', !$(item).prop('checked'));
437 });
438 this._selection_changed();
439 };
440
441 /**
442 * Unselect everything selected in the tree.
443 */
444 NotebookList.prototype.unselect_all = function() {
445 $('.list_item input[type=checkbox]').each(function(index, item) {
446 $(item).prop('checked', false);
447 });
448 this._selection_changed();
449 };
450
451
452 /**
359 * Handles when any row selector checkbox is toggled.
453 * Handles when any row selector checkbox is toggled.
360 */
454 */
361 NotebookList.prototype._selection_changed = function() {
455 NotebookList.prototype._selection_changed = function() {
362
363 // Use a JQuery selector to find each row with a checked checkbox. If
456 // Use a JQuery selector to find each row with a checked checkbox. If
364 // we decide to add more checkboxes in the future, this code will need
457 // we decide to add more checkboxes in the future, this code will need
365 // to be changed to distinguish which checkbox is the row selector.
458 // to be changed to distinguish which checkbox is the row selector.
366 var selected = [];
459 var selected = [];
367 var has_running_notebook = false;
460 var has_running_notebook = false;
368 var has_directory = false;
461 var has_directory = false;
369 var has_file = false;
462 var has_file = false;
370 var that = this;
463 var that = this;
464 var checked = 0;
371 $('.list_item :checked').each(function(index, item) {
465 $('.list_item :checked').each(function(index, item) {
372 var parent = $(item).parent().parent();
466 var parent = $(item).parent().parent();
373
467
374 // If the item doesn't have an upload button, it can be selected.
468 // If the item doesn't have an upload button and it's not the
375 if (parent.find('.upload_button').length === 0) {
469 // breadcrumbs, it can be selected. Breadcrumbs path == ''.
470 if (parent.find('.upload_button').length === 0 && parent.data('path') !== '') {
471 checked++;
376 selected.push({
472 selected.push({
377 name: parent.data('name'),
473 name: parent.data('name'),
378 path: parent.data('path'),
474 path: parent.data('path'),
379 type: parent.data('type')
475 type: parent.data('type')
380 });
476 });
381
477
382 // Set flags according to what is selected. Flags are later
478 // Set flags according to what is selected. Flags are later
383 // used to decide which action buttons are visible.
479 // used to decide which action buttons are visible.
384 has_running_notebook = has_running_notebook ||
480 has_running_notebook = has_running_notebook ||
385 (parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined);
481 (parent.data('type') == 'notebook' && that.sessions[parent.data('path')] !== undefined);
386 has_file = has_file || parent.data('type') == 'file';
482 has_file = has_file || parent.data('type') == 'file';
387 has_directory = has_directory || parent.data('type') == 'directory';
483 has_directory = has_directory || parent.data('type') == 'directory';
388 }
484 }
389 });
485 });
390 this.selected = selected;
486 this.selected = selected;
391
487
392 // Rename is only visible when one item is selected.
488 // Rename is only visible when one item is selected.
393 if (selected.length==1) {
489 if (selected.length==1) {
394 $('.rename-button').css('display', 'inline-block');
490 $('.rename-button').css('display', 'inline-block');
395 } else {
491 } else {
396 $('.rename-button').css('display', 'none');
492 $('.rename-button').css('display', 'none');
397 }
493 }
398
494
399 // Shutdown is only visible when one or more notebooks running notebooks
495 // Shutdown is only visible when one or more notebooks running notebooks
400 // are selected and no non-notebook items are selected.
496 // are selected and no non-notebook items are selected.
401 if (has_running_notebook && !(has_file || has_directory)) {
497 if (has_running_notebook && !(has_file || has_directory)) {
402 $('.shutdown-button').css('display', 'inline-block');
498 $('.shutdown-button').css('display', 'inline-block');
403 } else {
499 } else {
404 $('.shutdown-button').css('display', 'none');
500 $('.shutdown-button').css('display', 'none');
405 }
501 }
406
502
407 // Duplicate isn't visible when a directory is selected.
503 // Duplicate isn't visible when a directory is selected.
408 if (selected.length > 0 && !has_directory) {
504 if (selected.length > 0 && !has_directory) {
409 $('.duplicate-button').css('display', 'inline-block');
505 $('.duplicate-button').css('display', 'inline-block');
410 } else {
506 } else {
411 $('.duplicate-button').css('display', 'none');
507 $('.duplicate-button').css('display', 'none');
412 }
508 }
413
509
414 // Delete is visible if one or more items are selected.
510 // Delete is visible if one or more items are selected.
415 if (selected.length > 0) {
511 if (selected.length > 0) {
416 $('.delete-button').css('display', 'inline-block');
512 $('.delete-button').css('display', 'inline-block');
417 } else {
513 } else {
418 $('.delete-button').css('display', 'none');
514 $('.delete-button').css('display', 'none');
419 }
515 }
516
517 // If all of the items are selected, show the selector as checked. If
518 // some of the items are selected, show it as checked. Otherwise,
519 // uncheck it.
520 if (checked === 0) {
521 $('#tree-selector input[type=checkbox]').prop('checked', false);
522 } else {
523 $('#tree-selector input[type=checkbox]').prop('checked', true);
524 }
420 };
525 };
421
526
422 NotebookList.prototype.add_link = function (model, item) {
527 NotebookList.prototype.add_link = function (model, item) {
423 var path = model.path,
528 var path = model.path,
424 name = model.name;
529 name = model.name;
425 var running = (model.type == 'notebook' && this.sessions[path] !== undefined);
530 var running = (model.type == 'notebook' && this.sessions[path] !== undefined);
426
531
427 item.data('name', name);
532 item.data('name', name);
428 item.data('path', path);
533 item.data('path', path);
429 item.data('type', model.type);
534 item.data('type', model.type);
430 item.find(".item_name").text(name);
535 item.find(".item_name").text(name);
431 var icon = NotebookList.icons[model.type];
536 var icon = NotebookList.icons[model.type];
432 if (running) {
537 if (running) {
433 icon = 'running_' + icon;
538 icon = 'running_' + icon;
434 }
539 }
435 var uri_prefix = NotebookList.uri_prefixes[model.type];
540 var uri_prefix = NotebookList.uri_prefixes[model.type];
436 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
541 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
437 var link = item.find("a.item_link")
542 var link = item.find("a.item_link")
438 .attr('href',
543 .attr('href',
439 utils.url_join_encode(
544 utils.url_join_encode(
440 this.base_url,
545 this.base_url,
441 uri_prefix,
546 uri_prefix,
442 path
547 path
443 )
548 )
444 );
549 );
445
550
446 item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden');
551 item.find(".item_buttons .running-indicator").css('visibility', running ? '' : 'hidden');
447
552
448 // directory nav doesn't open new tabs
553 // directory nav doesn't open new tabs
449 // files, notebooks do
554 // files, notebooks do
450 if (model.type !== "directory") {
555 if (model.type !== "directory") {
451 link.attr('target','_blank');
556 link.attr('target','_blank');
452 }
557 }
453 };
558 };
454
559
455
560
456 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
561 NotebookList.prototype.add_name_input = function (name, item, icon_type) {
457 item.data('name', name);
562 item.data('name', name);
458 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
563 item.find(".item_icon").addClass(NotebookList.icons[icon_type]).addClass('icon-fixed-width');
459 item.find(".item_name").empty().append(
564 item.find(".item_name").empty().append(
460 $('<input/>')
565 $('<input/>')
461 .addClass("filename_input")
566 .addClass("filename_input")
462 .attr('value', name)
567 .attr('value', name)
463 .attr('size', '30')
568 .attr('size', '30')
464 .attr('type', 'text')
569 .attr('type', 'text')
465 .keyup(function(event){
570 .keyup(function(event){
466 if(event.keyCode == 13){item.find('.upload_button').click();}
571 if(event.keyCode == 13){item.find('.upload_button').click();}
467 else if(event.keyCode == 27){item.remove();}
572 else if(event.keyCode == 27){item.remove();}
468 })
573 })
469 );
574 );
470 };
575 };
471
576
472
577
473 NotebookList.prototype.add_file_data = function (data, item) {
578 NotebookList.prototype.add_file_data = function (data, item) {
474 item.data('filedata', data);
579 item.data('filedata', data);
475 };
580 };
476
581
477
582
478 NotebookList.prototype.shutdown_selected = function() {
583 NotebookList.prototype.shutdown_selected = function() {
479 var that = this;
584 var that = this;
480 this.selected.forEach(function(item) {
585 this.selected.forEach(function(item) {
481 if (item.type == 'notebook') {
586 if (item.type == 'notebook') {
482 that.shutdown_notebook(item.path);
587 that.shutdown_notebook(item.path);
483 }
588 }
484 });
589 });
485 };
590 };
486
591
487 NotebookList.prototype.shutdown_notebook = function(path) {
592 NotebookList.prototype.shutdown_notebook = function(path) {
488 var that = this;
593 var that = this;
489 var settings = {
594 var settings = {
490 processData : false,
595 processData : false,
491 cache : false,
596 cache : false,
492 type : "DELETE",
597 type : "DELETE",
493 dataType : "json",
598 dataType : "json",
494 success : function () {
599 success : function () {
495 that.load_sessions();
600 that.load_sessions();
496 },
601 },
497 error : utils.log_ajax_error,
602 error : utils.log_ajax_error,
498 };
603 };
499
604
500 var session = this.sessions[path];
605 var session = this.sessions[path];
501 if (session) {
606 if (session) {
502 var url = utils.url_join_encode(
607 var url = utils.url_join_encode(
503 this.base_url,
608 this.base_url,
504 'api/sessions',
609 'api/sessions',
505 session
610 session
506 );
611 );
507 $.ajax(url, settings);
612 $.ajax(url, settings);
508 }
613 }
509 };
614 };
510
615
511 NotebookList.prototype.rename_selected = function() {
616 NotebookList.prototype.rename_selected = function() {
512 if (this.selected.length != 1) return;
617 if (this.selected.length != 1) return;
513
618
514 var that = this;
619 var that = this;
515 var path = this.selected[0].path;
620 var path = this.selected[0].path;
516 var input = $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
621 var input = $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
517 .val(path);
622 .val(path);
518 var dialog_body = $('<div/>').append(
623 var dialog_body = $('<div/>').append(
519 $("<p/>").addClass("rename-message")
624 $("<p/>").addClass("rename-message")
520 .text('Enter a new directory name:')
625 .text('Enter a new directory name:')
521 ).append(
626 ).append(
522 $("<br/>")
627 $("<br/>")
523 ).append(input);
628 ).append(input);
524 var d = dialog.modal({
629 var d = dialog.modal({
525 title : "Rename directory",
630 title : "Rename directory",
526 body : dialog_body,
631 body : dialog_body,
527 buttons : {
632 buttons : {
528 OK : {
633 OK : {
529 class: "btn-primary",
634 class: "btn-primary",
530 click: function() {
635 click: function() {
531 that.contents.rename(path, input.val()).then(function() {
636 that.contents.rename(path, input.val()).then(function() {
532 that.load_list();
637 that.load_list();
533 }).catch(function(e) {
638 }).catch(function(e) {
534 dialog.modal({
639 dialog.modal({
535 title: "Rename Failed",
640 title: "Rename Failed",
536 body: $('<div/>')
641 body: $('<div/>')
537 .text("An error occurred while renaming \"" + path + "\" to \"" + input.val() + "\".")
642 .text("An error occurred while renaming \"" + path + "\" to \"" + input.val() + "\".")
538 .append($('<div/>')
643 .append($('<div/>')
539 .addClass('alert alert-danger')
644 .addClass('alert alert-danger')
540 .text(e.message || e)),
645 .text(e.message || e)),
541 buttons: {
646 buttons: {
542 OK: {'class': 'btn-primary'}
647 OK: {'class': 'btn-primary'}
543 }
648 }
544 });
649 });
545 });
650 });
546 }
651 }
547 },
652 },
548 Cancel : {}
653 Cancel : {}
549 },
654 },
550 open : function () {
655 open : function () {
551 // Upon ENTER, click the OK button.
656 // Upon ENTER, click the OK button.
552 input.keydown(function (event) {
657 input.keydown(function (event) {
553 if (event.which === keyboard.keycodes.enter) {
658 if (event.which === keyboard.keycodes.enter) {
554 d.find('.btn-primary').first().click();
659 d.find('.btn-primary').first().click();
555 return false;
660 return false;
556 }
661 }
557 });
662 });
558 input.focus().select();
663 input.focus().select();
559 }
664 }
560 });
665 });
561 };
666 };
562
667
563 NotebookList.prototype.delete_selected = function() {
668 NotebookList.prototype.delete_selected = function() {
564 var message;
669 var message;
565 if (this.selected.length == 1) {
670 if (this.selected.length == 1) {
566 message = 'Are you sure you want to permanently delete: ' + this.selected[0].name + '?';
671 message = 'Are you sure you want to permanently delete: ' + this.selected[0].name + '?';
567 } else {
672 } else {
568 message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files/folders selected?';
673 message = 'Are you sure you want to permanently delete the ' + this.selected.length + ' files/folders selected?';
569 }
674 }
570 var that = this;
675 var that = this;
571 dialog.modal({
676 dialog.modal({
572 title : "Delete",
677 title : "Delete",
573 body : message,
678 body : message,
574 buttons : {
679 buttons : {
575 Delete : {
680 Delete : {
576 class: "btn-danger",
681 class: "btn-danger",
577 click: function() {
682 click: function() {
578 // Shutdown any/all selected notebooks before deleting
683 // Shutdown any/all selected notebooks before deleting
579 // the files.
684 // the files.
580 that.shutdown_selected();
685 that.shutdown_selected();
581
686
582 // Delete selected.
687 // Delete selected.
583 that.selected.forEach(function(item) {
688 that.selected.forEach(function(item) {
584 that.contents.delete(item.path).then(function() {
689 that.contents.delete(item.path).then(function() {
585 that.notebook_deleted(item.path);
690 that.notebook_deleted(item.path);
586 }).catch(function(e) {
691 }).catch(function(e) {
587 dialog.modal({
692 dialog.modal({
588 title: "Delete Failed",
693 title: "Delete Failed",
589 body: $('<div/>')
694 body: $('<div/>')
590 .text("An error occurred while deleting \"" + item.path + "\".")
695 .text("An error occurred while deleting \"" + item.path + "\".")
591 .append($('<div/>')
696 .append($('<div/>')
592 .addClass('alert alert-danger')
697 .addClass('alert alert-danger')
593 .text(e.message || e)),
698 .text(e.message || e)),
594 buttons: {
699 buttons: {
595 OK: {'class': 'btn-primary'}
700 OK: {'class': 'btn-primary'}
596 }
701 }
597 });
702 });
598 });
703 });
599 });
704 });
600 }
705 }
601 },
706 },
602 Cancel : {}
707 Cancel : {}
603 }
708 }
604 });
709 });
605 };
710 };
606
711
607 NotebookList.prototype.duplicate_selected = function() {
712 NotebookList.prototype.duplicate_selected = function() {
608 var message;
713 var message;
609 if (this.selected.length == 1) {
714 if (this.selected.length == 1) {
610 message = 'Are you sure you want to duplicate: ' + this.selected[0].name + '?';
715 message = 'Are you sure you want to duplicate: ' + this.selected[0].name + '?';
611 } else {
716 } else {
612 message = 'Are you sure you want to duplicate the ' + this.selected.length + ' files selected?';
717 message = 'Are you sure you want to duplicate the ' + this.selected.length + ' files selected?';
613 }
718 }
614 var that = this;
719 var that = this;
615 dialog.modal({
720 dialog.modal({
616 title : "Delete",
721 title : "Delete",
617 body : message,
722 body : message,
618 buttons : {
723 buttons : {
619 Duplicate : {
724 Duplicate : {
620 class: "btn-primary",
725 class: "btn-primary",
621 click: function() {
726 click: function() {
622 that.selected.forEach(function(item) {
727 that.selected.forEach(function(item) {
623 that.contents.copy(item.path, that.notebook_path).then(function () {
728 that.contents.copy(item.path, that.notebook_path).then(function () {
624 that.load_list();
729 that.load_list();
625 }).catch(function(e) {
730 }).catch(function(e) {
626 dialog.modal({
731 dialog.modal({
627 title: "Delete Failed",
732 title: "Delete Failed",
628 body: $('<div/>')
733 body: $('<div/>')
629 .text("An error occurred while deleting \"" + item.path + "\".")
734 .text("An error occurred while deleting \"" + item.path + "\".")
630 .append($('<div/>')
735 .append($('<div/>')
631 .addClass('alert alert-danger')
736 .addClass('alert alert-danger')
632 .text(e.message || e)),
737 .text(e.message || e)),
633 buttons: {
738 buttons: {
634 OK: {'class': 'btn-primary'}
739 OK: {'class': 'btn-primary'}
635 }
740 }
636 });
741 });
637 });
742 });
638 });
743 });
639 }
744 }
640 },
745 },
641 Cancel : {}
746 Cancel : {}
642 }
747 }
643 });
748 });
644 };
749 };
645
750
646 NotebookList.prototype.notebook_deleted = function(path) {
751 NotebookList.prototype.notebook_deleted = function(path) {
647 /**
752 /**
648 * Remove the deleted notebook.
753 * Remove the deleted notebook.
649 */
754 */
650 var that = this;
755 var that = this;
651 $( ":data(path)" ).each(function() {
756 $( ":data(path)" ).each(function() {
652 var element = $(this);
757 var element = $(this);
653 if (element.data("path") === path) {
758 if (element.data("path") === path) {
654 element.remove();
759 element.remove();
655 events.trigger('notebook_deleted.NotebookList');
760 events.trigger('notebook_deleted.NotebookList');
656 that._selection_changed();
761 that._selection_changed();
657 }
762 }
658 });
763 });
659 };
764 };
660
765
661
766
662 NotebookList.prototype.add_upload_button = function (item) {
767 NotebookList.prototype.add_upload_button = function (item) {
663 var that = this;
768 var that = this;
664 var upload_button = $('<button/>').text("Upload")
769 var upload_button = $('<button/>').text("Upload")
665 .addClass('btn btn-primary btn-xs upload_button')
770 .addClass('btn btn-primary btn-xs upload_button')
666 .click(function (e) {
771 .click(function (e) {
667 var filename = item.find('.item_name > input').val();
772 var filename = item.find('.item_name > input').val();
668 var path = utils.url_path_join(that.notebook_path, filename);
773 var path = utils.url_path_join(that.notebook_path, filename);
669 var filedata = item.data('filedata');
774 var filedata = item.data('filedata');
670 var format = 'text';
775 var format = 'text';
671 if (filename.length === 0 || filename[0] === '.') {
776 if (filename.length === 0 || filename[0] === '.') {
672 dialog.modal({
777 dialog.modal({
673 title : 'Invalid file name',
778 title : 'Invalid file name',
674 body : "File names must be at least one character and not start with a dot",
779 body : "File names must be at least one character and not start with a dot",
675 buttons : {'OK' : { 'class' : 'btn-primary' }}
780 buttons : {'OK' : { 'class' : 'btn-primary' }}
676 });
781 });
677 return false;
782 return false;
678 }
783 }
679 if (filedata instanceof ArrayBuffer) {
784 if (filedata instanceof ArrayBuffer) {
680 // base64-encode binary file data
785 // base64-encode binary file data
681 var bytes = '';
786 var bytes = '';
682 var buf = new Uint8Array(filedata);
787 var buf = new Uint8Array(filedata);
683 var nbytes = buf.byteLength;
788 var nbytes = buf.byteLength;
684 for (var i=0; i<nbytes; i++) {
789 for (var i=0; i<nbytes; i++) {
685 bytes += String.fromCharCode(buf[i]);
790 bytes += String.fromCharCode(buf[i]);
686 }
791 }
687 filedata = btoa(bytes);
792 filedata = btoa(bytes);
688 format = 'base64';
793 format = 'base64';
689 }
794 }
690 var model = {};
795 var model = {};
691
796
692 var name_and_ext = utils.splitext(filename);
797 var name_and_ext = utils.splitext(filename);
693 var file_ext = name_and_ext[1];
798 var file_ext = name_and_ext[1];
694 var content_type;
799 var content_type;
695 if (file_ext === '.ipynb') {
800 if (file_ext === '.ipynb') {
696 model.type = 'notebook';
801 model.type = 'notebook';
697 model.format = 'json';
802 model.format = 'json';
698 try {
803 try {
699 model.content = JSON.parse(filedata);
804 model.content = JSON.parse(filedata);
700 } catch (e) {
805 } catch (e) {
701 dialog.modal({
806 dialog.modal({
702 title : 'Cannot upload invalid Notebook',
807 title : 'Cannot upload invalid Notebook',
703 body : "The error was: " + e,
808 body : "The error was: " + e,
704 buttons : {'OK' : {
809 buttons : {'OK' : {
705 'class' : 'btn-primary',
810 'class' : 'btn-primary',
706 click: function () {
811 click: function () {
707 item.remove();
812 item.remove();
708 }
813 }
709 }}
814 }}
710 });
815 });
711 return false;
816 return false;
712 }
817 }
713 content_type = 'application/json';
818 content_type = 'application/json';
714 } else {
819 } else {
715 model.type = 'file';
820 model.type = 'file';
716 model.format = format;
821 model.format = format;
717 model.content = filedata;
822 model.content = filedata;
718 content_type = 'application/octet-stream';
823 content_type = 'application/octet-stream';
719 }
824 }
720 filedata = item.data('filedata');
825 filedata = item.data('filedata');
721
826
722 var on_success = function () {
827 var on_success = function () {
723 item.removeClass('new-file');
828 item.removeClass('new-file');
724 that.add_link(model, item);
829 that.add_link(model, item);
725 that.session_list.load_sessions();
830 that.session_list.load_sessions();
726 };
831 };
727
832
728 var exists = false;
833 var exists = false;
729 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
834 $.each(that.element.find('.list_item:not(.new-file)'), function(k,v){
730 if ($(v).data('name') === filename) { exists = true; return false; }
835 if ($(v).data('name') === filename) { exists = true; return false; }
731 });
836 });
732
837
733 if (exists) {
838 if (exists) {
734 dialog.modal({
839 dialog.modal({
735 title : "Replace file",
840 title : "Replace file",
736 body : 'There is already a file named ' + filename + ', do you want to replace it?',
841 body : 'There is already a file named ' + filename + ', do you want to replace it?',
737 buttons : {
842 buttons : {
738 Overwrite : {
843 Overwrite : {
739 class: "btn-danger",
844 class: "btn-danger",
740 click: function () {
845 click: function () {
741 that.contents.save(path, model).then(on_success);
846 that.contents.save(path, model).then(on_success);
742 }
847 }
743 },
848 },
744 Cancel : {
849 Cancel : {
745 click: function() { item.remove(); }
850 click: function() { item.remove(); }
746 }
851 }
747 }
852 }
748 });
853 });
749 } else {
854 } else {
750 that.contents.save(path, model).then(on_success);
855 that.contents.save(path, model).then(on_success);
751 }
856 }
752
857
753 return false;
858 return false;
754 });
859 });
755 var cancel_button = $('<button/>').text("Cancel")
860 var cancel_button = $('<button/>').text("Cancel")
756 .addClass("btn btn-default btn-xs")
861 .addClass("btn btn-default btn-xs")
757 .click(function (e) {
862 .click(function (e) {
758 item.remove();
863 item.remove();
759 return false;
864 return false;
760 });
865 });
761 item.find(".item_buttons").empty()
866 item.find(".item_buttons").empty()
762 .append(upload_button)
867 .append(upload_button)
763 .append(cancel_button);
868 .append(cancel_button);
764 };
869 };
765
870
766
871
767 // Backwards compatability.
872 // Backwards compatability.
768 IPython.NotebookList = NotebookList;
873 IPython.NotebookList = NotebookList;
769
874
770 return {'NotebookList': NotebookList};
875 return {'NotebookList': NotebookList};
771 });
876 });
@@ -1,270 +1,289 b''
1
1
2 /**
2 /**
3 * Primary styles
3 * Primary styles
4 *
4 *
5 * Author: IPython Development Team
5 * Author: IPython Development Team
6 */
6 */
7
7
8 @dashboard_tb_pad: 4px;
8 @dashboard_tb_pad: 4px;
9 @dashboard_lr_pad: 7px;
9 @dashboard_lr_pad: 7px;
10 // These are the total heights of the Bootstrap small and mini buttons. These values
10 // These are the total heights of the Bootstrap small and mini buttons. These values
11 // are not less variables so we have to track them statically.
11 // are not less variables so we have to track them statically.
12 @btn_small_height: 24px;
12 @btn_small_height: 24px;
13 @btn_mini_height: 22px;
13 @btn_mini_height: 22px;
14 @dark_dashboard_color: @breadcrumb-color;
14 @dark_dashboard_color: @breadcrumb-color;
15 @list_stripe_color: lighten(@page-backdrop-color,3%);
15 @list_stripe_color: lighten(@page-backdrop-color,3%);
16
16
17 // The left padding of the selector button's contents.
18 @dashboard-selectorbtn-lpad: 7px;
19
17 ul#tabs {
20 ul#tabs {
18 margin-bottom: @dashboard_tb_pad;
21 margin-bottom: @dashboard_tb_pad;
19 }
22 }
20
23
21 ul#tabs a {
24 ul#tabs a {
22 padding-top: @dashboard_tb_pad + 2px;
25 padding-top: @dashboard_tb_pad + 2px;
23 padding-bottom: @dashboard_tb_pad;
26 padding-bottom: @dashboard_tb_pad;
24 }
27 }
25
28
26 ul.breadcrumb {
29 ul.breadcrumb {
27 a:focus, a:hover {
30 a:focus, a:hover {
28 text-decoration: none;
31 text-decoration: none;
29 }
32 }
30 i.icon-home {
33 i.icon-home {
31 font-size: 16px;
34 font-size: 16px;
32 margin-right: 4px;
35 margin-right: 4px;
33 }
36 }
34
37
35 span {
38 span {
36 color: @dark_dashboard_color;
39 color: @dark_dashboard_color;
37 }
40 }
38 }
41 }
39
42
40 .list_toolbar {
43 .list_toolbar {
41 padding: @dashboard_tb_pad 0 @dashboard_tb_pad 0;
44 padding: @dashboard_tb_pad 0 @dashboard_tb_pad 0;
42 vertical-align: middle;
45 vertical-align: middle;
43
46
44 .tree-buttons {
47 .tree-buttons {
45 padding-top: 1px;
48 padding-top: 1px;
46 }
49 }
47 }
50 }
48
51
49 .dynamic-buttons {
52 .dynamic-buttons {
50 display: inline-block;
53 display: inline-block;
51 }
54 }
52
55
53 .list_toolbar [class*="span"] {
56 .list_toolbar [class*="span"] {
54 min-height: @btn_small_height;
57 min-height: @btn_small_height;
55 }
58 }
56
59
57 .list_header {
60 .list_header {
58 font-weight: bold;
61 font-weight: bold;
59 background-color: @page-backdrop-color
62 background-color: @page-backdrop-color
60 }
63 }
61
64
62 .list_container {
65 .list_container {
63 margin-top: @dashboard_tb_pad;
66 margin-top: @dashboard_tb_pad;
64 margin-bottom: 5*@dashboard_tb_pad;
67 margin-bottom: 5*@dashboard_tb_pad;
65 border: 1px solid @table-border-color;
68 border: 1px solid @table-border-color;
66 border-radius: @border-radius-base;
69 border-radius: @border-radius-base;
67 }
70 }
68
71
69 .list_container > div {
72 .list_container > div {
70 border-bottom: 1px solid @table-border-color;
73 border-bottom: 1px solid @table-border-color;
71 &:hover .list-item{
74 &:hover .list-item{
72 background-color: red;
75 background-color: red;
73 };
76 };
74 }
77 }
75
78
76 .list_container > div:last-child {
79 .list_container > div:last-child {
77 border: none;
80 border: none;
78 }
81 }
79
82
80 .list_item {
83 .list_item {
81 &:hover .list_item {
84 &:hover .list_item {
82 background-color: @table-border-color;
85 background-color: @table-border-color;
83 };
86 };
84 a {text-decoration: none;}
87 a {text-decoration: none;}
85 &:hover {
88 &:hover {
86 background-color: darken(white,2%);
89 background-color: darken(white,2%);
87 }
90 }
88 }
91 }
89
92
90 .action_col {
93 .action_col {
91 text-align: right;
94 text-align: right;
92 }
95 }
93
96
94 .list_header>div, .list_item>div {
97 .list_header>div, .list_item>div {
95 padding-top: @dashboard_tb_pad;
98 padding-top: @dashboard_tb_pad;
96 padding-bottom: @dashboard_tb_pad;
99 padding-bottom: @dashboard_tb_pad;
97 padding-left: @dashboard_lr_pad;
100 padding-left: @dashboard_lr_pad;
98 padding-right: @dashboard_lr_pad;
101 padding-right: @dashboard_lr_pad;
99 line-height: @btn_mini_height;
102 line-height: @btn_mini_height;
100
103
101 input {
104 input {
102 margin-right: @dashboard_lr_pad;
105 margin-right: @dashboard_lr_pad;
106 margin-left: @dashboard_lr_pad + @dashboard-selectorbtn-lpad;
107 }
108
109 .item_link {
110 margin-left: 14px;
103 }
111 }
104 }
112 }
105
113
106 .new-file input[type=checkbox] {
114 .new-file input[type=checkbox] {
107 visibility: hidden;
115 visibility: hidden;
108 }
116 }
109
117
110 .item_name {
118 .item_name {
111 line-height: @btn_mini_height;
119 line-height: @btn_mini_height;
112 height: @btn_small_height;
120 height: @btn_small_height;
113 }
121 }
114
122
115 .item_icon {
123 .item_icon {
116 font-size: 14px;
124 font-size: 14px;
117 color: @dark_dashboard_color;
125 color: @dark_dashboard_color;
118 margin-right: @dashboard_lr_pad;
126 margin-right: @dashboard_lr_pad;
119 }
127 }
120
128
121 .item_buttons {
129 .item_buttons {
122 padding-top: @dashboard_tb_pad;
130 padding-top: @dashboard_tb_pad;
123 line-height: 1em;
131 line-height: 1em;
124 .btn-toolbar();
132 .btn-toolbar();
125 .btn {
133 .btn {
126 min-width: 13ex;
134 min-width: 13ex;
127 }
135 }
128 .running-indicator {
136 .running-indicator {
129 color: @brand-success;
137 color: @brand-success;
130 }
138 }
131 }
139 }
132
140
133 .toolbar_info {
141 .toolbar_info {
134 height: @btn_small_height;
142 height: @btn_small_height;
135 line-height: @btn_small_height;
143 line-height: @btn_small_height;
136 }
144 }
137
145
138 input.nbname_input, input.engine_num_input {
146 input.nbname_input, input.engine_num_input {
139 // These settings give these inputs a height that matches @btn_mini_height = 22
147 // These settings give these inputs a height that matches @btn_mini_height = 22
140 padding-top: 3px;
148 padding-top: 3px;
141 padding-bottom: 3px;
149 padding-bottom: 3px;
142 height: @btn_mini_height;
150 height: @btn_mini_height;
143 line-height: 14px;
151 line-height: 14px;
144 margin: 0px;
152 margin: 0px;
145 }
153 }
146
154
147 input.engine_num_input {
155 input.engine_num_input {
148 width: 60px;
156 width: 60px;
149 }
157 }
150
158
151 .highlight_text {
159 .highlight_text {
152 color: blue;
160 color: blue;
153 }
161 }
154
162
155 #project_name > .breadcrumb {
163 #project_name {
156 padding: 0px;
164 display: inline-block;
157 margin-bottom: 0px;
165
158 background-color: transparent;
166 > .breadcrumb {
159 font-weight: bold;
167 padding: 0px;
160
168 margin-bottom: 0px;
169 background-color: transparent;
170 font-weight: bold;
171 }
172 }
173
174 #tree-selector {
175 display: inline-block;
176
177 input[type=checkbox] {
178 margin-left: @dashboard_lr_pad;
179 }
161 }
180 }
162
181
163 .tab-content .row {
182 .tab-content .row {
164 margin-left: 0px;
183 margin-left: 0px;
165 margin-right: 0px;
184 margin-right: 0px;
166 }
185 }
167
186
168 .folder_icon:before {
187 .folder_icon:before {
169 .icon(@fa-var-folder-o)
188 .icon(@fa-var-folder-o)
170 }
189 }
171
190
172 .notebook_icon:before {
191 .notebook_icon:before {
173 .icon(@fa-var-book)
192 .icon(@fa-var-book)
174 }
193 }
175
194
176 .running_notebook_icon:before {
195 .running_notebook_icon:before {
177 .icon(@fa-var-book);
196 .icon(@fa-var-book);
178 color: @brand-success
197 color: @brand-success
179 }
198 }
180
199
181
200
182 .file_icon:before {
201 .file_icon:before {
183 .icon(@fa-var-file-o)
202 .icon(@fa-var-file-o)
184 }
203 }
185
204
186 #notebook_toolbar .pull-right {
205 #notebook_toolbar .pull-right {
187 padding-top: 0px;
206 padding-top: 0px;
188 margin-right: -1px;
207 margin-right: -1px;
189 }
208 }
190
209
191 ul#new-menu {
210 ul#new-menu {
192 // align right instead of left
211 // align right instead of left
193 left: auto;
212 left: auto;
194 right: 0;
213 right: 0;
195 }
214 }
196
215
197 .kernel-menu-icon {
216 .kernel-menu-icon {
198 padding-right: 12px;
217 padding-right: 12px;
199 width: 24px;
218 width: 24px;
200 content: @fa-var-square-o;
219 content: @fa-var-square-o;
201 }
220 }
202
221
203 .kernel-menu-icon:before {
222 .kernel-menu-icon:before {
204 content: @fa-var-square-o;
223 content: @fa-var-square-o;
205 }
224 }
206
225
207 .kernel-menu-icon-current:before {
226 .kernel-menu-icon-current:before {
208 content: @fa-var-check;
227 content: @fa-var-check;
209 }
228 }
210
229
211 #tab_content {
230 #tab_content {
212 padding-top: @page-header-padding;
231 padding-top: @page-header-padding;
213 }
232 }
214
233
215 #running {
234 #running {
216 .panel-group{
235 .panel-group{
217 .panel {
236 .panel {
218 margin-top: 3px;
237 margin-top: 3px;
219 margin-bottom: 1em;
238 margin-bottom: 1em;
220
239
221 .panel-heading {
240 .panel-heading {
222 background-color: @page-backdrop-color;
241 background-color: @page-backdrop-color;
223 padding-top: @dashboard_tb_pad;
242 padding-top: @dashboard_tb_pad;
224 padding-bottom: @dashboard_tb_pad;
243 padding-bottom: @dashboard_tb_pad;
225 padding-left: @dashboard_lr_pad;
244 padding-left: @dashboard_lr_pad;
226 padding-right: @dashboard_lr_pad;
245 padding-right: @dashboard_lr_pad;
227 line-height: @btn_mini_height;
246 line-height: @btn_mini_height;
228
247
229 a:focus, a:hover {
248 a:focus, a:hover {
230 text-decoration: none;
249 text-decoration: none;
231 }
250 }
232 }
251 }
233
252
234 .panel-body {
253 .panel-body {
235 padding: 0px;
254 padding: 0px;
236
255
237 .list_container {
256 .list_container {
238 margin-top: 0px;
257 margin-top: 0px;
239 margin-bottom: 0px;
258 margin-bottom: 0px;
240 border: 0px;
259 border: 0px;
241 border-radius: 0px;
260 border-radius: 0px;
242
261
243 .list_item {
262 .list_item {
244 border-bottom: 1px solid @table-border-color;
263 border-bottom: 1px solid @table-border-color;
245
264
246 &:last-child {
265 &:last-child {
247 border-bottom: 0px;
266 border-bottom: 0px;
248 }
267 }
249 }
268 }
250 }
269 }
251 }
270 }
252 }
271 }
253 }
272 }
254 }
273 }
255
274
256 .delete-button {
275 .delete-button {
257 display: none;
276 display: none;
258 }
277 }
259
278
260 .duplicate-button {
279 .duplicate-button {
261 display: none;
280 display: none;
262 }
281 }
263
282
264 .rename-button {
283 .rename-button {
265 display: none;
284 display: none;
266 }
285 }
267
286
268 .shutdown-button {
287 .shutdown-button {
269 display: none;
288 display: none;
270 }
289 }
@@ -1,170 +1,188 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block title %}{{page_title}}{% endblock %}
3 {% block title %}{{page_title}}{% endblock %}
4
4
5
5
6 {% block params %}
6 {% block params %}
7
7
8 data-base-url="{{base_url}}"
8 data-base-url="{{base_url}}"
9 data-notebook-path="{{notebook_path}}"
9 data-notebook-path="{{notebook_path}}"
10 data-terminals-available="{{terminals_available}}"
10 data-terminals-available="{{terminals_available}}"
11
11
12 {% endblock %}
12 {% endblock %}
13
13
14
14
15 {% block site %}
15 {% block site %}
16
16
17 <div id="ipython-main-app" class="container">
17 <div id="ipython-main-app" class="container">
18 <div id="tab_content" class="tabbable">
18 <div id="tab_content" class="tabbable">
19 <ul id="tabs" class="nav nav-tabs">
19 <ul id="tabs" class="nav nav-tabs">
20 <li class="active"><a href="#notebooks" data-toggle="tab">Files</a></li>
20 <li class="active"><a href="#notebooks" data-toggle="tab">Files</a></li>
21 <li><a href="#running" data-toggle="tab">Running</a></li>
21 <li><a href="#running" data-toggle="tab">Running</a></li>
22 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
22 <li><a href="#clusters" data-toggle="tab">Clusters</a></li>
23 </ul>
23 </ul>
24 <div class="tab-content">
24 <div class="tab-content">
25 <div id="notebooks" class="tab-pane active">
25 <div id="notebooks" class="tab-pane active">
26 <div id="notebook_toolbar" class="row">
26 <div id="notebook_toolbar" class="row">
27 <div class="col-sm-8 no-padding">
27 <div class="col-sm-8 no-padding">
28 <form id='alternate_upload' class='alternate_upload'>
28 <form id='alternate_upload' class='alternate_upload'>
29 <span id="notebook_list_info">
29 <span id="notebook_list_info">
30 To import a notebook, drag the file onto the listing below or
30 To import a notebook, drag the file onto the listing below or
31 <span class="input-overlay">
31 <span class="input-overlay">
32 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
32 <input type="file" name="datafile" class="fileinput" multiple='multiple'>
33 click here.
33 click here.
34 </span>
34 </span>
35 </span>
35 </span>
36 </form>
36 </form>
37 </div>
37 </div>
38 <div class="col-sm-4 no-padding tree-buttons">
38 <div class="col-sm-4 no-padding tree-buttons">
39 <div class="pull-right">
39 <div class="pull-right">
40 <div class="dynamic-buttons">
40 <div class="dynamic-buttons">
41 <button title="Duplicate selected" class="duplicate-button btn btn-default btn-xs">Duplicate</button>
41 <button title="Duplicate selected" class="duplicate-button btn btn-default btn-xs">Duplicate</button>
42 <button title="Rename selected" class="rename-button btn btn-default btn-xs">Rename</button>
42 <button title="Rename selected" class="rename-button btn btn-default btn-xs">Rename</button>
43 <button title="Shutdown selected notebook(s)" class="shutdown-button btn btn-default btn-xs btn-warning">Shutdown</button>
43 <button title="Shutdown selected notebook(s)" class="shutdown-button btn btn-default btn-xs btn-warning">Shutdown</button>
44 <button title="Deleted selected" class="delete-button btn btn-default btn-xs btn-danger"><i class="fa fa-trash"></i></button>
44 <button title="Deleted selected" class="delete-button btn btn-default btn-xs btn-danger"><i class="fa fa-trash"></i></button>
45 </div>
45 </div>
46 <div id="new-buttons" class="btn-group">
46 <div id="new-buttons" class="btn-group">
47 <button class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown">
47 <button class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown">
48 <span>New</span>
48 <span>New</span>
49 <span class="caret"></span>
49 <span class="caret"></span>
50 </button>
50 </button>
51 <ul id="new-menu" class="dropdown-menu">
51 <ul id="new-menu" class="dropdown-menu">
52 <li role="presentation" id="new-file">
52 <li role="presentation" id="new-file">
53 <a role="menuitem" tabindex="-1" href="#">Text File</a>
53 <a role="menuitem" tabindex="-1" href="#">Text File</a>
54 </li>
54 </li>
55 <li role="presentation" id="new-folder">
55 <li role="presentation" id="new-folder">
56 <a role="menuitem" tabindex="-1" href="#">Folder</a>
56 <a role="menuitem" tabindex="-1" href="#">Folder</a>
57 </li>
57 </li>
58 {% if terminals_available %}
58 {% if terminals_available %}
59 <li role="presentation" id="new-terminal">
59 <li role="presentation" id="new-terminal">
60 <a role="menuitem" tabindex="-1" href="#">Terminal</a>
60 <a role="menuitem" tabindex="-1" href="#">Terminal</a>
61 </li>
61 </li>
62 {% else %}
62 {% else %}
63 <li role="presentation" id="new-terminal-disabled" class="disabled">
63 <li role="presentation" id="new-terminal-disabled" class="disabled">
64 <a role="menuitem" tabindex="-1" href="#">Terminals Unavailable</a>
64 <a role="menuitem" tabindex="-1" href="#">Terminals Unavailable</a>
65 </li>
65 </li>
66 {% endif %}
66 {% endif %}
67 <li role="presentation" class="divider"></li>
67 <li role="presentation" class="divider"></li>
68 <li role="presentation" class="dropdown-header" id="notebook-kernels">Notebooks</li>
68 <li role="presentation" class="dropdown-header" id="notebook-kernels">Notebooks</li>
69 </ul>
69 </ul>
70 </div>
70 </div>
71 <div class="btn-group">
71 <div class="btn-group">
72 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
72 <button id="refresh_notebook_list" title="Refresh notebook list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
73 </div>
73 </div>
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 </div>
77 <div id="notebook_list">
77 <div id="notebook_list">
78 <div id="notebook_list_header" class="row list_header">
78 <div id="notebook_list_header" class="row list_header">
79
80 <div class="dropdown" id='tree-selector'>
81 <button class="btn btn-default btn-xs dropdown-toggle" type="button" id="tree-selector-btn" data-toggle="dropdown" aria-expanded="true">
82 <input type='checkbox'></input>
83 <i class='semicheck fa fa-minus'></i>
84 <span class="caret"></span>
85 </button>
86 <ul class="dropdown-menu" role="menu" aria-labelledby="tree-selector-btn">
87 <li role="presentation" class="select-all"><a role="menuitem" tabindex="-1" href="#">Select all</a></li>
88 <li role="presentation" class="select-notebooks"><a role="menuitem" tabindex="-1" href="#">Select notebooks</a></li>
89 <li role="presentation" class="select-running-notebooks"><a role="menuitem" tabindex="-1" href="#">Select running notebooks</a></li>
90 <li role="presentation" class="select-files"><a role="menuitem" tabindex="-1" href="#">Select files</a></li>
91 <li role="presentation" class="select-directories"><a role="menuitem" tabindex="-1" href="#">Select directories</a></li>
92 <li role="presentation" class="select-inverse"><a role="menuitem" tabindex="-1" href="#">Select inverse</a></li>
93 <li role="presentation" class="unselect-all"><a role="menuitem" tabindex="-1" href="#">Unselect all</a></li>
94 </ul>
95 </div>
96
79 <div id="project_name">
97 <div id="project_name">
80 <ul class="breadcrumb">
98 <ul class="breadcrumb">
81 <li><a href="{{breadcrumbs[0][0]}}"><i class="fa fa-home"></i></a></li>
99 <li><a href="{{breadcrumbs[0][0]}}"><i class="fa fa-home"></i></a></li>
82 {% for crumb in breadcrumbs[1:] %}
100 {% for crumb in breadcrumbs[1:] %}
83 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a></li>
101 <li><a href="{{crumb[0]}}">{{crumb[1]}}</a></li>
84 {% endfor %}
102 {% endfor %}
85 </ul>
103 </ul>
86 </div>
104 </div>
87 </div>
105 </div>
88 </div>
106 </div>
89 </div>
107 </div>
90 <div id="running" class="tab-pane">
108 <div id="running" class="tab-pane">
91 <div id="running_toolbar" class="row">
109 <div id="running_toolbar" class="row">
92 <div class="col-sm-8 no-padding">
110 <div class="col-sm-8 no-padding">
93 <span id="running_list_info">Currently running Jupyter processes</span>
111 <span id="running_list_info">Currently running Jupyter processes</span>
94 </div>
112 </div>
95 <div class="col-sm-4 no-padding tree-buttons">
113 <div class="col-sm-4 no-padding tree-buttons">
96 <span id="running_buttons" class="pull-right">
114 <span id="running_buttons" class="pull-right">
97 <button id="refresh_running_list" title="Refresh running list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
115 <button id="refresh_running_list" title="Refresh running list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
98 </span>
116 </span>
99 </div>
117 </div>
100 </div>
118 </div>
101 <div class="panel-group" id="accordion" >
119 <div class="panel-group" id="accordion" >
102 <div class="panel panel-default">
120 <div class="panel panel-default">
103 <div class="panel-heading">
121 <div class="panel-heading">
104 <a data-toggle="collapse" data-target="#collapseOne" href="#">
122 <a data-toggle="collapse" data-target="#collapseOne" href="#">
105 Terminals
123 Terminals
106 </a>
124 </a>
107 </div>
125 </div>
108 <div id="collapseOne" class=" collapse in">
126 <div id="collapseOne" class=" collapse in">
109 <div class="panel-body">
127 <div class="panel-body">
110 <div id="terminal_list">
128 <div id="terminal_list">
111 <div id="terminal_list_header" class="row list_header">
129 <div id="terminal_list_header" class="row list_header">
112 {% if terminals_available %}
130 {% if terminals_available %}
113 <div> There are no terminals running. </div>
131 <div> There are no terminals running. </div>
114 {% else %}
132 {% else %}
115 <div> Terminals are unavailable. </div>
133 <div> Terminals are unavailable. </div>
116 {% endif %}
134 {% endif %}
117 </div>
135 </div>
118 </div>
136 </div>
119 </div>
137 </div>
120 </div>
138 </div>
121 </div>
139 </div>
122 <div class="panel panel-default">
140 <div class="panel panel-default">
123 <div class="panel-heading">
141 <div class="panel-heading">
124 <a data-toggle="collapse" data-target="#collapseTwo" href="#">
142 <a data-toggle="collapse" data-target="#collapseTwo" href="#">
125 Notebooks
143 Notebooks
126 </a>
144 </a>
127 </div>
145 </div>
128 <div id="collapseTwo" class=" collapse in">
146 <div id="collapseTwo" class=" collapse in">
129 <div class="panel-body">
147 <div class="panel-body">
130 <div id="running_list">
148 <div id="running_list">
131 <div id="running_list_header" class="row list_header">
149 <div id="running_list_header" class="row list_header">
132 <div> There are no notebooks running. </div>
150 <div> There are no notebooks running. </div>
133 </div>
151 </div>
134 </div>
152 </div>
135 </div>
153 </div>
136 </div>
154 </div>
137 </div>
155 </div>
138 </div>
156 </div>
139 </div>
157 </div>
140 <div id="clusters" class="tab-pane">
158 <div id="clusters" class="tab-pane">
141 <div id="cluster_toolbar" class="row">
159 <div id="cluster_toolbar" class="row">
142 <div class="col-xs-8 no-padding">
160 <div class="col-xs-8 no-padding">
143 <span id="cluster_list_info">IPython parallel computing clusters</span>
161 <span id="cluster_list_info">IPython parallel computing clusters</span>
144 </div>
162 </div>
145 <div class="col-xs-4 no-padding tree-buttons">
163 <div class="col-xs-4 no-padding tree-buttons">
146 <span id="cluster_buttons" class="pull-right">
164 <span id="cluster_buttons" class="pull-right">
147 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
165 <button id="refresh_cluster_list" title="Refresh cluster list" class="btn btn-default btn-xs"><i class="fa fa-refresh"></i></button>
148 </span>
166 </span>
149 </div>
167 </div>
150 </div>
168 </div>
151 <div id="cluster_list">
169 <div id="cluster_list">
152 <div id="cluster_list_header" class="row list_header">
170 <div id="cluster_list_header" class="row list_header">
153 <div class="profile_col col-xs-4">profile</div>
171 <div class="profile_col col-xs-4">profile</div>
154 <div class="status_col col-xs-3">status</div>
172 <div class="status_col col-xs-3">status</div>
155 <div class="engines_col col-xs-3" title="Enter the number of engines to start or empty for default"># of engines</div>
173 <div class="engines_col col-xs-3" title="Enter the number of engines to start or empty for default"># of engines</div>
156 <div class="action_col col-xs-2">action</div>
174 <div class="action_col col-xs-2">action</div>
157 </div>
175 </div>
158 </div>
176 </div>
159 </div>
177 </div>
160 </div><!-- class:tab-content -->
178 </div><!-- class:tab-content -->
161 </div><!-- id:tab_content -->
179 </div><!-- id:tab_content -->
162 </div><!-- ipython-main-app -->
180 </div><!-- ipython-main-app -->
163
181
164 {% endblock %}
182 {% endblock %}
165
183
166 {% block script %}
184 {% block script %}
167 {{super()}}
185 {{super()}}
168
186
169 <script src="{{ static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
187 <script src="{{ static_url("tree/js/main.js") }}" type="text/javascript" charset="utf-8"></script>
170 {% endblock %}
188 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now