Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 123fd2f5

History | View | Annotate | Download (20.1 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
    var hasKey = Object.prototype.hasOwnProperty;
22

    
23
    // base class for views that contain/handle VMS
24
    views.VMListView = views.View.extend({
25

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

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

    
35
        initialize: function() {
36
            views.VMListView.__super__.initialize.call(this);
37
            this._vm_els = {};
38
            this.set_storage_handlers();
39
            this.set_handlers();
40
            this.vms_updated_handler();
41
            this.connect_overlay = new views.VMConnectView();
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
            if (hasKey.call(this._vm_els, vm.id)) {
55
                ret = this._vm_els[vm.id];
56
            } else {
57
                return $([]);
58
            }
59

    
60
            return ret;
61
        },
62
        
63
        // get vm model instance from DOM element
64
        vm_for_element: function(el) {
65
            return storage.vms.sel(this.vm_id_for_element(el));
66
        },
67
        
68

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

    
85
            if (method == "remove") {
86
                this.remove_vm(model)
87
                return;
88
            }
89
            
90
            this.update_vms(updated);
91
        },
92

    
93
        // create vm
94
        // append it on proper view container
95
        create_vm: function(vm) {
96
            // create dom element
97
            var vm_view = this.create_vm_element(vm);
98
            vm_view.find(".vm-actions").attr("id", this.view_id+"-actions-" + vm.id);
99
            this._vm_els[vm.id] = vm_view;
100
            var container = this.get_vm_container(vm);
101
            container.append(vm_view);
102
            vm_view.find(".action-indicator").text("");
103
            if (this.visible()) {
104
                container.show()
105
            }
106

    
107
            // initialize vm specific event handlers 
108
            this.__set_vm_handlers(vm);
109
            return vm_view;
110
        },
111
        
112
        // create vm dom element
113
        create_vm_element: function(vm) {
114
            // clone template
115
            return this.sel('tpl').clone().attr("id", this.id_tpl + vm.id)
116
        },
117

    
118
        // get proper vm container
119
        get_vm_container: function(vm) {
120
            if (vm.is_active()) {
121
                return this.sel("vm_cont_active");
122
            } else {
123
                return this.sel("vm_cont_terminated");
124
            }
125
        },
126

    
127
        // create and append inside the proper container the vm model
128
        // if it doesn't exist update vm data and make it visible
129
        add: function(vm) {
130
            // create if it does not exist
131
            if (!hasKey.call(this._vm_els, vm.id)) {
132
                var el = this.create_vm(vm);
133
                el.show();
134
                this.post_add(vm);
135
            }
136

    
137
            return this.vm(vm);
138
        },
139
        
140
        // helpers for VMListView descendants
141
        post_add: function(vm) { throw "Not implemented" },
142
        set_vm_handlers: function(vm) { throw "Not implemented" },
143
        set_handlers: function() { throw "Not implemented" },
144
        update_layout: function() { throw "Not implemented" },
145
        post_update_vm: function(vm) { throw "Not implemented" },
146
        update_details: function(vm) {},
147
        
148
        // remove vm
149
        remove_vm: function(vm) {
150
            // FIXME: some kind of transiton ??? effect maybe ???
151
            this.vm(vm).remove();
152
            this.post_remove_vm(vm);
153
            if (hasKey.call(this._vm_els, vm.id)) {
154
                delete this._vm_els[vm.id];
155
            }
156
        },
157
        
158
        // remove all vms from view
159
        clear: function() {
160
            this.sel('vms').remove();
161
            this.__update_layout();
162
        },
163

    
164
        show: function() {
165
            views.VMListView.__super__.show.apply(this, arguments);
166
            if (storage.vms.length == 0) { this.hide() };
167
            if (!snf.config.update_hidden_views) {
168
                this.update_vms(storage.vms.models);
169
            }
170
        },
171

    
172
        // do update for provided vms, then update the view layout
173
        update_vms: function(vms) {
174
            if (!this.visible() && !snf.config.update_hidden_views) { return };
175

    
176
            _.each(vms, _.bind(function(vm){
177
                // vm will be removed
178
                // no need to update
179
                if (vm.get("status") == "DELETED") {
180
                    return;
181
                }
182

    
183
                // this won't add it additional times
184
                this.add(vm);
185
                this.update_vm(vm);
186
            }, this))
187
            
188
            // update view stuff
189
            this.__update_layout();
190
        },
191
        
192
        // update ui for the given vm
193
        update_vm: function(vm) {
194
            // do not update deleted state vms
195
            if (!vm || vm.get("status") == 'DELETED') { return };
196
            this.check_vm_container(vm);
197

    
198
            this.update_details(vm);
199
            this.update_transition_state(vm);
200

    
201
            if (this.action_views) {
202
                this.action_views[vm.id].update();
203
                this.action_views[vm.id].update_layout();
204
            }
205
            
206
            try {
207
                this.post_update_vm(vm);
208
            } catch (err) {};
209
        },
210

    
211
        // check if vm is placed properly within the view
212
        // container (e.g. some views might have different
213
        // containers for terminated or running machines
214
        check_vm_container: function(vm){
215
            if (vm.state() == "DESTROY") { return };
216
            var el = this.vm(vm);
217
            if (!el.length) { return };
218
            var self = this;
219
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
220
            if (el.parent()[0] != this.sel(selector)[0]) {
221
                var cont = this.sel(selector);
222
                var self = this;
223

    
224
                el.hide().appendTo(cont).fadeIn(300);
225
                $(window).trigger('resize');
226

    
227
                //el.fadeOut(200, function() {
228
                    //el.appendTo(cont); 
229
                    //el.fadeIn(200);
230
                    //self.sel(selector).show(function(){
231
                        //$(window).trigger("resize");
232
                    //});
233
                //});
234
            }
235
        },
236

    
237
        __update_layout: function() {
238
            this.update_layout();
239
        },
240
        
241
        // append handlers for vm specific events
242
        __set_vm_handlers: function(vm) {
243
            // show transition on vm status transit
244
            vm.bind('transition', _.bind(function(){this.show_transition(vm)}, this));
245
            this.set_vm_handlers(vm);
246
        },
247
        
248
        // is vm in transition ??? show the progress spinner
249
        update_transition_state: function(vm) {
250
            if (vm.in_transition() && !vm.pending_action){
251
                this.sel('vm_spinner', vm.id).show();
252
            } else {
253
                this.sel('vm_spinner', vm.id).hide();
254
            }
255
        },
256
        
257
        show_indicator: function(vm, action) {
258
            var action = action || vm.pending_action;
259
            this.sel('vm_wave', vm.id).hide();
260
            this.sel('vm_spinner', vm.id).hide();
261
            this.vm(vm).find(".action-indicator").removeClass().addClass(action + " action-indicator").show();
262
        },
263

    
264
        hide_indicator: function(vm) {
265
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
266
            this.update_transition_state(vm);
267
        },
268

    
269
        // display transition animations
270
        show_transition: function(vm) {
271
            var wave = this.sel('vm_wave', vm.id);
272
            if (!wave || !wave.length) { return }
273

    
274
            var src = wave.attr('src');
275
            // change src to force gif play from the first frame
276
            // animate for 500 ms then hide
277
            wave.attr('src', "").show();
278
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
279
                wave.hide();
280
            });
281
        },
