Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37 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+
81
                                            ' class="subnet">'+subnet+
82
                                            '</option>'));
83
            }, this);
84

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

    
96
            this.check_dhcp_form();
97
            this.init_handlers();
98
        },
99

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

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

    
119
        init_handlers: function() {
120

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

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

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

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

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

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

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

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

    
202
        create: function() {
203
            this.create_button.addClass("in-progress");
204

    
205
            var name = this.text.val();
206
            var dhcp = this.dhcp_select.is(":checked");
207
            var subnet = null;
208
            var type = this.type_select.val();
209

    
210
            if (dhcp) {
211
                if (this.subnet_select.val() == "custom") {
212
                    subnet = this.subnet_custom.val();
213
                } else if (this.subnet_select.val() == "auto") {
214
                    subnet = this.get_next_available_subnet()
215
                } else {
216
                    subnet = this.subnet_select.val();
217
                }
218
                
219
            }
220

    
221
            snf.storage.networks.create(name, type, subnet, dhcp, _.bind(function(){
222
                this.hide();
223
                // trigger parent view create handler
224
                this.parent_view.post_create();
225
            }, this));
226
        },
227

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

    
241
        onOpen: function() {
242
            this.text.focus();
243
        }
244
    });
245

    
246
    views.NetworkPortView = views.ext.ModelView.extend({
247
      tpl: '#network-port-view-tpl',
248
      
249
      error_status: function(status) {
250
        return status == "ERROR"
251
      },
252

    
253
      vm_logo_url: function(vm) {
254
        if (!this.model.get('vm')) { return '' }
255
        return synnefo.ui.helpers.vm_icon_path(this.model.get('vm'), 'medium');
256
      },
257
      
258
      set_confirm: function(action) {
259
        var parent = this.parent_view.parent_view.el;
260
        parent.addClass("subactionpending");
261
      },
262

    
263
      unset_confirm: function(action) {
264
        var parent = this.parent_view.parent_view.el;
265
        parent.removeClass("subactionpending");
266
      },
267

    
268
      post_init_element: function() {
269
        this.in_progress = false;
270
        this.firewall = this.$(".firewall-content").hide();
271
        this.firewall_toggler = this.$(".firewall-toggle");
272
        this.firewall_apply = this.$(".firewall-apply");
273
        this.firewall_legends = this.firewall.find(".checkbox-legends");
274
        this.firewall_inputs = this.firewall.find("input");
275
        this.firewall_apply = this.firewall.find("button");
276
        this.firewall_visible = false;
277

    
278
        this.firewall_toggler.click(_.bind(function() {
279
          this.toggle_firewall();
280
        }, this));
281

    
282
        this.firewall.find(".checkbox-legends, input").click(
283
          _.bind(this.handle_firewall_choice_click, this));
284
        this.update_firewall();
285
      },
286
      
287
      toggle_firewall: function(e, hide, cb) {
288
          hide = hide === undefined ? false : hide;
289
          if (hide) {
290
            this.firewall.stop().hide();
291
          } else {
292
            this.firewall.slideToggle(function() {
293
              cb && cb();
294
              $(window).trigger("resize");
295
            });
296
          }
297
          this.firewall_toggler.toggleClass("open");
298
          this.firewall_visible = this.firewall_toggler.hasClass("open");
299
          if (!this.firewall_visible) {
300
            this.firewall_apply.fadeOut(50);
301
          } else {
302
            this.model.actions.reset_pending();
303
          }
304
          this.update_firewall();
305
      },
306
    
307
      post_hide: function() {
308
        views.NetworkPortView.__super__.post_hide.apply(this);
309
        if (this.firewall_visible) {
310
          this.toggle_firewall({}, true);
311
        }
312
      },
313

    
314
      handle_firewall_choice_click: function(e) {
315
          var el = $(e.currentTarget);
316
          if (el.get(0).tagName == "INPUT") {
317
            el = el.next();
318
          }
319
          var current = this.model.get("firewall_status");
320
          var selected = el.prev().val();
321

    
322
          el.parent().find("input").attr("checked", false);
323
          el.prev().attr("checked", true);
324

    
325
          if (selected != current) {
326
            this.firewall_apply.show();
327
          } else {
328
            this.firewall_apply.hide();
329
          }
330
      },
331
      
332
      disconnect_port: function(model, e) {
333
        var parent = this.parent_view.parent_view.el;
334
        parent.removeClass("subactionpending");
335
        e && e.stopPropagation();
336
        var network = this.model.get("network");
337
        this.model.actions.reset_pending();
338
        this.model.disconnect(_.bind(this.disconnect_port_complete, this));
339
      },
340

    
341
      disconnect_port_complete: function() {
342
      },
343

    
344
      set_firewall: function() {
345
        var parent = this.parent_view.parent_view.el;
346
        parent.removeClass("subactionpending");
347
        var value = this.get_selected_value();
348
        this.firewall_apply.addClass("in-progress");
349
        var vm = this.model.get('vm');
350
        if (!vm) { return }
351
        this.model.set({'pending_firewall': value});
352
        vm.set_firewall(this.model, value, this.set_firewall_success,
353
                        this.set_firewall_error)
354
        this.in_progress = true;
355
      },
356
      
357
      set_firewall_success: function() {
358
        this.set_firewall_complete();
359
      },
360

    
361
      set_firewall_error: function() {
362
        this.model.set({'pending_firewall': undefined});
363
        this.set_firewall_complete();
364
      },
365

    
366
      set_firewall_complete: function() {
367
        this.in_progress = false;
368
        this.toggle_firewall({}, false, _.bind(function() {
369
          this.firewall_apply.removeClass("in-progress").show();
370
        }, this));
371
      },
372

    
373
      get_selected_value: function() {
374
        return this.firewall_inputs.filter(":checked").val();
375
      },
376

    
377
      update_firewall: function() {
378
        var value = this.model.get("firewall_status");
379
        var value_selector = "[value=" + value + "]"
380
        var status_span = this.firewall_toggler.find("span span");
381
        var current_choice = this.firewall_inputs.filter(value_selector);
382

    
383
        if (_.contains(["PROTECTED", "ENABLED"], value)) {
384
          status_span.removeClass("firewall-off").addClass("firewall-on");
385
          status_span.text("On");
386
        } else {
387
          status_span.removeClass("firewall-on").addClass("firewall-off");
388
          status_span.text("Off");
389
        }
390
        
391
        this.firewall_inputs.attr("checked", false);
392
        this.firewall_legends.removeClass("current");
393
        current_choice.attr("checked", true)
394
        current_choice.next().addClass("current");
395
      },
396

    
397
      show_vm_details: function() {
398
        var vm = this.model.get('vm');
399
        if (vm) { snf.ui.main.show_vm_details(vm) }
400
      }
401
    });
