##// END OF EJS Templates
Better user experience when kernel isn't found
Jessica B. Hamrick -
Show More
@@ -1,118 +1,122
1 1 """Tornado handlers for the sessions web service."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import json
7 7
8 8 from tornado import web
9 9
10 10 from ...base.handlers import IPythonHandler, json_errors
11 11 from IPython.utils.jsonutil import date_default
12 12 from IPython.html.utils import url_path_join, url_escape
13 13 from IPython.kernel.kernelspec import NoSuchKernel
14 14
15 15
16 16 class SessionRootHandler(IPythonHandler):
17 17
18 18 @web.authenticated
19 19 @json_errors
20 20 def get(self):
21 21 # Return a list of running sessions
22 22 sm = self.session_manager
23 23 sessions = sm.list_sessions()
24 24 self.finish(json.dumps(sessions, default=date_default))
25 25
26 26 @web.authenticated
27 27 @json_errors
28 28 def post(self):
29 29 # Creates a new session
30 30 #(unless a session already exists for the named nb)
31 31 sm = self.session_manager
32 32 cm = self.contents_manager
33 33 km = self.kernel_manager
34 34
35 35 model = self.get_json_body()
36 36 if model is None:
37 37 raise web.HTTPError(400, "No JSON data provided")
38 38 try:
39 39 name = model['notebook']['name']
40 40 except KeyError:
41 41 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
42 42 try:
43 43 path = model['notebook']['path']
44 44 except KeyError:
45 45 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
46 46 try:
47 47 kernel_name = model['kernel']['name']
48 48 except KeyError:
49 49 self.log.debug("No kernel name specified, using default kernel")
50 50 kernel_name = None
51 51
52 52 # Check to see if session exists
53 53 if sm.session_exists(name=name, path=path):
54 54 model = sm.get_session(name=name, path=path)
55 55 else:
56 56 try:
57 57 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
58 58 except NoSuchKernel:
59 raise web.HTTPError(400, "No such kernel: %s" % kernel_name)
59 msg = ("The '%s' kernel is not available. Please pick another "
60 "suitable kernel instead, or install that kernel." % kernel_name)
61 status_msg = 'Kernel not found'
62 msg = dict(full=msg, short=status_msg)
63 raise web.HTTPError(400, json.dumps(msg))
60 64
61 65 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
62 66 self.set_header('Location', url_escape(location))
63 67 self.set_status(201)
64 68 self.finish(json.dumps(model, default=date_default))
65 69
66 70 class SessionHandler(IPythonHandler):
67 71
68 72 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
69 73
70 74 @web.authenticated
71 75 @json_errors
72 76 def get(self, session_id):
73 77 # Returns the JSON model for a single session
74 78 sm = self.session_manager
75 79 model = sm.get_session(session_id=session_id)
76 80 self.finish(json.dumps(model, default=date_default))
77 81
78 82 @web.authenticated
79 83 @json_errors
80 84 def patch(self, session_id):
81 85 # Currently, this handler is strictly for renaming notebooks
82 86 sm = self.session_manager
83 87 model = self.get_json_body()
84 88 if model is None:
85 89 raise web.HTTPError(400, "No JSON data provided")
86 90 changes = {}
87 91 if 'notebook' in model:
88 92 notebook = model['notebook']
89 93 if 'name' in notebook:
90 94 changes['name'] = notebook['name']
91 95 if 'path' in notebook:
92 96 changes['path'] = notebook['path']
93 97
94 98 sm.update_session(session_id, **changes)
95 99 model = sm.get_session(session_id=session_id)
96 100 self.finish(json.dumps(model, default=date_default))
97 101
98 102 @web.authenticated
99 103 @json_errors
100 104 def delete(self, session_id):
101 105 # Deletes the session with given session_id
102 106 sm = self.session_manager
103 107 sm.delete_session(session_id)
104 108 self.set_status(204)
105 109 self.finish()
106 110
107 111
108 112 #-----------------------------------------------------------------------------
109 113 # URL to handler mappings
110 114 #-----------------------------------------------------------------------------
111 115
112 116 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
113 117
114 118 default_handlers = [
115 119 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
116 120 (r"/api/sessions", SessionRootHandler)
117 121 ]
118 122
@@ -1,319 +1,309
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'base/js/dialog',
9 9 'notebook/js/notificationwidget',
10 10 'moment'
11 11 ], function(IPython, $, utils, dialog, notificationwidget, moment) {
12 12 "use strict";
13 13
14 14 // store reference to the NotificationWidget class
15 15 var NotificationWidget = notificationwidget.NotificationWidget;
16 16
17 17 /**
18 18 * Construct the NotificationArea object. Options are:
19 19 * events: $(Events) instance
20 20 * save_widget: SaveWidget instance
21 21 * notebook: Notebook instance
22 22 * keyboard_manager: KeyboardManager instance
23 23 *
24 24 * @constructor
25 25 * @param {string} selector - a jQuery selector string for the
26 26 * notification area element
27 27 * @param {Object} [options] - a dictionary of keyword arguments.
28 28 */
29 29 var NotificationArea = function (selector, options) {
30 30 this.selector = selector;
31 31 this.events = options.events;
32 32 this.save_widget = options.save_widget;
33 33 this.notebook = options.notebook;
34 34 this.keyboard_manager = options.keyboard_manager;
35 35 if (this.selector !== undefined) {
36 36 this.element = $(selector);
37 37 }
38 38 this.widget_dict = {};
39 39 };
40 40
41 41 /**
42 42 * Get a widget by name, creating it if it doesn't exist.
43 43 *
44 44 * @method widget
45 45 * @param {string} name - the widget name
46 46 */
47 47 NotificationArea.prototype.widget = function (name) {
48 48 if (this.widget_dict[name] === undefined) {
49 49 return this.new_notification_widget(name);
50 50 }
51 51 return this.get_widget(name);
52 52 };
53 53
54 54 /**
55 55 * Get a widget by name, throwing an error if it doesn't exist.
56 56 *
57 57 * @method get_widget
58 58 * @param {string} name - the widget name
59 59 */
60 60 NotificationArea.prototype.get_widget = function (name) {
61 61 if(this.widget_dict[name] === undefined) {
62 62 throw('no widgets with this name');
63 63 }
64 64 return this.widget_dict[name];
65 65 };
66 66
67 67 /**
68 68 * Create a new notification widget with the given name. The
69 69 * widget must not already exist.
70 70 *
71 71 * @method new_notification_widget
72 72 * @param {string} name - the widget name
73 73 */
74 74 NotificationArea.prototype.new_notification_widget = function (name) {
75 75 if (this.widget_dict[name] !== undefined) {
76 76 throw('widget with that name already exists!');
77 77 }
78 78
79 79 // create the element for the notification widget and add it
80 80 // to the notification aread element
81 81 var div = $('<div/>').attr('id', 'notification_' + name);
82 82 $(this.selector).append(div);
83 83
84 84 // create the widget object and return it
85 85 this.widget_dict[name] = new NotificationWidget('#notification_' + name);
86 86 return this.widget_dict[name];
87 87 };
88 88
89 89 /**
90 90 * Initialize the default set of notification widgets.
91 91 *
92 92 * @method init_notification_widgets
93 93 */
94 94 NotificationArea.prototype.init_notification_widgets = function () {
95 95 this.init_kernel_notification_widget();
96 96 this.init_notebook_notification_widget();
97 97 };
98 98
99 99 /**
100 100 * Initialize the notification widget for kernel status messages.
101 101 *
102 102 * @method init_kernel_notification_widget
103 103 */
104 104 NotificationArea.prototype.init_kernel_notification_widget = function () {
105 105 var that = this;
106 106 var knw = this.new_notification_widget('kernel');
107 107 var $kernel_ind_icon = $("#kernel_indicator_icon");
108 108 var $modal_ind_icon = $("#modal_indicator_icon");
109 109
110 110 // Command/Edit mode
111 111 this.events.on('edit_mode.Notebook',function () {
112 112 that.save_widget.update_document_title();
113 113 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
114 114 });
115 115
116 116 this.events.on('command_mode.Notebook',function () {
117 117 that.save_widget.update_document_title();
118 118 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
119 119 });
120 120
121 121 // Implicitly start off in Command mode, switching to Edit mode will trigger event
122 122 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
123 123
124 124 // Kernel events
125 125 this.events.on('status_idle.Kernel',function () {
126 126 that.save_widget.update_document_title();
127 127 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
128 128 });
129 129
130 130 this.events.on('status_busy.Kernel',function () {
131 131 window.document.title='(Busy) '+window.document.title;
132 132 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
133 133 });
134 134
135 135 this.events.on('status_restarting.Kernel',function () {
136 136 that.save_widget.update_document_title();
137 137 knw.set_message("Restarting kernel", 2000);
138 138 });
139 139
140 140 this.events.on('status_dead.Kernel',function () {
141 141 that.save_widget.update_document_title();
142 142 knw.danger("Dead kernel");
143 143 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
144 144 });
145 145
146 146 this.events.on('status_interrupting.Kernel',function () {
147 147 knw.set_message("Interrupting kernel", 2000);
148 148 });
149 149
150 150 // Start the kernel indicator in the busy state, and send a kernel_info request.
151 151 // When the kernel_info reply arrives, the kernel is idle.
152 152 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
153 153
154 154 this.events.on('status_started.Kernel', function (evt, data) {
155 155 knw.info("Websockets Connected", 500);
156 156 that.events.trigger('status_busy.Kernel');
157 157 data.kernel.kernel_info(function () {
158 158 that.events.trigger('status_idle.Kernel');
159 159 });
160 160 });
161 161
162 162 this.events.on('status_restart_failed.Kernel',function () {
163 163 var msg = 'The kernel has died, and the automatic restart has failed.' +
164 164 ' It is possible the kernel cannot be restarted.' +
165 165 ' If you are not able to restart the kernel, you will still be able to save' +
166 166 ' the notebook, but running code will no longer work until the notebook' +
167 167 ' is reopened.';
168 168
169 169 dialog.modal({
170 170 title: "Dead kernel",
171 171 body : msg,
172 172 keyboard_manager: that.keyboard_manager,
173 173 notebook: that.notebook,
174 174 buttons : {
175 175 "Manual Restart": {
176 176 class: "btn-danger",
177 177 click: function () {
178 178 that.events.trigger('status_restarting.Kernel');
179 179 that.notebook.start_kernel();
180 180 }
181 181 },
182 182 "Don't restart": {}
183 183 }
184 184 });
185 185 });
186 186
187 187 this.events.on('start_failed.Session',function (session, xhr, status, error) {
188 var msg = $('<div/>');
189 msg.append($('<div/>')
190 .text('The kernel could not be started. This might ' +
191 'happen if the notebook was previously run with a kernel ' +
192 'that you do not have installed. Please choose a different kernel, ' +
193 'or install the needed kernel and then refresh this page.')
194 .css('margin-bottom', '1em'));
195
196 msg.append($('<div/>')
197 .text('The exact error was:')
198 .css('margin-bottom', '1em'));
199
200 msg.append($('<div/>')
201 .attr('class', 'alert alert-danger')
202 .attr('role', 'alert')
203 .text(JSON.parse(status.responseText).message));
188 var msg = JSON.parse(status.responseJSON.message);
204 189
190 that.save_widget.update_document_title();
191 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
192 knw.danger(msg.short, undefined, function () {
205 193 dialog.modal({
206 194 title: "Failed to start the kernel",
207 body : msg,
195 body : msg.full,
208 196 keyboard_manager: that.keyboard_manager,
209 197 notebook: that.notebook,
210 198 buttons : {
211 199 "Ok": { class: 'btn-primary' }
212 200 }
213 201 });
202 return false;
203 });
214 204 });
215 205
216 206 this.events.on('websocket_closed.Kernel', function (event, data) {
217 207 var kernel = data.kernel;
218 208 var ws_url = data.ws_url;
219 209 var early = data.early;
220 210 var msg;
221 211
222 212 $kernel_ind_icon
223 213 .attr('class', 'kernel_disconnected_icon')
224 214 .attr('title', 'No Connection to Kernel');
225 215
226 216 if (!early) {
227 217 knw.warning('Reconnecting');
228 218 setTimeout(function () {
229 219 kernel.start_channels();
230 220 }, 5000);
231 221 return;
232 222 }
233 223 console.log('WebSocket connection failed: ', ws_url);
234 224 msg = "A WebSocket connection could not be established." +
235 225 " You will NOT be able to run code. Check your" +
236 226 " network connection or notebook server configuration.";
237 227 dialog.modal({
238 228 title: "WebSocket connection failed",
239 229 body: msg,
240 230 keyboard_manager: that.keyboard_manager,
241 231 notebook: that.notebook,
242 232 buttons : {
243 233 "OK": {},
244 234 "Reconnect": {
245 235 click: function () {
246 236 knw.warning('Reconnecting');
247 237 setTimeout(function () {
248 238 kernel.start_channels();
249 239 }, 5000);
250 240 }
251 241 }
252 242 }
253 243 });
254 244 });
255 245 };
256 246
257 247 /**
258 248 * Initialize the notification widget for notebook status messages.
259 249 *
260 250 * @method init_notebook_notification_widget
261 251 */
262 252 NotificationArea.prototype.init_notebook_notification_widget = function () {
263 253 var nnw = this.new_notification_widget('notebook');
264 254
265 255 // Notebook events
266 256 this.events.on('notebook_loading.Notebook', function () {
267 257 nnw.set_message("Loading notebook",500);
268 258 });
269 259 this.events.on('notebook_loaded.Notebook', function () {
270 260 nnw.set_message("Notebook loaded",500);
271 261 });
272 262 this.events.on('notebook_saving.Notebook', function () {
273 263 nnw.set_message("Saving notebook",500);
274 264 });
275 265 this.events.on('notebook_saved.Notebook', function () {
276 266 nnw.set_message("Notebook saved",2000);
277 267 });
278 268 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
279 269 nnw.warning(data || "Notebook save failed");
280 270 });
281 271
282 272 // Checkpoint events
283 273 this.events.on('checkpoint_created.Notebook', function (evt, data) {
284 274 var msg = "Checkpoint created";
285 275 if (data.last_modified) {
286 276 var d = new Date(data.last_modified);
287 277 msg = msg + ": " + moment(d).format("HH:mm:ss");
288 278 }
289 279 nnw.set_message(msg, 2000);
290 280 });
291 281 this.events.on('checkpoint_failed.Notebook', function () {
292 282 nnw.warning("Checkpoint failed");
293 283 });
294 284 this.events.on('checkpoint_deleted.Notebook', function () {
295 285 nnw.set_message("Checkpoint deleted", 500);
296 286 });
297 287 this.events.on('checkpoint_delete_failed.Notebook', function () {
298 288 nnw.warning("Checkpoint delete failed");
299 289 });
300 290 this.events.on('checkpoint_restoring.Notebook', function () {
301 291 nnw.set_message("Restoring to checkpoint...", 500);
302 292 });
303 293 this.events.on('checkpoint_restore_failed.Notebook', function () {
304 294 nnw.warning("Checkpoint restore failed");
305 295 });
306 296
307 297 // Autosave events
308 298 this.events.on('autosave_disabled.Notebook', function () {
309 299 nnw.set_message("Autosave disabled", 2000);
310 300 });
311 301 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
312 302 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
313 303 });
314 304 };
315 305
316 306 IPython.NotificationArea = NotificationArea;
317 307
318 308 return {'NotificationArea': NotificationArea};
319 309 });
@@ -1,150 +1,149
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'services/kernels/js/kernel',
9 9 ], function(IPython, $, utils, kernel) {
10 10 "use strict";
11 11
12 12 var Session = function(options){
13 13 this.kernel = null;
14 14 this.id = null;
15 15 this.notebook = options.notebook;
16 16 this.events = options.notebook.events;
17 17 this.name = options.notebook_name;
18 18 this.path = options.notebook_path;
19 19 this.kernel_name = options.kernel_name;
20 20 this.base_url = options.base_url;
21 21 this.ws_url = options.ws_url;
22 22 };
23 23
24 24 Session.prototype.start = function (success, error) {
25 25 var that = this;
26 26 var model = {
27 27 notebook : {
28 28 name : this.name,
29 29 path : this.path
30 30 },
31 31 kernel : {
32 32 name : this.kernel_name
33 33 }
34 34 };
35 35 var settings = {
36 36 processData : false,
37 37 cache : false,
38 38 type : "POST",
39 39 data: JSON.stringify(model),
40 40 dataType : "json",
41 41 success : function (data, status, xhr) {
42 42 that._handle_start_success(data);
43 43 if (success) {
44 44 success(data, status, xhr);
45 45 }
46 46 },
47 47 error : function (xhr, status, err) {
48 48 that._handle_start_failure(xhr, status, err);
49 49 if (error !== undefined) {
50 50 error(xhr, status, err);
51 51 }
52 52 utils.log_ajax_error(xhr, status, err);
53 53 }
54 54 };
55 55 var url = utils.url_join_encode(this.base_url, 'api/sessions');
56 56 $.ajax(url, settings);
57 57 };
58 58
59 59 Session.prototype.rename_notebook = function (name, path) {
60 60 this.name = name;
61 61 this.path = path;
62 62 var model = {
63 63 notebook : {
64 64 name : this.name,
65 65 path : this.path
66 66 }
67 67 };
68 68 var settings = {
69 69 processData : false,
70 70 cache : false,
71 71 type : "PATCH",
72 72 data: JSON.stringify(model),
73 73 dataType : "json",
74 74 error : utils.log_ajax_error,
75 75 };
76 76 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
77 77 $.ajax(url, settings);
78 78 };
79 79
80 80 Session.prototype.delete = function (success, error) {
81 81 var settings = {
82 82 processData : false,
83 83 cache : false,
84 84 type : "DELETE",
85 85 dataType : "json",
86 86 success : success,
87 87 error : error || utils.log_ajax_error,
88 88 };
89 89 if (this.kernel) {
90 90 this.kernel.running = false;
91 91 this.kernel.stop_channels();
92 92 }
93 93 var url = utils.url_join_encode(this.base_url, 'api/sessions', this.id);
94 94 $.ajax(url, settings);
95 95 };
96 96
97 97 // Kernel related things
98 98 /**
99 99 * Create the Kernel object associated with this Session.
100 100 *
101 101 * @method _handle_start_success
102 102 */
103 103 Session.prototype._handle_start_success = function (data, status, xhr) {
104 104 this.id = data.id;
105 105 // If we asked for 'python', the response will have 'python3' or 'python2'.
106 106 this.kernel_name = data.kernel.name;
107 107 this.events.trigger('started.Session', this);
108 108 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
109 109 this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
110 110 this.kernel._kernel_started(data.kernel);
111 111 };
112 112
113 113 Session.prototype._handle_start_failure = function (xhr, status, error) {
114 114 this.events.trigger('start_failed.Session', [this, xhr, status, error]);
115 this.events.trigger('status_dead.Kernel');
116 115 };
117 116
118 117 /**
119 118 * Prompt the user to restart the IPython kernel.
120 119 *
121 120 * @method restart_kernel
122 121 */
123 122 Session.prototype.restart_kernel = function () {
124 123 this.kernel.restart();
125 124 };
126 125
127 126 Session.prototype.interrupt_kernel = function() {
128 127 this.kernel.interrupt();
129 128 };
130 129
131 130
132 131 Session.prototype.kill_kernel = function() {
133 132 this.kernel.kill();
134 133 };
135 134
136 135 var SessionAlreadyStarting = function (message) {
137 136 this.name = "SessionAlreadyStarting";
138 137 this.message = (message || "");
139 138 };
140 139
141 140 SessionAlreadyStarting.prototype = Error.prototype;
142 141
143 142 // For backwards compatability.
144 143 IPython.Session = Session;
145 144
146 145 return {
147 146 Session: Session,
148 147 SessionAlreadyStarting: SessionAlreadyStarting,
149 148 };
150 149 });
General Comments 0
You need to be logged in to leave comments. Login now