Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 6a3a5bf7

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

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

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

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

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

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

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

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

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

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

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

    
174
            this.update_details(vm);
175
            this.update_transition_state(vm);
176

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

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

    
197
                el.hide().appendTo(cont).show();
198
                $(window).trigger('resize');
199

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

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

    
237
        hide_indicator: function(vm) {
238
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
239
            this.update_transition_state(vm);
240
        },
241

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

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

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

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

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

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

    
290
            this.hovered = false;
291
        },
292

    
293
        action: function(name) {
294
            return $(this.el).find(".action-container." + name);
295
        },
296

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

    
305
        action_confirm: function(name) {
306
            return this.action(name).find("button.yes");
307
        },
308

    
309
        action_cancel: function(name) {
310
            return this.action(name).find("button.no");
311
        },
312

    
313
        hide_actions: function() {
314
            $(this.el).find("a").css("visibility", "hidden");
315
        },
316

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

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

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

    
397
            this.vm.bind("action:fail", this.update_layout)
398
            this.vm.bind("action:fail:reset", this.update_layout)
399
        },
400

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

    
414
            }, this),  _.bind(function() {
415
                if (self.hide) {
416
                    this.hovered = false;
417
                    this.update_layout();
418
                }
419
            }, this));
420

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

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

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

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

    
479

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

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

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

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

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

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

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

    
538
})(this);