Statistics
| Branch: | Tag: | Revision:

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);