Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_networks_view.js @ 198b546d

History | View | Annotate | Download (39.6 kB)

1
// Copyright 2014 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.project_select = this.$(".project-select");
77
                
78
            this.dhcp_form = this.$("#network-create-dhcp-fields");
79
            
80
            this.subnet_select.find(".subnet").remove();
81
            _.each(synnefo.config.network_suggested_subnets, function(subnet){
82
                this.subnet_select.append($('<option value='+subnet+
83
                                            ' class="subnet">'+subnet+
84
                                            '</option>'));
85
            }, this);
86

    
87
            this.type_select.find(".subnet").remove();
88
            _.each(synnefo.config.network_available_types, function(name, value){
89
                this.type_select.append($('<option value='+value+
90
                                          ' class="subnet">'+name+
91
                                          '</option>'));
92
            }, this);
93
            
94
            if (_.keys(synnefo.config.network_available_types).length <= 1) {
95
                this.type_select.closest(".form-field").hide();
96
            }
97
            
98
            this.check_dhcp_form();
99
            this.init_handlers();
100
        },
101

    
102
        reset_dhcp_form: function() {
103
          this.subnet_select.find("option")[0].selected = 1;
104
          this.subnet_custom.val("");
105
        },
106

    
107
        check_dhcp_form: function() {
108
            if (this.dhcp_select.is(":checked")) {
109
                this.dhcp_form.show();
110
            } else {
111
                this.dhcp_form.hide();
112
            }
113
            
114
            if (this.subnet_select.val() == "custom") {
115
                this.subnet_custom.show();
116
            } else {
117
                this.subnet_custom.hide();
118
            }
119
        },
120

    
121
        init_handlers: function() {
122

    
123
            this.dhcp_select.click(_.bind(function(e){
124
                this.check_dhcp_form();
125
                this.reset_dhcp_form();
126
            }, this));
127

    
128
            this.subnet_select.change(_.bind(function(e){
129
                this.check_dhcp_form();
130
                if (this.subnet_custom.is(":visible")) {
131
                    this.subnet_custom.focus();
132
                }
133
            }, this));
134

    
135
            this.create_button.click(_.bind(function(e){
136
                this.submit();
137
            }, this));
138

    
139
            this.form.submit(_.bind(function(e){
140
                e.preventDefault();
141
                this.submit;
142
                return false;
143
            }, this))
144

    
145
            this.text.keypress(_.bind(function(e){
146
                if (e.which == 13) {this.submit()};
147
            },this))
148
        },
149

    
150
        submit: function() {
151
            if (this.validate()) {
152
                this.create();
153
            };
154
        },
155
        
156
        validate: function() {
157
            // sanitazie
158
            var t = this.text.val();
159
            t = t.replace(/^\s+|\s+$/g,"");
160
            this.text.val(t);
161

    
162
            if (this.text.val() == "") {
163
                this.text.closest(".form-field").addClass("error");
164
                this.text.focus();
165
                return false;
166
            } else {
167
                this.text.closest(".form-field").removeClass("error");
168
            }
169
            
170
            var project = this.get_project();
171
            if (!project || !project.quotas.can_fit({'cyclades.network.private': 1})) {
172
                this.project_select.closest(".form-field").addClass("error");
173
                this.project_select.focus();
174
                return false;
175
            }
176

    
177
            if (this.dhcp_select.is(":checked")) {
178
                if (this.subnet_select.val() == "custom") {
179
                    var sub = this.subnet_custom.val();
180
                    sub = sub.replace(/^\s+|\s+$/g,"");
181
                    this.subnet_custom.val(sub);
182
                        
183
                    if (!synnefo.util.IP_REGEX.exec(this.subnet_custom.val())) {
184
                        this.subnet_custom.closest(".form-field").prev().addClass("error");
185
                        return false;
186
                    } else {
187
                        this.subnet_custom.closest(".form-field").prev().removeClass("error");
188
                    }
189
                };
190
            }
191

    
192
            return true;
193
        },
194
        
195
        get_next_available_subnet: function() {
196
            var auto_tpl = synnefo.config.automatic_network_range_format;
197
            if (!auto_tpl) {
198
                return null
199
            }
200
            var index = 0;
201
            var subnet = auto_tpl.format(index);
202
            var networks = synnefo.storage.networks;
203
            var check_existing = function(n) { return n.get('cidr') == subnet }
204
            while (networks.filter(check_existing).length > 0 && index <= 255) {
205
                index++;
206
                subnet = auto_tpl.format(index); 
207
            }
208
            return subnet;
209
        },