282

    
283
        connect_to_console: function(vm) {
284
            vm.call("console", function(console_data) {
285
                var url = vm.get_console_url(console_data);
286
                snf.util.open_window(url, "VM_" + vm.get("id") + "_CONSOLE", {});
287
            })
288
        }
289

    
290
    });
291
    
292
    // empty message view (just a wrapper to the element containing 
293
    // the empty information message)
294
    views.EmptyView = views.View.extend({
295
        el: '#emptymachineslist'
296
    })
297

    
298
    views.VMActionsView = views.View.extend({
299
        
300
        initialize: function(vm, parent, el, hide) {
301
            this.hide = hide || false;
302
            this.view = parent;
303
            this.vm = vm;
304
            this.vm_el = el;
305
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
306
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
307
            
308
            // state params
309
            this.selected_action = false;
310

    
311
            _.bindAll(this);
312
            window.acts = this;
313
            this.view_id = "vm_" + vm.id + "_actions";
314
            views.VMActionsView.__super__.initialize.call(this);
315

    
316
            this.hovered = false;
317
            this.set_hover_handlers();
318
        },
319

    
320
        action: function(name) {
321
            return $(this.el).find(".action-container." + name);
322
        },
323

    
324
        action_link: function(name) {
325
            return this.action(name).find("a");
326
        },
327
        
328
        action_confirm_cont: function(name) {
329
            return this.action_confirm(name).parent();
330
        },
331

    
332
        action_confirm: function(name) {
333
            return this.action(name).find("button.yes");
334
        },
335

    
336
        action_cancel: function(name) {
337
            return this.action(name).find("button.no");
338
        },
339

    
340
        hide_actions: function() {
341
            $(this.el).find("a").css("visibility", "hidden");
342
        },
343

    
344
        // update the actions layout, depending on the selected actions
345
        update_layout: function() {
346
            try {
347
                // it doesn't seem to work without this
348
                // some serious debugging is needed to 
349
                // find out what is going on
350
                this.vm = storage.vms.get(this.vm.id);
351
                this.init_vm_handlers();
352
            } catch (err) { console.error(err); return }
353

    
354
            if (!this.vm) { return }
355
            
356
            if (!this.hovered && !this.vm.has_pending_action() && this.hide && !this.vm.action_error) { 
357
                this.el.hide();
358
                this.view.hide_indicator(this.vm);
359
                return 
360
            };
361

    
362
            if (!this.el.is(":visible") && !this.hide) { return };
363
            if (!this.handlers_initialized) { this.set_handlers(); }
364

    
365

    
366
            // update selected action
367
            if (this.vm.pending_action) {
368
                this.selected_action = this.vm.pending_action;
369
            } else {
370
                this.selected_action = false;
371
            }
372
            
373
            // vm actions tha can be performed
374
            var actions = this.vm.get_available_actions();
375
            
376
            // had pending action but actions changed and now selected action is
377
            // not available, hide it from user
378
            if (this.selected_action && actions.indexOf(this.selected_action) == -1) {
379
                this.reset();
380
            }
381
            
382
            this.el.show();
383
            
384
            if ((this.selected_action || this.hovered) && !this.vm.action_error) {
385
                // show selected action
386
                $(this.el).show();
387
                $(this.el).find("a").css("visibility", "visible");
388
                // show action icon
389
                this.view.show_indicator(this.vm);
390
            } else {
391
                if (this.hide || this.vm.action_error) {
392
                    // view shows actions on machine hover
393
                    $(this.el).find("a").css("visibility", "hidden");
394
                } else {
395
                    if (!this.vm.action_error) {
396
                        // view shows actions always
397
                        $(this.el).find("a").css("visibility", "visible");
398
                        $(this.el).show();
399
                    }
400
                }
401
                
402
                this.view.hide_indicator(this.vm);
403
            }
404
            
405
            // update action link styles and shit
406
            _.each(models.VM.ACTIONS, function(action, index) {
407
                if (actions.indexOf(action) > -1) {
408
                    this.action(action).removeClass("disabled");
409
                    if (this.selected_action == action) {
410
                        this.action_confirm_cont(action).css('display', 'block');
411
                        this.action_confirm(action).show();
412
                        this.action(action).removeClass("disabled");
413
                        this.action_link(action).addClass("selected");
414
                    } else {
415
                        this.action_confirm_cont(action).hide();
416
                        this.action_confirm(action).hide();
417
                        this.action_link(action).removeClass("selected");
418
                    }
419
                } else {
420
                    this.action().hide();
421
                    this.action(action).addClass("disabled");
422
                    this.action_confirm(action).hide();
423
                }
424

    
425
            }, this);
426
        },
427
        
428
        init_vm_handlers: function() {
429
            try {
430
                this.vm.unbind("action:fail", this.update_layout)
431
                this.vm.unbind("action:fail:reset", this.update_layout)
432
            } catch (err) {};
433

    
434
            this.vm.bind("action:fail", this.update_layout)
435
            this.vm.bind("action:fail:reset", this.update_layout)
436
        },
437
        
438
        set_hover_handlers: function() {
439
            // vm container hover (icon view)
440
            this.view.vm(this.vm).hover(_.bind(function() {
441
                this.hovered = true;
442
                this.update_layout();
443

    
444
            }, this),  _.bind(function() {
445
                this.hovered = false;
446
                this.update_layout();
447
            }, this));
448
        },
449

    
450
        // bind event handlers
451
        set_handlers: function() {
452
            var self = this;
453
            var vm = this.vm;
454
            
455
            // initial hide
456
            if (this.hide) { $(this.el).hide() };
457
            
458
            // action links events
459
            _.each(models.VM.ACTIONS, function(action) {
460
                var action = action;
461
                // indicator hovers
462
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
463
                    self.view.show_indicator(self.vm, action);
464
                }, function() {
465
                    // clear or show selected action indicator
466
                    if (self.vm.pending_action) {
467
                        self.view.show_indicator(self.vm);
468
                    } else {
469
                        self.view.hide_indicator(self.vm);
470
                    }
471
                })
472
                
473
                // action links click events
474
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
475
                    ev.preventDefault();
476
                    self.set(action);
477
                }).data("action", action);
478

    
479
                // confirms
480
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
481
                    ev.preventDefault();
482
                    self.reset();
483
                });
