Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_vms_base_view.js @ 13925182

History | View | Annotate | Download (28.3 kB)

1
// Copyright 2011 GRNET S.A. All rights reserved.
2
// 
3
// Redistribution and use in source and binary forms, with or
4
// without modification, are permitted provided that the following
5
// conditions are met:
6
// 
7
//   1. Redistributions of source code must retain the above
8
//      copyright notice, this list of conditions and the following
9
//      disclaimer.
10
// 
11
//   2. Redistributions in binary form must reproduce the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer in the documentation and/or other materials
14
//      provided with the distribution.
15
// 
16
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
// POSSIBILITY OF SUCH DAMAGE.
28
// 
29
// The views and conclusions contained in the software and
30
// documentation are those of the authors and should not be
31
// interpreted as representing official policies, either expressed
32
// or implied, of GRNET S.A.
33
// 
34

    
35
;(function(root){
36
    
37
    // root
38
    var root = root;
39
    
40
    // setup namepsaces
41
    var snf = root.synnefo = root.synnefo || {};
42
    var models = snf.models = snf.models || {}
43
    var storage = snf.storage = snf.storage || {};
44
    var ui = snf.ui = snf.ui || {};
45

    
46
    var views = snf.views = snf.views || {}
47

    
48
    // shortcuts
49
    var bb = root.Backbone;
50
    
51
    // logging
52
    var logger = new snf.logging.logger("SNF-VIEWS");
53
    var debug = _.bind(logger.debug, logger);
54
    
55
    var hasKey = Object.prototype.hasOwnProperty;
56

    
57
    // base class for views that contain/handle VMS
58
    views.VMListView = views.View.extend({
59

    
60
        // just a flag to identify that
61
        // views of this type handle vms
62
        vms_view: true,
63

    
64
        selectors: {},
65
        hide_actions: true,
66
        pane: "#machines-pane",
67
        metadata_view: undefined,
68

    
69
        initialize: function() {
70
            views.VMListView.__super__.initialize.call(this);
71
            this._vm_els = {};
72
            this.set_storage_handlers();
73
            this.set_handlers();
74
            this.vms_updated_handler();
75
            this.connect_overlay = new views.VMConnectView();
76
        },
77

    
78
        // display vm diagnostics detail overlay view, update based on
79
        // diagnostics_update_interval config value.
80
        show_build_details_for_vm: function(vm) {
81
            var cont = null;
82
            var success = function(data) {
83
                var message = "";
84
                var title = vm.get('name');
85
                var info = '<em>Status log messages:</em>';
86

    
87
                var list_el = $('<div class="diagnostics-list">');
88
                list_el.append('<div class="empty">No data available</div>');
89

    
90
                cont = list_el;
91
                var messages = _.clone(data);
92

    
93
                update_overlay_diagnostics(data, cont);
94
                synnefo.ui.main.details_view.show(title, info, cont);
95
            }
96

    
97
            var update_overlay_diagnostics = function(data) {
98
                var existing = cont.find(".msg-log-entry");
99
                var messages = _.clone(data);
100
                
101
                var to_append = messages.slice(0, messages.length - existing.length);
102
                to_append.reverse();
103

    
104
                var appending = to_append.length != messages.length;
105
                
106
                if (to_append.length) { cont.find(".empty").hide() } else {
107
                    if (!messages.length) { cont.find(".empty").show() }
108
                }
109
                _.each(to_append, function(msg){
110
                    var el = $('<div class="clearfix msg-log-entry ' + msg.level.toLowerCase() + '">');
111
                    if (msg.details) {
112
                      el.addClass("with-details");
113
                    }
114
                    var display_source_date = synnefo.config.diagnostics_display_source_date;
115
                    var source_date = "";
116
                    if (display_source_date) {
117
                        source_date = '('+synnefo.util.formatDate(new Date(msg.source_date))+')';
118
                    }
119

    
120
                    el.append('<span class="date">' + synnefo.util.formatDate(new Date(msg.created)) + source_date + '</span>');
121
                    el.append('<span class="src">' + _.escape(msg.source) + '</span>');
122
                    el.append('<span class="msg">' + _.escape(msg.message) + '</span>');
123
                    if (msg.details) {
124
                        el.append('<pre class="details">' + _.escape(msg.details) + '</pre>');
125
                    }
126
                    if (appending) { el.hide(0); el.css({'display':'none'})}
127
                    cont.prepend(el);
128
                    el.click(function(el) {
129
                        $(this).find(".details").slideToggle();
130
                        $(this).toggleClass("expanded");
131
                    });
132

    
133
                    if (appending) { el.fadeIn(800); }
134
                });
135
                
136
                window.setTimeout(function(){
137
                    if (cont.is(":visible")) {
138
                        vm.get_diagnostics(update_overlay_diagnostics);
139
                    }
140
                }, synnefo.config.diagnostics_update_interval);
141
            }
142

    
143
            vm.get_diagnostics(success);
144
        },
145

    
146
        // Helpers
147
        //
148
        // get element based on this.selectors key/value pairs
149
        sel: function(id, params) {
150
            if (!this.selectors[id]){ return };
151
            return $(this.selectors[id].format(params));
152
        },
153
        
154
        // vm element based on vm model instance provided
155
        vm: function(vm) {
156
            if (hasKey.call(this._vm_els, vm.id)) {
157
                ret = this._vm_els[vm.id];
158
            } else {
159
                return $([]);
160
            }
161

    
162
            return ret;
163
        },
164
        
165
        // get vm model instance from DOM element
166
        vm_for_element: function(el) {
167
            return storage.vms.sel(this.vm_id_for_element(el));
168
        },
169
        
170

    
171
        // Event binding and stuff like that
172
        //
173
        set_storage_handlers: function() {
174
            storage.vms.bind("add", _.bind(this.vms_updated_handler, this, "add"));
175
            storage.vms.bind("change", _.bind(this.vms_updated_handler, this, "change"));
176
            storage.vms.bind("reset", _.bind(this.vms_updated_handler, this, "reset"));
177
            storage.vms.bind("remove", _.bind(this.vms_updated_handler, this, "remove"));
178
        },
179
        
180
        // vms updated triggered, update view vms
181
        vms_updated_handler: function (method, model, arg2, arg3) {
182
            var updated = storage.vms.models;
183
            if (method == "add") { updated = [model] };
184
            if (method == "change") { updated = [model] };
185
            if (method == "remove") { updated = [model] };
186

    
187
            if (method == "remove") {
188
                this.remove_vm(model)
189
                return;
190
            }
191
            
192
            this.update_vms(updated);
193
        },
194

    
195
        // create vm
196
        // append it on proper view container
197
        create_vm: function(vm) {
198
            // create dom element
199
            var vm_view = this.create_vm_element(vm);
200
            vm_view.find(".vm-actions").attr("id", this.view_id+"-actions-" + vm.id);
201
            this._vm_els[vm.id] = vm_view;
202
            var container = this.get_vm_container(vm);
203
            container.append(vm_view);
204
            vm_view.find(".action-indicator").text("");
205
            if (this.visible()) {
206
                container.show()
207
            }
208

    
209
            // initialize vm specific event handlers 
210
            this.__set_vm_handlers(vm);
211
            vm_view.find(".suspended-notice").click(function(){
212
              synnefo.ui.main.suspended_view.show(vm);
213
            })
214
            return vm_view;
215
        },
216
        
217
        // create vm dom element
218
        create_vm_element: function(vm) {
219
            // clone template
220
            return this.sel('tpl').clone().attr("id", this.id_tpl + vm.id)
221
        },
222

    
223
        // get proper vm container
224
        get_vm_container: function(vm) {
225
            if (vm.is_active()) {
226
                return this.sel("vm_cont_active");
227
            } else {
228
                return this.sel("vm_cont_terminated");
229
            }
230
        },
231

    
232
        // create and append inside the proper container the vm model
233
        // if it doesn't exist update vm data and make it visible
234
        add: function(vm) {
235
            // create if it does not exist
236
            if (!hasKey.call(this._vm_els, vm.id)) {
237
                var el = this.create_vm(vm);
238
                el.show();
239
                this.post_add(vm);
240
                this.init_vm_view_handlers(vm);
241
            }
242

    
243
            return this.vm(vm);
244
        },
245

    
246
        init_vm_view_handlers: function(vm) {
247
            var self = this;
248
            var el = this.vm(vm);
249

    
250
            // hidden feature, double click on indicators to display 
251
            // vm diagnostics.
252
            el.find(".indicators").bind("dblclick", function(){
253
                self.show_build_details_for_vm(vm);
254
            });
255

    
256
            // this button gets visible if vm creation failed.
257
            el.find("div.build-progress .btn").click(function(){
258
                self.show_build_details_for_vm(vm);
259
            });
260
        },
261
        
262
        // helpers for VMListView descendants
263
        post_add: function(vm) { throw "Not implemented" },
264
        set_vm_handlers: function(vm) { throw "Not implemented" },
265
        set_handlers: function() { throw "Not implemented" },
266
        update_layout: function() { throw "Not implemented" },
267
        post_update_vm: function(vm) { throw "Not implemented" },
268
        update_details: function(vm) {},
269
        
270
        // remove vm
271
        remove_vm: function(vm) {
272
            // FIXME: some kind of transiton ??? effect maybe ???
273
            this.vm(vm).remove();
274
            this.post_remove_vm(vm);
275
            if (hasKey.call(this._vm_els, vm.id)) {
276
                delete this._vm_els[vm.id];
277
            }
278
        },
279
        
280
        // remove all vms from view
281
        clear: function() {
282
            this.sel('vms').remove();
283
            this.__update_layout();
284
        },
285

    
286
        show: function() {
287
            views.VMListView.__super__.show.apply(this, arguments);
288
            if (storage.vms.length == 0) { this.hide() };
289
            if (!snf.config.update_hidden_views) {
290
                this.update_vms(storage.vms.models);
291
            }
292
        },
293

    
294
        // do update for provided vms, then update the view layout
295
        update_vms: function(vms) {
296
            if (!this.visible() && !snf.config.update_hidden_views) { return };
297

    
298
            _.each(vms, _.bind(function(vm){
299
                // vm will be removed
300
                // no need to update
301
                if (vm.get("status") == "DELETED") {
302
                    return;
303
                }
304

    
305
                // this won't add it additional times
306
                this.add(vm);
307
                this.update_vm(vm);
308
            }, this))
309
            
310
            // update view stuff
311
            this.__update_layout();
312
        },
313
        
314
        // update ui for the given vm
315
        update_vm: function(vm) {
316
            // do not update deleted state vms
317
            if (!vm || vm.get("status") == 'DELETED') { return };
318
            this.check_vm_container(vm);
319

    
320
            this.update_details(vm);
321
            this.update_transition_state(vm);
322

    
323
            if (this.action_views) {
324
                this.action_views[vm.id].update();
325
                this.action_views[vm.id].update_layout();
326
            }
327
            
328
            var el = this.vm(vm);
329
            if (vm.can_resize()) {
330
              el.addClass("can-resize");
331
            } else {
332
              el.removeClass("can-resize");
333
            }
334

    
335
            if (vm.get('suspended')) {
336
              el.addClass("suspended");
337
            } else {
338
              el.removeClass("suspended");
339
            }
340

    
341
            try {
342
                this.post_update_vm(vm);
343
            } catch (err) {};
344
        },
345

    
346
        // check if vm is placed properly within the view
347
        // container (e.g. some views might have different
348
        // containers for terminated or running machines
349
        check_vm_container: function(vm){
350
            var el = this.vm(vm);
351
            if (!el.length) { return };
352
            var self = this;
353
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
354
            if (el.parent()[0] != this.sel(selector)[0]) {
355
                var cont = this.sel(selector);
356
                var self = this;
357

    
358
                el.hide().appendTo(cont).fadeIn(300);
359
                $(window).trigger('resize');
360

    
361
                //el.fadeOut(200, function() {
362
                    //el.appendTo(cont); 
363
                    //el.fadeIn(200);
364
                    //self.sel(selector).show(function(){
365
                        //$(window).trigger("resize");
366
                    //});
367
                //});
368
            }
369
        },
370

    
371
        __update_layout: function() {
372
            this.update_layout();
373
        },
374
        
375
        // append handlers for vm specific events
376
        __set_vm_handlers: function(vm) {
377
            // show transition on vm status transit
378
            vm.bind('transition', _.bind(function(){this.show_transition(vm)}, this));
379
            this.set_vm_handlers(vm);
380
        },
381
        
382
        // is vm in transition ??? show the progress spinner
383
        update_transition_state: function(vm) {
384
            if (vm.in_transition() && !vm.has_pending_action()){
385
                this.sel('vm_spinner', vm.id).show();
386
            } else {
387
                this.sel('vm_spinner', vm.id).hide();
388
            }
389
        },
390
        
391
        show_indicator: function(vm, action) {
392
            var action = action || vm.pending_action;
393
            this.sel('vm_wave', vm.id).hide();
394
            this.sel('vm_spinner', vm.id).hide();
395
            this.vm(vm).find(".action-indicator").removeClass().addClass(action + " action-indicator").show();
396
        },
397

    
398
        hide_indicator: function(vm) {
399
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
400
            this.update_transition_state(vm);
401
        },
402

    
403
        // display transition animations
404
        show_transition: function(vm) {
405
            var wave = this.sel('vm_wave', vm.id);
406
            if (!wave || !wave.length) { return }
407

    
408
            var src = wave.attr('src');
409
            // change src to force gif play from the first frame
410
            // animate for 500 ms then hide
411
            wave.attr('src', "").show();
412
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
413
                wave.hide();
414
            });
415
        },
416

    
417
        connect_to_console: function(vm) {
418
            // It seems that Safari allows popup windows only if the window.open
419
            // call is made within an html element click event context. 
420
            // Otherwise its behaviour is based on "Block Pop-Up Windows" 
421
            // setting, which when enabled no notification appears for the user. 
422
            // Since there is no easy way to check for the setting value we use
423
            // async:false for the action call so that action success handler 
424
            // which opens the new window is called inside the click event 
425
            // context.
426
            var use_async = true;
427
            if ($.client.browser == "Safari") {
428
                use_async = false;
429
            }
430

    
431
            vm.call("console", function(console_data) {
432
                var url = vm.get_console_url(console_data);
433
                snf.util.open_window(url, "VM_" + vm.get("id") + "_CONSOLE", {'scrollbars': 1, 'fullscreen': 0});
434
            }, undefined, {async: use_async});
435
        }
436

    
437
    });
