Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_networks_view.js @ 365af933

History | View | Annotate | Download (22.9 kB)

1
// Copyright 2013 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
    var util = snf.util || {};
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
    views.NetworkCreateView = views.Overlay.extend({
56
        view_id: "network_create_view",
57
        content_selector: "#networks-create-content",
58
        css_class: 'overlay-networks-create overlay-info',
59
        overlay_id: "network-create-overlay",
60

    
61
        title: "Create new private network",
62
        subtitle: "Networks",
63

    
64
        initialize: function(options) {
65
            views.NetworkCreateView.__super__.initialize.apply(this);
66

    
67
            this.create_button = this.$("form .form-action.create");
68
            this.text = this.$(".network-create-name");
69
            this.form = this.$("form");
70

    
71
            this.dhcp_select = this.$("#network-create-dhcp");
72
            this.type_select = this.$("#network-create-type");
73
            this.subnet_select = this.$("#network-create-subnet");
74
            this.subnet_custom = this.$("#network-create-subnet-custom");
75
                
76
            this.dhcp_form = this.$("#network-create-dhcp-fields");
77
            
78
            this.subnet_select.find(".subnet").remove();
79
            _.each(synnefo.config.network_suggested_subnets, function(subnet){
80
                this.subnet_select.append($('<option value='+subnet+' class="subnet">'+subnet+'</option>'));
81
            }, this);
82

    
83
            this.type_select.find(".subnet").remove();
84
            _.each(synnefo.config.network_available_types, function(name, value){
85
                this.type_select.append($('<option value='+value+' class="subnet">'+name+'</option>'));
86
            }, this);
87
            
88
            if (_.keys(synnefo.config.network_available_types).length <= 1) {
89
                this.type_select.closest(".form-field").hide();
90
            }
91

    
92
            this.check_dhcp_form();
93
            this.init_handlers();
94
        },
95

    
96
        reset_dhcp_form: function() {
97
          this.subnet_select.find("option")[0].selected = 1;
98
          this.subnet_custom.val("");
99
        },
100

    
101
        check_dhcp_form: function() {
102
            if (this.dhcp_select.is(":checked")) {
103
                this.dhcp_form.show();
104
            } else {
105
                this.dhcp_form.hide();
106
            }
107
            
108
            if (this.subnet_select.val() == "custom") {
109
                this.subnet_custom.show();
110
            } else {
111
                this.subnet_custom.hide();
112
            }
113
        },
114

    
115
        init_handlers: function() {
116

    
117
            this.dhcp_select.click(_.bind(function(e){
118
                this.check_dhcp_form();
119
                this.reset_dhcp_form();
120
            }, this));
121

    
122
            this.subnet_select.change(_.bind(function(e){
123
                this.check_dhcp_form();
124
                if (this.subnet_custom.is(":visible")) {
125
                    this.subnet_custom.focus();
126
                }
127
            }, this));
128

    
129
            this.create_button.click(_.bind(function(e){
130
                this.submit();
131
            }, this));
132

    
133
            this.form.submit(_.bind(function(e){
134
                e.preventDefault();
135
                this.submit;
136
                return false;
137
            }, this))
138

    
139
            this.text.keypress(_.bind(function(e){
140
                if (e.which == 13) {this.submit()};
141
            },this))
142
        },
143

    
144
        submit: function() {
145
            if (this.validate()) {
146
                this.create();
147
            };
148
        },
149
        
150
        validate: function() {
151
            // sanitazie
152
            var t = this.text.val();
153
            t = t.replace(/^\s+|\s+$/g,"");
154
            this.text.val(t);
155

    
156
            if (this.text.val() == "") {
157
                this.text.closest(".form-field").addClass("error");
158
                this.text.focus();
159
                return false;
160
            } else {
161
                this.text.closest(".form-field").removeClass("error");
162
            }
163
            
164
            if (this.dhcp_select.is(":checked")) {
165
                if (this.subnet_select.val() == "custom") {
166
                    var sub = this.subnet_custom.val();
167
                    sub = sub.replace(/^\s+|\s+$/g,"");
168
                    this.subnet_custom.val(sub);
169
                        
170
                    if (!synnefo.util.IP_REGEX.exec(this.subnet_custom.val())) {
171
                        this.subnet_custom.closest(".form-field").prev().addClass("error");
172
                        return false;
173
                    } else {
174
                        this.subnet_custom.closest(".form-field").prev().removeClass("error");
175
                    }
176
                };
177
            }
178

    
179
            return true;
180
        },
181
        
182
        get_next_available_subnet: function() {
183
            var auto_tpl = synnefo.config.automatic_network_range_format;
184
            if (!auto_tpl) {
185
                return null
186
            }
187
            var index = 0;
188
            var subnet = auto_tpl.format(index);
189
            var networks = synnefo.storage.networks;
190
            var check_existing = function(n) { return n.get('cidr') == subnet }
191
            while (networks.filter(check_existing).length > 0 && index <= 255) {
192
                index++;
193
                subnet = auto_tpl.format(index); 
194
            }
195
            return subnet;
196
        },
197

    
198
        create: function() {
199
            this.create_button.addClass("in-progress");
200

    
201
            var name = this.text.val();
202
            var dhcp = this.dhcp_select.is(":checked");
203
            var subnet = null;
204
            var type = this.type_select.val();
205

    
206
            if (dhcp) {
207
                if (this.subnet_select.val() == "custom") {
208
                    subnet = this.subnet_custom.val();
209
                } else if (this.subnet_select.val() == "auto") {
210
                    subnet = this.get_next_available_subnet()
211
                } else {
212
                    subnet = this.subnet_select.val();
213
                }
214
                
215
            }
216

    
217
            snf.storage.networks.create(name, type, subnet, dhcp, _.bind(function(){
218
                this.hide();
219
            }, this));
220
        },
221

    
222
        beforeOpen: function() {
223
            this.create_button.removeClass("in-progress")
224
            this.text.closest(".form-field").removeClass("error");
225
            this.text.val("");
226
            this.text.show();
227
            this.text.focus();
228
            this.subnet_custom.val("");
229
            this.subnet_select.val("auto");
230
            this.dhcp_select.attr("checked", true);
231
            this.type_select.val(_.keys(synnefo.config.network_available_types)[0]);
232
            this.check_dhcp_form();
233
        },
234

    
235
        onOpen: function() {
236
            this.text.focus();
237
        }
238
    });
