Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 027bdc60

History | View | Annotate | Download (18.6 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

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

    
14
    // shortcuts
15
    var bb = root.Backbone;
16
    
17
    // logging
18
    var logger = new snf.logging.logger("SNF-VIEWS");
19
    var debug = _.bind(logger.debug, logger);
20
    
21
    // base class for views that contain/handle VMS
22
    views.VMListView = views.View.extend({
23

    
24
        // just a flag to identify that
25
        // views of this type handle vms
26
        vms_view: true,
27

    
28
        selectors: {},
29
        hide_actions: true,
30
        pane: "#machines-pane",
31
        metadata_view: undefined,
32

    
33
        initialize: function() {
34
            views.VMListView.__super__.initialize.call(this);
35

    
36
            this.set_storage_handlers();
37
            this.set_handlers();
38
            this.vms_updated_handler();
39

    
40
            this.connect_overlay = new views.VMConnectView();
41
            
42
        },
43

    
44
        // Helpers
45
        //
46
        // get element based on this.selectors key/value pairs
47
        sel: function(id, params) {
48
            if (!this.selectors[id]){ return };
49
            return $(this.selectors[id].format(params));
50
        },
51
        
52
        // vm element based on vm model instance provided
53
        vm: function(vm) {
54
            return this.sel('vm', vm.id);
55
        },
56
        
57
        // get vm model instance from DOM element
58
        vm_for_element: function(el) {
59
            return storage.vms.sel(this.vm_id_for_element(el));
60
        },
61
        
62

    
63
        // Event binding and stuff like that
64
        //
65
        set_storage_handlers: function() {
66
            storage.vms.bind("add", _.bind(this.vms_updated_handler, this, "add"));
67
            storage.vms.bind("change", _.bind(this.vms_updated_handler, this, "change"));
68
            storage.vms.bind("reset", _.bind(this.vms_updated_handler, this, "reset"));
69
            storage.vms.bind("remove", _.bind(this.vms_updated_handler, this, "remove"));
70
        },
71
        
72
        // vms updated triggered, update view vms
73
        vms_updated_handler: function (method, model, arg2, arg3) {
74
            var updated = storage.vms.models;
75
            if (method == "add") { updated = [model] };
76
            if (method == "change") { updated = [model] };
77
            if (method == "remove") { updated = [model] };
78

    
79
            if (method == "remove") {
80
                this.remove_vm(model)
81
                return;
82
            }
83

    
84
            this.update_vms(updated);
85
        },
86

    
87
        // create vm
88
        // append it on proper view container
89
        create_vm: function(vm) {
90
            // create dom element
91
            var vm_view = this.create_vm_element(vm);
92
            vm_view.find(".vm-actions").attr("id", this.view_id+"-actions-" + vm.id);
93
            var container = this.get_vm_container(vm)
94
            container.append(vm_view);
95
            vm_view.find(".action-indicator").text("");
96
            if (this.visible()) {
97
                container.show()
98
            }
99

    
100
            // initialize vm specific event handlers 
101
            this.__set_vm_handlers(vm);
102
        },
103
        
104
        // create vm dom element
105
        create_vm_element: function(vm) {
106
            // clone template
107
            return this.sel('tpl').clone().attr("id", this.id_tpl + vm.id)
108
        },
109

    
110
        // get proper vm container
111
        get_vm_container: function(vm) {
112
            if (vm.is_active()) {
113
                return this.sel("vm_cont_active");
114
            } else {
115
                return this.sel("vm_cont_terminated");
116
            }
117
        },
118

    
119
        // create and append inside the proper container the vm model
120
        // if it doesn't exist update vm data and make it visible
121
        add: function(vm) {
122
            // create if it does not exist
123
            if (this.vm(vm).length == 0) {
124
                this.create_vm(vm);
125
                this.vm(vm).show();
126
                this.post_add(vm);
127
            }
128

    
129
            return this.vm(vm);
130
        },
131
        
132
        // helpers for VMListView descendants
133
        post_add: function(vm) { throw "Not implemented" },
134
        set_vm_handlers: function(vm) { throw "Not implemented" },
135
        set_handlers: function() { throw "Not implemented" },
136
        update_layout: function() { throw "Not implemented" },
137
        post_update_vm: function(vm) { throw "Not implemented" },
138
        update_details: function(vm) {},
139
        
140
        // remove vm
141
        remove_vm: function(vm) {
142
            // FIXME: some kind of transiton ??? effect maybe ???
143
            this.vm(vm).remove();
144
            this.post_remove_vm(vm);
145
        },
146
        
147
        // remove all vms from view
148
        clear: function() {
149
            this.sel('vms').remove();
150
            this.__update_layout();
151
        },
152

    
153
        show: function() {
154
            views.VMListView.__super__.show.apply(this, arguments);
155
            if (!snf.config.update_hidden_views) {
156
                this.update_vms(storage.vms.models);
157
            }
158
        },
159

    
160
        // do update for provided vms, then update the view layout
161
        update_vms: function(vms) {
162

    
163
            if (!this.visible() && !snf.config.update_hidden_views) { return };
164

    
165
            _.each(vms, _.bind(function(vm){
166
                // vm will be removed
167
                // no need to update
168
                if (vm.get("status") == "DELETED") {
169
                    return;
170
                }
171

    
172
                // this won't add it additional times
173
                this.add(vm);
174
                this.update_vm(vm);
175
            }, this))
176
            
177
            // update view stuff
178
            this.__update_layout();
179
        },
180
        
181
        // update ui for the given vm
182
        update_vm: function(vm) {
183
            // do not update deleted state vms
184
            if (!vm || vm.get("status") == 'DELETED') { return };
185
            this.check_vm_container(vm);
186

    
187
            this.update_details(vm);
188
            this.update_transition_state(vm);
189

    
190
            if (this.action_views) {
191
                this.action_views[vm.id].update();
192
                this.action_views[vm.id].update_layout();
193
            }
194
            
195
            try {
196
                this.post_update_vm(vm);
197
            } catch (err) {};
198
        },
199

    
200
        // check if vm is placed properly within the view
201
        // container (e.g. some views might have different
202
        // containers for terminated or running machines
203
        check_vm_container: function(vm){
204
            var el = this.vm(vm);
205
            if (!el.length) { return };
206
            var self = this;
207
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
208
            if (el.parent()[0] != this.sel(selector)[0]) {
209
                var cont = this.sel(selector);
210
                var self = this;
211

    
212
                el.hide().appendTo(cont).show();
213
                $(window).trigger('resize');
214

    
215
                //el.fadeOut(200, function() {
216
                    //el.appendTo(cont); 
217
                    //el.fadeIn(200);
218
                    //self.sel(selector).show(function(){
219
                        //$(window).trigger("resize");
220
                    //});
221
                //});
222
            }
223
        },
224

    
225
        __update_layout: function() {
226
            this.update_layout();
227
        },
228
        
229
        // append handlers for vm specific events
230
        __set_vm_handlers: function(vm) {
231
            // show transition on vm status transit
232
            vm.bind('transition', _.bind(function(){this.show_transition(vm)}, this));
233
            this.set_vm_handlers(vm);
234
        },
235
        
236
        // is vm in transition ??? show the progress spinner
237
        update_transition_state: function(vm) {
238
            if (vm.in_transition() && !vm.pending_action){
239
                this.sel('vm_spinner', vm.id).show();
240
            } else {
241
                this.sel('vm_spinner', vm.id).hide();
242
            }
243
        },
244
        
245
        show_indicator: function(vm, action) {
246
            var action = action || vm.pending_action;
247
            this.sel('vm_wave', vm.id).hide();
248
            this.sel('vm_spinner', vm.id).hide();
249
            this.vm(vm).find(".action-indicator").removeClass().addClass(action + " action-indicator").show();
250
        },
251

    
252
        hide_indicator: function(vm) {
253
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
254
            this.update_transition_state(vm);
255
        },
256

    
257
        // display transition animations
258
        show_transition: function(vm) {
259
            var wave = this.sel('vm_wave', vm.id);
260
            if (!wave || !wave.length) { return }
261

    
262
            var src = wave.attr('src');
263
            // change src to force gif play from the first frame
264
            // animate for 500 ms then hide
265
            wave.attr('src', "").show();
266
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
267
                wave.hide();
268
            });
269
        },
270

    
271
        connect_to_console: function(vm) {
272
            vm.call("console", function(console_data) {
273
                var url = vm.get_console_url(console_data);
274
                snf.util.open_window(url, "Console", {});
275
            })
276
        }
277

    
278
    });