402

    
403
    views.NetworkPortCollectionView = views.ext.CollectionView.extend({
404
      tpl: '#network-port-collection-view-tpl',
405
      model_view_cls: views.NetworkPortView,
406
      rivets_view: true,
407
      get_rivet_object: function() {
408
        return {
409
          model: this.collection.network
410
        }
411
      },
412
      resolve_storage_object: function() {
413
        return this.collection
414
      },
415

    
416
      show_connect_vms_overlay: function() {
417
        this.parent_view.show_connect_vms_overlay();
418
      }
419
    });
420

    
421
    views.NetworkView = views.ext.ModelView.extend({
422
      tpl: '#network-view-tpl',
423
      auto_bind: ['connect_vm'],
424
      post_init_element: function() {
425
        this.ports = this.$(".ports.nested-model-list");
426
        this.ports.hide();
427
        this.ports_toggler = this.$(".network-ports-toggler");
428
        this.ports_toggler.click(this.toggle_ports);
429
        this.ports_visible = false;
430
      },
431

    
432
      toggle_ports: function(e, hide) {
433
        $(window).trigger("resize");
434
        hide = hide === undefined ? false : hide;
435
        if (hide) {
436
          this.ports.stop().hide();
437
        } else {
438
          var self = this;
439
          this.ports.parent().parent().css({overflow: 'hidden'});
440
          this.ports.stop().slideToggle(function() {
441
              $(window).trigger("resize");
442
              self.ports.parent().parent().css({overflow: 'visible'});
443
            });
444
        }
445
        this.ports_toggler.find(".cont-toggler").toggleClass("open");
446
        this.ports_visible = this.ports_toggler.find(".cont-toggler").hasClass("open");
447
        if (this.ports_visible) {
448
          $(this.el).addClass("hovered");
449
        } else {
450
          $(this.el).removeClass("hovered");
451
        }
452
      },
453
      
454
      get_network_icon: function() {
455
        var ico = this.model.get('is_public') ? 'internet.png' : 'network.png';
456
        return synnefo.config.media_url + 'images/' + ico;
457
      },
458

    
459
      post_hide: function() {
460
        views.NetworkView.__super__.post_hide.apply(this);
461
        if (this.ports_visible) {
462
          this.toggle_ports({}, true);
463
        }
464
      },
465
      
466
      status_map: {
467
        'ACTIVE': 'Active',
468
        'SNF:DRAINED': 'Drained',
469
        'CONNECTING': 'Connecting',
470
        'DISCONNECTING': 'Disconnecting',
471
        'REMOVING': 'Destroying'
472
      },
473

    
474
      status_cls_map: {
475
        'ACTIVE': 'status-active',
476
        'SNF:DRAINED': 'status-terminated',
477
        'DISCONNECTING': 'status-progress',
478
        'CONNECTING': 'status-progress',
479
        'REMOVING': 'status-progress'
480
      },
481
      
482
      status_cls: function(status) {    
483
        return this.status_cls_map[this.model.get('ext_status')]
484
      },
485

    
486
      status_display: function(status) {
487
        var status;
488
        var cidr = this.model.get('cidr');
489
        var status = this.model.get('ext_status');
490
        if (status != 'REMOVING' && cidr) {
491
          return cidr
492
        }
493
        if (this.model.id == "snf-combined-public-network" && !_.contains(
494
          ["CONNECTING", "DISCONNECTING"], status)) {
495
          return "Public"
496
        }
497

    
498
        return this.status_map[status];
499
      },
500
      
501
      connect_vms: function(vms, cb) {
502
        var finished = 0;
503
        var completed = function() {
504
          finished++;
505
          if (finished == vms.length) {
506
            cb();
507
          }
508
        }
509
        _.each(vms, function(vm) {
510
          this.model.connect_vm(vm, completed);
511
        }, this);
512
      },
513
      
514
      remove: function(model, e) {
515
        e && e.stopPropagation();
516
        this.model.do_remove();
517
      },
518

    
519
      show_connect_vms_overlay: function() {
520
        var view = new views.NetworkConnectVMsOverlay();
521
        this.model.actions.reset_pending();
522
        vms = this.model.connectable_vms;
523
        var cb = _.bind(function(vms) {
524
          view.set_in_progress();
525
          var cbinner = function() {
526
            view.hide();
527
            delete view;
528
          }
529
          this.connect_vms(vms, cbinner);
530
        }, this);
531
        view.show_vms(this.model, vms, [], cb, "subtitle", this);
532
      }
533

    
534
    });