239

    
240
    views.NetworkPortView = views.ext.ModelView.extend({
241
      tpl: '#network-port-view-tpl',
242
      
243
      vm_logo_url: function(vm) {
244
        if (!this.model.get('vm')) { return '' }
245
        return synnefo.ui.helpers.vm_icon_path(this.model.get('vm'), 'medium');
246
      },
247
      
248
      set_confirm: function() {
249
        var parent = this.parent_view.parent_view.el;
250
        parent.addClass("subactionpending");
251
      },
252

    
253
      unset_confirm: function() {
254
        var parent = this.parent_view.parent_view.el;
255
        parent.removeClass("subactionpending");
256
      },
257

    
258
      post_init_element: function() {
259
        this.in_progress = false;
260
        this.firewall = this.$(".firewall-content").hide();
261
        this.firewall_toggler = this.$(".firewall-toggle");
262
        this.firewall_apply = this.$(".firewall-apply");
263
        this.firewall_legends = this.firewall.find(".checkbox-legends");
264
        this.firewall_inputs = this.firewall.find("input");
265
        this.firewall_apply = this.firewall.find("button");
266
        this.firewall_visible = false;
267

    
268
        this.firewall_toggler.click(_.bind(function() {
269
          this.toggle_firewall();
270
        }, this));
271

    
272
        this.firewall.find(".checkbox-legends, input").click(
273
          _.bind(this.handle_firewall_choice_click, this));
274
        this.update_firewall();
275
      },
276
      
277
      toggle_firewall: function(e, hide, cb) {
278
          hide = hide === undefined ? false : hide;
279
          if (hide) {
280
            this.firewall.stop().hide();
281
          } else {
282
            if (!cb) { cb = function() {}}
283
            this.firewall.slideToggle(cb);
284
          }
285
          this.firewall_toggler.toggleClass("open");
286
          this.firewall_visible = this.firewall_toggler.hasClass("open");
287
          if (!this.firewall_visible) {
288
            this.firewall_apply.fadeOut(50);
289
          }
290
          this.update_firewall();
291
      },
292
    
293
      post_hide: function() {
294
        views.NetworkPortView.__super__.post_hide.apply(this);
295
        if (this.firewall_visible) {
296
          this.toggle_firewall({}, true);
297
        }
298
      },
299

    
300
      handle_firewall_choice_click: function(e) {
301
          var el = $(e.currentTarget);
302
          if (el.get(0).tagName == "INPUT") {
303
            el = el.next();
304
          }
305
          var current = this.model.get("firewall_status");
306
          var selected = el.prev().val();
307

    
308
          el.parent().find("input").attr("checked", false);
309
          el.prev().attr("checked", true);
310

    
311
          if (selected != current) {
312
            this.firewall_apply.show();
313
          } else {
314
            this.firewall_apply.hide();
315
          }
316
      },
317
      
318
      disconnect_port: function(model, e) {
319
        e && e.stopPropagation();
320
        this.model.actions.reset_pending();
321
        this.model.disconnect(_.bind(this.disconnect_port_complete, this));
322
      },
323

    
324
      disconnect_port_complete: function() {
325
      },
326

    
327
      set_firewall: function() {
328
        var value = this.get_selected_value();
329
        this.firewall_apply.addClass("in-progress");
330
        this.model.set({'pending_firewall': value});
331
        this.model.set_firewall(value, this.set_firewall_complete, 
332
                                       this.set_firewall_complete);
333
        this.in_progress = true;
334
      },
335
      
336
      set_firewall_complete: function() {
337
        this.in_progress = false;
338
        this.toggle_firewall({}, false, _.bind(function() {
339
          this.firewall_apply.removeClass("in-progress").show();
340
        }, this));
341
      },
342

    
343
      get_selected_value: function() {
344
        return this.firewall_inputs.filter(":checked").val();
345
      },
346

    
347
      update_firewall: function() {
348
        var value = this.model.get("firewall_status");
349
        var value_selector = "[value=" + value + "]"
350
        var status_span = this.firewall_toggler.find("span span");
351
        var current_choice = this.firewall_inputs.filter(value_selector);
352

    
353
        if (_.contains(["PROTECTED", "ENABLED"], value)) {
354
          status_span.removeClass("firewall-off").addClass("firewall-on");
355
          status_span.text("On");
356
        } else {
357
          status_span.removeClass("firewall-on").addClass("firewall-off");
358
          status_span.text("Off");
359
        }
360
        
361
        this.firewall_inputs.attr("checked", false);
362
        this.firewall_legends.removeClass("current");
363
        current_choice.attr("checked", true)
364
        current_choice.next().addClass("current");
365
      },
366

    
367
      show_vm_details: function() {
368
        var vm = this.model.get('vm');
369
        if (vm) { snf.ui.main.show_vm_details(vm) }
370
      }
371
    });