210

    
211
        create: function() {
212
            if (this.create_button.hasClass("in-progress")) { return }
213
            this.create_button.addClass("in-progress");
214

    
215
            var name = this.text.val();
216
            var dhcp = this.dhcp_select.is(":checked");
217
            var subnet = null;
218
            var type = this.type_select.val();
219
            var project_id = this.project_select.val();
220
            var project = synnefo.storage.projects.get(project_id);
221

    
222

    
223
            if (dhcp) {
224
                if (this.subnet_select.val() == "custom") {
225
                    subnet = this.subnet_custom.val();
226
                } else if (this.subnet_select.val() == "auto") {
227
                    subnet = this.get_next_available_subnet()
228
                } else {
229
                    subnet = this.subnet_select.val();
230
                }
231
                
232
            }
233

    
234
            snf.storage.networks.create(
235
              project, name, type, subnet, dhcp, _.bind(function(){
236
                this.hide();
237
            }, this));
238
        },
239
        
240
        update_projects: function() {
241
          this.project_select.find("option").remove();
242
          var min_network_quota = {'cyclades.network.private': 1}
243
          synnefo.storage.projects.each(function(project){
244
            var el = $("<option></option>");
245
            el.attr("value", project.id);
246
            var name = '{0} ({1} available)'.format(project.get('name'), 
247
              project.quotas.get('cyclades.network.private').get('available'));
248
            el.text(name);
249
            if (!project.quotas.can_fit(min_network_quota)) {
250
              el.attr('disabled', true);
251
            }
252
            this.project_select.append(el);
253
          }, this);
254
        },
255
        
256
        get_project: function() {
257
          var project_id = this.project_select.val();
258
          var project = synnefo.storage.projects.get(project_id);
259
          return project;
260
        },
261

    
262
        beforeOpen: function() {
263
            this.update_projects();
264
            this.create_button.removeClass("in-progress")
265
            this.$(".form-field").removeClass("error");
266
            this.text.val("");
267
            this.text.show();
268
            this.text.focus();
269
            this.subnet_custom.val("");
270
            this.subnet_select.val("auto");
271
            this.dhcp_select.attr("checked", true);
272
            this.type_select.val(_.keys(synnefo.config.network_available_types)[0]);
273
            this.check_dhcp_form();
274
        },
275

    
276
        onOpen: function() {
277
            this.text.focus();
278
        }    
279
    });
