root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_list_view.js @ 8fa1cbc9
History | View | Annotate | Download (18.8 kB)
1 |
// Copyright 2011 GRNET S.A. All rights reserved.
|
---|---|
2 |
//
|
3 |
// Redistribution and use in source and binary forms, with or
|
4 |
// without modification, are permitted provided that the following
|
5 |
// conditions are met:
|
6 |
//
|
7 |
// 1. Redistributions of source code must retain the above
|
8 |
// copyright notice, this list of conditions and the following
|
9 |
// disclaimer.
|
10 |
//
|
11 |
// 2. Redistributions in binary form must reproduce the above
|
12 |
// copyright notice, this list of conditions and the following
|
13 |
// disclaimer in the documentation and/or other materials
|
14 |
// provided with the distribution.
|
15 |
//
|
16 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
// POSSIBILITY OF SUCH DAMAGE.
|
28 |
//
|
29 |
// The views and conclusions contained in the software and
|
30 |
// documentation are those of the authors and should not be
|
31 |
// interpreted as representing official policies, either expressed
|
32 |
// or implied, of GRNET S.A.
|
33 |
//
|
34 |
|
35 |
;(function(root){
|
36 |
|
37 |
// root
|
38 |
var root = root;
|
39 |
|
40 |
// setup namepsaces
|
41 |
var snf = root.synnefo = root.synnefo || {};
|
42 |
var models = snf.models = snf.models || {}
|
43 |
var storage = snf.storage = snf.storage || {};
|
44 |
var ui = snf.ui = snf.ui || {};
|
45 |
var util = snf.util = snf.util || {};
|
46 |
|
47 |
var views = snf.views = snf.views || {}
|
48 |
|
49 |
// shortcuts
|
50 |
var bb = root.Backbone;
|
51 |
|
52 |
var hasKey = Object.prototype.hasOwnProperty;
|
53 |
|
54 |
views.ListMultipleActions = views.View.extend({ |
55 |
|
56 |
view_id: "list_actions", |
57 |
|
58 |
initialize: function(view) { |
59 |
this.parent = view;
|
60 |
this.el = this.parent.el; |
61 |
|
62 |
views.ListMultipleActions.__super__.initialize.call(this);
|
63 |
this.set_handlers();
|
64 |
this.update_layout();
|
65 |
|
66 |
this.selected_action = undefined; |
67 |
this.available_actions = [];
|
68 |
this.multi_view = synnefo.ui.main.multiple_actions_view;
|
69 |
this.hovered = false; |
70 |
|
71 |
this.update_actions = _.throttle(this.update_actions, 100); |
72 |
}, |
73 |
|
74 |
set_handlers: function() { |
75 |
var self = this; |
76 |
storage.vms.bind("change:pending_action", function() { |
77 |
if (!storage.vms.has_pending_actions()) {
|
78 |
self.parent.$(".actions a").removeClass("selected"); |
79 |
self.parent.clear_indicators(); |
80 |
} |
81 |
}); |
82 |
|
83 |
var self = this; |
84 |
this.parent.$(".actions a.enabled").live('click', function() { |
85 |
self.parent.$(".actions a").removeClass("selected"); |
86 |
$(this).addClass("selected"); |
87 |
self.parent.select_action($(this).attr("id").replace("action-","")); |
88 |
}); |
89 |
|
90 |
this.parent.$(".actions a.enabled").live({ |
91 |
'mouseenter': function() { |
92 |
self.hovered = true;
|
93 |
self.parent.set_indicator_for($(this).attr("id").replace("action-","")); |
94 |
}, |
95 |
'mouseleave': function() { |
96 |
self.hovered = false;
|
97 |
self.parent.clear_indicators(); |
98 |
} |
99 |
}); |
100 |
}, |
101 |
|
102 |
update_actions: function() { |
103 |
actions = undefined;
|
104 |
this.available_actions = [];
|
105 |
_.each(this.parent.get_selected_vms(), function(vm) { |
106 |
if (!actions) {
|
107 |
actions = vm.get_available_actions(); |
108 |
return;
|
109 |
} |
110 |
actions = _.intersection(actions, vm.get_available_actions()); |
111 |
}); |
112 |
|
113 |
this.available_actions = actions;
|
114 |
|
115 |
this.$(".actions a").removeClass("enabled"); |
116 |
_.each(this.available_actions, _.bind(function(name){ |
117 |
this.$("#action-" + name).addClass("enabled"); |
118 |
}, this))
|
119 |
}, |
120 |
|
121 |
update_selected: function() { |
122 |
this.$("tr").removeClass("checked"); |
123 |
this.$("tr input:checked").parent().parent().addClass("checked"); |
124 |
}, |
125 |
|
126 |
update_layout: function() { |
127 |
this.update_actions();
|
128 |
this.update_selected();
|
129 |
} |
130 |
}); |
131 |
|
132 |
// VMs list view
|
133 |
views.ListView = views.VMListView.extend({ |
134 |
|
135 |
// view id (this could be used to identify
|
136 |
// the view object from global context
|
137 |
view_id: 'vm_list', |
138 |
|
139 |
el: '#machinesview-list', |
140 |
id_tpl: 'list-vm-', |
141 |
link_id_tpl: 'list-vm-at-', |
142 |
|
143 |
hide_actions: false, |
144 |
|
145 |
selectors: {
|
146 |
'vms': '.list-container', |
147 |
'vm': '#list-vm-', |
148 |
'view': '#machinesview-list', |
149 |
'tpl': '.list-container#machine-container-template', |
150 |
'spinner': '.large-spinner', |
151 |
'vm_spinner': '#list-vm-{0} .spinner', |
152 |
'vm_wave': '#list-vm-{0} .wave', |
153 |
'os_icon': '#list-vm-{0} .os_icon', |
154 |
'vm_cont_active': '#machinesview-list', |
155 |
'vm_cont_terminated': '#machinesview-list' |
156 |
}, |
157 |
|
158 |
initialize: function() { |
159 |
this.current_vm = 0; |
160 |
|
161 |
// button selectors
|
162 |
this.prev_button = this.$(".controls .previous"); |
163 |
this.next_button = this.$(".controls .next"); |
164 |
|
165 |
this.actions = this.$(".actions").show(); |
166 |
this.datatable_cont = this.$(".dataTables_wrapper").show(); |
167 |
this.content = this.$("#machinesview_content").show(); |
168 |
this.filter = this.$(".dataTables_filter").show().css({'display':'block'}); |
169 |
this.table_el = this.$(".list-machines").show(); |
170 |
this.select_all = $("#list-view-select-all"); |
171 |
|
172 |
this.actions = new views.ListMultipleActions(this); |
173 |
|
174 |
this.table = $("div.list table.list-machines").dataTable({ |
175 |
"bInfo": false, |
176 |
"bRetrieve": true, |
177 |
"bPaginate": false, |
178 |
"bAutoWidth": false, |
179 |
"bSort": true, |
180 |
"bStateSave": true, |
181 |
"sScrollXInner": "500px", |
182 |
"aoColumnDefs": [
|
183 |
{ "bSortable": false, "aTargets": [ 0 ] } |
184 |
] |
185 |
}); |
186 |
|
187 |
this.table_data = {};
|
188 |
views.ListView.__super__.initialize.apply(this, arguments); |
189 |
|
190 |
this.update_layout = _.throttle(this.update_layout, 100); |
191 |
}, |
192 |
|
193 |
reset: function() { |
194 |
}, |
195 |
|
196 |
hide_actions: function() { |
197 |
this.$(".actions a").removeClass("selected"); |
198 |
}, |
199 |
|
200 |
// overload show function
|
201 |
show_view: function() { |
202 |
this.log.debug("showing"); |
203 |
this.sel('spinner').hide(); |
204 |
this.__update_layout();
|
205 |
}, |
206 |
|
207 |
check_vm_container: function() { |
208 |
}, |
209 |
|
210 |
// identify vm model instance id based on DOM element
|
211 |
vm_id_for_element: function(el) { |
212 |
return el.attr('id').replace("list-vm-", ""); |
213 |
}, |
214 |
|
215 |
reset_actions: function() { |
216 |
this.$(".actions a").removeClass("selected"); |
217 |
storage.vms.reset_pending_actions(); |
218 |
}, |
219 |
|
220 |
// set generic view handlers
|
221 |
set_handlers: function() { |
222 |
this.$(".list-vm-checkbox").live('change', _.bind(function(){ |
223 |
this.reset_actions();
|
224 |
this.actions.update_layout();
|
225 |
if (this.$("tbody input:checked").length > 0) { |
226 |
this.select_all.attr("checked", true); |
227 |
} else {
|
228 |
this.select_all.attr("checked", false); |
229 |
} |
230 |
self.actions.update_layout(); |
231 |
}, this))
|
232 |
|
233 |
var self = this; |
234 |
this.select_all.click(function(){ |
235 |
if ($(this).is(":checked")) { |
236 |
self.$("tbody input").attr("checked", true); |
237 |
} else {
|
238 |
self.$("tbody input").attr("checked", false); |
239 |
} |
240 |
self.actions.update_layout(); |
241 |
}); |
242 |
}, |
243 |
|
244 |
get_selected_vms: function() { |
245 |
var selected = $(this.el).find(".list-vm-checkbox:checked"); |
246 |
var vms = []
|
247 |
_.each(selected, function(el){
|
248 |
var id = parseInt($(el).attr("id").replace("checkbox-list-vm-", "")); |
249 |
vm = storage.vms.get(id); |
250 |
if (!vm) { return }; |
251 |
vms.push(vm); |
252 |
}); |
253 |
|
254 |
return vms;
|
255 |
}, |
256 |
|
257 |
select_action: function(action) { |
258 |
this.reset_actions();
|
259 |
this.$(".actions a#action-" + action).addClass("selected"); |
260 |
var vms = this.get_selected_vms(); |
261 |
_.each(vms, function(vm){
|
262 |
vm.update_pending_action(action); |
263 |
}) |
264 |
}, |
265 |
|
266 |
reset: function() { |
267 |
this.reset_actions();
|
268 |
}, |
269 |
|
270 |
create_vm: function(vm) { |
271 |
params = this.get_vm_table_data(vm);
|
272 |
var index = this.table.fnAddData.call(this.table, params); |
273 |
this.table_data["vm_" + vm.id] = {index: index[0], params: params}; |
274 |
// append row id
|
275 |
$(this.table.fnGetNodes(index)).attr("id", this.id_tpl + vm.id); |
276 |
|
277 |
var vm_el = $("#" + this.id_tpl + vm.id); |
278 |
this._vm_els[vm.id] = vm_el;
|
279 |
// hide indicators on creation
|
280 |
this.vm(vm).find(".spinner").hide(); |
281 |
this.vm(vm).find(".wave").hide(); |
282 |
this.vm(vm).find(".os_icon").show(); |
283 |
this.vm(vm).find(".action-indicator").hide(); |
284 |
|
285 |
// ancestor method
|
286 |
this.__set_vm_handlers(vm);
|
287 |
this.post_add(vm);
|
288 |
return this.vm(vm); |
289 |
}, |
290 |
|
291 |
// remove vm
|
292 |
remove_vm: function(vm) { |
293 |
this.vm(vm).find("input[type=checkbox]").removeAttr("checked"); |
294 |
var vm_data = this.table_data["vm_" + vm.id]; |
295 |
|
296 |
// update triggered on removed vm, skipping
|
297 |
if (!vm_data) { return }; |
298 |
|
299 |
var index = vm_data.index;
|
300 |
this.table.fnDeleteRow(index);
|
301 |
delete this.table_data["vm_" + vm.id]; |
302 |
this.update_data();
|
303 |
|
304 |
if (hasKey.call(this._vm_els, vm.id)) { |
305 |
delete this._vm_els[vm.id]; |
306 |
} |
307 |
}, |
308 |
|
309 |
update_data: function() { |
310 |
var new_data = this.table.fnGetData(); |
311 |
_.each(new_data, _.bind(function(row, i){
|
312 |
this.table_data["vm_" + row[5]].index = i; |
313 |
this.table_data["vm_" + row[5]].params = row; |
314 |
}, this));
|
315 |
}, |
316 |
|
317 |
set_indicator_for: function(action) { |
318 |
var vms = this.get_selected_vms(); |
319 |
_.each(vms, _.bind(function(vm){
|
320 |
var vmel = this.vm(vm); |
321 |
vmel.find("img.spinner, img.wave, img.os_icon").hide();
|
322 |
vmel.find("span.action-indicator").show().removeClass().addClass(action + " action-indicator"); |
323 |
}, this));
|
324 |
}, |
325 |
|
326 |
clear_indicators: function() { |
327 |
var vms = storage.vms.models;
|
328 |
_.each(vms, _.bind(function(vm){
|
329 |
var vmel = this.vm(vm); |
330 |
|
331 |
vmel.find("img.wave").hide();
|
332 |
|
333 |
if (vm.pending_action) {
|
334 |
vmel.find("img.os_icon").hide();
|
335 |
vmel.find("span.action-indicator").show().removeClass().addClass(vm.pending_action + " action-indicator"); |
336 |
return;
|
337 |
} |
338 |
|
339 |
if (vm.in_transition()) {
|
340 |
vmel.find("span.action-indicator").hide();
|
341 |
vmel.find("img.spinner").show();
|
342 |
return;
|
343 |
} |
344 |
|
345 |
if (!this.actions.hovered) { |
346 |
vmel.find("img.os_icon").show();
|
347 |
vmel.find("span.action-indicator").hide();
|
348 |
vmel.find("img.spinner").hide();
|
349 |
} |
350 |
|
351 |
|
352 |
}, this));
|
353 |
}, |
354 |
|
355 |
get_vm_table_data: function(vm) { |
356 |
var checkbox = '<input type="checkbox" class="' + |
357 |
views.ListView.STATE_CLASSES[vm.state()].join(" ") +
|
358 |
' list-vm-checkbox" id="checkbox-' + this.id_tpl + vm.id + '"/>'; |
359 |
|
360 |
var img = '<img class="os_icon" src="'+ this.get_vm_icon_path(vm, "small") +'" />'; |
361 |
img = img + '<img src="'+snf.config.indicators_icons_url+'small/progress.gif" class="spinner" />'; |
362 |
img = img + '<img src="'+snf.config.indicators_icons_url+'medium/wave.gif" class="wave" />'; |
363 |
img = img + '<span class="action-indicator" />';
|
364 |
|
365 |
var name = util.truncate(vm.get('name'), 25); |
366 |
var flavor = vm.get_flavor().details_string();
|
367 |
var status = STATE_TEXTS[vm.state()];
|
368 |
|
369 |
return [checkbox, img, name, flavor, status, vm.id];
|
370 |
}, |
371 |
|
372 |
post_add: function(vm) { |
373 |
}, |
374 |
|
375 |
// is vm in transition ??? show the progress spinner
|
376 |
update_transition_state: function(vm) { |
377 |
if (!vm) { return }; |
378 |
if (this.in_transition) { return }; |
379 |
|
380 |
if ((this.actions.hovered && this.vm(vm).find("input").is(":checked")) || vm.pending_action) { |
381 |
this.sel('vm_spinner', vm.id).hide(); |
382 |
this.sel('vm_wave', vm.id).hide(); |
383 |
this.sel('os_icon', vm.id).hide(); |
384 |
this.vm(vm).find(".action-indicator").show(); |
385 |
return;
|
386 |
} |
387 |
|
388 |
if (vm.in_transition()){
|
389 |
this.sel('vm_spinner', vm.id).show(); |
390 |
this.sel('vm_wave', vm.id).hide(); |
391 |
this.sel('os_icon', vm.id).hide(); |
392 |
this.vm(vm).find(".action-indicator").hide(); |
393 |
} else {
|
394 |
this.sel('vm_spinner', vm.id).hide(); |
395 |
this.vm(vm).find(".action-indicator").hide(); |
396 |
this.sel('os_icon', vm.id).show(); |
397 |
} |
398 |
}, |
399 |
|
400 |
// display transition animations
|
401 |
show_transition: function(vm) { |
402 |
this.in_transition = true; |
403 |
|
404 |
if (!this.visible()) { |
405 |
this.in_transition = false; |
406 |
this.update_transition_state();
|
407 |
return
|
408 |
}; |
409 |
|
410 |
var wave = this.sel('vm_wave', vm.id); |
411 |
if (!wave.length) {
|
412 |
this.in_transition = false |
413 |
return
|
414 |
} |
415 |
|
416 |
this.sel('vm_spinner', vm.id).hide(); |
417 |
this.sel('os_icon', vm.id).hide(); |
418 |
|
419 |
var src = wave.attr('src'); |
420 |
var self = this; |
421 |
|
422 |
// change src to force gif play from the first frame
|
423 |
// animate for 500 ms then hide
|
424 |
wave.attr('src', "").show().attr('src', src).fadeIn(500).delay(700).fadeOut(300, function() { |
425 |
self.in_transition = false;
|
426 |
self.update_transition_state(vm); |
427 |
}); |
428 |
}, |
429 |
|
430 |
update_actions_layout: function(vm) { |
431 |
}, |
432 |
|
433 |
post_update_vm: function(vm) { |
434 |
|
435 |
// skip update for these changes for performance issues
|
436 |
if (vm.hasOnlyChange(["pending_action", "stats"])) { return }; |
437 |
|
438 |
var index = this.table_data["vm_" + vm.id].index; |
439 |
params = this.get_vm_table_data(vm);
|
440 |
this.table_data["vm_" + vm.id].params = params; |
441 |
data = this.table.fnGetData()[index];
|
442 |
|
443 |
// do not recreate checkboxes and images to avoid messing
|
444 |
// with user interaction
|
445 |
this.table.fnUpdate(params[2], parseInt(index), 2); |
446 |
this.table.fnUpdate(params[3], parseInt(index), 3); |
447 |
this.table.fnUpdate(params[4], parseInt(index), 4); |
448 |
|
449 |
var active_class = vm.is_active() ? "active" : "inactive"; |
450 |
this.vm(vm).removeClass("active").removeClass("inactive").addClass(active_class); |
451 |
$(this.vm(vm).find("td").get(4)).addClass("status"); |
452 |
$(this.vm(vm).find("td").get(3)).addClass("flavor"); |
453 |
$(this.vm(vm).find("td").get(2)).addClass("name"); |
454 |
|
455 |
if (vm.status() == "ERROR") { |
456 |
this.vm(vm).removeClass("active").removeClass("inactive").addClass("error"); |
457 |
} else {
|
458 |
this.vm(vm).removeClass("error").addClass(active_class); |
459 |
} |
460 |
|
461 |
this.update_os_icon(vm);
|
462 |
this.update_transition_state(vm);
|
463 |
}, |
464 |
|
465 |
update_os_icon: function(vm) { |
466 |
this.sel('os_icon', vm.id).attr('src', this.get_vm_icon_path(vm, "small")); |
467 |
}, |
468 |
|
469 |
// vm specific event handlers
|
470 |
set_vm_handlers: function(vm) { |
471 |
}, |
472 |
|
473 |
// generic stuff to do on each view update
|
474 |
// called once after each vm has been updated
|
475 |
update_layout: function() { |
476 |
this.actions.update_layout();
|
477 |
}, |
478 |
|
479 |
// update vm details
|
480 |
update_details: function(vm) { |
481 |
}, |
482 |
|
483 |
get_vm_icon_os: function(vm) { |
484 |
var os = vm.get_os();
|
485 |
var icons = window.os_icons || views.ListView.VM_OS_ICONS;
|
486 |
if (icons.indexOf(os) == -1) { |
487 |
os = snf.config.unknown_os; |
488 |
} |
489 |
return os;
|
490 |
}, |
491 |
|
492 |
// TODO: move to views.utils (the method and the VM_OS_ICON vars)
|
493 |
get_vm_icon_path: function(vm, icon_type) { |
494 |
var os = vm.get_os();
|
495 |
var icons = window.os_icons || views.ListView.VM_OS_ICONS;
|
496 |
|
497 |
if (icons.indexOf(os) == -1) { |
498 |
os = snf.config.unknown_os; |
499 |
} |
500 |
|
501 |
var st = "off"; |
502 |
if (vm.is_active()) {
|
503 |
st = "on"
|
504 |
} |
505 |
|
506 |
return views.ListView.VM_OS_ICON_TPLS()[icon_type].format(os, st);
|
507 |
} |
508 |
}) |
509 |
|
510 |
views.ListView.VM_OS_ICON_TPLS = function() { |
511 |
return {
|
512 |
"small": snf.config.machines_icons_url + "small/{0}-{1}.png" |
513 |
} |
514 |
} |
515 |
|
516 |
views.ListView.VM_OS_ICONS = window.os_icons || []; |
517 |
|
518 |
views.ListView.STATE_CLASSES = { |
519 |
'UNKNOWN': ['error-state'], |
520 |
'BUILD': ['build-state'], |
521 |
'REBOOT': ['rebooting-state'], |
522 |
'STOPPED': ['terminated-state'], |
523 |
'ACTIVE': ['running-state'], |
524 |
'ERROR': ['error-state'], |
525 |
'DELETED': ['destroying-state'], |
526 |
'DESTROY': ['destroying-state'], |
527 |
'BUILD_INIT': ['build-state'], |
528 |
'BUILD_COPY': ['build-state'], |
529 |
'BUILD_FINAL': ['build-state'], |
530 |
'SHUTDOWN': ['shutting-state'], |
531 |
'START': ['starting-state'], |
532 |
'CONNECT': ['connecting-state'], |
533 |
'DISCONNECT': ['disconnecting-state'] |
534 |
}; |
535 |
|
536 |
})(this);
|