438
    
439
    // empty message view (just a wrapper to the element containing 
440
    // the empty information message)
441
    views.EmptyView = views.View.extend({
442
        el: '#emptymachineslist'
443
    })
444

    
445
    views.VMActionsView = views.View.extend({
446
        
447
        initialize: function(vm, parent, el, hide) {
448
            this.hide = hide || false;
449
            this.view = parent;
450
            this.vm = vm;
451
            this.vm_el = el;
452
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
453
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
454
            
455
            // state params
456
            this.selected_action = false;
457

    
458
            _.bindAll(this);
459
            window.acts = this;
460
            this.view_id = "vm_" + vm.id + "_actions";
461
            views.VMActionsView.__super__.initialize.call(this);
462

    
463
            this.hovered = false;
464
            this.set_hover_handlers();
465
        },
466

    
467
        action: function(name) {
468
            return $(this.el).find(".action-container." + name);
469
        },
470

    
471
        action_link: function(name) {
472
            return this.action(name).find("a");
473
        },
474
        
475
        action_confirm_cont: function(name) {
476
            return this.action_confirm(name).parent();
477
        },
478

    
479
        action_confirm: function(name) {
480
            return this.action(name).find("button.yes");
481
        },
482

    
483
        action_cancel: function(name) {
484
            return this.action(name).find("button.no");
485
        },
486

    
487
        hide_actions: function() {
488
            $(this.el).find("a").css("visibility", "hidden");
489
        },
490

    
491
        // update the actions layout, depending on the selected actions
492
        update_layout: function() {
493

    
494
            if (!this.vm_handlers_initialized) {
495
                this.vm = storage.vms.get(this.vm.id);
496
                this.init_vm_handlers();
497
            }
498

    
499
            if (!this.vm) { return }
500
            
501
            if (!this.hovered && !this.vm.has_pending_action() && this.hide && !this.vm.action_error) { 
502
                this.el.hide();
503
                this.view.hide_indicator(this.vm);
504
                return 
505
            };
506

    
507
            if (!this.el.is(":visible") && !this.hide) { return };
508
            if (!this.handlers_initialized) { this.set_handlers(); }
509

    
510

    
511
            // update selected action
512
            if (this.vm.pending_action) {
513
                this.selected_action = this.vm.pending_action;
514
            } else {
515
                this.selected_action = false;
516
            }
517
            
518
            // vm actions tha can be performed
519
            var actions = this.vm.get_available_actions();
520
            
521
            // had pending action but actions changed and now selected action is
522
            // not available, hide it from user
523
            if (this.selected_action && actions.indexOf(this.selected_action) == -1) {
524
                this.reset();
525
            }
526
            
527
            this.el.show();
528
            
529
            if ((this.selected_action || this.hovered) && !this.vm.action_error) {
530
                // show selected action
531
                $(this.el).show();
532
                $(this.el).find("a").css("visibility", "visible");
533
                // show action icon
534
                this.view.show_indicator(this.vm);
535
            } else {
536
                if (this.hide || this.vm.action_error) {
537
                    // view shows actions on machine hover
538
                    $(this.el).find("a").css("visibility", "hidden");
539
                } else {
540
                    if (!this.vm.action_error) {
541
                        // view shows actions always
542
                        $(this.el).find("a").css("visibility", "visible");
543
                        $(this.el).show();
544
                    }
545
                }
546
                
547
                this.view.hide_indicator(this.vm);
548
            }
549
                
550
            // update action link styles and shit
551
            _.each(models.VM.ACTIONS, function(action, index) {
552
                if (actions.indexOf(action) > -1) {
553
                    this.action(action).removeClass("disabled");
554
                    var inactive = models.VM.AVAILABLE_ACTIONS_INACTIVE[action];
555

    
556
                    if (inactive && _.contains(inactive, this.vm.get('status'))) {
557
                      this.action(action).addClass("inactive");
558
                    } else {
559
                      this.action(action).removeClass("inactive");
560
                    }
561

    
562
                    if (this.selected_action == action) {
563
                        this.action_confirm_cont(action).css('display', 'block');
564
                        this.action_confirm(action).show();
565
                        this.action(action).removeClass("disabled");
566
                        this.action_link(action).addClass("selected");
567
                    } else {
568
                        this.action_confirm_cont(action).hide();
569
                        this.action_confirm(action).hide();
570
                        this.action_link(action).removeClass("selected");
571
                    }
572
                } else {
573
                    this.action().hide();
574
                    this.action(action).addClass("disabled");
575
                    this.action_confirm(action).hide();
576
                }
577
            }, this);
578
        },
579
        
580
        init_vm_handlers: function() {
581
            try {
582
                this.vm.unbind("action:fail", this.update_layout)
583
                this.vm.unbind("action:fail:reset", this.update_layout)
584
            } catch (err) { console.log("Error")};
585
            
586
            this.vm.bind("action:fail", this.update_layout)
587
            this.vm.bind("action:fail:reset", this.update_layout)
588

    
589
            this.vm_handlers_initialized = true;
590
        },
591
        
592
        set_hover_handlers: function() {
593
            // vm container hover (icon view)
594
            this.view.vm(this.vm).hover(_.bind(function() {
595
                this.hovered = true;
596
                this.update_layout();
597

    
598
            }, this),  _.bind(function() {
599
                this.hovered = false;
600
                this.update_layout();
601
            }, this));
602
        },
603

    
604
        // bind event handlers
605
        set_handlers: function() {
606
            var self = this;
607
            var vm = this.vm;
608
            
609
            // initial hide
610
            if (this.hide) { $(this.el).hide() };
611
            
612
            // action links events
613
            _.each(models.VM.ACTIONS, function(action) {
614
                var action = action;
615
                // indicator hovers
616
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
617
                    self.view.show_indicator(self.vm, action);
618
                }, function() {
619
                    // clear or show selected action indicator
620
                    if (self.vm.pending_action) {
621
                        self.view.show_indicator(self.vm);
622
                    } else {
623
                        self.view.hide_indicator(self.vm);
624
                    }
625
                })
626
                
627
                // action links click events
628
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
629
                    ev.preventDefault();
630
                    if ($(ev.currentTarget).parent().hasClass("inactive")) {
631
                      return;
632
                    }
633
                    if (action == "resize") {
634
                        ui.main.vm_resize_view.show(self.vm);
635
                    } else {
636
                        self.set(action);
637
                    }
638
                }).data("action", action);
639

    
640
                // confirms
641
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
642
                    ev.preventDefault();
643
                    self.reset();
644
                });
