Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 8c923194

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

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

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

    
76
            if (method == "remove") {
77
                this.remove_vm(model)
78
                return;
79
            }
80

    
81
            this.update_vms(updated);
82
        },
83

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

    
97
            // initialize vm specific event handlers 
98
            this.__set_vm_handlers(vm);
99
        },
100
        
101
        // create vm dom element
102
        create_vm_element: function(vm) {
103
            // clone template
104
            return this.sel('tpl').clone().attr("id", this.id_tpl.format([vm.id]))
105
        },
106

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

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

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

    
150
        // do update for provided vms, then update the view layout
151
        update_vms: function(vms) {
152
            _.each(vms, _.bind(function(vm){
153
                // vm will be removed
154
                // no need to update
155
                if (vm.get("status") == "DELETED") {
156
                    return;
157
                }
158

    
159
                // this won't add it additional times
160
                this.add(vm);
161
                this.update_vm(vm);
162
            }, this))
163
            
164
            // update view stuff
165
            this.__update_layout();
166
        },
167
        
168
        // update ui for the given vm
169
        update_vm: function(vm) {
170
            this.check_vm_container(vm);
171

    
172
            this.update_details(vm);
173
            this.update_transition_state(vm);
174

    
175
            if (this.action_views) {
176
                this.action_views[vm.id].update();
177
                this.action_views[vm.id].update_layout();
178
            }
179
            
180
            this.post_update_vm(vm);
181
        },
182

    
183
        // check if vm is placed properly within the view
184
        // container (e.g. some views might have different
185
        // containers for terminated or running machines
186
        check_vm_container: function(vm){
187
            var el = this.vm(vm);
188
            if (!el.length) { return };
189
            var self = this;
190
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
191
            if (el.parent()[0] != this.sel(selector)[0]) {
192
                var cont = this.sel(selector);
193
                var self = this;
194
                el.fadeOut(200, function() {
195
                    el.appendTo(cont); 
196
                    el.fadeIn(200);
197
                    self.sel(selector).show(function(){
198
                        $(window).trigger("resize");
199
                    });
200
                });
201
            }
202
        },
203

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

    
231
        hide_indicator: function(vm) {
232
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
233
            this.update_transition_state(vm);
234
        },
235

    
236
        // display transition animations
237
        show_transition: function(vm) {
238
            var wave = this.sel('vm_wave', vm.id);
239
            if (!wave || !wave.length) { return }
240

    
241
            var src = wave.attr('src');
242
            // change src to force gif play from the first frame
243
            // animate for 500 ms then hide
244
            wave.attr('src', "").show();
245
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
246
                wave.hide();
247
            });
248
        },
249

    
250
        connect_to_console: function(vm) {
251
            vm.call("console", function(console_data) {
252
                var url = vm.get_console_url(console_data);
253
                snf.util.open_window(url, "Console", {});
254
            })
255
        }
256

    
257
    });
258
    
259
    // empty message view (just a wrapper to the element containing 
260
    // the empty information message)
261
    views.EmptyView = views.View.extend({
262
        el: '#emptymachineslist'
263
    })