535
    
536
    views.NetworksCollectionView = views.ext.CollectionView.extend({
537
      collection: storage.networks,
538
      collection_name: 'networks',
539
      model_view_cls: views.NetworkView,
540
      create_view_cls: views.NetworkCreateView,
541
      quota_key: 'cyclades.network.private',
542

    
543
      group_key: 'name',
544
      group_network: function(n) {
545
        return n.get('is_public')
546
      },
547
      
548
      init: function() {
549
        this.grouped_networks = {};
550
        views.NetworksCollectionView.__super__.init.apply(this, arguments);
551
      },
552
      
553
      check_empty: function() {
554
        views.NetworksCollectionView.__super__.check_empty.apply(this, arguments);
555
        if (this.collection.filter(function(n){ return !n.is_public()}).length == 0) {
556
          this.$(".private").hide();  
557
        } else {
558
          this.$(".private").show();  
559
        }
560
      },
561

    
562
      add_model: function(m) {
563
        var CombinedPublic = models.networks.CombinedPublicNetwork;
564
        if (this.group_network(m) && synnefo.config.group_public_networks) {
565
          var group_value = m.get(this.group_key);
566
          if (!(group_value in this.grouped_networks)) {
567
            var combined_public = new CombinedPublic({name: group_value});
568
            combined_public_view = new views.NetworkView({
569
              model: combined_public
570
            });
571

    
572
            this.add_model_view(combined_public_view, 
573
                                combined_public, 0);
574
            this.grouped_networks[group_value] = combined_public;
575
          }
576
        }
577
        return views.NetworksCollectionView.__super__.add_model.call(this, m);
578
      },
579

    
580
      remove_model: function(m) {
581
        if (m.id == 'snf-combined-public-network' ||
582
            (this.group_network(m) && 
583
            synnefo.config.group_public_networks)) {
584
          return false;
585
        } else {
586
          return views.NetworksCollectionView.__super__.remove_model.call(this, m);
587
        }
588
      },
589

    
590
      get_model_view_cls: function(m) {
591
        if (m.id == 'snf-combined-public-network' || 
592
            (this.group_network(m) && 
593
             synnefo.config.group_public_networks)) {
594
          return false;
595
        }
596
        return views.NetworksCollectionView.__super__.get_model_view_cls.apply(this, [m]);
597
      },
598
      
599
      parent_for_model: function(m) {
600
        if (m.get('is_public')) {
601
          return this.list_el.find(".public");
602
        } else {
603
          return this.list_el.find(".private");
604
        }
605
      }
606
    });