645

    
646
                // cancels
647
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
648
                    ev.preventDefault();
649
                    // override console
650
                    // ui needs to act (open the console window)
651
                    if (action == "console") {
652
                        self.view.connect_to_console(self.vm);
653
                    } else {
654
                        self.vm.call(action);
655
                    }
656
                    self.reset();
657
                });
658
            }, this);
659

    
660
            this.handlers_initialized = true;
661
        },
662
        
663
        // reset actions
664
        reset: function() {
665
            var prev_action = this.selected_action;
666
            this.selected_action = false;
667
            this.vm.clear_pending_action();
668
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
669
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
670
        },
671
        
672
        // set selected action
673
        set: function(action_name) {
674
            this.selected_action = action_name;
675
            this.vm.update_pending_action(this.selected_action);
676
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
677
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
678
        },
679

    
680
        update: function() {
681
        }
682
    })
683

    
684

    
685
    views.VMActionsView.STATUS_ACTIONS = { 
686
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
687
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
688
        'console':       ['ACTIVE'],
689
        'start':         ['UNKOWN', 'STOPPED'],
690
        'resize':        ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD'],
691
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
692
    };
693

    
694
    // UI helpers
695
    var uihelpers = snf.ui.helpers = {};
696
    
697
    // OS icon helpers
698
    var os_icon = uihelpers.os_icon = function(os) {
699
        var icons = window.os_icons;
700
        if (!icons) { return "unknown" }
701
        if (icons.indexOf(os) == -1) {
702
            os = "unknown";
703
        }
704
        return os;
705
    }