484

    
485
                // cancels
486
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
487
                    ev.preventDefault();
488
                    // override console
489
                    // ui needs to act (open the console window)
490
                    if (action == "console") {
491
                        self.view.connect_to_console(self.vm);
492
                    } else {
493
                        self.vm.call(action);
494
                    }
495
                    self.reset();
496
                });
497
            }, this);
498

    
499
            this.handlers_initialized = true;
500
        },
501
        
502
        // reset actions
503
        reset: function() {
504
            var prev_action = this.selected_action;
505
            this.selected_action = false;
506
            this.vm.clear_pending_action();
507
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
508
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
509
        },
510
        
511
        // set selected action
512
        set: function(action_name) {
513
            this.selected_action = action_name;
514
            this.vm.update_pending_action(this.selected_action);
515
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
516
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
517
        },
518

    
519
        update: function() {
520
        }
521
    })
522

    
523

    
524
    views.VMActionsView.STATUS_ACTIONS = { 
525
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
526
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
527
        'console':       ['ACTIVE'],
528
        'start':         ['UNKOWN', 'STOPPED'],
529
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
530
    };
531

    
532
    // UI helpers
533
    var uihelpers = snf.ui.helpers = {};
534
    
535
    // OS icon helpers
536
    var os_icon = uihelpers.os_icon = function(os) {
537
        var icons = window.os_icons;
538
        if (!icons) { return "okeanos" }
539
        if (icons.indexOf(os) == -1) {
540
            os = "okeanos";
541
        }
542
        return os;
543
    }
544

    
545
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
546
        size = size || "small";
547
        if (active == undefined) { active = true };
548

    
549
        var icon = os_icon(os);
550
        if (active) {
551
            icon = icon + "-on";
552
        } else {
553
            icon = icon + "-off";
554
        }
555

    
556
        return (snf.config.machines_icons_url + "{0}/{1}.png").format(size, icon)
557
    }
558

    
559
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
560
        attrs = attrs || {};
561
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
562
    }
563

    
564
    // VM Icon helpers
565
    //
566
    // identify icon
567
    var vm_icon = uihelpers.vm_icon = function(vm) {
568
        return os_icon(vm.get_os());
569
    }
570
    
571
    // get icon url
572
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
573
        return os_icon_path(vm.get_os(), size, vm.is_active());
574
    }
575
    
576
    // get icon IMG tag
577
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
578
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
579
    }
580
    
581

    
582
    snf.ui = _.extend(snf.ui, bb.Events);
583
    snf.ui.trigger_error = function(code, msg, error, extra) {
584
        snf.ui.trigger("error", { code:code, msg:msg, error:error, extra:extra || {} })
585
    };
586

    
587
})(this);