280

    
281
    views.NetworkPortView = views.ext.ModelView.extend({
282
      tpl: '#network-port-view-tpl',
283
      
284
      error_status: function(status) {
285
        return status == "ERROR"
286
      },
287

    
288
      vm_logo_url: function(vm) {
289
        if (!this.model.get('vm')) { return '' }
290
        return synnefo.ui.helpers.vm_icon_path(this.model.get('vm'), 'medium');
291
      },
292

    
293
      vm_status_cls: function(vm) {
294
        var cls = 'inner clearfix main-content';
295
        if (!this.model.get('vm')) { return cls }
296
        if (this.model.get('vm').in_error_state()) {
297
          cls += ' vm-status-error';
298
        }
299
        return cls
300
      },
301
      
302
      set_confirm: function(action) {
303
        var parent = this.parent_view.parent_view.el;
304
        parent.addClass("subactionpending");
305
      },
306

    
307
      unset_confirm: function(action) {
308
        var parent = this.parent_view.parent_view.el;
309
        parent.removeClass("subactionpending");
310
      },
311

    
312
      post_init_element: function() {
313
        this.in_progress = false;
314
        this.firewall = this.$(".firewall-content").hide();
315
        this.firewall_toggler = this.$(".firewall-toggle");
316
        this.firewall_apply = this.$(".firewall-apply");
317
        this.firewall_legends = this.firewall.find(".checkbox-legends");
318
        this.firewall_inputs = this.firewall.find("input");
319
        this.firewall_apply = this.firewall.find("button");
320
        this.firewall_visible = false;
321

    
322
        this.firewall_toggler.click(_.bind(function() {
323
          this.toggle_firewall();
324
        }, this));
325

    
326
        this.firewall.find(".checkbox-legends, input").click(
327
          _.bind(this.handle_firewall_choice_click, this));
328
        this.update_firewall();
329
      },
330
      
331
      toggle_firewall: function(e, hide, cb) {
332
          hide = hide === undefined ? false : hide;
333
          if (hide) {
334
            this.firewall.stop().hide();
335
          } else {
336
            this.firewall.slideToggle(function() {
337
              cb && cb();
338
              $(window).trigger("resize");
339
            });
340
          }
341
          this.firewall_toggler.toggleClass("open");
342
          this.firewall_visible = this.firewall_toggler.hasClass("open");
343
          if (!this.firewall_visible) {
344
            this.firewall_apply.fadeOut(50);
345
          } else {
346
            this.model.actions.reset_pending();
347
          }
348
          this.update_firewall();
349
      },
350
    
351
      post_hide: function() {
352
        views.NetworkPortView.__super__.post_hide.apply(this);
353
        if (this.firewall_visible) {
354
          this.toggle_firewall({}, true);
355
        }
356
      },
357

    
358
      handle_firewall_choice_click: function(e) {
359
          var el = $(e.currentTarget);
360
          if (el.get(0).tagName == "INPUT") {
361
            el = el.next();
362
          }
363
          var current = this.model.get("firewall_status");
364
          var selected = el.prev().val();
365

    
366
          el.parent().find("input").attr("checked", false);
367
          el.prev().attr("checked", true);
368

    
369
          if (selected != current) {
370
            this.firewall_apply.show();
371
          } else {
372
            this.firewall_apply.hide();
373
          }
374
      },
375
      
376
      disconnect_port: function(model, e) {
377
        var parent = this.parent_view.parent_view.el;
378
        parent.removeClass("subactionpending");
379
        e && e.stopPropagation();
380
        var network = this.model.get("network");
381
        this.model.actions.reset_pending();
382
        this.model.disconnect(_.bind(this.disconnect_port_complete, this));
383
      },
384

    
385
      disconnect_port_complete: function() {
386
      },
387

    
388
      set_firewall: function() {
389
        var parent = this.parent_view.parent_view.el;
390
        parent.removeClass("subactionpending");
391
        var value = this.get_selected_value();
392
        this.firewall_apply.addClass("in-progress");
393
        var vm = this.model.get('vm');
394
        if (!vm) { return }
395
        this.model.set({'pending_firewall': value});
396
        vm.set_firewall(this.model, value, this.set_firewall_success,
397
                        this.set_firewall_error)
398
        this.in_progress = true;
399
      },
400
      
401
      set_firewall_success: function() {
402
        this.set_firewall_complete();
403
      },
404

    
405
      set_firewall_error: function() {
406
        this.model.set({'pending_firewall': undefined});
407
        this.set_firewall_complete();
408
      },
409

    
410
      set_firewall_complete: function() {
411
        this.in_progress = false;
412
        this.toggle_firewall({}, false, _.bind(function() {
413
          this.firewall_apply.removeClass("in-progress").show();
414
        }, this));
415
      },
416

    
417
      get_selected_value: function() {
418
        return this.firewall_inputs.filter(":checked").val();
419
      },
420

    
421
      update_firewall: function() {
422
        var value = this.model.get("firewall_status");
423
        var value_selector = "[value=" + value + "]"
424
        var status_span = this.firewall_toggler.find("span span");
425
        var current_choice = this.firewall_inputs.filter(value_selector);
426

    
427
        if (_.contains(["PROTECTED", "ENABLED"], value)) {
428
          status_span.removeClass("firewall-off").addClass("firewall-on");
429
          status_span.text("On");
430
        } else {
431
          status_span.removeClass("firewall-on").addClass("firewall-off");
432
          status_span.text("Off");
433
        }
434
        
435
        this.firewall_inputs.attr("checked", false);
436
        this.firewall_legends.removeClass("current");
437
        current_choice.attr("checked", true)
438
        current_choice.next().addClass("current");
439
      },
440

    
441
      show_vm_details: function() {
442
        var vm = this.model.get('vm');
443
        if (vm) { snf.ui.main.show_vm_details(vm) }
444
      }
445
    });
446

    
447
    views.NetworkPortCollectionView = views.ext.CollectionView.extend({
448
      tpl: '#network-port-collection-view-tpl',
449
      model_view_cls: views.NetworkPortView,
450
      rivets_view: true,
451
      get_rivet_object: function() {
452
        return {
453
          model: this.collection.network
454
        }
455
      },
456
      resolve_storage_object: function() {
457
        return this.collection
458
      },
459

    
460
      show_connect_vms_overlay: function() {
461
        this.parent_view.show_connect_vms_overlay();
462
      },
463

    
464
      check_empty: function() {
465
        views.NetworkPortCollectionView.__super__.check_empty.apply(this, arguments);
466
        if (this.collection.length == 0) {
467
          this.parent_view.set_ports_empty();
468
        } else {
469
          this.parent_view.unset_ports_empty();
470
        }
471
      }
472
    });
