Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_vms_base_view.js @ d0986ab0

History | View | Annotate | Download (19.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
            this.set_storage_handlers();
36
            this.set_handlers();
37
            this.vms_updated_handler();
38
            this.connect_overlay = new views.VMConnectView();
39
            this.vm_selector = this.selectors.vm;
40
        },
41

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

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

    
77
            if (method == "remove") {
78
                this.remove_vm(model)
79
                return;
80
            }
81
            
82
            this.update_vms(updated);
83
        },
84

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

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

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

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

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

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

    
159
        // do update for provided vms, then update the view layout
160
        update_vms: function(vms) {
161
            if (!this.visible() && !snf.config.update_hidden_views) { return };
162

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

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

    
185
            this.update_details(vm);
186
            this.update_transition_state(vm);
187

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

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

    
211
                el.hide().appendTo(cont).fadeIn(300);
212
                $(window).trigger('resize');
213

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

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

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

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

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

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

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

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

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

    
304
            this.hovered = false;
305
        },
306

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
493
        update: function() {
494
        }
495
    })
496

    
497

    
498
    views.VMActionsView.STATUS_ACTIONS = { 
499
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
500
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
501
        'console':       ['ACTIVE'],
502
        'start':         ['UNKOWN', 'STOPPED'],
503
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
504
    };
505

    
506
    // UI helpers
507
    var uihelpers = snf.ui.helpers = {};
508
    
509
    // OS icon helpers
510
    var os_icon = uihelpers.os_icon = function(os) {
511
        var icons = window.os_icons;
512
        if (icons.indexOf(os) == -1) {
513
            os = "unknown";
514
        }
515
        return os;
516
    }
517

    
518
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
519
        size = size || "small";
520
        if (active == undefined) { active = true };
521

    
522
        var icon = os_icon(os);
523
        if (active) {
524
            icon = icon + "-on";
525
        } else {
526
            icon = icon + "-off";
527
        }
528

    
529
        return "/static/icons/machines/{0}/{1}.png".format(size, icon)
530
    }
531

    
532
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
533
        attrs = attrs || {};
534
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
535
    }
536

    
537
    // VM Icon helpers
538
    //
539
    // identify icon
540
    var vm_icon = uihelpers.vm_icon = function(vm) {
541
        return os_icon(vm.get_os());
542
    }
543
    
544
    // get icon url
545
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
546
        return os_icon_path(vm.get_os(), size, vm.is_active());
547
    }
548
    
549
    // get icon IMG tag
550
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
551
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
552
    }
553
    
554

    
555
    snf.ui = _.extend(snf.ui, bb.Events);
556
    snf.ui.trigger_error = function(code, msg, error, extra) {
557
        snf.ui.trigger("error", { code:code, msg:msg, error:error, extra:extra || {} })
558
    };
559

    
560
})(this);