372

    
373
    views.NetworkPortCollectionView = views.ext.CollectionView.extend({
374
      tpl: '#network-port-collection-view-tpl',
375
      model_view_cls: views.NetworkPortView,
376
      rivets_view: true,
377
      get_rivet_object: function() {
378
        return {
379
          model: this.collection.network
380
        }
381
      },
382
      resolve_storage_object: function() {
383
        return this.collection
384
      },
385
      show_connect_vms_overlay: function() {
386
        this.parent_view.show_connect_vms_overlay();
387
      }
388
    });
389

    
390
    views.NetworkView = views.ext.ModelView.extend({
391
      tpl: '#network-view-tpl',
392
      auto_bind: ['connect_vm'],
393
      post_init_element: function() {
394
        this.ports = this.$(".ports.nested-model-list");
395
        this.ports.hide();
396
        this.ports_toggler = this.$(".network-ports-toggler");
397
        this.ports_toggler.click(this.toggle_ports);
398
        this.ports_visible = false;
399
      },
400

    
401
      toggle_ports: function(e, hide) {
402
        hide = hide === undefined ? false : hide;
403
        if (hide) {
404
          this.ports.stop().hide();
405
        } else {
406
          this.ports.stop().slideToggle();
407
        }
408
        this.ports_toggler.find(".cont-toggler").toggleClass("open");
409
        this.ports_visible = this.ports_toggler.find(".cont-toggler").hasClass("open");
410
      },
411
      
412
      get_network_icon: function() {
413
        var ico = this.model.get('is_public') ? 'internet.png' : 'network.png';
414
        return synnefo.config.media_url + 'images/' + ico;
415
      },
416

    
417
      post_hide: function() {
418
        views.NetworkView.__super__.post_hide.apply(this);
419
        if (this.ports_visible) {
420
          this.toggle_ports({}, true);
421
        }
422
      },
423
      
424
      status_map: {
425
        'ACTIVE': 'Active',
426
        'CONNECTING': 'Connecting',
427
        'DISCONNECTING': 'Disconnecting',
428
        'REMOVING': 'Destroying'
429
      },
430

    
431
      status_cls_map: {
432
        'ACTIVE': 'status-active',
433
        'DISCONNECTING': 'status-progress',
434
        'CONNECTING': 'status-progress',
435
        'REMOVING': 'status-progress'
436
      },
437
      
438
      status_cls: function(status) {    
439
        return this.status_cls_map[this.model.get('ext_status')]
440
      },
441

    
442
      status_display: function(status) {
443
        var status;
444
        if (this.model.id == "snf-combined-public-network") {
445
          return "Internet"
446
        }
447

    
448
        var cidr = this.model.get('cidr');
449
        var status = this.model.get('ext_status');
450
        if (status != 'REMOVING' && cidr) {
451
          return cidr
452
        }
453
        return this.status_map[status];
454
      },
455
      
456
      connect_vms: function(vms, cb) {
457
        var finished = 0;
458
        var completed = function() {
459
          finished++;
460
          if (finished == vms.length) {
461
            cb();
462
          }
463
        }
464
        _.each(vms, function(vm) {
465
          this.model.connect_vm(vm, completed);
466
        }, this);
467
      },
468
      
469
      remove: function(model, e) {
470
        e && e.stopPropagation();
471
        this.model.actions.reset_pending();
472
        this.model.destroy({
473
          success: _.bind(function() {
474
            this.model.set({status: 'REMOVING'});
475
            this.model.set({ext_status: 'REMOVING'});
476
            // force status display update
477
            this.model.set({cidr: 'REMOVING'});
478
          }, this),
479
          silent: true
480
        });
481
      },
482

    
483
      show_connect_vms_overlay: function() {
484
        var view = new views.NetworkConnectVMsOverlay();
485
        vms = this.model.pluggable_vms();
486
        var cb = _.bind(function(vms) {
487
          view.set_in_progress();
488
          var cbinner = function() {
489
            view.hide();
490
            delete view;
491
          }
492
          this.connect_vms(vms, cbinner);
493
        }, this);
494
        view.show_vms(this.model, vms, [], cb, "subtitle");
495
      }
496

    
497
    });