279
    
280
    // empty message view (just a wrapper to the element containing 
281
    // the empty information message)
282
    views.EmptyView = views.View.extend({
283
        el: '#emptymachineslist'
284
    })
285

    
286
    views.VMActionsView = views.View.extend({
287
        
288
        initialize: function(vm, parent, el, hide) {
289
            this.hide = hide || false;
290
            this.view = parent;
291
            this.vm = vm;
292
            this.vm_el = el;
293
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
294
            this.set_handlers();
295
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
296
            
297
            // state params
298
            this.selected_action = false;
299

    
300
            _.bindAll(this);
301
            window.acts = this;
302
            this.view_id = "vm_" + vm.id + "_actions";
303
            views.VMActionsView.__super__.initialize.call(this);
304

    
305
            this.hovered = false;
306
        },
307

    
308
        action: function(name) {
309
            return $(this.el).find(".action-container." + name);
310
        },
311

    
312
        action_link: function(name) {
313
            return this.action(name).find("a");
314
        },
315
        
316
        action_confirm_cont: function(name) {
317
            return this.action_confirm(name).parent();
318
        },
319

    
320
        action_confirm: function(name) {
321
            return this.action(name).find("button.yes");
322
        },
323

    
324
        action_cancel: function(name) {
325
            return this.action(name).find("button.no");
326
        },
327

    
328
        hide_actions: function() {
329
            $(this.el).find("a").css("visibility", "hidden");
330
        },
331

    
332
        // update the actions layout, depending on the selected actions
333
        update_layout: function() {
334
            try {
335
                // it doesn't seem to work without this
336
                // some serious debugging is needed to 
337
                // find out what is going on
338
                this.vm = storage.vms.get(this.vm.id);
339
                this.init_vm_handlers();
340
            } catch (err) { console.error(err); return }
341

    
342
            if (!this.vm) { return }
343
            
344
            // update selected action
345
            if (this.vm.pending_action) {
346
                this.selected_action = this.vm.pending_action;
347
            } else {
348
                this.selected_action = false;
349
            }
350
            
351
            // vm actions tha can be performed
352
            var actions = this.vm.get_available_actions();
353
            
354
            // had pending action but actions changed and now selected action is
355
            // not available, hide it from user
356
            if (this.selected_action && actions.indexOf(this.selected_action) == -1) {
357
                this.reset();
358
            }
359
            
360
            this.el.show();
361
            
362
            if ((this.selected_action || this.hovered) && !this.vm.action_error) {
363
                // show selected action
364
                $(this.el).show();
365
                $(this.el).find("a").css("visibility", "visible");
366
                // show action icon
367
                this.view.show_indicator(this.vm);
368
            } else {
369
                if (this.hide || this.vm.action_error) {
370
                    // view shows actions on machine hover
371
                    $(this.el).find("a").css("visibility", "hidden");
372
                } else {
373
                    if (!this.vm.action_error) {
374
                        // view shows actions always
375
                        $(this.el).find("a").css("visibility", "visible");
376
                        $(this.el).show();
377
                    }
378
                }
379
                
380
                this.view.hide_indicator(this.vm);
381
            }
382
            
383
            // update action link styles and shit
384
            _.each(models.VM.ACTIONS, function(action, index) {
385
                if (actions.indexOf(action) > -1) {
386
                    this.action(action).removeClass("disabled");
387
                    if (this.selected_action == action) {
388
                        this.action_confirm_cont(action).css('display', 'block');
389
                        this.action_confirm(action).show();
390
                        this.action(action).removeClass("disabled");
391
                        this.action_link(action).addClass("selected");
392
                    } else {
393
                        this.action_confirm_cont(action).hide();
394
                        this.action_confirm(action).hide();
395
                        this.action_link(action).removeClass("selected");
396
                    }
397
                } else {
398
                    this.action().hide();
399
                    this.action(action).addClass("disabled");
400
                    this.action_confirm(action).hide();
401
                }
402

    
403
            }, this);
404
        },
405
        
406
        init_vm_handlers: function() {
407
            try {
408
                this.vm.unbind("action:fail", this.update_layout)
409
                this.vm.unbind("action:fail:reset", this.update_layout)
410
            } catch (err) {};
411

    
412
            this.vm.bind("action:fail", this.update_layout)
413
            this.vm.bind("action:fail:reset", this.update_layout)
414
        },
415

    
416
        // bind event handlers
417
        set_handlers: function() {
418
            var self = this;
419
            var vm = this.vm;
420
            
421
            // initial hide
422
            if (this.hide) { $(this.el).hide() };
423
            
424
            // vm container hover (icon view)
425
            this.view.vm(this.vm).hover(_.bind(function() {
426
                this.hovered = true;
427
                this.update_layout();
428

    
429
            }, this),  _.bind(function() {
430
                this.hovered = false;
431
                this.update_layout();
432
            }, this));
433

    
434
            
435
            // action links events
436
            _.each(models.VM.ACTIONS, function(action) {
437
                var action = action;
438
                // indicator hovers
439
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
440
                    self.view.show_indicator(self.vm, action);
441
                }, function() {
442
                    // clear or show selected action indicator
443
                    if (self.vm.pending_action) {
444
                        self.view.show_indicator(self.vm);
445
                    } else {
446
                        self.view.hide_indicator(self.vm);
447
                    }
448
                })
449
                
450
                // action links click events
451
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
452
                    ev.preventDefault();
453
                    self.set(action);
454
                }).data("action", action);
455

    
456
                // confirms
457
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
458
                    ev.preventDefault();
459
                    self.reset();
460
                });