607

    
608
    views.NetworksPaneView = views.ext.PaneView.extend({
609
      id: "pane",
610
      el: '#networks-pane',
611
      collection_view_cls: views.NetworksCollectionView,
612
      collection_view_selector: '#networks-list-view'
613
    });
614
    
615
    views.VMSelectView = views.ext.SelectModelView.extend({
616
      tpl: '#vm-select-model-tpl',
617
      get_vm_icon: function() {
618
        return $(snf.ui.helpers.vm_icon_tag(this.model, "small")).attr("src")
619
      },
620
      status_cls: function() {
621
        return (views.IconView.STATE_CLASSES[this.model.get("state")] || []).join(" ") + " status clearfix"
622
      },
623
      status_display: function() {
624
        return STATE_TEXTS[this.model.get("state")]
625
      }
626
    });
627

    
628
    views.VMSelectView = views.ext.CollectionView.extend({
629
      init: function() {
630
        views.VMSelectView.__super__.init.apply(this);
631
      },
632
      tpl: '#vm-select-collection-tpl',
633
      model_view_cls: views.VMSelectView,
634
      
635
      trigger_select: function(view, select) {
636
        this.trigger("change:select", view, select);
637
      },
638

    
639
      post_add_model_view: function(view) {
640
        view.bind("change:select", this.trigger_select, this);
641
        if (!this.options.allow_multiple) {
642
          view.input.prop("type", "radio");
643
        }
644
      },
645

    
646
      post_remove_model_view: function(view) {
647
        view.unbind("change:select", this.trigger_select, this);
648
      },
649

    
650
      deselect_all: function(except) {
651
        _.each(this._subviews, function(view) {
652
          if (view != except) { view.deselect() }
653
        });
654
      },
655

    
656
      get_selected: function() {
657
        return _.filter(_.map(this._subviews, function(view) {
658
          if (view.selected) {
659
            return view.model;
660
          }
661
        }), function(m) { return m });
662
      }
663
    });