473

    
474
    views.NetworkView = views.ext.ModelView.extend({
475
      tpl: '#network-view-tpl',
476
      auto_bind: ['connect_vm'],
477
      post_init_element: function() {
478
        this.ports = this.$(".ports.nested-model-list");
479
        this.ports.hide();
480
        this.ports_toggler = this.$(".network-ports-toggler");
481
        this.ports_toggler.click(this.toggle_ports);
482
        this.ports_visible = false;
483
      },
484
      
485
      show_reassign_view: function() {
486
          synnefo.ui.main.network_reassign_view.show(this.model);
487
      },
488

    
489
      set_ports_empty: function() {
490
        if (this.ports_visible) {
491
          this.toggle_ports();
492
        }
493
        this.ports_empty = true;
494
        this.ports_toggler.find(".cont-toggler").addClass("disabled");
495
      },
496

    
497
      unset_ports_empty: function() {
498
        this.ports_toggler.find(".cont-toggler").removeClass("disabled");
499
        this.ports_empty = false;
500
      },
501

    
502
      toggle_ports: function(e, hide) {
503
        $(window).trigger("resize");
504
        hide = hide === undefined ? false : hide;
505
        if (hide) {
506
          this.ports.stop().hide();
507
        } else {
508
          if (this.ports_empty) { return }
509
          var self = this;
510
          this.ports.parent().parent().css({overflow: 'hidden'});
511
          this.ports.stop().slideToggle(function() {
512
              $(window).trigger("resize");
513
              self.ports.parent().parent().css({overflow: 'visible'});
514
            });
515
        }
516
        this.ports_toggler.find(".cont-toggler").toggleClass("open");
517
        this.ports_visible = this.ports_toggler.find(".cont-toggler").hasClass("open");
518
        if (this.ports_visible) {
519
          $(this.el).addClass("hovered");
520
        } else {
521
          $(this.el).removeClass("hovered");
522
        }
523
      },
524
      
525
      get_network_icon: function() {
526
        var ico = this.model.get('is_public') ? 'internet.png' : 'network.png';
527
        return synnefo.config.media_url + 'images/' + ico;
528
      },
529

    
530
      post_hide: function() {
531
        views.NetworkView.__super__.post_hide.apply(this);
532
        if (this.ports_visible) {
533
          this.toggle_ports({}, true);
534
        }
535
      },
536
      
537
      status_map: {
538
        'ACTIVE': 'Active',
539
        'SNF:DRAINED': 'Drained',
540
        'CONNECTING': 'Connecting',
541
        'DISCONNECTING': 'Disconnecting',
542
        'REMOVING': 'Destroying'
543
      },
544

    
545
      status_cls_map: {
546
        'ACTIVE': 'status-active',
547
        'SNF:DRAINED': 'status-terminated',
548
        'DISCONNECTING': 'status-progress',
549
        'CONNECTING': 'status-progress',
550
        'REMOVING': 'status-progress'
551
      },
552
      
553
      status_cls: function(status) {    
554
        return this.status_cls_map[this.model.get('ext_status')]
555
      },
556

    
557
      status_display: function(status) {
558
        var status;
559
        var cidr = this.model.get('cidr');
560
        var status = this.model.get('ext_status');
561
        if (status != 'REMOVING' && cidr) {
562
          return cidr
563
        }
564
        if (this.model.id == "snf-combined-public-network" && !_.contains(
565
          ["CONNECTING", "DISCONNECTING"], status)) {
566
          return "Public"
567
        }
568

    
569
        return this.status_map[status];
570
      },
571
      
572
      connect_vms: function(vms, cb) {
573
        var finished = 0;
574
        var completed = function() {
575
          finished++;
576
          if (finished == vms.length) {
577
            cb();
578
          }
579
        }
580
        _.each(vms, function(vm) {
581
          this.model.connect_vm(vm, completed);
582
        }, this);
583
      },
584
      
585
      remove: function(model, e) {
586
        e && e.stopPropagation();
587
        this.model.do_remove();
588
      },
589

    
590
      show_connect_vms_overlay: function() {
591
        var view = new views.NetworkConnectVMsOverlay();
592
        this.model.actions.reset_pending();
593
        vms = this.model.connectable_vms;
594
        var cb = _.bind(function(vms) {
595
          view.set_in_progress();
596
          var cbinner = function() {
597
            view.hide();
598
            delete view;
599
          }
600
          this.connect_vms(vms, cbinner);
601
        }, this);
602
        view.show_vms(this.model, vms, [], cb, "subtitle", this);
603
      }
604

    
605
    });
606
    