461

    
462
                // cancels
463
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
464
                    ev.preventDefault();
465
                    self.vm.call(action);
466
                    self.reset();
467
                });
468
            }, this);
469
        },
470
        
471
        // reset actions
472
        reset: function() {
473
            var prev_action = this.selected_action;
474
            this.selected_action = false;
475
            this.vm.clear_pending_action();
476
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
477
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
478
        },
479
        
480
        // set selected action
481
        set: function(action_name) {
482
            this.selected_action = action_name;
483
            this.vm.update_pending_action(this.selected_action);
484
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
485
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
486
        },
487

    
488
        update: function() {
489
        }
490
    })
491

    
492

    
493
    views.VMActionsView.STATUS_ACTIONS = { 
494
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
495
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
496
        'console':       ['ACTIVE'],
497
        'start':         ['UNKOWN', 'STOPPED'],
498
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
499
    };
500

    
501
    // UI helpers
502
    var uihelpers = snf.ui.helpers = {};
503
    
504
    // OS icon helpers
505
    var os_icon = uihelpers.os_icon = function(os) {
506
        var icons = window.os_icons;
507
        if (icons.indexOf(os) == -1) {
508
            os = "unknown";
509
        }
510
        return os;
511
    }
512

    
513
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
514
        size = size || "small";
515
        if (active == undefined) { active = true };
516

    
517
        var icon = os_icon(os);
518
        if (active) {
519
            icon = icon + "-on";
520
        } else {
521
            icon = icon + "-off";
522
        }
523

    
524
        return "/static/icons/machines/{0}/{1}.png".format(size, icon)
525
    }
526

    
527
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
528
        attrs = attrs || {};
529
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
530
    }
531

    
532
    // VM Icon helpers
533
    //
534
    // identify icon
535
    var vm_icon = uihelpers.vm_icon = function(vm) {
536
        return os_icon(vm.get_os());
537
    }
538
    
539
    // get icon url
540
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
541
        return os_icon_path(vm.get_os(), size, vm.is_active());
542
    }
543
    
544
    // get icon IMG tag
545
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
546
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
547
    }
548
    
549
    snf.ui = _.extend(snf.ui, bb.Events);
550

    
551
})(this);