664

    
665
    views.NetworkConnectVMsOverlay = views.Overlay.extend({
666
        title: "Connect machine",
667
        overlay_id: "overlay-select-vms",
668
        content_selector: "#network-vms-select-content",
669
        css_class: "overlay-info",
670
        allow_multiple: true,
671

    
672
        initialize: function() {
673
            views.NetworkConnectVMsOverlay.__super__.initialize.apply(this);
674
            this.list = this.$(".vms-list ul");
675
            this.empty_message = this.$(".empty-message");
676
            // flag for submit handler to avoid duplicate bindings
677
            this.submit_handler_set = false;
678
            this.in_progress = false;
679

    
680
        },
681
        
682
        init_collection_view: function(collection) {
683
            this.collection_view = new views.VMSelectView({
684
              collection: collection,
685
              el: this.list,
686
              allow_multiple: this.allow_multiple
687
            });
688
            this.collection_view.show(true);
689
            this.list.append($(this.collection_view.el));
690
            if (!this.allow_multiple) {
691
              this.collection_view.bind("change:select", 
692
                                        function(view, selected) {
693
                if (!selected) { return }
694
                this.collection_view.deselect_all(view);
695
              }, this);
696
            }
697
        },
698

    
699
        handle_vm_click: function(el) {
700
            if (!this.allow_multiple) {
701
              $(el).closest("ul").find(".selected").removeClass("selected");
702
              $(el).addClass("selected");
703
            } else {
704
              $(el).toggleClass("selected");
705
            }
706
        },
707

    
708
        init_handlers: function() {
709
            var self = this;
710
            
711
            if (!this.submit_handler_set) {
712
                // avoid duplicate submits
713
                this.el.find(".create, .assign").click(_.bind(function() {
714
                  if (!this.in_progress) {
715
                    this.submit();
716
                  }
717
                }, this));
718
                this.submit_handler_set = true;
719
            }
720
        },
721
        
722
        reset: function() {},
723
        beforeOpen: function() {
724
            this.reset();
725
            this.update_layout();
726
        },
727
        
728
        get_selected: function() {
729
          return this.collection_view.get_selected();
730
        },
731

    
732
        update_layout: function() {
733
            this.unset_in_progress();
734
            this.in_progress = false;
735

    
736
            if (this.vms.length == 0) {
737
                this.empty_message.show();
738
            } else {
739
                this.empty_message.hide();
740
            }
741

    
742
            this.init_handlers();
743
        },
744

    
745
        set_in_progress: function() {
746
          this.$(".form-action").addClass("in-progress");
747
          this.in_progress = true;
748
        },
749

    
750
        unset_in_progress: function() {
751
          this.$(".form-action").removeClass("in-progress");
752
          this.in_progress = false;
753
        },
754

    
755
        show_vms: function(network, vms, selected, callback, subtitle) {
756
            this.init_collection_view(vms);
757
            this.network = network;
758
            this.reset();
759
            if (network) {
760
              this.set_subtitle(network.escape("name"));
761
            } else {
762
              this.set_subtitle(subtitle);
763
            }
764
            this.vms = vms;
765
            this.selected = selected;
766
            this.cb = callback;
767
            this.unset_in_progress();
768
            this.show(true);
769
        },
770
        
771
        onClose: function() {
772
          this.collection_view && this.collection_view.hide(true);
773
          delete this.collection_view;
774
        },
775

    
776
        submit: function() {
777
            if (!this.get_selected().length) { return }
778
            this.cb(this.get_selected());
779
        }
780
    });
781
    
782
    views.NetworkSelectModelView = views.ext.SelectModelView.extend({});
783

    
784
    views.NetworkSelectNetworkTypeModelView = views.NetworkSelectModelView.extend({
785
      get_network_icon: function() {
786
        var ico = this.model.get('is_public') ? 'internet-small.png' : 'network-small.png';
787
        return synnefo.config.media_url + 'images/' + ico;
788
      },
789
      forced_title: 'You machine will be automatically connected ' +
790
                    'to this network.'
791
    });
