Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 643de8c0

History | View | Annotate | Download (18.3 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
        // do update for provided vms, then update the view layout
154
        update_vms: function(vms) {
155
            _.each(vms, _.bind(function(vm){
156
                // vm will be removed
157
                // no need to update
158
                if (vm.get("status") == "DELETED") {
159
                    return;
160
                }
161

    
162
                // this won't add it additional times
163
                this.add(vm);
164
                this.update_vm(vm);
165
            }, this))
166
            
167
            // update view stuff
168
            this.__update_layout();
169
        },
170
        
171
        // update ui for the given vm
172
        update_vm: function(vm) {
173
            // do not update deleted state vms
174
            if (!vm || vm.get("status") == 'DELETED') { return };
175
            this.check_vm_container(vm);
176

    
177
            this.update_details(vm);
178
            this.update_transition_state(vm);
179

    
180
            if (this.action_views) {
181
                this.action_views[vm.id].update();
182
                this.action_views[vm.id].update_layout();
183
            }
184
            
185
            this.post_update_vm(vm);
186
        },
187

    
188
        // check if vm is placed properly within the view
189
        // container (e.g. some views might have different
190
        // containers for terminated or running machines
191
        check_vm_container: function(vm){
192
            var el = this.vm(vm);
193
            if (!el.length) { return };
194
            var self = this;
195
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
196
            if (el.parent()[0] != this.sel(selector)[0]) {
197
                var cont = this.sel(selector);
198
                var self = this;
199

    
200
                el.hide().appendTo(cont).show();
201
                $(window).trigger('resize');
202

    
203
                //el.fadeOut(200, function() {
204
                    //el.appendTo(cont); 
205
                    //el.fadeIn(200);
206
                    //self.sel(selector).show(function(){
207
                        //$(window).trigger("resize");
208
                    //});
209
                //});
210
            }
211
        },
212

    
213
        __update_layout: function() {
214
            this.update_layout();
215
        },
216
        
217
        // append handlers for vm specific events
218
        __set_vm_handlers: function(vm) {
219
            // show transition on vm status transit
220
            vm.bind('transition', _.bind(function(){this.show_transition(vm)}, this));
221
            this.set_vm_handlers(vm);
222
        },
223
        
224
        // is vm in transition ??? show the progress spinner
225
        update_transition_state: function(vm) {
226
            if (vm.in_transition() && !vm.pending_action){
227
                this.sel('vm_spinner', vm.id).show();
228
            } else {
229
                this.sel('vm_spinner', vm.id).hide();
230
            }
231
        },
232
        
233
        show_indicator: function(vm, action) {
234
            var action = action || vm.pending_action;
235
            this.sel('vm_wave', vm.id).hide();
236
            this.sel('vm_spinner', vm.id).hide();
237
            this.vm(vm).find(".action-indicator").removeClass().addClass(action + " action-indicator").show();
238
        },
239

    
240
        hide_indicator: function(vm) {
241
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
242
            this.update_transition_state(vm);
243
        },
244

    
245
        // display transition animations
246
        show_transition: function(vm) {
247
            var wave = this.sel('vm_wave', vm.id);
248
            if (!wave || !wave.length) { return }
249

    
250
            var src = wave.attr('src');
251
            // change src to force gif play from the first frame
252
            // animate for 500 ms then hide
253
            wave.attr('src', "").show();
254
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
255
                wave.hide();
256
            });
257
        },
258

    
259
        connect_to_console: function(vm) {
260
            vm.call("console", function(console_data) {
261
                var url = vm.get_console_url(console_data);
262
                snf.util.open_window(url, "Console", {});
263
            })
264
        }
265

    
266
    });
267
    
268
    // empty message view (just a wrapper to the element containing 
269
    // the empty information message)
270
    views.EmptyView = views.View.extend({
271
        el: '#emptymachineslist'
272
    })