706

    
707
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
708
        size = size || "small";
709
        if (active == undefined) { active = true };
710

    
711
        var icon = os_icon(os);
712
        if (active) {
713
            icon = icon + "-on";
714
        } else {
715
            icon = icon + "-off";
716
        }
717

    
718
        return (snf.config.machines_icons_url + "{0}/{1}.png").format(size, icon)
719
    }
720

    
721
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
722
        attrs = attrs || {};
723
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
724
    }
725

    
726
    // VM Icon helpers
727
    //
728
    // identify icon
729
    var vm_icon = uihelpers.vm_icon = function(vm) {
730
        return os_icon(vm.get_os());
731
    }
732
    
733
    // get icon url
734
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
735
        return os_icon_path(vm.get_os(), size, vm.is_active());
736
    }
737
    
738
    // get icon IMG tag
739
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
740
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
741
    }
742
    
743

    
744
    snf.ui = _.extend(snf.ui, bb.Events);
745
    snf.ui.trigger_error = function(code, msg, error, extra) {
746
        if (msg.match(/Script error/i)) {
747
          // No usefull information to display in this case. Plus it's an
748
          // exception that probably doesn't affect our app.
749
          return 0;
750
        }
751
        snf.ui.trigger("error", { code:code, msg:msg, error:error, extra:extra || {} });
752
    };