792

    
793
    views.NetworkSelectPublicNetwork = views.NetworkSelectNetworkTypeModelView.extend({
794
      tpl: '#networks-select-public-item-tpl',
795
      classes: 'public-network',
796
      post_init_element: function() {
797
        views.NetworkSelectPublicNetwork.__super__.post_init_element.apply(this);
798
      }
799
    });
800

    
801
    views.NetworkSelectPrivateNetwork = views.NetworkSelectNetworkTypeModelView.extend({
802
      tpl: '#networks-select-private-item-tpl',
803
      classes: 'private-network'
804
    });
805
    
806
    views.NetworkSelectTypeView = views.ext.CollectionView.extend({});
807
    views.NetworkSelectPublicNetworks = views.NetworkSelectTypeView.extend({
808
      tpl: '#networks-select-public-tpl',
809
      model_view_cls: views.NetworkSelectPublicNetwork,
810
      get_floating_ips: function() {
811
        var ips = [];
812
        _.each(this._subviews, function(view) {
813
          _.each(view._subviews, function(view) {
814
            if (view.selected_ips) {
815
              _.each(view.selected_ips, function(m) {
816
                ips.push(m.id);
817
              }, this);
818
            }
819
          }, this);
820
        }, this);
821
        return ips;
822
      }
823
    });
824
    
825
    views.NetworkSelectFloatingIpView = views.NetworkSelectModelView.extend({
826
      tpl: '#networks-select-floating-ip-tpl'
827
    });
