Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_icon_view.js @ 8d08f18a

History | View | Annotate | Download (24.7 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
    var util = snf.util = snf.util || {};
12

    
13
    var views = snf.views = snf.views || {}
14

    
15
    // shortcuts
16
    var bb = root.Backbone;
17
    
18
    // handle extended info toggler
19
    views.IconInfoView = views.View.extend({
20
    
21
        initialize: function (vm, view) {
22
            this.vm = vm;
23
            this.view = view;
24
            this.vm_view = this.view.vm(vm);
25
            
26
            this.info_link = $(".toggler", this.vm_view);
27
            this.el = $("div.info-content", this.vm_view);
28
            this.toggler = $(".toggler", this.vm_view);
29
            this.label = $(".label", this.vm_view);
30

    
31
            this.set_handlers();
32
        },
33

    
34
        set_handlers: function() {
35
            this.info_link.click(_.bind(function(){
36
                this.el.slideToggle();
37
                this.view.vm(this.vm).toggleClass("light-background");
38

    
39
                if (this.toggler.hasClass("open")) {
40
                    this.toggler.removeClass("open");
41
                } else {
42
                    this.toggler.addClass("open");
43
                    get_server_stats(this.vm.id);
44
                }
45
                
46
                var self = this;
47
                window.setTimeout(function() {$(self.view).trigger("resize")}, 300);
48
            }, this));
49

    
50
            this.$(".stats-report").click(_.bind(function(){
51
                snf.ui.main.show_vm_details(this.vm);
52
            }, this))
53
        }
54
    
55
    })
56

    
57
    // rename handler view
58
    // only icon view contains rename capability
59
    views.IconRenameView = views.View.extend({
60
        
61
        initialize: function(vm, view) {
62
            this.vm = vm;
63
            this.view = view;
64
            // name container
65
            this.el = $('.machine-container#' + this.view.id_tpl.format(vm.id) + " div.name").get(0);
66
            // name inline element
67
            this.name = this.$('span.name');
68
            // rename button
69
            this.rename = this.$('span.rename');
70
            // save button
71
            this.save = this.$('.save');
72
            // cancel rename button
73
            this.cancel = this.$('.cancel');
74
            // where to place the input field
75
            this.edit_cont = this.$(".namecontainer");
76
            // buttons container
77
            this.buttons = this.$(".editbuttons");
78
            // current state
79
            this.renaming = false;
80
            // init event handlers
81
            this.set_handlers();
82
            // paint
83
            this.update_layout();
84
            views.IconRenameView.__super__.initialize.call(this);
85
        },
86
        
87
        // update elements visibility/state
88
        update_layout: function() {
89
            // if in renaming state
90
            if (this.renaming) {
91
                // if name is hidden we are already in renaming state
92
                // dont do nothing
93
                if (this.name.is(":hidden")){return}
94
                
95
                // hide name element to make space for the 
96
                // text input
97
                this.name.hide();
98
                this.rename.hide();
99
                // show confirm/cancel buttons
100
                this.buttons.show();
101
                // create text element
102
                this.create_input();
103
            } else {
104
                // name is visible not in edit mode
105
                if (this.name.is(":visible")){return}
106

    
107
                this.name.show();
108
                this.rename.show();
109
                this.buttons.hide();
110
                this.remove_input();
111
            }
112
        },
113
        
114
        // create rename input field and set appropriate 
115
        // event handlers
116
        create_input: function() {
117
            var self = this;
118
            this.edit_cont.append('<input class="vm-rename nametextbox" type="text" />');
119
            this.$('input').val(this.vm.get('name'));
120
            // give edit focus
121
            this.$('input').focus();
122
            // handle enter press
123
            this.$('input').keypress(function(ev){
124
                if (ev.charCode == 13) {
125
                    self.submit();
126
                }
127
            })
128
        },
129
        
130
        // remove input element
131
        remove_input: function() {
132
            this.$('input').remove();
133
        },
134
        
135
        // initialize event handlers
136
        set_handlers: function() {
137
            var self = this;
138
            // start rename when rename button is pressed
139
            this.rename.click(function() {
140
                self.renaming = true;
141
                self.update_layout();
142
            });
143
            
144
            // double click on name
145
            $(this.el).dblclick(function() {
146
                self.renaming = true;
147
                self.update_layout();
148
            });
149

    
150
            // cancel rename
151
            this.cancel.click(function() {
152
                self.renaming = false;
153
                self.update_layout();
154
            })
155
            
156
            // apply the rename
157
            // TODO: check if name is equal than the previous value
158
            this.save.click(function() {
159
                self.submit();
160
            })
161
        },
162

    
163
        submit: function() {
164
            var value = _(self.$('input').val()).trim();
165
            if (value == "") { return };
166
            this.renaming = false;
167
            this.vm.rename(self.$('input').val());
168
            this.update_layout();
169
        }
170
    });
171
    
172
    // VM connect interaction view
173
    views.VMConnectView = views.View.extend({
174
        
175
        initialize: function(vm, view) {
176
            // parent view (single, icon, list)
177
            this.parent = view;
178
            this.vm = vm;
179
            this.el = view.vm(vm);
180
            this.set_handlers();
181
            views.VMConnectView.__super__.initialize.call(this);
182
        },
183
        
184
        // set the appropriate handlers
185
        set_handlers: function() {
186
            // setup connect handler on vm icon interaction
187
            var el = this.el;
188
            var vm = this.vm;
189

    
190
            // element that triggers the connect handler
191
            var connect = el.find("div.connect-arrow, .logo");
192
            // connect status handler
193
            var handler = _.bind(this.connect_handler, {vm:vm, el:el});
194
            $(connect).bind({'mouseover': handler, 'mouseleave': handler, 
195
                            'mousedown': handler, 'mouseup': handler});
196
            
197
            // setup connect arrow display handlers 
198
            // while hovering vm container
199
            el.bind("mouseover", function(){
200
                if (vm.is_connectable()) {
201
                    el.find(".connect-border").show();
202
                    el.find(".connect-arrow").show();
203
                    el.find(".logo").css({cursor:"pointer"});
204
                } else {
205
                    el.find(".connect-border").hide();
206
                    el.find(".connect-arrow").hide();
207
                    el.find(".logo").css({cursor: "default"});
208
                }
209
            }).bind("mouseleave", function(){
210
                el.find(".connect-border").hide();
211
                el.find(".connect-arrow").hide();
212
            });
213
        },
214
        
215
        // connect arrow interaction handlers
216
        // BEWARE, this function has different context
217
        // than the View object itself, see set_vm_handlers
218
        connect_handler: function(event) {
219
            // nothing to do if we cannot connect to the vm
220
            if (!this.vm.is_connectable()) {return}
221
            
222
            var logo = this.el.find(".logo");
223
            var arrow = this.el.find(".connect-arrow");
224
            var border = this.el.find(".connect-border");
225
            
226
            // clear icon states
227
            logo.removeClass('single-image-state1 single-image-state2 single-image-state3 single-image-state4');
228
            
229
            // append the appropriate state class
230
            switch (event.type) {
231
                case "mouseover":       
232
                    logo.addClass('single-image-state4');
233
                    arrow.addClass('border-hover');
234
                    break;
235
                
236
                case "mouseleave":
237
                    logo.addClass('single-image-state1');
238
                    arrow.removeClass('border-hover');
239
                    break;
240

    
241
                case "mouseup":
242
                    logo.addClass('single-image-state4');
243
                    machine_connect([machine_connect, this.vm.id]);
244
                    break;
245

    
246
                case "mousedown":
247
                    logo.addClass('single-image-state2');
248
                    break;
249

    
250
                case "mouseclick":
251
                    logo.addCLass('single-image-state4');
252
                    break;
253

    
254
                default:
255
                    ;
256
            }
257
        },
258
        
259
        update_layout: function() {
260
        }
261

    
262
    });
263
    
264
    // vm metadata subview for icon and single view
265
    views.VMTagsView = views.View.extend({
266
        view_id: 'vm_tags',
267
        // metadata container selector
268
        el_sel: '.vm-metadata',
269
        // metadata row template
270
        tag_tpl: '<span class="tag-item"><span class="key">{0}</span><span class="value">{1}</span></span>',
271
        // how many tags to show
272
        tag_limit: 4,
273
        // truncate options (because container has different size on icon/single views)
274
        tag_key_truncate: 7,
275
        tag_value_truncate: 15,
276

    
277
        initialize: function(vm, view, toggle, limit, tag_key_truncate, tag_value_truncate) {
278
            this.tag_limit = limit || this.tag_limit;
279

    
280
            this.tag_key_truncate = tag_key_truncate || this.tag_key_truncate;
281
            this.tag_value_truncate = tag_value_truncate || this.tag_value_truncate;
282

    
283
            // does the view toggles the metadata container (single view)
284
            this.toggle = toggle || false;
285
            // parent view
286
            this.parent = view;
287
            this.vm = vm;
288
            this.el = this.parent.vm(vm);
289
            this.view_id = this.view_id + "_" + vm.id;
290

    
291
            // link to raise the metadata manager overlay
292
            this.link = this.$('a.manage-metadata');
293

    
294
            views.VMTagsView.__super__.initialize.call(this);
295
            this.set_handlers();
296
            this.update_layout();
297
        },
298
        
299
        // set the appropriate handlers
300
        set_handlers: function() {
301
            var self = this;
302
            // show the metadata editor overlay
303
            this.link.click(_.bind(function(ev) {
304
                ev.preventDefault();
305
                this.parent.metadata_view.show(this.vm);
306
            }, this));
307

    
308
            // tags have show/hide control ? bind events for them
309
            var self = this;
310
            if (this.toggle) {
311
                $(this.el).find(".tags-header").click(_.bind(function(){
312
                    $(self.el).find(".tags-content").slideToggle(600);
313
                    var toggler = $(this.el).find(".tags-header .cont-toggler");
314
                    
315
                    if (toggler.hasClass("open")) {
316
                        toggler.removeClass("open");
317
                    } else {
318
                        toggler.addClass("open");
319
                    }
320
                }, this));
321
                $(self.el).find(".tags-content").hide();
322
            }
323
        },
324
        
325
        // update metadata container and data
326
        update_layout: function() {
327

    
328
            // api metadata object
329
            var meta =  this.vm.get_meta();
330

    
331
            var i = 0;
332
            var cont = $(this.el).find(".items");
333

    
334
            // clear existing items
335
            cont.find(".tag-item").remove();
336
            
337
            // create tag elements
338
            _.each(meta, function(value, key){
339
                // respect the limit
340
                if (i > this.tag_limit) {
341
                    return;
342
                }
343
                
344
                // create element
345
                var new_el = $(this.tag_tpl.format(util.truncate(key, this.tag_key_truncate), 
346
                                                 util.truncate(": " + value, this.tag_value_truncate)));
347

    
348
                // add title attributes, improve accesibility
349
                // truncated values
350
                new_el.find("span.key").attr("title", key);
351
                new_el.find("span.value").attr("title", value);
352

    
353
                cont.append(new_el);
354
            }, this);
355
        }
356
    });
357
    
358

    
359
    // stats subview for single/icon views
360
    views.VMStatsView = views.View.extend({
361

    
362
        initialize: function(vm, view, options) {
363
            if (!options) {options = {}};
364
            this.vm = vm;
365
            this.parent = view;
366
            this.sel = options.el || this.el_sel || ".lower";
367
            this.el = this.parent.vm(vm).find(this.sel);
368
            
369
            // elements shortcuts
370
            this.cpu_loading = this.el.find(".cpu-graph .stat-busy");
371
            this.cpu_error = this.el.find(".cpu-graph .stat-error");
372
            this.cpu_img = this.el.find(".cpu-graph .stat-img");
373
            this.net_loading = this.el.find(".network-graph .stat-busy");
374
            this.net_error = this.el.find(".network-graph .stat-error");
375
            this.net_img = this.el.find(".network-graph .stat-img");
376
            
377
            // initial state paremeters
378
            this.is_building = (this.vm.get("status") == "BUILD");
379
            this.stats_error = false;
380
            this.stats = this.vm.get("stats");
381
            this.loading = false;
382

    
383
            // timeseries or bar images ?
384
            this.stats_type = options.stats_type || "bar";
385
            
386
            // stats undefined so probably not loaded yet
387
            if (this.stats === undefined) {
388
                this.loading = true;
389
            }
390

    
391
            views.VMStatsView.__super__.initialize.apply(this, arguments);
392
            this.set_handlers();
393
            this.update_layout();
394
        },
395

    
396
        
397
        set_handlers: function() {
398
            // update view state when vm stats update gets triggered
399
            this.vm.bind("stats:update", _.bind(function(){
400
                // update building state
401
                if (this.vm.get("status") == "BUILD") {
402
                    this.is_building = true;
403
                } else {
404
                    this.is_building = false;
405
                }
406
                
407
                // update loading state
408
                this.stats = this.vm.get("stats");
409
                if (this.stats == undefined) {
410
                    this.loading = true
411
                } else {
412
                    this.loading = false;
413
                }
414
                
415
                // update the layout
416
                this.update_layout();
417
            }, this));
418

    
419
            this.vm.bind("stats:error", _.bind(function(){
420
                this.stats_error = true;
421
            }, this))
422

    
423
            this.cpu_img.error(_.bind(function(){
424
                this.stats_error = true;
425
                this.update_layout();
426
            }, this));
427

    
428
            this.net_img.error(_.bind(function(){
429
                this.stats_error = true;
430
                this.update_layout();
431
            }, this));
432
        },
433
        
434
        get_images: function (type) {
435
            if (type == "bar") {
436
                return {'cpu': this.stats.cpuBar, 'net': this.stats.netBar };
437
            } else {
438
                return {'cpu': this.stats.cpuTimeSeries, 'net': this.stats.netTimeSeries };
439
            }
440
        },
441

    
442
        update_layout: function() {
443
            if (this.stats_error) {
444
                this.net_loading.hide();
445
                this.cpu_loading.hide();
446
                this.net_img.hide();
447
                this.cpu_img.hide();
448
                this.cpu_error.show();
449
                this.net_error.show();
450
                return;
451
            }
452

    
453
            if (this.loading) {
454
                this.net_loading.show();
455
                this.cpu_loading.show();
456
                this.net_img.hide();
457
                this.cpu_img.hide();
458
                this.cpu_error.hide();
459
                this.net_error.hide();
460
                return;
461
            }
462

    
463
            this.net_loading.hide();
464
            this.cpu_loading.hide();
465
            this.cpu_error.hide();
466
            this.net_error.hide();
467
            
468
            this.net_img.attr("src", this.get_images(this.stats_type).net);
469
            this.cpu_img.attr("src", this.get_images(this.stats_type).cpu);
470
            this.net_img.show();
471
            this.cpu_img.show();
472
        }
473
    });
474

    
475
    views.VMDetailsView = views.View.extend({
476
        view_id: "vm_details",
477
        el_sel: '.vm-details',
478
        
479

    
480
        selectors: {
481
            'cpu': '.cpu-data',
482
            'ram': '.ram-data',
483
            'disk': '.disk-data',
484
            'image_name': '.image-data',
485
            'image_size': '.image-size-data'
486
        },
487

    
488
        initialize: function(vm, view) {
489
            this.parent = view;
490
            this.vm = vm;
491
            this.el = $(this.parent.vm(vm)).find(this.el_sel).get(0);
492
            this.view_id = "vm_{0}_details".format(vm.id);
493

    
494
            views.VMDetailsView.__super__.initialize.call(this);
495
            this.update_layout();
496
        },
497

    
498
        update_layout: function() {
499
            var image = this.vm.get_image();
500
            var flavor = this.vm.get_flavor();
501
            if (!flavor || !image) {
502
                return;
503
            }
504

    
505
            this.sel('image_name').text(image.get('name'));
506
            this.sel('image_size').text(image.get('metadata').values.size);
507

    
508
            this.sel('cpu').text(flavor.get('cpu'));
509
            this.sel('ram').text(flavor.get('ram'));
510
            this.sel('disk').text(flavor.get('disk'));
511
        }
512
    });
513
    
514
    // VMs icon view
515
    views.IconView = views.VMListView.extend({
516
        
517
        // view id (this could be used to identify 
518
        // the view object from global context
519
        view_id: 'vm_icon',
520

    
521
        el: '#machinesview-icon',
522
        id_tpl: 'icon-vm-{0}',
523

    
524
        selectors: {
525
            'vms': '.machine-container',
526
            'vm': '#icon-vm-{0}',
527
            'view': '#machinesview-icon',
528
            'tpl': '#machinesview-icon.standard #machine-container-template',
529
            'spinner': '.large-spinner',
530
            'vm_spinner': '.machine-container#icon-vm-{0} .state .spinner',
531
            'vm_wave': '.machine-container#icon-vm-{0} .wave',
532
            'vm_cont_active': '#machinesview-icon.standard .running',
533
            'vm_cont_terminated': '#machinesview-icon.standard .terminated'
534
        },
535
        
536
        // overload show function
537
        show_view: function() {
538
            $(this.el).show();
539
            this.__update_layout();
540
        },
541

    
542
        post_update_vm: function(vm) {
543
        },
544

    
545
        // identify vm model instance id based on DOM element
546
        vm_id_for_element: function(el) {
547
            return el.attr('id').replace("icon-vm-","");
548
        },
549
        
550
        // set generic view handlers
551
        set_handlers: function() {
552
        },  
553
        
554
        // stuff to do when a new vm has been created.
555
        // - create vm subviews
556
        post_add: function(vm) {
557
            // rename views index
558
            this.rename_views = this.rename_views || {};
559
            this.stats_views = this.stats_views || {};
560
            this.connect_views = this.connect_views || {};
561
            this.tags_views = this.tags_views || {};
562
            this.details_views = this.details_views || {};
563
            this.info_views = this.info_views || {};
564
            this.action_views = this.action_views || {};
565

    
566
            this.action_views[vm.id] = new views.VMActionsView(vm, this, this.vm(vm), this.hide_actions);
567
            this.rename_views[vm.id] = new views.IconRenameView(vm, this);
568
            this.stats_views[vm.id] = new views.VMStatsView(vm, this, {el:'.vm-stats'});
569
            this.connect_views[vm.id] = new views.VMConnectView(vm, this);
570
            this.tags_views[vm.id] = new views.VMTagsView(vm, this);
571
            this.details_views[vm.id] = new views.VMDetailsView(vm, this);
572
            this.info_views[vm.id] = new views.IconInfoView(vm, this);
573
        },
574
        
575
        // vm specific event handlers
576
        set_vm_handlers: function(vm) {
577
            var el = this.vm(vm);
578

    
579
        },
580

    
581
        check_terminated_is_empty: function() {
582
            // hide/show terminated container
583
            if (this.$(".terminated .machine-container").length == 0) {
584
                this.$(".terminated").hide()
585
            } else {
586
                this.$(".terminated").show()
587
            }
588

    
589
            $(window).trigger("resize");
590
        },
591
        
592
        // generic stuff to do on each view update
593
        // called once after each vm has been updated
594
        update_layout: function() {
595
            // TODO: why do we do this ??
596
            if (storage.vms.models.length > 0) {
597
                this.$(".running").removeClass("disabled");
598
            } else {
599
                this.$(".running").addClass("disabled");
600
            }
601
            
602
            this.check_terminated_is_empty();
603
    
604
            // FIXME: code from old js api
605
            this.$("div.separator").show();
606
            this.$("div.machine-container:last-child").find("div.separator").hide();
607
            fix_v6_addresses();
608
        },
609

    
610
        // update vm details
611
        update_details: function(vm) {
612
            var el = this.vm(vm);
613
            // truncate name
614
            el.find("span.name").text(util.truncate(vm.get("name"), 40));
615
            // set ips
616
            el.find(".ipv4-text").text(vm.get_addresses().ip4 || "undefined");
617
            // TODO: fix ipv6 truncates and tooltip handler
618
            el.find(".ipv6-text").text(vm.get_addresses().ip6 || "undefined");
619
            // set the state (i18n ??)
620
            el.find(".status").text(STATE_TEXTS[vm.state()]);
621
            // set state class
622
            el.find(".state").removeClass().addClass(views.IconView.STATE_CLASSES[vm.state()].join(" "));
623
            // os icon
624
            el.find(".logo").css({'background-image': "url(" + this.get_vm_icon_path(vm, "medium") + ")"});
625
            
626
            el.removeClass("connectable");
627
            if (vm.is_connectable()) {
628
                el.addClass("connectable");
629
            }
630
            
631
            if (vm.get('status') == 'BUILD') {
632
                // update bulding progress
633
                el.find(".machine-ips").hide();
634
                el.find(".build-progress").show().text(vm.get('progress_message'));
635
            } else {
636
                // hide building progress
637
                el.find(".machine-ips").show()
638
                el.find(".build-progress").hide();
639
            }
640

    
641
            if (vm.state() == "DESTROY") {
642
                el.find(".machine-ips").hide();
643
                el.find(".build-progress").show().text("Terminating...");
644
            }
645

    
646
            icon_state = vm.is_active() ? "on" : "off";
647
            set_machine_os_image(el, "icon", icon_state, this.get_vm_icon_os(vm));
648
            
649
            // update subviews
650
            this.rename_views[vm.id].update_layout();
651
            this.stats_views[vm.id].update_layout();
652
            this.connect_views[vm.id].update_layout();
653
            this.tags_views[vm.id].update_layout();
654
            this.details_views[vm.id].update_layout();
655
        },
656

    
657
        post_remove_vm: function(vm) {
658
            this.check_terminated_is_empty();
659
            $(window).trigger("resize");
660
        },
661
            
662
        get_vm_icon_os: function(vm) {
663
            var os = vm.get_os();
664
            var icons = window.os_icons || views.IconView.VM_OS_ICONS;
665

    
666
            if (icons.indexOf(os) == -1) {
667
                os = "unknown";
668
            }
669

    
670
            return os;
671
        },
672

    
673
        // TODO: move to views.utils (the method and the VM_OS_ICON vars)
674
        get_vm_icon_path: function(vm, icon_type) {
675
            var os = vm.get_os();
676
            var icons = window.os_icons || views.IconView.VM_OS_ICONS;
677

    
678
            if (icons.indexOf(os) == -1) {
679
                os = "unknown";
680
            }
681

    
682
            return views.IconView.VM_OS_ICON_TPLS[icon_type].format(os);
683
        }
684
    })
685

    
686
    views.IconView.VM_OS_ICON_TPLS = {
687
        "medium": "/static/icons/machines/medium/{0}-sprite.png"
688
    }
689

    
690
    views.IconView.VM_OS_ICONS = window.os_icons || [];
691

    
692
    views.IconView.STATE_CLASSES = {
693
        'UNKNOWN':          ['state', 'error-state'],
694
        'BUILD':            ['state', 'build-state'],
695
        'REBOOT':           ['state', 'rebooting-state'],
696
        'STOPPED':          ['state', 'terminated-state'],
697
        'ACTIVE':           ['state', 'running-state'],
698
        'ERROR':            ['state', 'error-state'],
699
        'DELETE':           ['state', 'destroying-state'],
700
        'DESTROY':          ['state', 'destroying-state'],
701
        'BUILD_INIT':       ['state', 'build-state'], 
702
        'BUILD_COPY':       ['state', 'build-state'],
703
        'BUILD_FINAL':      ['state', 'build-state'],
704
        'SHUTDOWN':         ['state', 'shutting-state'],
705
        'START':            ['state', 'starting-state'],
706
        'CONNECT':          ['state', 'connecting-state'],
707
        'DISCONNECT':       ['state', 'disconnecting-state']
708
    };
709

    
710
})(this);