607
    views.NetworksCollectionView = views.ext.CollectionView.extend({
608
      collection: storage.networks,
609
      collection_name: 'networks',
610
      model_view_cls: views.NetworkView,
611
      create_view_cls: views.NetworkCreateView,
612
      quota_key: 'network',
613

    
614
      group_key: 'name',
615
      group_network: function(n) {
616
        return n.get('is_public')
617
      },
618
      
619
      init: function() {
620
        this.grouped_networks = {};
621
        views.NetworksCollectionView.__super__.init.apply(this, arguments);
622
      },
623
      
624
      check_empty: function() {
625
        views.NetworksCollectionView.__super__.check_empty.apply(this, arguments);
626
        if (this.collection.filter(function(n){ return !n.is_public()}).length == 0) {
627
          this.$(".private").hide();  
628
        } else {
629
          this.$(".private").show();  
630
        }
631
      },
632

    
633
      add_model: function(m) {
634
        var CombinedPublic = models.networks.CombinedPublicNetwork;
635
        if (this.group_network(m) && synnefo.config.group_public_networks) {
636
          var group_value = m.get(this.group_key);
637
          if (!(group_value in this.grouped_networks)) {
638
            var combined_public = new CombinedPublic({name: group_value});
639
            combined_public_view = new views.NetworkView({
640
              model: combined_public
641
            });
642

    
643
            this.add_model_view(combined_public_view, 
644
                                combined_public, 0);
645
            this.grouped_networks[group_value] = combined_public;
646
          }
647
        }
648
        return views.NetworksCollectionView.__super__.add_model.call(this, m);
649
      },
650

    
651
      remove_model: function(m) {
652
        if (m.id == 'snf-combined-public-network' ||
653
            (this.group_network(m) && 
654
            synnefo.config.group_public_networks)) {
655
          return false;
656
        } else {
657
          return views.NetworksCollectionView.__super__.remove_model.call(this, m);
658
        }
659
      },
660

    
661
      get_model_view_cls: function(m) {
662
        if (m.id == 'snf-combined-public-network' || 
663
            (this.group_network(m) && 
664
             synnefo.config.group_public_networks)) {
665
          return false;
666
        }
667
        return views.NetworksCollectionView.__super__.get_model_view_cls.apply(this, [m]);
668
      },
669
      
670
      parent_for_model: function(m) {
671
        if (m.get('is_public')) {
672
          return this.list_el.find(".public");
673
        } else {
674
          return this.list_el.find(".private");
675
        }
676
      }
677
    });
678

    
679
    views.NetworksPaneView = views.ext.PaneView.extend({
680
      id: "pane",
681
      el: '#networks-pane',
682
      collection_view_cls: views.NetworksCollectionView,
683
      collection_view_selector: '#networks-list-view'
684
    });
685
    
686
    views.VMSelectView = views.ext.SelectModelView.extend({
687
      tpl: '#vm-select-model-tpl',
688
      get_vm_icon: function() {
689
        return $(snf.ui.helpers.vm_icon_tag(this.model, "small")).attr("src")
690
      },
691
      status_cls: function() {
692
        return (views.IconView.STATE_CLASSES[this.model.get("state")] || []).join(" ") + " status clearfix"
693
      },
694
      status_display: function() {
695
        return STATE_TEXTS[this.model.get("state")]
696
      }
697
    });
698

    
699
    views.VMSelectView = views.ext.CollectionView.extend({
700
      init: function() {
701
        views.VMSelectView.__super__.init.apply(this);
702
      },
703
      tpl: '#vm-select-collection-tpl',
704
      model_view_cls: views.VMSelectView,
705
      
706
      trigger_select: function(view, select) {
707
        this.trigger("change:select", view, select);
708
      },
709

    
710
      post_add_model_view: function(view) {
711
        view.bind("change:select", this.trigger_select, this);
712
        if (!this.options.allow_multiple) {
713
          view.input.prop("type", "radio");
714
        }
715
      },
716

    
717
      post_remove_model_view: function(view) {
718
        view.unbind("change:select", this.trigger_select, this);
719
      },
720

    
721
      deselect_all: function(except) {
722
        _.each(this._subviews, function(view) {
723
          if (view != except) { view.deselect() }
724
        });
725
      },
726

    
727
      get_selected: function() {
728
        return _.filter(_.map(this._subviews, function(view) {
729
          if (view.selected) {
730
            return view.model;
731
          }
732
        }), function(m) { return m });
733
      }
734
    });