828

    
829
    views.NetworkSelectFloatingIpsView = views.ext.CollectionView.extend({
830
      tpl: '#networks-select-floating-ips-tpl',
831
      model_view_cls: views.NetworkSelectFloatingIpView,
832

    
833
      deselect_all: function() {
834
        this.each_ip_view(function(v) { v.deselect() });
835
      },
836

    
837
      each_ip_view: function(cb) {
838
        _.each(this._subviews, function(view) {
839
          if (view instanceof views.NetworkSelectFloatingIpView) {
840
            cb(view);
841
          }
842
        })
843
      },
844

    
845
      post_init: function() {
846
        var parent = this.parent_view;
847
        var self = this;
848

    
849
        this.quota = synnefo.storage.quotas.get("cyclades.floating_ip");
850
        this.selected_ips = [];
851
        this.handle_ip_select = _.bind(this.handle_ip_select, this);
852
        this.create = this.$(".floating-ip.create");
853
        
854
        this.quota.bind("change", _.bind(this.update_available, this));
855
        this.collection.bind("change", _.bind(this.update_available, this))
856
        this.collection.bind("add", _.bind(this.update_available, this))
857
        this.collection.bind("remove", _.bind(this.update_available, this))
858

    
859
        parent.bind("change:select", function(view, selected) {
860
          if (selected) { this.show_parent() } else { this.hide_parent() }
861
        }, this);
862

    
863
        this.create.click(function(e) {
864
          e.preventDefault();
865
          self.create_ip();
866
        });
867
        this.reset_creating();
868
      },
869
      
870
      hide_parent: function() {
871
        this.parent_view.item.removeClass("selected");
872
        this.parent_view.input.attr("checked", false);
873
        this.parent_view.selected = false;
874
        this.deselect_all();
875
        this.hide(true);
876
      },
877

    
878
      show_parent: function() {
879
        var left = this.quota.get_available();
880
        var available = this.collection.length || left;
881
        if (!available) { 
882
          this.hide_parent();
883
          return;
884
        }
885
        this.select_first();
886
        this.parent_view.item.addClass("selected");
887
        this.parent_view.input.attr("checked", true);
888
        this.parent_view.selected = true;
889
        this.show(true);
890
      },
891

    
892
      update_available: function() {
893
        var left = this.quota.get_available();
894
        var available = this.collection.length || left;
895
        var available_el = this.parent_view.$(".available");
896
        var no_available_el = this.parent_view.$(".no-available");
897
        var parent_check = this.parent_view.$("input[type=checkbox]");
898
        var create = this.$(".create.model-item");
899
        var create_link = this.$(".create a");
900
        var create_no_available = this.$(".create .no-available");
901

    
902
        if (!available) {
903
          // no ip's available to select
904
          this.hide_parent();
905
          available_el.hide();
906
          no_available_el.show();
907
          parent_check.attr("disabled", true);
908
        } else {
909
          // available floating ip
910
          var available_text = "".format(
911
            this.collection.length + this.quota.get_available());
912
          available_el.removeClass("hidden").text(available_text).show();
913
          available_el.show();
914
          no_available_el.hide();
915
          parent_check.attr("disabled", false);
916
        }
917

    
918
        if (left) {
919
          // available quota
920
          create.removeClass("no-available");
921
          create.show();
922
          create_link.show();
923
          create_no_available.hide();
924
        } else {
925
          // no available quota
926
          create.addClass("no-available");
927
          create.hide();
928
          create_link.hide();
929
          //create_no_available.show();
930
        }
931
        this.update_selected();
932
      },
933
      
934
      update_selected: function() {
935
        // reset missing entries
936
        _.each(this.selected_ips.length, function(ip) {
937
          if (!this.collection.get(ip.id)) {
938
            this.selected_ips = _.without(this.selected_ips, ip);
939
          }
940
        }, this);
941

    
942
        if (this.selected_ips.length) {
943
          this.parent_view.input.attr("checked", true);
944
          this.parent_view.item.addClass("selected");
945
          this.parent_view.selected = true;
946
        } else {
947
          this.parent_view.input.attr("checked", false);
948
          this.parent_view.item.removeClass("selected");
949
          this.parent_view.selected = false;
950
        }
951
      },
952

    
953
      post_remove_model_view: function(view) {
954
        view.deselect();
955
        view.unbind("change:select", this.handle_ip_select)
956
      },
957

    
958
      handle_create_error: function() {},
959
      
960
      set_creating: function() {
961
        var create_link = this.$(".create a");
962
        var create_no_available = this.$(".create .no-available");
963
        var loading = this.$(".create .loading");
964
        create_link.hide();
965
        loading.show();
966
      },
967

    
968
      reset_creating: function() {
969
        var loading = this.$(".create .loading");
970
        loading.hide();
971
        this.update_available();
972
      },
973

    
974
      create_ip: function() {
975
        if (!this.quota.get_available()) { return }
976
        var self = this;
977
        this.set_creating();
978
        synnefo.storage.floating_ips.create({floatingip:{}}, {
979
          error: _.bind(this.handle_create_error, this),
980
          complete: function() {
981
            synnefo.storage.quotas.fetch();
982
            self.reset_creating();
983
          }
984
        });
985
      },
986
      
987
      select_first: function() {
988
        if (this.selected_ips.length > 0) { return }
989
        if (this._subviews.length == 0) { return }
990
        this._subviews[0].select();
991
        if (!_.contains(this.selected_ips, this._subviews[0].model)) {
992
          this.selected_ips.push(this._subviews[0].model);
993
        }
994
      },
995

    
996
      post_add_model_view: function(view, model) {
997
        view.bind("change:select", this.handle_ip_select)
998
        if (!this.selected_ips.length && this._subviews.length == 1) {
999
          this.select_first();
1000
        }
1001
      },
1002

    
1003
      handle_ip_select: function(view) {
1004
        if (view.selected) {
1005
          if (!_.contains(this.selected_ips, view.model)) {
1006
            this.selected_ips.push(view.model);
1007
          }
1008
        } else {
1009
          this.selected_ips = _.without(this.selected_ips, view.model);
1010
        }
1011
        this.update_selected();
1012
      },
1013
      
1014
      post_show: function() {
1015
        this.update_available();
1016
      },
1017

    
1018
      get_floating_ips: function() {
1019
        return this.selected_ips;
1020
      }
1021
    });
