Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (34.4 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
    views.CreateSnapshotView = views.Overlay.extend({
58
        view_id: "snapshot_create_view",
59
        content_selector: "#snapshot-create-content",
60
        css_class: 'overlay-snapshot-create overlay-info',
61
        overlay_id: "snapshot-create-overlay",
62

    
63
        title: "Create new snapshot",
64
        subtitle: "Machines",
65

    
66
        initialize: function(options) {
67
            views.CreateSnapshotView.__super__.initialize.apply(this);
68

    
69
            this.create_button = this.$("form .form-action.create");
70
            this.text = this.$(".snapshot-create-name");
71
            this.description = this.$(".snapshot-create-desc");
72
            this.form = this.$("form");
73
            this.init_handlers();
74
            this.creating = false;
75
        },
76
        
77
        show: function(vm) {
78
          this.vm = vm;
79
          views.CreateSnapshotView.__super__.show.apply(this);
80
        },
81

    
82
        init_handlers: function() {
83

    
84
            this.create_button.click(_.bind(function(e){
85
                this.submit();
86
            }, this));
87

    
88
            this.form.submit(_.bind(function(e){
89
                e.preventDefault();
90
                this.submit();
91
                return false;
92
            }, this))
93

    
94
            this.text.keypress(_.bind(function(e){
95
                if (e.which == 13) {this.submit()};
96
            },this))
97
        },
98

    
99
        submit: function() {
100
            if (this.validate()) {
101
                this.create();
102
            };
103
        },
104
        
105
        validate: function() {
106
            // sanitazie
107
            var t = this.text.val();
108
            t = t.replace(/^\s+|\s+$/g,"");
109
            this.text.val(t);
110

    
111
            if (this.text.val() == "") {
112
                this.text.closest(".form-field").addClass("error");
113
                this.text.focus();
114
                return false;
115
            } else {
116
                this.text.closest(".form-field").removeClass("error");
117
            }
118
            return true;
119
        },
120
        
121
        create: function() {
122
            if (this.creating) { return }
123
            this.create_button.addClass("in-progress");
124

    
125
            var name = this.text.val();
126
            var desc = this.description.val();
127
            
128
            this.creating = true;
129
            this.vm.create_snapshot({display_name:name, display_description:desc}, _.bind(function() {
130
              this.creating = false;
131
              this.hide();
132
            }, this));
133
        },
134
        
135
        _default_values: function() {
136
          var d = new Date();
137
          var vmname = this.vm.get('name');
138
          var vmid = this.vm.id;
139
          var index = this.volume_index;
140
          var id = this.vm.id;
141
          var date = '{0}-{1}-{2} {3}:{4}:{5}'.format(
142
            d.getFullYear(), d.getMonth()+1, d.getDate(), d.getHours(), 
143
            d.getMinutes(), d.getSeconds());
144
          var name = "\"{0}\" snapshot [{1}]".format(vmname, date);
145
          if (this.volume) { name += "[volume:" + this.volume + "]" }
146
          var description = "Volume id: {0}".format(this.volume || 'primary');
147
          description += "\n" + "Server id: {0}".format(vmid);
148
          description += "\n" + "Server name: {0}".format(vmname);
149
          description += "\n" + "Timestamp: {0}".format(d.toJSON());
150

    
151
          return {
152
            'name': name,
153
            'description': description
154
          }
155
        },
156

    
157
        beforeOpen: function() {
158
            this.create_button.removeClass("in-progress")
159
            this.text.closest(".form-field").removeClass("error");
160
            var defaults = this._default_values();
161

    
162
            this.text.val(defaults.name);
163
            this.description.val(defaults.description);
164
            this.text.show();
165
            this.text.focus();
166
            this.description.show();
167
        },
168

    
169
        onOpen: function() {
170
            this.text.focus();
171
        }
172
    });
173

    
174
    // base class for views that contain/handle VMS
175
    views.VMListView = views.View.extend({
176

    
177
        // just a flag to identify that
178
        // views of this type handle vms
179
        vms_view: true,
180

    
181
        selectors: {},
182
        hide_actions: true,
183
        pane: "#machines-pane",
184
        metadata_view: undefined,
185

    
186
        initialize: function() {
187
            views.VMListView.__super__.initialize.call(this);
188
            this._vm_els = {};
189
            this.set_storage_handlers();
190
            this.set_handlers();
191
            this.vms_updated_handler();
192
            this.connect_overlay = new views.VMConnectView();
193
        },
194

    
195
        // display vm diagnostics detail overlay view, update based on
196
        // diagnostics_update_interval config value.
197
        show_build_details_for_vm: function(vm) {
198
            var cont = null;
199
            var success = function(data) {
200
                var message = "";
201
                var title = vm.get('name');
202
                var info = '<em>Status log messages:</em>';
203

    
204
                var list_el = $('<div class="diagnostics-list">');
205
                list_el.append('<div class="empty">No data available</div>');
206

    
207
                cont = list_el;
208
                var messages = _.clone(data);
209

    
210
                update_overlay_diagnostics(data, cont);
211
                synnefo.ui.main.details_view.show(title, info, cont);
212
            }
213

    
214
            var update_overlay_diagnostics = function(data) {
215
                var existing = cont.find(".msg-log-entry");
216
                var messages = _.clone(data);
217
                
218
                var to_append = messages.slice(0, messages.length - existing.length);
219
                to_append.reverse();
220

    
221
                var appending = to_append.length != messages.length;
222
                
223
                if (to_append.length) { cont.find(".empty").hide() } else {
224
                    if (!messages.length) { cont.find(".empty").show() }
225
                }
226
                _.each(to_append, function(msg){
227
                    var el = $('<div class="clearfix msg-log-entry ' + msg.level.toLowerCase() + '">');
228
                    if (msg.details) {
229
                      el.addClass("with-details");
230
                    }
231
                    var display_source_date = synnefo.config.diagnostics_display_source_date;
232
                    var source_date = "";
233
                    if (display_source_date) {
234
                        source_date = '('+synnefo.util.formatDate(new Date(msg.source_date))+')';
235
                    }
236

    
237
                    el.append('<span class="date">' + synnefo.util.formatDate(new Date(msg.created)) + source_date + '</span>');
238
                    el.append('<span class="src">' + _.escape(msg.source) + '</span>');
239
                    el.append('<span class="msg">' + _.escape(msg.message) + '</span>');
240
                    if (msg.details) {
241
                        el.append('<pre class="details">' + _.escape(msg.details) + '</pre>');
242
                    }
243
                    if (appending) { el.hide(0); el.css({'display':'none'})}
244
                    cont.prepend(el);
245
                    el.click(function(el) {
246
                        $(this).find(".details").slideToggle();
247
                        $(this).toggleClass("expanded");
248
                    });
249

    
250
                    if (appending) { el.fadeIn(800); }
251
                });
252
                
253
                window.setTimeout(function(){
254
                    if (cont.is(":visible")) {
255
                        vm.get_diagnostics(update_overlay_diagnostics);
256
                    }
257
                }, synnefo.config.diagnostics_update_interval);
258
            }
259

    
260
            vm.get_diagnostics(success);
261
        },
262

    
263
        // Helpers
264
        //
265
        // get element based on this.selectors key/value pairs
266
        sel: function(id, params) {
267
            if (!this.selectors[id]){ return };
268
            return $(this.selectors[id].format(params));
269
        },
270
        
271
        // vm element based on vm model instance provided
272
        vm: function(vm) {
273
            if (hasKey.call(this._vm_els, vm.id)) {
274
                ret = this._vm_els[vm.id];
275
            } else {
276
                return $([]);
277
            }
278

    
279
            return ret;
280
        },
281
        
282
        // get vm model instance from DOM element
283
        vm_for_element: function(el) {
284
            return storage.vms.sel(this.vm_id_for_element(el));
285
        },
286
        
287

    
288
        // Event binding and stuff like that
289
        //
290
        set_storage_handlers: function() {
291
            storage.vms.bind("add", _.bind(this.vms_updated_handler, this, "add"));
292
            storage.vms.bind("change", _.bind(this.vms_updated_handler, this, "change"));
293
            storage.vms.bind("reset", _.bind(this.vms_updated_handler, this, "reset"));
294
            storage.vms.bind("remove", _.bind(this.vms_updated_handler, this, "remove"));
295
        },
296
        
297
        // vms updated triggered, update view vms
298
        vms_updated_handler: function (method, model, arg2, arg3) {
299
            var updated = storage.vms.models;
300
            if (method == "add") { updated = [model] };
301
            if (method == "change") { updated = [model] };
302
            if (method == "remove") { updated = [model] };
303

    
304
            if (method == "remove") {
305
                this.remove_vm(model)
306
                return;
307
            }
308
            
309
            this.update_vms(updated);
310
        },
311

    
312
        // create vm
313
        // append it on proper view container
314
        create_vm: function(vm) {
315
            // create dom element
316
            var vm_view = this.create_vm_element(vm);
317
            vm_view.find(".vm-actions").attr("id", this.view_id+"-actions-" + vm.id);
318
            this._vm_els[vm.id] = vm_view;
319
            var container = this.get_vm_container(vm);
320
            container.append(vm_view);
321
            vm_view.find(".action-indicator").text("");
322
            if (this.visible()) {
323
                container.show()
324
            }
325

    
326
            // initialize vm specific event handlers 
327
            this.__set_vm_handlers(vm);
328
            vm_view.find(".suspended-notice").click(function(){
329
              synnefo.ui.main.suspended_view.show(vm);
330
            })
331
            return vm_view;
332
        },
333
        
334
        // create vm dom element
335
        create_vm_element: function(vm) {
336
            // clone template
337
            return this.sel('tpl').clone().attr("id", this.id_tpl + vm.id)
338
        },
339

    
340
        // get proper vm container
341
        get_vm_container: function(vm) {
342
            if (vm.is_active()) {
343
                return this.sel("vm_cont_active");
344
            } else {
345
                return this.sel("vm_cont_terminated");
346
            }
347
        },
348

    
349
        // create and append inside the proper container the vm model
350
        // if it doesn't exist update vm data and make it visible
351
        add: function(vm) {
352
            // create if it does not exist
353
            if (!hasKey.call(this._vm_els, vm.id)) {
354
                var el = this.create_vm(vm);
355
                el.show();
356
                this.post_add(vm);
357
                this.init_vm_view_handlers(vm);
358
            }
359

    
360
            return this.vm(vm);
361
        },
362

    
363
        init_vm_view_handlers: function(vm) {
364
            var self = this;
365
            var el = this.vm(vm);
366

    
367
            // hidden feature, double click on indicators to display 
368
            // vm diagnostics.
369
            el.find(".indicators").bind("dblclick", function(){
370
                self.show_build_details_for_vm(vm);
371
            });
372

    
373
            // this button gets visible if vm creation failed.
374
            el.find("div.build-progress .btn").click(function(){
375
                self.show_build_details_for_vm(vm);
376
            });
377
        },
378
        
379
        // helpers for VMListView descendants
380
        post_add: function(vm) { throw "Not implemented" },
381
        set_vm_handlers: function(vm) { throw "Not implemented" },
382
        set_handlers: function() { throw "Not implemented" },
383
        update_layout: function() { throw "Not implemented" },
384
        post_update_vm: function(vm) { throw "Not implemented" },
385
        update_details: function(vm) {},
386
        
387
        // remove vm
388
        remove_vm: function(vm) {
389
            // FIXME: some kind of transiton ??? effect maybe ???
390
            this.vm(vm).remove();
391
            this.post_remove_vm(vm);
392
            if (hasKey.call(this._vm_els, vm.id)) {
393
                delete this._vm_els[vm.id];
394
            }
395
        },
396
        
397
        // remove all vms from view
398
        clear: function() {
399
            this.sel('vms').remove();
400
            this.__update_layout();
401
        },
402

    
403
        show: function() {
404
            views.VMListView.__super__.show.apply(this, arguments);
405
            if (storage.vms.length == 0) { this.hide() };
406
            if (!snf.config.update_hidden_views) {
407
                this.update_vms(storage.vms.models);
408
            }
409
        },
410

    
411
        // do update for provided vms, then update the view layout
412
        update_vms: function(vms) {
413
            if (!this.visible() && !snf.config.update_hidden_views) { return };
414

    
415
            _.each(vms, _.bind(function(vm){
416
                // vm will be removed
417
                // no need to update
418
                if (vm.get("status") == "DELETED") {
419
                    return;
420
                }
421

    
422
                // this won't add it additional times
423
                this.add(vm);
424
                this.update_vm(vm);
425
            }, this))
426
            
427
            // update view stuff
428
            this.__update_layout();
429
        },
430
        
431
        update_toggles_visibility: function(vm) {
432
          if (vm.is_building() || vm.in_error_state() || vm.get("status") == "DESTROY") {
433
            this.vm(vm).find(".cont-toggler-wrapper.ips").addClass("disabled");
434
          } else {
435
            this.vm(vm).find(".cont-toggler-wrapper.ips").removeClass("disabled");
436
          }
437
        },
438

    
439
        // update ui for the given vm
440
        update_vm: function(vm) {
441
            // do not update deleted state vms
442
            if (!vm || vm.get("status") == 'DELETED') { return };
443
            this.check_vm_container(vm);
444

    
445
            this.update_details(vm);
446
            this.update_transition_state(vm);
447
            this.update_toggles_visibility(vm);
448

    
449
            if (this.action_views) {
450
                this.action_views[vm.id].update();
451
                this.action_views[vm.id].update_layout();
452
            }
453
            
454
            var el = this.vm(vm);
455
            if (vm.can_resize()) {
456
              el.addClass("can-resize");
457
            } else {
458
              el.removeClass("can-resize");
459
            }
460

    
461
            if (vm.get('suspended')) {
462
              el.addClass("suspended");
463
            } else {
464
              el.removeClass("suspended");
465
            }
466

    
467
            try {
468
                this.post_update_vm(vm);
469
            } catch (err) {};
470
        },
471

    
472
        // check if vm is placed properly within the view
473
        // container (e.g. some views might have different
474
        // containers for terminated or running machines
475
        check_vm_container: function(vm){
476
            var el = this.vm(vm);
477
            if (!el.length) { return };
478
            var self = this;
479
            var selector = vm.is_active() ? 'vm_cont_active' : 'vm_cont_terminated';
480
            if (el.parent()[0] != this.sel(selector)[0]) {
481
                var cont = this.sel(selector);
482
                var self = this;
483

    
484
                el.hide().appendTo(cont).fadeIn(300);
485
                $(window).trigger('resize');
486

    
487
                //el.fadeOut(200, function() {
488
                    //el.appendTo(cont); 
489
                    //el.fadeIn(200);
490
                    //self.sel(selector).show(function(){
491
                        //$(window).trigger("resize");
492
                    //});
493
                //});
494
            }
495
        },
496

    
497
        __update_layout: function() {
498
            this.update_layout();
499
        },
500
        
501
        // append handlers for vm specific events
502
        __set_vm_handlers: function(vm) {
503
            // show transition on vm status transit
504
            vm.bind('transition', _.bind(function(){this.show_transition(vm)}, this));
505
            this.set_vm_handlers(vm);
506
        },
507
        
508
        // is vm in transition ??? show the progress spinner
509
        update_transition_state: function(vm) {
510
            if (vm.in_transition() && !vm.has_pending_action()){
511
                this.sel('vm_spinner', vm.id).show();
512
            } else {
513
                this.sel('vm_spinner', vm.id).hide();
514
            }
515
        },
516
        
517
        show_indicator: function(vm, action) {
518
            var action = action || vm.pending_action;
519
            this.sel('vm_wave', vm.id).hide();
520
            this.sel('vm_spinner', vm.id).hide();
521
            this.vm(vm).find(".action-indicator").removeClass().addClass(action + " action-indicator").show();
522
        },
523

    
524
        hide_indicator: function(vm) {
525
            this.vm(vm).find(".action-indicator").removeClass().addClass("action-indicator").hide();
526
            this.update_transition_state(vm);
527
        },
528

    
529
        // display transition animations
530
        show_transition: function(vm) {
531
            var wave = this.sel('vm_wave', vm.id);
532
            if (!wave || !wave.length) { return }
533

    
534
            var src = wave.attr('src');
535
            // change src to force gif play from the first frame
536
            // animate for 500 ms then hide
537
            wave.attr('src', "").show();
538
            wave.attr('src', src).fadeIn(200).delay(700).fadeOut(300, function() {
539
                wave.hide();
540
            });
541
        },
542

    
543
        connect_to_console: function(vm) {
544
            // It seems that Safari allows popup windows only if the window.open
545
            // call is made within an html element click event context. 
546
            // Otherwise its behaviour is based on "Block Pop-Up Windows" 
547
            // setting, which when enabled no notification appears for the user. 
548
            // Since there is no easy way to check for the setting value we use
549
            // async:false for the action call so that action success handler 
550
            // which opens the new window is called inside the click event 
551
            // context.
552
            var use_async = true;
553
            if ($.client.browser == "Safari") {
554
                use_async = false;
555
            }
556

    
557
            vm.call("console", function(console_data) {
558
                var url = vm.get_console_url(console_data);
559
                snf.util.open_window(url, "VM_" + vm.get("id") + "_CONSOLE", {'scrollbars': 1, 'fullscreen': 0});
560
            }, undefined, {async: use_async});
561
        }
562

    
563
    });
564
    
565
    // empty message view (just a wrapper to the element containing 
566
    // the empty information message)
567
    views.EmptyView = views.View.extend({
568
        el: '#emptymachineslist'
569
    })
570

    
571
    views.VMActionsView = views.View.extend({
572
        
573
        initialize: function(vm, parent, el, hide) {
574
            this.hide = hide || false;
575
            this.view = parent;
576
            this.vm = vm;
577
            this.vm_el = el;
578
            this.el = $("#" + parent.view_id + "-actions-" + vm.id);
579
            this.all_action_names = _.keys(views.VMActionsView.STATUS_ACTIONS);
580
            
581
            // state params
582
            this.selected_action = false;
583

    
584
            _.bindAll(this);
585
            window.acts = this;
586
            this.view_id = "vm_" + vm.id + "_actions";
587
            views.VMActionsView.__super__.initialize.call(this);
588

    
589
            this.hovered = false;
590
            this.set_hover_handlers();
591
        },
592

    
593
        action: function(name) {
594
            return $(this.el).find(".action-container." + name);
595
        },
596

    
597
        action_link: function(name) {
598
            return this.action(name).find("a");
599
        },
600
        
601
        action_confirm_cont: function(name) {
602
            return this.action_confirm(name).parent();
603
        },
604

    
605
        action_confirm: function(name) {
606
            return this.action(name).find("button.yes");
607
        },
608

    
609
        action_cancel: function(name) {
610
            return this.action(name).find("button.no");
611
        },
612

    
613
        hide_actions: function() {
614
            $(this.el).find("a").css("visibility", "hidden");
615
        },
616
        
617
        set_can_start: function() {
618
          var el = $(this.el).find("a.action-start").parent();
619
          el.removeClass("disabled-visible");
620
        },
621

    
622
        set_cannot_start: function() {
623
          var el = $(this.el).find("a.action-start").parent();
624
          el.addClass("disabled-visible")
625
        },
626

    
627
        // update the actions layout, depending on the selected actions
628
        update_layout: function() {
629
            
630
            if (this.vm.get('status') == 'STOPPED') {
631
              if (this.vm.can_start()) {
632
                this.set_can_start();
633
              } else {
634
                this.set_cannot_start();
635
              }
636
            }
637

    
638
            if (!this.vm_handlers_initialized) {
639
                this.vm = storage.vms.get(this.vm.id);
640
                this.init_vm_handlers();
641
            }
642

    
643
            if (!this.vm) { return }
644
            
645
            if (!this.hovered && !this.vm.has_pending_action() && this.hide && !this.vm.action_error) { 
646
                this.el.hide();
647
                this.view.hide_indicator(this.vm);
648
                return 
649
            };
650

    
651
            if (!this.el.is(":visible") && !this.hide) { return };
652
            if (!this.handlers_initialized) { this.set_handlers(); }
653

    
654

    
655
            // update selected action
656
            if (this.vm.pending_action) {
657
                this.selected_action = this.vm.pending_action;
658
            } else {
659
                this.selected_action = false;
660
            }
661
            
662
            // vm actions tha can be performed
663
            var actions = this.vm.get_available_actions();
664
            
665
            // had pending action but actions changed and now selected action is
666
            // not available, hide it from user
667
            if (this.selected_action && actions.indexOf(this.selected_action) == -1) {
668
                this.reset();
669
            }
670
            
671
            this.el.show();
672
            
673
            if ((this.selected_action || this.hovered) && !this.vm.action_error) {
674
                // show selected action
675
                $(this.el).show();
676
                $(this.el).find("a").css("visibility", "visible");
677
                // show action icon
678
                this.view.show_indicator(this.vm);
679
            } else {
680
                if (this.hide || this.vm.action_error) {
681
                    // view shows actions on machine hover
682
                    $(this.el).find("a").css("visibility", "hidden");
683
                } else {
684
                    if (!this.vm.action_error) {
685
                        // view shows actions always
686
                        $(this.el).find("a").css("visibility", "visible");
687
                        $(this.el).show();
688
                    }
689
                }
690
                
691
                this.view.hide_indicator(this.vm);
692
            }
693
                
694
            // update action link styles and shit
695
            _.each(models.VM.ACTIONS, function(action, index) {
696
                if (actions.indexOf(action) > -1) {
697
                    this.action(action).removeClass("disabled");
698
                    var inactive = models.VM.AVAILABLE_ACTIONS_INACTIVE[action];
699

    
700
                    if (inactive && !_.contains(inactive, this.vm.get('status'))) {
701
                      this.action(action).addClass("inactive");
702
                    } else {
703
                      this.action(action).removeClass("inactive");
704
                    }
705

    
706
                    if (this.selected_action == action) {
707
                        this.action_confirm_cont(action).css('display', 'block');
708
                        this.action_confirm(action).show();
709
                        this.action(action).removeClass("disabled");
710
                        this.action_link(action).addClass("selected");
711
                    } else {
712
                        this.action_confirm_cont(action).hide();
713
                        this.action_confirm(action).hide();
714
                        this.action_link(action).removeClass("selected");
715
                    }
716
                } else {
717
                    this.action().hide();
718
                    this.action(action).addClass("disabled");
719
                    this.action_confirm(action).hide();
720
                }
721
            }, this);
722
        },
723
        
724
        init_vm_handlers: function() {
725
            try {
726
                this.vm.unbind("action:fail", this.update_layout)
727
                this.vm.unbind("action:fail:reset", this.update_layout)
728
            } catch (err) { console.log("Error")};
729
            
730
            this.vm.bind("action:fail", this.update_layout)
731
            this.vm.bind("action:fail:reset", this.update_layout)
732

    
733
            this.vm_handlers_initialized = true;
734
        },
735
        
736
        set_hover_handlers: function() {
737
            // vm container hover (icon view)
738
            this.view.vm(this.vm).hover(_.bind(function() {
739
                this.hovered = true;
740
                this.update_layout();
741

    
742
            }, this),  _.bind(function() {
743
                this.hovered = false;
744
                this.update_layout();
745
            }, this));
746
        },
747

    
748
        // bind event handlers
749
        set_handlers: function() {
750
            var self = this;
751
            var vm = this.vm;
752
            
753
            // initial hide
754
            if (this.hide) { $(this.el).hide() };
755
            
756
            if (this.$('.snapshot').length) {
757
              this.$('.snapshot').click(_.bind(function() {
758
                synnefo.ui.main.create_snapshot_view.show(this.vm);
759
              }, this));
760
            }
761
            // action links events
762
            _.each(models.VM.ACTIONS, function(action) {
763
                var action = action;
764
                // indicator hovers
765
                this.view.vm(this.vm).find(".action-container."+action+" a").hover(function() {
766
                    self.view.show_indicator(self.vm, action);
767
                }, function() {
768
                    // clear or show selected action indicator
769
                    if (self.vm.pending_action) {
770
                        self.view.show_indicator(self.vm);
771
                    } else {
772
                        self.view.hide_indicator(self.vm);
773
                    }
774
                })
775
                
776
                // action links click events
777
                $(this.el).find(".action-container."+action+" a").click(function(ev) {
778
                    ev.preventDefault();
779
                    if (action == "start" && !self.vm.can_start() && !vm.in_error_state()) {
780
                        ui.main.vm_resize_view.show_with_warning(self.vm);
781
                        return;
782
                    }
783

    
784
                    if (action == "resize") {
785
                        ui.main.vm_resize_view.show(self.vm);
786
                        return;
787
                    } else {
788
                        self.set(action);
789
                    }
790
                }).data("action", action);
791

    
792
                // confirms
793
                $(this.el).find(".action-container."+action+" button.no").click(function(ev) {
794
                    ev.preventDefault();
795
                    self.reset();
796
                });
797

    
798
                // cancels
799
                $(this.el).find(".action-container."+action+" button.yes").click(function(ev) {
800
                    ev.preventDefault();
801
                    // override console
802
                    // ui needs to act (open the console window)
803
                    if (action == "console") {
804
                        self.view.connect_to_console(self.vm);
805
                    } else {
806
                        self.vm.call(action);
807
                    }
808
                    self.reset();
809
                });
810
            }, this);
811

    
812
            this.handlers_initialized = true;
813
        },
814
        
815
        // reset actions
816
        reset: function() {
817
            var prev_action = this.selected_action;
818
            this.selected_action = false;
819
            this.vm.clear_pending_action();
820
            this.trigger("change", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
821
            this.trigger("remove", {'action': prev_action, 'vm': this.vm, 'view': this, remove: true});
822
        },
823
        
824
        // set selected action
825
        set: function(action_name) {
826
            if (action_name == "snapshot") { return }
827
            this.selected_action = action_name;
828
            this.vm.update_pending_action(this.selected_action);
829
            this.view.vm(this.vm).find(".action-indicator").show().removeClass().addClass(action_name + " action-indicator");
830
            this.trigger("change", {'action': this.selected_action, 'vm': this.vm, 'view': this});
831
        },
832

    
833
        update: function() {
834
        }
835
    })
836

    
837

    
838
    views.VMActionsView.STATUS_ACTIONS = { 
839
        'reboot':        ['UNKOWN', 'ACTIVE', 'REBOOT'],
840
        'shutdown':      ['UNKOWN', 'ACTIVE', 'REBOOT'],
841
        'console':       ['ACTIVE'],
842
        'start':         ['UNKOWN', 'STOPPED'],
843
        'resize':        ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD'],
844
        'snapshot':      ['ACTIVE', 'STOPPED'],
845
        'destroy':       ['UNKOWN', 'ACTIVE', 'STOPPED', 'REBOOT', 'ERROR', 'BUILD']
846
    };
847

    
848
    // UI helpers
849
    var uihelpers = snf.ui.helpers = {};
850
    
851
    // OS icon helpers
852
    var os_icon = uihelpers.os_icon = function(os) {
853
        var icons = window.os_icons;
854
        if (!icons) { return "unknown" }
855
        if (icons.indexOf(os) == -1) {
856
            os = "unknown";
857
        }
858
        return os;
859
    }
860

    
861
    var os_icon_path = uihelpers.os_icon_path = function(os, size, active) {
862
        size = size || "small";
863
        if (active == undefined) { active = true };
864

    
865
        var icon = os_icon(os);
866
        if (active) {
867
            icon = icon + "-on";
868
        } else {
869
            icon = icon + "-off";
870
        }
871

    
872
        return (snf.config.machines_icons_url + "{0}/{1}.png").format(size, icon)
873
    }
874

    
875
    var os_icon_tag = uihelpers.os_icon_tag = function (os, size, active, attrs) {
876
        attrs = attrs || {};
877
        return '<img src="{0}" />'.format(os_icon_path(os, size, active));
878
    }
879

    
880
    // VM Icon helpers
881
    //
882
    // identify icon
883
    var vm_icon = uihelpers.vm_icon = function(vm) {
884
        return os_icon(vm.get_os());
885
    }
886
    
887
    // get icon url
888
    var vm_icon_path = uihelpers.vm_icon_path = function(vm, size) {
889
        return os_icon_path(vm.get_os(), size, vm.is_active());
890
    }
891
    
892
    // get icon IMG tag
893
    var vm_icon_tag = uihelpers.vm_icon_tag = function (vm, size, attrs) {
894
       return os_icon_tag(vm.get_os(), size, vm.is_active(), attrs);
895
    }
896
    
897

    
898
    snf.ui = _.extend(snf.ui, bb.Events);
899
    snf.ui.trigger_error = function(code, msg, error, extra) {
900
        if (msg.match(/Script error/i)) {
901
          // No usefull information to display in this case. Plus it's an
902
          // exception that probably doesn't affect our app.
903
          return 0;
904
        }
905
        snf.ui.trigger("error", { code:code, msg:msg, error:error, extra:extra || {} });
906
    };
907
    
908
    views.VMPortView = views.ext.ModelView.extend({
909
      tpl: '#vm-port-view-tpl',
910
      classes: 'port-item clearfix',
911
      
912
      update_in_progress: function() {
913
        if (this.model.get("in_progress_no_vm")) {
914
          this.set_in_progress();
915
        } else {
916
          this.unset_in_progress();
917
        }
918
      },
919

    
920
      set_in_progress: function() {
921
        this.el.find(".type").hide();
922
        this.el.find(".in-progress").removeClass("hidden").show();
923
      },
924

    
925
      unset_in_progress: function() {
926
        this.el.find(".type").show();
927
        this.el.find(".in-progress").hide();
928
      },
929

    
930
      disconnect_port: function(model, e) {
931
        e && e.stopPropagation();
932
        var network = this.model.get("network");
933
        this.model.actions.reset_pending();
934
        this.model.disconnect(_.bind(this.disconnect_port_complete, this));
935
      },
936

    
937
      disconnect_port_complete: function() {
938
      },
939

    
940
      get_network_name: function() {
941
        var network = this.model.get('network');
942
        var name = network && network.get('name');
943
        if (!name) {
944
          if (network && network.get('is_public')) {
945
            name = 'Internet'
946
          } else {
947
            name = '(no network name)'
948
          }
949
        }
950
        var truncate_length = this.parent_view.options.truncate || 40;
951
        name = synnefo.util.truncate(name, truncate_length, '...');
952
        return name || 'Loading...';
953
      },
954

    
955
      get_network_icon: function() {
956
        var ico;
957
        var network = this.model.get('network');
958
        if (network) {
959
          ico = network.get('is_public') ? 'internet-small.png' : 'network-small.png';
960
        } else {
961
          ico = 'network-small.png';
962
        }
963
        return synnefo.config.media_url + 'images/' + ico;
964
      },
965
    });
966
    
967
    views.VMPortListView = views.ext.CollectionView.extend({
968
      tpl: '#vm-port-list-view-tpl',
969
      model_view_cls: views.VMPortView
970
    });
971

    
972
    views.VMPortIpView = views.ext.ModelView.extend({
973
      tpl: '#vm-port-ip-tpl',
974
      css_classes: "clearfix"
975
    });
976

    
977
    views.VMPortIpsView = views.ext.CollectionView.extend({
978
      tpl: '#vm-port-ips-tpl',
979
      model_view_cls: views.VMPortIpView
980
    });
981

    
982
})(this);