735

    
736
    views.NetworkConnectVMsOverlay = views.Overlay.extend({
737
        title: "Connect machine",
738
        overlay_id: "overlay-select-vms",
739
        content_selector: "#network-vms-select-content",
740
        css_class: "overlay-info",
741
        allow_multiple: true,
742

    
743
        initialize: function() {
744
            views.NetworkConnectVMsOverlay.__super__.initialize.apply(this);
745
            this.list = this.$(".vms-list ul");
746
            this.empty_message = this.$(".empty-message");
747
            // flag for submit handler to avoid duplicate bindings
748
            this.submit_handler_set = false;
749
            this.in_progress = false;
750

    
751
        },
752
        
753
        init_collection_view: function(collection) {
754
            this.collection_view = new views.VMSelectView({
755
              collection: collection,
756
              el: this.list,
757
              allow_multiple: this.allow_multiple
758
            });
759
            this.collection_view.show(true);
760
            this.list.append($(this.collection_view.el));
761
            if (!this.allow_multiple) {
762
              this.collection_view.bind("change:select", 
763
                                        function(view, selected) {
764
                if (!selected) { return }
765
                this.collection_view.deselect_all(view);
766
              }, this);
767
            }
768
        },
769

    
770
        handle_vm_click: function(el) {
771
            if (!this.allow_multiple) {
772
              $(el).closest("ul").find(".selected").removeClass("selected");
773
              $(el).addClass("selected");
774
            } else {
775
              $(el).toggleClass("selected");
776
            }
777
        },
778

    
779
        init_handlers: function() {
780
            var self = this;
781
            
782
            if (!this.submit_handler_set) {
783
                // avoid duplicate submits
784
                this.el.find(".create, .assign").click(_.bind(function() {
785
                  if (!this.in_progress) {
786
                    this.submit();
787
                  }
788
                }, this));
789
                this.submit_handler_set = true;
790
            }
791
        },
792
        
793
        reset: function() {},
794
        beforeOpen: function() {
795
            this.reset();
796
            this.update_layout();
797
        },
798
        
799
        get_selected: function() {
800
          return this.collection_view.get_selected();
801
        },
802

    
803
        update_layout: function() {
804
            this.unset_in_progress();
805
            this.in_progress = false;
806

    
807
            if (this.vms.length == 0) {
808
                this.empty_message.show();
809
            } else {
810
                this.empty_message.hide();
811
            }
812

    
813
            this.init_handlers();
814
        },
815

    
816
        set_in_progress: function() {
817
          this.$(".form-action").addClass("in-progress");
818
          this.in_progress = true;
819
        },
820

    
821
        unset_in_progress: function() {
822
          this.$(".form-action").removeClass("in-progress");
823
          this.in_progress = false;
824
        },
825

    
826
        show_vms: function(network, vms, selected, callback, subtitle) {
827
            this.init_collection_view(vms);
828
            this.network = network;
829
            this.reset();
830
            if (network) {
831
              this.set_subtitle(network.escape("name"));
832
            } else {
833
              this.set_subtitle(subtitle);
834
            }
835
            this.vms = vms;
836
            this.selected = selected;
837
            this.cb = callback;
838
            this.unset_in_progress();
839
            this.show(true);
840
        },
841
        
842
        onClose: function() {
843
          this.collection_view && this.collection_view.hide(true);
844
          delete this.collection_view;
845
        },
846

    
847
        submit: function() {
848
            if (!this.get_selected().length) { return }
849
            this.cb(this.get_selected());
850
        }
851
    });
852
    
853
    views.NetworkSelectModelView = views.ext.SelectModelView.extend({});
854

    
855
    views.NetworkSelectNetworkTypeModelView = views.NetworkSelectModelView.extend({
856
      get_network_icon: function() {
857
        var ico = this.model.get('is_public') ? 'internet-small.png' : 'network-small.png';
858
        return synnefo.config.media_url + 'images/' + ico;
859
      },
860
      forced_title: 'You machine will be automatically connected ' +
861
                    'to this network.'
862
    });
863

    
864
    views.NetworkSelectPublicNetwork = views.NetworkSelectNetworkTypeModelView.extend({
865
      tpl: '#networks-select-public-item-tpl',
866
      classes: 'public-network',
867
      post_init_element: function() {
868
        views.NetworkSelectPublicNetwork.__super__.post_init_element.apply(this);
869
      },
870
      resolve_floating_ip_view_params: function() {
871
        return {
872
          project: this.parent_view.parent_view.project
873
        }
874
      }
875
    });
876

    
877
    views.NetworkSelectPrivateNetwork = views.NetworkSelectNetworkTypeModelView.extend({
878
      tpl: '#networks-select-private-item-tpl',
879
      classes: 'private-network'
880
    });
881
    
882
    views.NetworkSelectTypeView = views.ext.CollectionView.extend({});
883
    views.NetworkSelectPublicNetworks = views.NetworkSelectTypeView.extend({
884
      tpl: '#networks-select-public-tpl',
885
      model_view_cls: views.NetworkSelectPublicNetwork,
886
      get_floating_ips: function() {
887
        var ips = [];
888
        _.each(this._subviews, function(view) {
889
          _.each(view._subviews, function(view) {
890
            if (view.selected_ips) {
891
              _.each(view.selected_ips, function(m) {
892
                ips.push(m.id);
893
              }, this);
894
            }
895
          }, this);
896
        }, this);
897
        return ips;
898
      }
899
    });
900
    
901
    views.NetworkSelectFloatingIpView = views.NetworkSelectModelView.extend({
902
      tpl: '#networks-select-floating-ip-tpl'
903
    });