1022

    
1023
    views.NetworkSelectPrivateNetworks = views.NetworkSelectTypeView.extend({
1024
      tpl: '#networks-select-private-tpl',
1025
      model_view_cls: views.NetworkSelectPrivateNetwork,
1026
      get_networks: function() {
1027
        return _.filter(_.map(this._subviews, function(view) {
1028
          if (view.selected) { return view.model.id }
1029
        }), function(id) { return id });
1030
      }
1031

    
1032
    });
1033

    
1034
    views.NetworkSelectView = views.ext.ModelView.extend({
1035
      rivets_view: true,
1036
      tpl: '#networks-select-view-tpl',
1037
      select_public: true,
1038
      
1039
      forced_values_title_map: {
1040
        "SNF:ANY_PUBLIC_IPV6": "Internet (public IPv6)",
1041
        "SNF:ANY_PUBLIC_IPV4": "Internet (public IPv4)"
1042
      },
1043

    
1044
      initialize: function(options) {
1045
        this.quotas = synnefo.storage.quotas.get('cyclades.private_network');
1046
        options = options || {};
1047
        options.model = options.model || new models.Model();
1048
        this.private_networks = new Backbone.FilteredCollection(undefined, {
1049
          collection: synnefo.storage.networks,
1050
          collectionFilter: function(m) {
1051
            return !m.get('is_public')
1052
        }});
1053

    
1054
        this.public_networks = new Backbone.Collection();
1055
        this.public_networks.comparator = function(m) {
1056
          if (m.get('forced')) {
1057
            return -1
1058
          }  
1059
          return 100;
1060
        }
1061
        
1062
        if (synnefo.config.forced_server_networks.length) {
1063
          _.each(synnefo.config.forced_server_networks, function(network) {
1064
            var forced = synnefo.storage.networks.get(network);
1065
            if (!forced) {
1066
              var name = this.forced_values_title_map[network];
1067
              if (!name) { name = "Forced network ({0})".format(network)}
1068
              forced = new models.networks.Network({
1069
                id: network,
1070
                name: name, 
1071
                subnets: [],
1072
                is_public: true,
1073
                forced: true
1074
              });
1075
            } else {
1076
              forced.set({'forced': true});
1077
            }
1078
            this.public_networks.add(forced);
1079
          }, this);
1080
        }
1081

    
1082
        // combined public
1083
        this.floating_public = new models.networks.CombinedPublicNetwork('Internet');
1084
        this.floating_public.set({noselect: true, 
1085
                                  name: 'Internet (public IPv4)', 
1086
                                  forced: false});
1087
        this.public_networks.add(this.floating_public);
1088

    
1089
        model_attrs = {
1090
          public_collection: this.public_networks,
1091
          private_collection: this.private_networks,
1092
          floating_selected: true
1093
        }
1094

    
1095
        options.model.set(model_attrs);
1096
        this._configure(options);
1097
        return views.NetworkSelectView.__super__.initialize.call(this, options);
1098
      },
1099

    
1100
      get_selected_floating_ips: function() {
1101
        var ips = [];
1102
        _.each(this._subviews, function(view) {
1103
          if (view.get_floating_ips) {
1104
            ips = _.union(ips, view.get_floating_ips());
1105
          }
1106
        }, this);
1107
        return _.filter(
1108
          _.map(ips, function(ipid) { 
1109
          return synnefo.storage.floating_ips.get(parseInt(ipid))
1110
        }), function(ip) { return ip });
1111
      },
1112

    
1113
      get_selected_networks: function() {
1114
        var networks = [];
1115
        _.each(this._subviews, function(view) {
1116
          if (view.get_networks) {
1117
            networks = _.union(networks, view.get_networks());
1118
          }
1119
        }, this);
1120
        return _.filter(
1121
          _.map(networks, function(netid) { 
1122
          return synnefo.storage.networks.get(netid)
1123
        }), function(net) { return net });
1124
      }
1125
    });
1126
 
1127
})(this);