264

    
265
    views.VMActionsView = views.View.extend({
266
        
267
        initialize: function(vm, parent, el, hide) {
268
            this.hide = hide || false;
269
            this.view = parent;
270
            this.vm = vm;
271
            this.vm_el = el;
272
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
273
            this.set_handlers();
274
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
275
            
276
            // state params
277
            this.selected_action = false;
278

    
279
            _.bindAll(this);
280
            window.acts = this;
281
            this.view_id = "vm_" + vm.id + "_actions";
282
            views.VMActionsView.__super__.initialize.call(this);
283

    
284
        },
285

    
286
        action: function(name) {
287
            return $(this.el).find(".action-container." + name);
288
        },
289

    
290
        action_link: function(name) {
291
            return this.action(name).find("a");
292
        },
293
        
294
        action_confirm_cont: function(name) {
295
            return this.action_confirm(name).parent();
296
        },
297

    
298
        action_confirm: function(name) {
299
            return this.action(name).find("button.yes");
300
        },
301

    
302
        action_cancel: function(name) {
303
            return this.action(name).find("button.no");
304
        },
305

    
306
        hide_actions: function() {
307
            $(this.el).find("a").css("visibility", "hidden");
308
        },
309

    
310
        // update the actions layout, depending on the selected actions
311
        update_layout: function() {
312
            try {
313
                // it doesn't seem to work without this
314
                // some serious debugging is needed to 
315
                // find out what is going on
316
                this.vm = storage.vms.get(this.vm.id);
317
            } catch (err) { return }
318

    
319
            if (!this.vm) { return }
320

    
321
            // update selected action
322
            if (this.vm.pending_action) {
323
                this.selected_action = this.vm.pending_action;
324
            } else {
325
                this.selected_action = false;
326
            }
327
            
328
            // vm actions tha can be performed
329
            var actions = this.vm.get_available_actions();
330
            
331
            // had pending action but actions changed and now selected action is
332
            // not available, hide it from user
333
            if (this.selected_action && actions.indexOf(this.selected_action) == -1) {
334
                this.reset();
335
            }
336
            
337
            if (this.selected_action) {
338
                // show selected action
339
                $(this.el).show();
340
                $(this.el).find("a").css("visibility", "visible");
341

    
342
                // show action icon
343
                this.view.show_indicator(this.vm);
344
            } else {
345
                if (this.hide) {
346
                    // view shows actions on machine hover
347
                    $(this.el).find("a").css("visibility", "hidden");
348
                } else {
349
                    // view shows actions always
350
                    $(this.el).find("a").css("visibility", "visible");
351
                    $(this.el).show();
352
                }
353
                
354
                this.view.hide_indicator(this.vm);
355
            }
356
            
357
            // update action link styles and shit
358
            _.each(models.VM.ACTIONS, function(action, index) {
359
                if (actions.indexOf(action) > -1) {
360
                    this.action(action).removeClass("disabled");
361
                    if (this.selected_action == action) {
362
                        this.action_confirm_cont(action).css('display', 'block');
363
                        this.action_confirm(action).show();
364
                        this.action(action).removeClass("disabled");
365
                        this.action_link(action).addClass("selected");
366
                    } else {
367
                        this.action_confirm_cont(action).hide();
368
                        this.action_confirm(action).hide();
369
                        this.action_link(action).removeClass("selected");
370
                    }
371
                } else {
372
                    this.action().hide();
373
                    this.action(action).addClass("disabled");
374
                    this.action_confirm(action).hide();
375
                }
376

    
377
            }, this);
378
        },
379
        
380
        // bind event handlers
381
        set_handlers: function() {
382
            var self = this;
383
            var vm = this.vm;
384
            
385
            // initial hide
386
            if (this.hide) { $(this.el).hide() };
387
            
388
            // vm container hover (icon view)
389
            this.view.vm(this.vm).hover(function(){
390
                $(self.el).show();
391
                $(self.el).find("a").css("visibility", "visible");
392

    
393
            }, function() {
394
                if (self.hide) {
395
                    // icon view
396
                    if (!self.selected_action) {
397
                        $(self.el).find("a").css("visibility", "hidden");
398
                    } else {
399
                        // single view (always visible) or icon view with
400
                        // selected action
401
                        $(self.el).show();
402
                        $(self.el).find("a").css("visibility", "visible");
403
                    }
404
                }
405
            });
406
            
407
            // action links events
408
            _.each(models.VM.ACTIONS, function(action) {
409
                var action = action;
410
                // indicator hovers
411
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
412
                    self.view.show_indicator(self.vm, action);
413
                }, function() {
414
                    // clear or show selected action indicator
415
                    if (self.vm.pending_action) {
416
                        self.view.show_indicator(self.vm);
417
                    } else {
418
                        self.view.hide_indicator(self.vm);
419
                    }
420
                })
421
                
422
                // action links click events
423
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
424
                    ev.preventDefault();
425
                    self.set(action);
426
                }).data("action", action);
427

    
428
                // confirms
429
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
430
                    ev.preventDefault();
431
                    self.reset();
432
                });
433

    
434
                // cancels
435
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
436
                    ev.preventDefault();
437
                    self.vm.call(action);
438
                    self.reset();
439
                });
440
            }, this);
441
        },
442
        
443
        // reset actions
444
        reset: function() {
445
            var prev_action = this.selected_action;
446
            this.selected_action = false;
447
            this.vm.clear_pending_action();
448
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
449
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
450
        },
451
        
452
        // set selected action
453
        set: function(action_name) {
454
            this.selected_action = action_name;
455
            this.vm.update_pending_action(this.selected_action);
456
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
457
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
458
        },
459

    
460
        update: function() {
461
        }
462
    })
463

    
464

    
465
    views.VMActionsView.STATUS_ACTIONS = { 
466
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
467
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
468
        'console':       ['ACTIVE'],
469
        'start':         ['UNKOWN', 'STOPPED'],
470
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
471
    };
472

    
473
    // UI helpers
474
    var uihelpers = snf.ui.helpers = {};
475

    
476
    // VM Icon helpers
477
    //
478
    // identify icon
479
    var vm_icon = uihelpers.vm_icon = function(vm) {
480
        var os = vm.get_os();
481
        var icons = window.os_icons;
482
        if (icons.indexOf(os) == -1) {
483
            os = "unknown";
484
        }
485
        return os;
486
    }
487
    
488
    // get icon url
489
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
490
        size = size || "small";
491

    
492
        var icon = vm_icon(vm);
493
        if (vm.is_active()) {
494
            icon = icon + "-on";
495
        } else {
496
            icon = icon + "-off";
497
        }
498

    
499
        return "/static/icons/machines/{0}/{1}.png".format(size, icon)
500
    }
501
    
502
    // get icon IMG tag
503
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
504
        attrs = attrs || {};
505
        return '<img src="{0}" />'.format(vm_icon_path(vm, size));
506
    }
507
    
508
    snf.ui = _.extend(snf.ui, bb.Events);
509

    
510
})(this);