Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_list_view.js @ 5dad72ea

History | View | Annotate | Download (16.8 kB)

1
;(function(root){
2

    
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var models = snf.models = snf.models || {}
9
    var storage = snf.storage = snf.storage || {};
10
    var ui = snf.ui = snf.ui || {};
11
    var util = snf.util = snf.util || {};
12

    
13
    var views = snf.views = snf.views || {}
14

    
15
    // shortcuts
16
    var bb = root.Backbone;
17
    
18
    views.ListMultipleActions = views.View.extend({
19
        
20
        view_id: "list_actions",
21

    
22
        initialize: function(view) {
23
            this.parent = view;
24
            this.el = this.parent.el;
25
           
26
            views.ListMultipleActions.__super__.initialize.call(this);
27
            this.set_handlers();
28
            this.update_layout();
29

    
30
            this.selected_action = undefined;
31
            this.available_actions = [];
32
            this.multi_view = synnefo.ui.main.multiple_actions_view;
33
            this.hovered = false;
34

    
35
            this.update_actions = _.throttle(this.update_actions, 100);
36
        },
37

    
38
        set_handlers: function() {
39
            var self = this;
40
            storage.vms.bind("change:pending_action", function() {
41
                if (!storage.vms.has_pending_actions()) {
42
                    self.parent.$(".actions a").removeClass("selected");
43
                    self.parent.clear_indicators();
44
                }
45
            });
46
            
47
            var self = this;
48
            this.parent.$(".actions a.enabled").live('click', function() {
49
                self.parent.$(".actions a").removeClass("selected");
50
                $(this).addClass("selected");
51
                self.parent.select_action($(this).attr("id").replace("action-",""));
52
            });
53

    
54
            this.parent.$(".actions a.enabled").live({
55
                'mouseenter': function() {
56
                    self.hovered = true;
57
                    self.parent.set_indicator_for($(this).attr("id").replace("action-",""))
58
                }, 
59
                'mouseleave': function() {
60
                    self.hovered = false;
61
                    self.parent.clear_indicators();
62
                }
63
            });
64
        },
65
        
66
        update_actions: function() {
67
            actions = undefined;
68
            this.available_actions = [];
69
            _.each(this.parent.get_selected_vms(), function(vm) {
70
                if (!actions) {
71
                    actions = vm.get_available_actions();
72
                    return;
73
                }
74
                actions = _.intersection(actions, vm.get_available_actions());
75
            });
76

    
77
            this.available_actions = actions;
78

    
79
            this.$(".actions a").removeClass("enabled");
80
            _.each(this.available_actions, _.bind(function(name){
81
                this.$("#action-" + name).addClass("enabled");
82
            }, this))
83
        },
84

    
85
        update_selected: function() {
86
            this.$("tr").removeClass("checked");
87
            this.$("tr input:checked").parent().parent().addClass("checked");
88
        },
89

    
90
        update_layout: function() {
91
            this.update_actions();
92
            this.update_selected();
93
        }
94
    });
95

    
96
    // VMs list view
97
    views.ListView = views.VMListView.extend({
98
        
99
        // view id (this could be used to identify 
100
        // the view object from global context
101
        view_id: 'vm_list',
102

    
103
        el: '#machinesview-list',
104
        id_tpl: 'list-vm-',
105
        link_id_tpl: 'list-vm-at-',
106

    
107
        hide_actions: false,
108

    
109
        selectors: {
110
            'vms': '.list-container',
111
            'vm': '#list-vm-{0}',
112
            'view': '#machinesview-list',
113
            'tpl': '.list-container#machine-container-template',
114
            'spinner': '.large-spinner',
115
            'vm_spinner': '#list-vm-{0} .spinner',
116
            'vm_wave': '#list-vm-{0} .wave',
117
            'os_icon': '#list-vm-{0} .os_icon',
118
            'vm_cont_active': '#machinesview-list',
119
            'vm_cont_terminated': '#machinesview-list'
120
        },
121
        
122
        initialize: function() {
123
            this.current_vm = 0;
124
            
125
            // button selectors
126
            this.prev_button = this.$(".controls .previous");
127
            this.next_button = this.$(".controls .next");
128

    
129
            this.actions = this.$(".actions").show();
130
            this.datatable_cont = this.$(".dataTables_wrapper").show();
131
            this.content = this.$("#machinesview_content").show();
132
            this.filter = this.$(".dataTables_filter").show().css({'display':'block'});
133
            this.table_el = this.$(".list-machines").show();
134
            this.select_all = $("#list-view-select-all");
135

    
136
            this.actions = new views.ListMultipleActions(this);
137

    
138
            this.table = $("div.list table.list-machines").dataTable({
139
                "bInfo": false,
140
                "bRetrieve": true,
141
                "bPaginate": false,
142
                "bAutoWidth": false,
143
                "bSort": true,
144
                "bStateSave": true,
145
                "sScrollXInner": "500px",
146
                "aoColumnDefs": [
147
                    { "bSortable": false, "aTargets": [ 0 ] }
148
                ]
149
            });
150

    
151
            this.table_data = {};
152
            views.ListView.__super__.initialize.apply(this, arguments);
153

    
154
            this.update_layout = _.throttle(this.update_layout, 100);
155
        },
156
        
157
        reset: function() {
158
        },
159

    
160
        hide_actions: function() {
161
            this.$(".actions a").removeClass("selected");
162
        },
163

    
164
        // overload show function
165
        show_view: function() {
166
            this.log.debug("showing");
167
            this.sel('spinner').hide();
168
            this.__update_layout();
169
        },
170
        
171
        check_vm_container: function() {
172
        },
173

    
174
        // identify vm model instance id based on DOM element
175
        vm_id_for_element: function(el) {
176
            return el.attr('id').replace("list-vm-", "");
177
        },
178

    
179
        reset_actions: function() {
180
            this.$(".actions a").removeClass("selected");
181
            storage.vms.reset_pending_actions();
182
        },
183
        
184
        // set generic view handlers
185
        set_handlers: function() {
186
            this.$(".list-vm-checkbox").live('change', _.bind(function(){
187
                this.reset_actions();
188
                this.actions.update_layout();
189
                if (this.$("tbody input:checked").length > 0) {
190
                    this.select_all.attr("checked", true);
191
                } else {
192
                    this.select_all.attr("checked", false);
193
                }
194
                self.actions.update_layout();
195
            }, this))
196

    
197
            var self = this;
198
            this.select_all.click(function(){
199
                if ($(this).is(":checked")) {
200
                    self.$("tbody input").attr("checked", true);
201
                } else {
202
                    self.$("tbody input").attr("checked", false);
203
                }
204
                self.actions.update_layout();
205
            });
206
        },  
207

    
208
        get_selected_vms: function() {
209
            var selected = $(this.el).find(".list-vm-checkbox:checked");
210
            var vms = []
211
            _.each(selected, function(el){
212
                var id = parseInt($(el).attr("id").replace("checkbox-list-vm-", ""));
213
                vm = storage.vms.get(id);
214
                if (!vm) { return };
215
                vms.push(vm);
216
            });
217

    
218
            return vms;
219
        },
220

    
221
        select_action: function(action) {
222
            this.reset_actions();
223
            this.$(".actions a#action-" + action).addClass("selected");
224
            var vms = this.get_selected_vms();
225
            _.each(vms, function(vm){
226
                vm.update_pending_action(action);
227
            })
228
        },
229

    
230
        reset: function() {
231
            this.reset_actions();
232
        },
233

    
234
        create_vm: function(vm) {
235
            params = this.get_vm_table_data(vm);
236
            var index = this.table.fnAddData.call(this.table, params);
237
            this.table_data["vm_" + vm.id] = {index: index[0], params: params};
238
            
239
            // append row id
240
            $(this.table.fnGetNodes(index)).attr("id", this.id_tpl + vm.id);
241
            
242
            // hide indicators on creation
243
            this.vm(vm).find(".spinner").hide();
244
            this.vm(vm).find(".wave").hide();
245
            this.vm(vm).find(".os_icon").show();
246
            this.vm(vm).find(".action-indicator").hide();
247
            
248
            // ancestor method
249
            this.__set_vm_handlers(vm);
250
            this.post_add(vm);
251
        },
252

    
253
        // remove vm
254
        remove_vm: function(vm) {
255
            this.vm(vm).find("input[type=checkbox]").removeAttr("checked");
256
            var vm_data = this.table_data["vm_" + vm.id];
257

    
258
            // update triggered on removed vm, skipping
259
            if (!vm_data) { return };
260

    
261
            var index = vm_data.index;
262
            this.table.fnDeleteRow(index);
263
            delete this.table_data["vm_" + vm.id];
264
            this.update_data();
265
        },
266

    
267
        update_data: function() {
268
            var new_data = this.table.fnGetData();
269
            _.each(new_data, _.bind(function(row, i){
270
                this.table_data["vm_" + row[5]].index = i;
271
                this.table_data["vm_" + row[5]].params = row;
272
            }, this));
273
        },
274

    
275
        set_indicator_for: function(action) {
276
            var vms = this.get_selected_vms();
277
            _.each(vms, _.bind(function(vm){
278
                var vmel = this.vm(vm);
279
                vmel.find("img.spinner, img.wave, img.os_icon").hide();
280
                vmel.find("span.action-indicator").show().removeClass().addClass(action + " action-indicator");
281
            }, this));
282
        },
283

    
284
        clear_indicators: function() {
285
            var vms = storage.vms.models;
286
            _.each(vms, _.bind(function(vm){
287
                var vmel = this.vm(vm);
288

    
289
                vmel.find("img.wave").hide();
290
                
291
                if (vm.pending_action) {
292
                    vmel.find("img.os_icon").hide();
293
                    vmel.find("span.action-indicator").show().removeClass().addClass(vm.pending_action + " action-indicator");
294
                    return;
295
                }
296

    
297
                if (vm.in_transition()) {
298
                    vmel.find("span.action-indicator").hide();
299
                    vmel.find("img.spinner").show();
300
                    return;
301
                }
302

    
303
                if (!this.actions.hovered) {
304
                    vmel.find("img.os_icon").show();
305
                    vmel.find("span.action-indicator").hide();
306
                    vmel.find("img.spinner").hide();
307
                }
308
                
309

    
310
            }, this));
311
        },
312

    
313
        get_vm_table_data: function(vm) {
314
            var checkbox = '<input type="checkbox" class="' + 
315
                views.ListView.STATE_CLASSES[vm.state()].join(" ") + 
316
                ' list-vm-checkbox" id="checkbox-' + this.id_tpl + vm.id + '"/>';
317

    
318
            var img = '<img class="os_icon" src="'+ this.get_vm_icon_path(vm, "small") +'" />';
319
            img = img + '<img src="static/icons/indicators/small/progress.gif" class="spinner" />';
320
            img = img + '<img src="static/icons/indicators/medium/wave.gif" class="wave" />';
321
            img = img + '<span class="action-indicator" />';
322

    
323
            var name = util.truncate(vm.get('name'), 25);
324
            var flavor = vm.get_flavor().details_string();
325
            var status = STATE_TEXTS[vm.state()];
326
            
327
            return [checkbox, img, name, flavor, status, vm.id];
328
        },
329

    
330
        post_add: function(vm) {
331
        },
332

    
333
        // is vm in transition ??? show the progress spinner
334
        update_transition_state: function(vm) {
335
            if ((this.actions.hovered && this.vm(vm).find("input").is(":checked")) || vm.pending_action) {
336
                this.sel('vm_spinner', vm.id).hide();
337
                this.sel('vm_wave', vm.id).hide();
338
                this.sel('os_icon', vm.id).hide();
339
                this.vm(vm).find(".action-indicator").show();
340
                return;
341
            }
342

    
343
            if (vm.in_transition()){
344
                this.sel('vm_spinner', vm.id).show();
345
                this.sel('vm_wave', vm.id).hide();
346
                this.sel('os_icon', vm.id).hide();
347
                this.$(".action-indicator").hide();
348
            } else {
349
                this.sel('vm_spinner', vm.id).hide();
350
                this.$(".action-indicator").hide();
351
            }
352
        },
353

    
354
        // display transition animations
355
        show_transition: function(vm) {
356
            if (!this.visible()) { return };
357
            var wave = this.sel('vm_wave', vm.id);
358
            if (!wave.length) {
359
                return
360
            }
361
            
362
            this.sel('vm_spinner', vm.id).hide();
363
            this.sel('os_icon', vm.id).hide();
364

    
365
            var src = wave.attr('src');
366
            var self = this;
367

    
368
            // change src to force gif play from the first frame
369
            // animate for 500 ms then hide
370
            wave.attr('src', "").show().attr('src', src).fadeIn(500).delay(700).fadeOut(300, function() {
371
                if (vm.in_transition()) {
372
                    self.sel("vm_spinner", vm.id).fadeIn(200);
373
                } else {
374
                    if (vm.pending_action || (self.actions.hovered && this.vm(vm).find("input").is(":checked"))) {
375
                        self.vm(vm).find(".action-indicator").show();
376
                    } else {
377
                        self.sel("os_icon", vm.id).fadeIn(200);
378
                    }
379
                }
380
            });
381
        },
382

    
383
        update_actions_layout: function(vm) {
384
        },
385

    
386
        post_update_vm: function(vm) {
387
            
388
            // skip update for these changes for performance issues
389
            if (vm.hasOnlyChange(["pending_action", "stats"])) { return };
390

    
391
            var index = this.table_data["vm_" + vm.id].index;
392
            params = this.get_vm_table_data(vm);
393
            this.table_data["vm_" + vm.id].params = params;
394
            data = this.table.fnGetData()[index];
395

    
396
            // do not recreate checkboxes and images to avoid messing
397
            // with user interaction
398
            this.table.fnUpdate(params[2], parseInt(index), 2);
399
            this.table.fnUpdate(params[3], parseInt(index), 3);
400
            this.table.fnUpdate(params[4], parseInt(index), 4);
401

    
402
            var active_class = vm.is_active() ? "active" : "inactive";
403
            this.vm(vm).removeClass("active").removeClass("inactive").addClass(active_class);
404
            $(this.vm(vm).find("td").get(4)).addClass("status");
405
            $(this.vm(vm).find("td").get(3)).addClass("flavor");
406
            $(this.vm(vm).find("td").get(2)).addClass("name");
407

    
408
            if (vm.status() == "ERROR") {
409
                this.vm(vm).removeClass("active").removeClass("inactive").addClass("error");
410
            }
411
            
412
            this.update_os_icon(vm);
413
            this.update_transition_state(vm);
414
        },
415

    
416
        update_os_icon: function(vm) {
417
            this.sel('os_icon', vm.id).attr('src', this.get_vm_icon_path(vm, "small"));
418
        },
419
        
420
        // vm specific event handlers
421
        set_vm_handlers: function(vm) {
422
        },
423

    
424
        // generic stuff to do on each view update
425
        // called once after each vm has been updated
426
        update_layout: function() {
427
            this.actions.update_layout();
428
        },
429

    
430
        // update vm details
431
        update_details: function(vm) {
432
        },
433
            
434
        get_vm_icon_os: function(vm) {
435
            var os = vm.get_os();
436
            var icons = window.os_icons || views.ListView.VM_OS_ICONS;
437
            if (icons.indexOf(os) == -1) {
438
                os = "unknown";
439
            }
440
            return os;
441
        },
442

    
443
        // TODO: move to views.utils (the method and the VM_OS_ICON vars)
444
        get_vm_icon_path: function(vm, icon_type) {
445
            var os = vm.get_os();
446
            var icons = window.os_icons || views.ListView.VM_OS_ICONS;
447

    
448
            if (icons.indexOf(os) == -1) {
449
                os = "unknown";
450
            }
451
            
452
            var st = "off";
453
            if (vm.is_active()) {
454
                st = "on"
455
            }
456

    
457
            return views.ListView.VM_OS_ICON_TPLS[icon_type].format(os, st);
458
        }
459
    })
460

    
461
    views.ListView.VM_OS_ICON_TPLS = {
462
        "small": "/static/icons/machines/small/{0}-{1}.png"
463
    }
464

    
465
    views.ListView.VM_OS_ICONS = window.os_icons || [];
466

    
467
    views.ListView.STATE_CLASSES = {
468
        'UNKNOWN':          ['error-state'],
469
        'BUILD':            ['build-state'],
470
        'REBOOT':           ['rebooting-state'],
471
        'STOPPED':          ['terminated-state'],
472
        'ACTIVE':           ['running-state'],
473
        'ERROR':            ['error-state'],
474
        'DELETED':          ['destroying-state'],
475
        'DESTROY':          ['destroying-state'],
476
        'BUILD_INIT':       ['build-state'], 
477
        'BUILD_COPY':       ['build-state'],
478
        'BUILD_FINAL':      ['build-state'],
479
        'SHUTDOWN':         ['shutting-state'],
480
        'START':            ['starting-state'],
481
        'CONNECT':          ['connecting-state'],
482
        'DISCONNECT':       ['disconnecting-state']
483
    };
484

    
485
})(this);