498
    
499
    views.NetworksCollectionView = views.ext.CollectionView.extend({
500
      collection: storage.networks,
501
      collection_name: 'networks',
502
      model_view_cls: views.NetworkView,
503
      create_view_cls: views.NetworkCreateView,
504
      
505
      init: function() {
506
        this.public_added = false;
507
        views.NetworksCollectionView.__super__.init.apply(this, arguments);
508
      },
509
      
510
      check_empty: function() {
511
        views.NetworksCollectionView.__super__.check_empty.apply(this, arguments);
512
        //if (this.$(".private").children().length == 0) {
513
          //this.$(".private").hide();
514
        //} else {
515
          //this.$(".private").show();
516
        //}
517
      },
518

    
519
      add_model: function(m) {
520
        if (m.get('is_public') && !this.public_added) {
521
          this.combined_public = new models.networks.CombinedPublicNetwork();
522
          this.combined_public_view = new views.NetworkView({
523
            model: this.combined_public
524
          });
525
          this.add_model_view(this.combined_public_view, this.combined_public, 0);
526
          this.combined_public_view.$("i").hide();
527
          this.public_added = true;
528
        }
529
        return views.NetworksCollectionView.__super__.add_model.call(this, m);
530
      },
531

    
532
      remove_model: function(m) {
533
        if (m.id == 'snf-combined-public-network') {
534
          return;
535
        } else {
536
          return views.NetworksCollectionView.__super__.remove_model.call(this, m);
537
        }
538
      },
539

    
540
      get_model_view_cls: function(m) {
541
        if (!this.public_added) {
542
        }
543
        if (m.get('is_public')) {
544
          return false;
545
        }
546
        return views.NetworksCollectionView.__super__.get_model_view_cls.apply(this, [m]);
547
      },
548
      
549
      parent_for_model: function(m) {
550
        if (m.get('is_public')) {
551
          return this.list_el.find(".public");
552
        } else {
553
          return this.list_el.find(".private");
554
        }
555
      }
556
    });