273

    
274
    views.VMActionsView = views.View.extend({
275
        
276
        initialize: function(vm, parent, el, hide) {
277
            this.hide = hide || false;
278
            this.view = parent;
279
            this.vm = vm;
280
            this.vm_el = el;
281
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
282
            this.set_handlers();
283
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
284
            
285
            // state params
286
            this.selected_action = false;
287

    
288
            _.bindAll(this);
289
            window.acts = this;
290
            this.view_id = "vm_" + vm.id + "_actions";
291
            views.VMActionsView.__super__.initialize.call(this);
292

    
293
            this.hovered = false;
294
        },
295

    
296
        action: function(name) {
297
            return $(this.el).find(".action-container." + name);
298
        },
299

    
300
        action_link: function(name) {
301
            return this.action(name).find("a");
302
        },
303
        
304
        action_confirm_cont: function(name) {
305
            return this.action_confirm(name).parent();
306
        },
307

    
308
        action_confirm: function(name) {
309
            return this.action(name).find("button.yes");
310
        },
311

    
312
        action_cancel: function(name) {
313
            return this.action(name).find("button.no");
314
        },
315

    
316
        hide_actions: function() {
317
            $(this.el).find("a").css("visibility", "hidden");
318
        },
319

    
320
        // update the actions layout, depending on the selected actions
321
        update_layout: function() {
322
            try {
323
                // it doesn't seem to work without this
324
                // some serious debugging is needed to 
325
                // find out what is going on
326
                this.vm = storage.vms.get(this.vm.id);
327
                this.init_vm_handlers();
328
            } catch (err) { console.error(err); return }
329

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

    
391
            }, this);
392
        },
393
        
394
        init_vm_handlers: function() {
395
            try {
396
                this.vm.unbind("action:fail", this.update_layout)
397
                this.vm.unbind("action:fail:reset", this.update_layout)
398
            } catch (err) {};
399

    
400
            this.vm.bind("action:fail", this.update_layout)
401
            this.vm.bind("action:fail:reset", this.update_layout)
402
        },
403

    
404
        // bind event handlers
405
        set_handlers: function() {
406
            var self = this;
407
            var vm = this.vm;
408
            
409
            // initial hide
410
            if (this.hide) { $(this.el).hide() };
411
            
412
            // vm container hover (icon view)
413
            this.view.vm(this.vm).hover(_.bind(function() {
414
                this.hovered = true;
415
                this.update_layout();
416

    
417
            }, this),  _.bind(function() {
418
                this.hovered = false;
419
                this.update_layout();
420
            }, this));
421

    
422
            
423
            // action links events
424
            _.each(models.VM.ACTIONS, function(action) {
425
                var action = action;
426
                // indicator hovers
427
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
428
                    self.view.show_indicator(self.vm, action);
429
                }, function() {
430
                    // clear or show selected action indicator
431
                    if (self.vm.pending_action) {
432
                        self.view.show_indicator(self.vm);
433
                    } else {
434
                        self.view.hide_indicator(self.vm);
435
                    }
436
                })
437
                
438
                // action links click events
439
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
440
                    ev.preventDefault();
441
                    self.set(action);
442
                }).data("action", action);
443

    
444
                // confirms
445
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
446
                    ev.preventDefault();
447
                    self.reset();
448
                });
449

    
450
                // cancels
451
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
452
                    ev.preventDefault();
453
                    self.vm.call(action);
454
                    self.reset();
455
                });
456
            }, this);
457
        },
458
        
459
        // reset actions
460
        reset: function() {
461
            var prev_action = this.selected_action;
462
            this.selected_action = false;
463
            this.vm.clear_pending_action();
464
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
465
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
466
        },
467
        
468
        // set selected action
469
        set: function(action_name) {
470
            this.selected_action = action_name;
471
            this.vm.update_pending_action(this.selected_action);
472
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
473
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
474
        },
475

    
476
        update: function() {
477
        }
478
    })
479

    
480

    
481
    views.VMActionsView.STATUS_ACTIONS = { 
482
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
483
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
484
        'console':       ['ACTIVE'],
485
        'start':         ['UNKOWN', 'STOPPED'],
486
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
487
    };
488

    
489
    // UI helpers
490
    var uihelpers = snf.ui.helpers = {};
491
    
492
    // OS icon helpers
493
    var os_icon = uihelpers.os_icon = function(os) {
494
        var icons = window.os_icons;
495
        if (icons.indexOf(os) == -1) {
496
            os = "unknown";
497
        }
498
        return os;
499
    }
500

    
501
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
502
        size = size || "small";
503
        if (active == undefined) { active = true };
504

    
505
        var icon = os_icon(os);
506
        if (active) {
507
            icon = icon + "-on";
508
        } else {
509
            icon = icon + "-off";
510
        }
511

    
512
        return "/static/icons/machines/{0}/{1}.png".format(size, icon)
513
    }
514

    
515
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
516
        attrs = attrs || {};
517
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
518
    }
519

    
520
    // VM Icon helpers
521
    //
522
    // identify icon
523
    var vm_icon = uihelpers.vm_icon = function(vm) {
524
        return os_icon(vm.get_os());
525
    }
526
    
527
    // get icon url
528
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
529
        return os_icon_path(vm.get_os(), size, vm.is_active());
530
    }
531
    
532
    // get icon IMG tag
533
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
534
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
535
    }
536
    
537
    snf.ui = _.extend(snf.ui, bb.Events);
538

    
539
})(this);