753
    
754
    views.VMPortView = views.ext.ModelView.extend({
755
      tpl: '#vm-port-view-tpl',
756
      classes: 'port-item clearfix',
757
      get_network_name: function() {
758
        var network = this.model.get('network');
759
        var name = network && network.get('name');
760
        if (!name) {
761
          name = 'Internet'
762
        }
763
        name = synnefo.util.truncate(name, 18, '...');
764
        return name || 'Loading...';
765
      },
766

    
767
      get_network_icon: function() {
768
        var ico;
769
        var network = this.model.get('network');
770
        if (network) {
771
          ico = network.get('is_public') ? 'internet-small.png' : 'network-small.png';
772
        } else {
773
          ico = 'network-small.png';
774
        }
775
        return synnefo.config.media_url + 'images/' + ico;
776
      },
777
    });
778
    
779
    views.VMPortListView = views.ext.CollectionView.extend({
780
      tpl: '#vm-port-list-view-tpl',
781
      model_view_cls: views.VMPortView
782
    });
783

    
784
    views.VMPortIpView = views.ext.ModelView.extend({
785
      tpl: '#vm-port-ip-tpl'
786
    });
787

    
788
    views.VMPortIpsView = views.ext.CollectionView.extend({
789
      tpl: '#vm-port-ips-tpl',
790
      model_view_cls: views.VMPortIpView
791
    });
792

    
793
})(this);