557

    
558
    views.NetworksPaneView = views.ext.PaneView.extend({
559
      id: "pane",
560
      el: '#networks-pane',
561
      collection_view_cls: views.NetworksCollectionView,
562
      collection_view_selector: '#networks-list-view'
563
    });
564

    
565
    views.NetworkConnectVMsOverlay = views.Overlay.extend({
566
        title: "Connect machine",
567
        overlay_id: "overlay-select-vms",
568
        content_selector: "#network-vms-select-content",
569
        css_class: "overlay-info",
570
        allow_multiple: true,
571

    
572
        initialize: function() {
573
            views.NetworkConnectVMsOverlay.__super__.initialize.apply(this);
574
            this.list = this.$(".vms-list ul");
575
            this.empty_message = this.$(".empty-message");
576
            // flag for submit handler to avoid duplicate bindings
577
            this.submit_handler_set = false;
578
            this.in_progress = false;
579
        },
580
        
581
        handle_vm_click: function(el) {
582
            if (!this.allow_multiple) {
583
              $(el).closest("ul").find(".selected").removeClass("selected");
584
              $(el).addClass("selected");
585
            } else {
586
              $(el).toggleClass("selected");
587
            }
588
        },
589

    
590
        init_handlers: function() {
591
            var self = this;
592
            this.list.find("li").click(function() {
593
                self.handle_vm_click($(this));
594
            });
595
            
596
            if (!this.submit_handler_set) {
597
                // avoid duplicate submits
598
                this.el.find(".create, .assign").click(_.bind(function() {
599
                  if (!this.in_progress) {
600
                    this.submit();
601
                  }
602
                }, this));
603
                this.submit_handler_set = true;
604
            }
605
        },
606

    
607
        reset: function() {
608
            this.list.find("li").remove();
609
        },
610

    
611
        beforeOpen: function() {
612
            this.reset();
613
            this.update_layout();
614
        },
615
        
616
        vm: function(vm) {
617
            if (vm.id) { var id = vm.id } else {var id = vm}
618
            return this.list.find(".vm-" + id);
619
        },
620

    
621
        get_selected: function() {
622
            return this.list.find(".selected").map(function() {return $(this).data('vm')})
623
        },
624

    
625
        update_layout: function() {
626
            this.unset_in_progress();
627
            this.in_progress = false;
628

    
629
            if (this.vms.length == 0) {
630
                this.empty_message.show();
631
            } else {
632
                this.empty_message.hide();
633
            }
634

    
635
            _.each(this.vms, _.bind(function(vm){
636
                var html = '<li class="vm option options-object vm-{0}">' +
637
                           '<div class="options-object-cont">' +
638
                           '{2}' + 
639
                           '<span class="title">{1}</span>' + 
640
                           '<span class="value">{3}</span></div>' + 
641
                           '</li>';
642
                var el = $(html.format(vm.id, 
643
                       util.truncate(_.escape(vm.get("name")), 23), 
644
                       snf.ui.helpers.vm_icon_tag(vm, "small", {'class':'os'}),
645
                       _.escape(vm.get_os())
646
                ));
647
                el.data({vm:vm, vm_id:vm.id});
648
                this.list.append(el);
649

    
650
                vm.bind("remove", function(){ el.remove()})
651
                vm.bind("change:name", function(i,v){el.find(".title").text(v)})
652
            }, this));
653
            
654
            this.init_handlers();
655
            this.set_selected();
656
        },
657

    
658
        set_selected: function() {
659
            _.each(this.selected, _.bind(function(el){
660
                this.vm(el).addClass("selected");
661
            }, this));
662
        },
663
        
664
        set_in_progress: function() {
665
          this.$(".form-action").addClass("in-progress");
666
          this.in_progress = true;
667
        },
668

    
669
        unset_in_progress: function() {
670
          this.$(".form-action").removeClass("in-progress");
671
          this.in_progress = false;
672
        },
673

    
674
        show_vms: function(network, vms, selected, callback, subtitle) {
675
            this.network = network;
676
            this.reset();
677
            if (network) {
678
              this.set_subtitle(network.escape("name"));
679
            } else {
680
              this.set_subtitle(subtitle);
681
            }
682

    
683
            this.vms = vms;
684
            this.selected = selected;
685
            this.cb = callback;
686
            this.unset_in_progress();
687
            this.show(true);
688
        },
689

    
690
        submit: function() {
691
            if (!this.get_selected().length) { return }
692
            this.cb(this.get_selected());
693
        }
694
    });
695
 
696
})(this);