904

    
905
    views.NetworkSelectFloatingIpsView = views.ext.CollectionView.extend({
906
      tpl: '#networks-select-floating-ips-tpl',
907
      model_view_cls: views.NetworkSelectFloatingIpView,
908
      
909
      deselect_all: function() {
910
        this.each_ip_view(function(v) { v.deselect() });
911
      },
912

    
913
      each_ip_view: function(cb) {
914
        _.each(this._subviews, function(view) {
915
          if (view instanceof views.NetworkSelectFloatingIpView) {
916
            cb(view);
917
          }
918
        })
919
      },
920

    
921
      post_init: function(options) {
922
        var parent = this.parent_view;
923
        var self = this;
924
        
925
        this.quota = this.options.project.quotas.get("cyclades.floating_ip");
926
        this.selected_ips = [];
927
        this.handle_ip_select = _.bind(this.handle_ip_select, this);
928
        this.create = this.$(".floating-ip.create");
929
        
930
        this.quota.bind("change", _.bind(this.update_available, this));
931
        this.collection.bind("change", _.bind(this.update_available, this))
932
        this.collection.bind("add", _.bind(this.update_available, this))
933
        this.collection.bind("remove", _.bind(this.update_available, this))
934

    
935
        parent.bind("change:select", function(view, selected) {
936
          if (selected) { this.show_parent() } else { this.hide_parent() }
937
        }, this);
938

    
939
        this.create.click(function(e) {
940
          e.preventDefault();
941
          self.create_ip();
942
        });
943
        this.reset_creating();
944
      },
945
      
946
      hide_parent: function() {
947
        this.parent_view.item.removeClass("selected");
948
        this.parent_view.input.attr("checked", false);
949
        this.parent_view.selected = false;
950
        this.deselect_all();
951
        this.hide(true);
952
      },
953

    
954
      show_parent: function() {
955
        var left = this.quota.get_available();
956
        var available = this.collection.length || left;
957
        if (!available) { 
958
          this.hide_parent();
959
          return;
960
        }
961
        this.select_first();
962
        this.parent_view.item.addClass("selected");
963
        this.parent_view.input.attr("checked", true);
964
        this.parent_view.selected = true;
965
        this.show(true);
966
      },
967

    
968
      update_available: function() {
969
        var left = this.quota.get_available();
970
        var available = this.collection.length || left;
971
        var available_el = this.parent_view.$(".available");
972
        var no_available_el = this.parent_view.$(".no-available");
973
        var parent_check = this.parent_view.$("input[type=checkbox]");
974
        var create = this.$(".create.model-item");
975
        var create_link = this.$(".create a");
976
        var create_no_available = this.$(".create .no-available");
977

    
978
        if (!available) {
979
          // no ip's available to select
980
          this.hide_parent();
981
          available_el.hide();
982
          no_available_el.show();
983
          parent_check.attr("disabled", true);
984
        } else {
985
          // available floating ip
986
          var available_text = "".format(
987
            this.collection.length + this.quota.get_available());
988
          available_el.removeClass("hidden").text(available_text).show();
989
          available_el.show();
990
          no_available_el.hide();
991
          parent_check.attr("disabled", false);
992
        }
993

    
994
        if (left) {
995
          // available quota
996
          create.removeClass("no-available");
997
          create.show();
998
          create_link.show();
999
          create_no_available.hide();
1000
        } else {
1001
          // no available quota
1002
          create.addClass("no-available");
1003
          create.hide();
1004
          create_link.hide();
1005
          //create_no_available.show();
1006
        }
1007
        this.update_selected();
1008
      },
1009
      
1010
      update_selected: function() {
1011
        // reset missing entries
1012
        _.each(this.selected_ips.length, function(ip) {
1013
          if (!this.collection.get(ip.id)) {
1014
            this.selected_ips = _.without(this.selected_ips, ip);
1015
          }
1016
        }, this);
1017

    
1018
        if (this.selected_ips.length) {
1019
          this.parent_view.input.attr("checked", true);
1020
          this.parent_view.item.addClass("selected");
1021
          this.parent_view.selected = true;
1022
        } else {
1023
          this.parent_view.input.attr("checked", false);
1024
          this.parent_view.item.removeClass("selected");
1025
          this.parent_view.selected = false;
1026
        }
1027
      },
1028

    
1029
      post_remove_model_view: function(view) {
1030
        view.deselect();
1031
        view.unbind("change:select", this.handle_ip_select)
1032
      },
1033

    
1034
      handle_create_error: function() {},
1035
      
1036
      set_creating: function() {
1037
        var create_link = this.$(".create a");
1038
        var create_no_available = this.$(".create .no-available");
1039
        var loading = this.$(".create .loading");
1040
        create_link.hide();
1041
        loading.show();
1042
      },
1043

    
1044
      reset_creating: function() {
1045
        var loading = this.$(".create .loading");
1046
        loading.hide();
1047
        this.update_available();
1048
      },
1049

    
1050
      create_ip: function() {
1051
        if (!this.quota.get_available()) { return }
1052
        var self = this;
1053
        this.set_creating();
1054
        synnefo.storage.floating_ips.create({floatingip:{}}, {
1055
          error: _.bind(this.handle_create_error, this),
1056
          complete: function() {
1057
            synnefo.storage.quotas.fetch();
1058
            self.reset_creating();
1059
          }
1060
        });
1061
      },
1062
      
1063
      select_first: function() {
1064
        if (this.selected_ips.length > 0) { return }
1065
        if (this._subviews.length == 0) { return }
1066
        this._subviews[0].select();
1067
        if (!_.contains(this.selected_ips, this._subviews[0].model)) {
1068
          this.selected_ips.push(this._subviews[0].model);
1069
        }
1070
      },
1071

    
1072
      post_add_model_view: function(view, model) {
1073
        view.bind("change:select", this.handle_ip_select)
1074
        if (!this.selected_ips.length && this._subviews.length == 1) {
1075
          this.select_first();
1076
        }
1077
      },
1078

    
1079
      handle_ip_select: function(view) {
1080
        if (view.selected) {
1081
          if (!_.contains(this.selected_ips, view.model)) {
1082
            this.selected_ips.push(view.model);
1083
          }
1084
        } else {
1085
          this.selected_ips = _.without(this.selected_ips, view.model);
1086
        }
1087
        this.update_selected();
1088
      },
1089
      
1090
      post_show: function() {
1091
        this.update_available();
1092
      },
1093

    
1094
      get_floating_ips: function() {
1095
        return this.selected_ips;
1096
      }
1097
    });
1098

    
1099
    views.NetworkSelectPrivateNetworks = views.NetworkSelectTypeView.extend({
1100
      tpl: '#networks-select-private-tpl',
1101
      model_view_cls: views.NetworkSelectPrivateNetwork,
1102
      get_networks: function() {
1103
        return _.filter(_.map(this._subviews, function(view) {
1104
          if (view.selected) { return view.model.id }
1105
        }), function(id) { return id });
1106
      }
1107

    
1108
    });
1109

    
1110
    views.NetworkSelectView = views.ext.ModelView.extend({
1111
      rivets_view: true,
1112
      tpl: '#networks-select-view-tpl',
1113
      select_public: true,
1114
      
1115
      forced_values_title_map: {
1116
        "SNF:ANY_PUBLIC_IPV6": "Internet (public IPv6)",
1117
        "SNF:ANY_PUBLIC_IPV4": "Internet (public IPv4)"
1118
      },
1119

    
1120
      initialize: function(options) {
1121
        this.project = options.project;
1122
        this.quotas = this.project.quotas.get('cyclades.private_network');
1123
        options = options || {};
1124
        options.model = options.model || new models.Model();
1125
        this.private_networks = new Backbone.FilteredCollection(undefined, {
1126
          collection: synnefo.storage.networks,
1127
          collectionFilter: function(m) {
1128
            return !m.get('is_public')
1129
        }});
1130

    
1131
        this.public_networks = new Backbone.Collection();
1132
        this.public_networks.comparator = function(m) {
1133
          if (m.get('forced')) {
1134
            return -1
1135
          }  
1136
          return 100;
1137
        }
1138
        
1139
        if (synnefo.config.forced_server_networks.length) {
1140
          _.each(synnefo.config.forced_server_networks, function(network) {
1141
            var forced = synnefo.storage.networks.get(network);
1142
            if (!forced) {
1143
              var name = this.forced_values_title_map[network];
1144
              if (!name) { name = "Forced network ({0})".format(network)}
1145
              forced = new models.networks.Network({
1146
                id: network,
1147
                name: name, 
1148
                subnets: [],
1149
                is_public: true,
1150
                forced: true
1151
              });
1152
            } else {
1153
              forced.set({'forced': true});
1154
            }
1155
            this.public_networks.add(forced);
1156
          }, this);
1157
        }
1158

    
1159
        // combined public
1160
        this.floating_public = new models.networks.CombinedPublicNetwork('Internet');
1161
        this.floating_public.set({noselect: true, 
1162
                                  name: 'Internet (public IPv4)', 
1163
                                  forced: false});
1164
        this.public_networks.add(this.floating_public);
1165

    
1166
        model_attrs = {
1167
          public_collection: this.public_networks,
1168
          private_collection: this.private_networks,
1169
          floating_selected: true
1170
        }
1171

    
1172
        options.model.set(model_attrs);
1173
        this._configure(options);
1174
        return views.NetworkSelectView.__super__.initialize.call(this, options);
1175
      },
1176

    
1177
      get_selected_floating_ips: function() {
1178
        var ips = [];
1179
        _.each(this._subviews, function(view) {
1180
          if (view.get_floating_ips) {
1181
            ips = _.union(ips, view.get_floating_ips());
1182
          }
1183
        }, this);
1184
        return _.filter(
1185
          _.map(ips, function(ipid) { 
1186
          return synnefo.storage.floating_ips.get(parseInt(ipid))
1187
        }), function(ip) { return ip });
1188
      },
1189

    
1190
      get_selected_networks: function() {
1191
        var networks = [];
1192
        _.each(this._subviews, function(view) {
1193
          if (view.get_networks) {
1194
            networks = _.union(networks, view.get_networks());
1195
          }
1196
        }, this);
1197
        return _.filter(
1198
          _.map(networks, function(netid) { 
1199
          return synnefo.storage.networks.get(netid)
1200
        }), function(net) { return net });
1201
      }
1202
    });
1203
 
1204
})(this);