Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (38 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
      vm_status_cls: function(vm) {
259
        var cls = 'inner clearfix main-content';
260
        if (!this.model.get('vm')) { return cls }
261
        if (this.model.get('vm').in_error_state()) {
262
          cls += ' vm-status-error';
263
        }
264
        return cls
265
      },
266
      
267
      set_confirm: function(action) {
268
        var parent = this.parent_view.parent_view.el;
269
        parent.addClass("subactionpending");
270
      },
271

    
272
      unset_confirm: function(action) {
273
        var parent = this.parent_view.parent_view.el;
274
        parent.removeClass("subactionpending");
275
      },
276

    
277
      post_init_element: function() {
278
        this.in_progress = false;
279
        this.firewall = this.$(".firewall-content").hide();
280
        this.firewall_toggler = this.$(".firewall-toggle");
281
        this.firewall_apply = this.$(".firewall-apply");
282
        this.firewall_legends = this.firewall.find(".checkbox-legends");
283
        this.firewall_inputs = this.firewall.find("input");
284
        this.firewall_apply = this.firewall.find("button");
285
        this.firewall_visible = false;
286

    
287
        this.firewall_toggler.click(_.bind(function() {
288
          this.toggle_firewall();
289
        }, this));
290

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

    
323
      handle_firewall_choice_click: function(e) {
324
          var el = $(e.currentTarget);
325
          if (el.get(0).tagName == "INPUT") {
326
            el = el.next();
327
          }
328
          var current = this.model.get("firewall_status");
329
          var selected = el.prev().val();
330

    
331
          el.parent().find("input").attr("checked", false);
332
          el.prev().attr("checked", true);
333

    
334
          if (selected != current) {
335
            this.firewall_apply.show();
336
          } else {
337
            this.firewall_apply.hide();
338
          }
339
      },
340
      
341
      disconnect_port: function(model, e) {
342
        var parent = this.parent_view.parent_view.el;
343
        parent.removeClass("subactionpending");
344
        e && e.stopPropagation();
345
        var network = this.model.get("network");
346
        this.model.actions.reset_pending();
347
        this.model.disconnect(_.bind(this.disconnect_port_complete, this));
348
      },
349

    
350
      disconnect_port_complete: function() {
351
      },
352

    
353
      set_firewall: function() {
354
        var parent = this.parent_view.parent_view.el;
355
        parent.removeClass("subactionpending");
356
        var value = this.get_selected_value();
357
        this.firewall_apply.addClass("in-progress");
358
        var vm = this.model.get('vm');
359
        if (!vm) { return }
360
        this.model.set({'pending_firewall': value});
361
        vm.set_firewall(this.model, value, this.set_firewall_success,
362
                        this.set_firewall_error)
363
        this.in_progress = true;
364
      },
365
      
366
      set_firewall_success: function() {
367
        this.set_firewall_complete();
368
      },
369

    
370
      set_firewall_error: function() {
371
        this.model.set({'pending_firewall': undefined});
372
        this.set_firewall_complete();
373
      },
374

    
375
      set_firewall_complete: function() {
376
        this.in_progress = false;
377
        this.toggle_firewall({}, false, _.bind(function() {
378
          this.firewall_apply.removeClass("in-progress").show();
379
        }, this));
380
      },
381

    
382
      get_selected_value: function() {
383
        return this.firewall_inputs.filter(":checked").val();
384
      },
385

    
386
      update_firewall: function() {
387
        var value = this.model.get("firewall_status");
388
        var value_selector = "[value=" + value + "]"
389
        var status_span = this.firewall_toggler.find("span span");
390
        var current_choice = this.firewall_inputs.filter(value_selector);
391

    
392
        if (_.contains(["PROTECTED", "ENABLED"], value)) {
393
          status_span.removeClass("firewall-off").addClass("firewall-on");
394
          status_span.text("On");
395
        } else {
396
          status_span.removeClass("firewall-on").addClass("firewall-off");
397
          status_span.text("Off");
398
        }
399
        
400
        this.firewall_inputs.attr("checked", false);
401
        this.firewall_legends.removeClass("current");
402
        current_choice.attr("checked", true)
403
        current_choice.next().addClass("current");
404
      },
405

    
406
      show_vm_details: function() {
407
        var vm = this.model.get('vm');
408
        if (vm) { snf.ui.main.show_vm_details(vm) }
409
      }
410
    });
411

    
412
    views.NetworkPortCollectionView = views.ext.CollectionView.extend({
413
      tpl: '#network-port-collection-view-tpl',
414
      model_view_cls: views.NetworkPortView,
415
      rivets_view: true,
416
      get_rivet_object: function() {
417
        return {
418
          model: this.collection.network
419
        }
420
      },
421
      resolve_storage_object: function() {
422
        return this.collection
423
      },
424

    
425
      show_connect_vms_overlay: function() {
426
        this.parent_view.show_connect_vms_overlay();
427
      },
428

    
429
      check_empty: function() {
430
        views.NetworkPortCollectionView.__super__.check_empty.apply(this, arguments);
431
        if (this.collection.length == 0) {
432
          this.parent_view.set_ports_empty();
433
        } else {
434
          this.parent_view.unset_ports_empty();
435
        }
436
      }
437
    });
438

    
439
    views.NetworkView = views.ext.ModelView.extend({
440
      tpl: '#network-view-tpl',
441
      auto_bind: ['connect_vm'],
442
      post_init_element: function() {
443
        this.ports = this.$(".ports.nested-model-list");
444
        this.ports.hide();
445
        this.ports_toggler = this.$(".network-ports-toggler");
446
        this.ports_toggler.click(this.toggle_ports);
447
        this.ports_visible = false;
448
      },
449
      
450
      set_ports_empty: function() {
451
        if (this.ports_visible) {
452
          this.toggle_ports();
453
        }
454
        this.ports_empty = true;
455
        this.ports_toggler.find(".cont-toggler").addClass("disabled");
456
      },
457

    
458
      unset_ports_empty: function() {
459
        this.ports_toggler.find(".cont-toggler").removeClass("disabled");
460
        this.ports_empty = false;
461
      },
462

    
463
      toggle_ports: function(e, hide) {
464
        $(window).trigger("resize");
465
        hide = hide === undefined ? false : hide;
466
        if (hide) {
467
          this.ports.stop().hide();
468
        } else {
469
          if (this.ports_empty) { return }
470
          var self = this;
471
          this.ports.parent().parent().css({overflow: 'hidden'});
472
          this.ports.stop().slideToggle(function() {
473
              $(window).trigger("resize");
474
              self.ports.parent().parent().css({overflow: 'visible'});
475
            });
476
        }
477
        this.ports_toggler.find(".cont-toggler").toggleClass("open");
478
        this.ports_visible = this.ports_toggler.find(".cont-toggler").hasClass("open");
479
        if (this.ports_visible) {
480
          $(this.el).addClass("hovered");
481
        } else {
482
          $(this.el).removeClass("hovered");
483
        }
484
      },
485
      
486
      get_network_icon: function() {
487
        var ico = this.model.get('is_public') ? 'internet.png' : 'network.png';
488
        return synnefo.config.media_url + 'images/' + ico;
489
      },
490

    
491
      post_hide: function() {
492
        views.NetworkView.__super__.post_hide.apply(this);
493
        if (this.ports_visible) {
494
          this.toggle_ports({}, true);
495
        }
496
      },
497
      
498
      status_map: {
499
        'ACTIVE': 'Active',
500
        'SNF:DRAINED': 'Drained',
501
        'CONNECTING': 'Connecting',
502
        'DISCONNECTING': 'Disconnecting',
503
        'REMOVING': 'Destroying'
504
      },
505

    
506
      status_cls_map: {
507
        'ACTIVE': 'status-active',
508
        'SNF:DRAINED': 'status-terminated',
509
        'DISCONNECTING': 'status-progress',
510
        'CONNECTING': 'status-progress',
511
        'REMOVING': 'status-progress'
512
      },
513
      
514
      status_cls: function(status) {    
515
        return this.status_cls_map[this.model.get('ext_status')]
516
      },
517

    
518
      status_display: function(status) {
519
        var status;
520
        var cidr = this.model.get('cidr');
521
        var status = this.model.get('ext_status');
522
        if (status != 'REMOVING' && cidr) {
523
          return cidr
524
        }
525
        if (this.model.id == "snf-combined-public-network" && !_.contains(
526
          ["CONNECTING", "DISCONNECTING"], status)) {
527
          return "Public"
528
        }
529

    
530
        return this.status_map[status];
531
      },
532
      
533
      connect_vms: function(vms, cb) {
534
        var finished = 0;
535
        var completed = function() {
536
          finished++;
537
          if (finished == vms.length) {
538
            cb();
539
          }
540
        }
541
        _.each(vms, function(vm) {
542
          this.model.connect_vm(vm, completed);
543
        }, this);
544
      },
545
      
546
      remove: function(model, e) {
547
        e && e.stopPropagation();
548
        this.model.do_remove();
549
      },
550

    
551
      show_connect_vms_overlay: function() {
552
        var view = new views.NetworkConnectVMsOverlay();
553
        this.model.actions.reset_pending();
554
        vms = this.model.connectable_vms;
555
        var cb = _.bind(function(vms) {
556
          view.set_in_progress();
557
          var cbinner = function() {
558
            view.hide();
559
            delete view;
560
          }
561
          this.connect_vms(vms, cbinner);
562
        }, this);
563
        view.show_vms(this.model, vms, [], cb, "subtitle", this);
564
      }
565

    
566
    });
567
    
568
    views.NetworksCollectionView = views.ext.CollectionView.extend({
569
      collection: storage.networks,
570
      collection_name: 'networks',
571
      model_view_cls: views.NetworkView,
572
      create_view_cls: views.NetworkCreateView,
573
      quota_key: 'cyclades.network.private',
574

    
575
      group_key: 'name',
576
      group_network: function(n) {
577
        return n.get('is_public')
578
      },
579
      
580
      init: function() {
581
        this.grouped_networks = {};
582
        views.NetworksCollectionView.__super__.init.apply(this, arguments);
583
      },
584
      
585
      check_empty: function() {
586
        views.NetworksCollectionView.__super__.check_empty.apply(this, arguments);
587
        if (this.collection.filter(function(n){ return !n.is_public()}).length == 0) {
588
          this.$(".private").hide();  
589
        } else {
590
          this.$(".private").show();  
591
        }
592
      },
593

    
594
      add_model: function(m) {
595
        var CombinedPublic = models.networks.CombinedPublicNetwork;
596
        if (this.group_network(m) && synnefo.config.group_public_networks) {
597
          var group_value = m.get(this.group_key);
598
          if (!(group_value in this.grouped_networks)) {
599
            var combined_public = new CombinedPublic({name: group_value});
600
            combined_public_view = new views.NetworkView({
601
              model: combined_public
602
            });
603

    
604
            this.add_model_view(combined_public_view, 
605
                                combined_public, 0);
606
            this.grouped_networks[group_value] = combined_public;
607
          }
608
        }
609
        return views.NetworksCollectionView.__super__.add_model.call(this, m);
610
      },
611

    
612
      remove_model: function(m) {
613
        if (m.id == 'snf-combined-public-network' ||
614
            (this.group_network(m) && 
615
            synnefo.config.group_public_networks)) {
616
          return false;
617
        } else {
618
          return views.NetworksCollectionView.__super__.remove_model.call(this, m);
619
        }
620
      },
621

    
622
      get_model_view_cls: function(m) {
623
        if (m.id == 'snf-combined-public-network' || 
624
            (this.group_network(m) && 
625
             synnefo.config.group_public_networks)) {
626
          return false;
627
        }
628
        return views.NetworksCollectionView.__super__.get_model_view_cls.apply(this, [m]);
629
      },
630
      
631
      parent_for_model: function(m) {
632
        if (m.get('is_public')) {
633
          return this.list_el.find(".public");
634
        } else {
635
          return this.list_el.find(".private");
636
        }
637
      }
638
    });
639

    
640
    views.NetworksPaneView = views.ext.PaneView.extend({
641
      id: "pane",
642
      el: '#networks-pane',
643
      collection_view_cls: views.NetworksCollectionView,
644
      collection_view_selector: '#networks-list-view'
645
    });
646
    
647
    views.VMSelectView = views.ext.SelectModelView.extend({
648
      tpl: '#vm-select-model-tpl',
649
      get_vm_icon: function() {
650
        return $(snf.ui.helpers.vm_icon_tag(this.model, "small")).attr("src")
651
      },
652
      status_cls: function() {
653
        return (views.IconView.STATE_CLASSES[this.model.get("state")] || []).join(" ") + " status clearfix"
654
      },
655
      status_display: function() {
656
        return STATE_TEXTS[this.model.get("state")]
657
      }
658
    });
659

    
660
    views.VMSelectView = views.ext.CollectionView.extend({
661
      init: function() {
662
        views.VMSelectView.__super__.init.apply(this);
663
      },
664
      tpl: '#vm-select-collection-tpl',
665
      model_view_cls: views.VMSelectView,
666
      
667
      trigger_select: function(view, select) {
668
        this.trigger("change:select", view, select);
669
      },
670

    
671
      post_add_model_view: function(view) {
672
        view.bind("change:select", this.trigger_select, this);
673
        if (!this.options.allow_multiple) {
674
          view.input.prop("type", "radio");
675
        }
676
      },
677

    
678
      post_remove_model_view: function(view) {
679
        view.unbind("change:select", this.trigger_select, this);
680
      },
681

    
682
      deselect_all: function(except) {
683
        _.each(this._subviews, function(view) {
684
          if (view != except) { view.deselect() }
685
        });
686
      },
687

    
688
      get_selected: function() {
689
        return _.filter(_.map(this._subviews, function(view) {
690
          if (view.selected) {
691
            return view.model;
692
          }
693
        }), function(m) { return m });
694
      }
695
    });
696

    
697
    views.NetworkConnectVMsOverlay = views.Overlay.extend({
698
        title: "Connect machine",
699
        overlay_id: "overlay-select-vms",
700
        content_selector: "#network-vms-select-content",
701
        css_class: "overlay-info",
702
        allow_multiple: true,
703

    
704
        initialize: function() {
705
            views.NetworkConnectVMsOverlay.__super__.initialize.apply(this);
706
            this.list = this.$(".vms-list ul");
707
            this.empty_message = this.$(".empty-message");
708
            // flag for submit handler to avoid duplicate bindings
709
            this.submit_handler_set = false;
710
            this.in_progress = false;
711

    
712
        },
713
        
714
        init_collection_view: function(collection) {
715
            this.collection_view = new views.VMSelectView({
716
              collection: collection,
717
              el: this.list,
718
              allow_multiple: this.allow_multiple
719
            });
720
            this.collection_view.show(true);
721
            this.list.append($(this.collection_view.el));
722
            if (!this.allow_multiple) {
723
              this.collection_view.bind("change:select", 
724
                                        function(view, selected) {
725
                if (!selected) { return }
726
                this.collection_view.deselect_all(view);
727
              }, this);
728
            }
729
        },
730

    
731
        handle_vm_click: function(el) {
732
            if (!this.allow_multiple) {
733
              $(el).closest("ul").find(".selected").removeClass("selected");
734
              $(el).addClass("selected");
735
            } else {
736
              $(el).toggleClass("selected");
737
            }
738
        },
739

    
740
        init_handlers: function() {
741
            var self = this;
742
            
743
            if (!this.submit_handler_set) {
744
                // avoid duplicate submits
745
                this.el.find(".create, .assign").click(_.bind(function() {
746
                  if (!this.in_progress) {
747
                    this.submit();
748
                  }
749
                }, this));
750
                this.submit_handler_set = true;
751
            }
752
        },
753
        
754
        reset: function() {},
755
        beforeOpen: function() {
756
            this.reset();
757
            this.update_layout();
758
        },
759
        
760
        get_selected: function() {
761
          return this.collection_view.get_selected();
762
        },
763

    
764
        update_layout: function() {
765
            this.unset_in_progress();
766
            this.in_progress = false;
767

    
768
            if (this.vms.length == 0) {
769
                this.empty_message.show();
770
            } else {
771
                this.empty_message.hide();
772
            }
773

    
774
            this.init_handlers();
775
        },
776

    
777
        set_in_progress: function() {
778
          this.$(".form-action").addClass("in-progress");
779
          this.in_progress = true;
780
        },
781

    
782
        unset_in_progress: function() {
783
          this.$(".form-action").removeClass("in-progress");
784
          this.in_progress = false;
785
        },
786

    
787
        show_vms: function(network, vms, selected, callback, subtitle) {
788
            this.init_collection_view(vms);
789
            this.network = network;
790
            this.reset();
791
            if (network) {
792
              this.set_subtitle(network.escape("name"));
793
            } else {
794
              this.set_subtitle(subtitle);
795
            }
796
            this.vms = vms;
797
            this.selected = selected;
798
            this.cb = callback;
799
            this.unset_in_progress();
800
            this.show(true);
801
        },
802
        
803
        onClose: function() {
804
          this.collection_view && this.collection_view.hide(true);
805
          delete this.collection_view;
806
        },
807

    
808
        submit: function() {
809
            if (!this.get_selected().length) { return }
810
            this.cb(this.get_selected());
811
        }
812
    });
813
    
814
    views.NetworkSelectModelView = views.ext.SelectModelView.extend({});
815

    
816
    views.NetworkSelectNetworkTypeModelView = views.NetworkSelectModelView.extend({
817
      get_network_icon: function() {
818
        var ico = this.model.get('is_public') ? 'internet-small.png' : 'network-small.png';
819
        return synnefo.config.media_url + 'images/' + ico;
820
      },
821
      forced_title: 'You machine will be automatically connected ' +
822
                    'to this network.'
823
    });
824

    
825
    views.NetworkSelectPublicNetwork = views.NetworkSelectNetworkTypeModelView.extend({
826
      tpl: '#networks-select-public-item-tpl',
827
      classes: 'public-network',
828
      post_init_element: function() {
829
        views.NetworkSelectPublicNetwork.__super__.post_init_element.apply(this);
830
      }
831
    });
832

    
833
    views.NetworkSelectPrivateNetwork = views.NetworkSelectNetworkTypeModelView.extend({
834
      tpl: '#networks-select-private-item-tpl',
835
      classes: 'private-network'
836
    });
837
    
838
    views.NetworkSelectTypeView = views.ext.CollectionView.extend({});
839
    views.NetworkSelectPublicNetworks = views.NetworkSelectTypeView.extend({
840
      tpl: '#networks-select-public-tpl',
841
      model_view_cls: views.NetworkSelectPublicNetwork,
842
      get_floating_ips: function() {
843
        var ips = [];
844
        _.each(this._subviews, function(view) {
845
          _.each(view._subviews, function(view) {
846
            if (view.selected_ips) {
847
              _.each(view.selected_ips, function(m) {
848
                ips.push(m.id);
849
              }, this);
850
            }
851
          }, this);
852
        }, this);
853
        return ips;
854
      }
855
    });
856
    
857
    views.NetworkSelectFloatingIpView = views.NetworkSelectModelView.extend({
858
      tpl: '#networks-select-floating-ip-tpl'
859
    });
860

    
861
    views.NetworkSelectFloatingIpsView = views.ext.CollectionView.extend({
862
      tpl: '#networks-select-floating-ips-tpl',
863
      model_view_cls: views.NetworkSelectFloatingIpView,
864

    
865
      deselect_all: function() {
866
        this.each_ip_view(function(v) { v.deselect() });
867
      },
868

    
869
      each_ip_view: function(cb) {
870
        _.each(this._subviews, function(view) {
871
          if (view instanceof views.NetworkSelectFloatingIpView) {
872
            cb(view);
873
          }
874
        })
875
      },
876

    
877
      post_init: function() {
878
        var parent = this.parent_view;
879
        var self = this;
880

    
881
        this.quota = synnefo.storage.quotas.get("cyclades.floating_ip");
882
        this.selected_ips = [];
883
        this.handle_ip_select = _.bind(this.handle_ip_select, this);
884
        this.create = this.$(".floating-ip.create");
885
        
886
        this.quota.bind("change", _.bind(this.update_available, this));
887
        this.collection.bind("change", _.bind(this.update_available, this))
888
        this.collection.bind("add", _.bind(this.update_available, this))
889
        this.collection.bind("remove", _.bind(this.update_available, this))
890

    
891
        parent.bind("change:select", function(view, selected) {
892
          if (selected) { this.show_parent() } else { this.hide_parent() }
893
        }, this);
894

    
895
        this.create.click(function(e) {
896
          e.preventDefault();
897
          self.create_ip();
898
        });
899
        this.reset_creating();
900
      },
901
      
902
      hide_parent: function() {
903
        this.parent_view.item.removeClass("selected");
904
        this.parent_view.input.attr("checked", false);
905
        this.parent_view.selected = false;
906
        this.deselect_all();
907
        this.hide(true);
908
      },
909

    
910
      show_parent: function() {
911
        var left = this.quota.get_available();
912
        var available = this.collection.length || left;
913
        if (!available) { 
914
          this.hide_parent();
915
          return;
916
        }
917
        this.select_first();
918
        this.parent_view.item.addClass("selected");
919
        this.parent_view.input.attr("checked", true);
920
        this.parent_view.selected = true;
921
        this.show(true);
922
      },
923

    
924
      update_available: function() {
925
        var left = this.quota.get_available();
926
        var available = this.collection.length || left;
927
        var available_el = this.parent_view.$(".available");
928
        var no_available_el = this.parent_view.$(".no-available");
929
        var parent_check = this.parent_view.$("input[type=checkbox]");
930
        var create = this.$(".create.model-item");
931
        var create_link = this.$(".create a");
932
        var create_no_available = this.$(".create .no-available");
933

    
934
        if (!available) {
935
          // no ip's available to select
936
          this.hide_parent();
937
          available_el.hide();
938
          no_available_el.show();
939
          parent_check.attr("disabled", true);
940
        } else {
941
          // available floating ip
942
          var available_text = "".format(
943
            this.collection.length + this.quota.get_available());
944
          available_el.removeClass("hidden").text(available_text).show();
945
          available_el.show();
946
          no_available_el.hide();
947
          parent_check.attr("disabled", false);
948
        }
949

    
950
        if (left) {
951
          // available quota
952
          create.removeClass("no-available");
953
          create.show();
954
          create_link.show();
955
          create_no_available.hide();
956
        } else {
957
          // no available quota
958
          create.addClass("no-available");
959
          create.hide();
960
          create_link.hide();
961
          //create_no_available.show();
962
        }
963
        this.update_selected();
964
      },
965
      
966
      update_selected: function() {
967
        // reset missing entries
968
        _.each(this.selected_ips.length, function(ip) {
969
          if (!this.collection.get(ip.id)) {
970
            this.selected_ips = _.without(this.selected_ips, ip);
971
          }
972
        }, this);
973

    
974
        if (this.selected_ips.length) {
975
          this.parent_view.input.attr("checked", true);
976
          this.parent_view.item.addClass("selected");
977
          this.parent_view.selected = true;
978
        } else {
979
          this.parent_view.input.attr("checked", false);
980
          this.parent_view.item.removeClass("selected");
981
          this.parent_view.selected = false;
982
        }
983
      },
984

    
985
      post_remove_model_view: function(view) {
986
        view.deselect();
987
        view.unbind("change:select", this.handle_ip_select)
988
      },
989

    
990
      handle_create_error: function() {},
991
      
992
      set_creating: function() {
993
        var create_link = this.$(".create a");
994
        var create_no_available = this.$(".create .no-available");
995
        var loading = this.$(".create .loading");
996
        create_link.hide();
997
        loading.show();
998
      },
999

    
1000
      reset_creating: function() {
1001
        var loading = this.$(".create .loading");
1002
        loading.hide();
1003
        this.update_available();
1004
      },
1005

    
1006
      create_ip: function() {
1007
        if (!this.quota.get_available()) { return }
1008
        var self = this;
1009
        this.set_creating();
1010
        synnefo.storage.floating_ips.create({floatingip:{}}, {
1011
          error: _.bind(this.handle_create_error, this),
1012
          complete: function() {
1013
            synnefo.storage.quotas.fetch();
1014
            self.reset_creating();
1015
          }
1016
        });
1017
      },
1018
      
1019
      select_first: function() {
1020
        if (this.selected_ips.length > 0) { return }
1021
        if (this._subviews.length == 0) { return }
1022
        this._subviews[0].select();
1023
        if (!_.contains(this.selected_ips, this._subviews[0].model)) {
1024
          this.selected_ips.push(this._subviews[0].model);
1025
        }
1026
      },
1027

    
1028
      post_add_model_view: function(view, model) {
1029
        view.bind("change:select", this.handle_ip_select)
1030
        if (!this.selected_ips.length && this._subviews.length == 1) {
1031
          this.select_first();
1032
        }
1033
      },
1034

    
1035
      handle_ip_select: function(view) {
1036
        if (view.selected) {
1037
          if (!_.contains(this.selected_ips, view.model)) {
1038
            this.selected_ips.push(view.model);
1039
          }
1040
        } else {
1041
          this.selected_ips = _.without(this.selected_ips, view.model);
1042
        }
1043
        this.update_selected();
1044
      },
1045
      
1046
      post_show: function() {
1047
        this.update_available();
1048
      },
1049

    
1050
      get_floating_ips: function() {
1051
        return this.selected_ips;
1052
      }
1053
    });
1054

    
1055
    views.NetworkSelectPrivateNetworks = views.NetworkSelectTypeView.extend({
1056
      tpl: '#networks-select-private-tpl',
1057
      model_view_cls: views.NetworkSelectPrivateNetwork,
1058
      get_networks: function() {
1059
        return _.filter(_.map(this._subviews, function(view) {
1060
          if (view.selected) { return view.model.id }
1061
        }), function(id) { return id });
1062
      }
1063

    
1064
    });
1065

    
1066
    views.NetworkSelectView = views.ext.ModelView.extend({
1067
      rivets_view: true,
1068
      tpl: '#networks-select-view-tpl',
1069
      select_public: true,
1070
      
1071
      forced_values_title_map: {
1072
        "SNF:ANY_PUBLIC_IPV6": "Internet (public IPv6)",
1073
        "SNF:ANY_PUBLIC_IPV4": "Internet (public IPv4)"
1074
      },
1075

    
1076
      initialize: function(options) {
1077
        this.quotas = synnefo.storage.quotas.get('cyclades.private_network');
1078
        options = options || {};
1079
        options.model = options.model || new models.Model();
1080
        this.private_networks = new Backbone.FilteredCollection(undefined, {
1081
          collection: synnefo.storage.networks,
1082
          collectionFilter: function(m) {
1083
            return !m.get('is_public')
1084
        }});
1085

    
1086
        this.public_networks = new Backbone.Collection();
1087
        this.public_networks.comparator = function(m) {
1088
          if (m.get('forced')) {
1089
            return -1
1090
          }  
1091
          return 100;
1092
        }
1093
        
1094
        if (synnefo.config.forced_server_networks.length) {
1095
          _.each(synnefo.config.forced_server_networks, function(network) {
1096
            var forced = synnefo.storage.networks.get(network);
1097
            if (!forced) {
1098
              var name = this.forced_values_title_map[network];
1099
              if (!name) { name = "Forced network ({0})".format(network)}
1100
              forced = new models.networks.Network({
1101
                id: network,
1102
                name: name, 
1103
                subnets: [],
1104
                is_public: true,
1105
                forced: true
1106
              });
1107
            } else {
1108
              forced.set({'forced': true});
1109
            }
1110
            this.public_networks.add(forced);
1111
          }, this);
1112
        }
1113

    
1114
        // combined public
1115
        this.floating_public = new models.networks.CombinedPublicNetwork('Internet');
1116
        this.floating_public.set({noselect: true, 
1117
                                  name: 'Internet (public IPv4)', 
1118
                                  forced: false});
1119
        this.public_networks.add(this.floating_public);
1120

    
1121
        model_attrs = {
1122
          public_collection: this.public_networks,
1123
          private_collection: this.private_networks,
1124
          floating_selected: true
1125
        }
1126

    
1127
        options.model.set(model_attrs);
1128
        this._configure(options);
1129
        return views.NetworkSelectView.__super__.initialize.call(this, options);
1130
      },
1131

    
1132
      get_selected_floating_ips: function() {
1133
        var ips = [];
1134
        _.each(this._subviews, function(view) {
1135
          if (view.get_floating_ips) {
1136
            ips = _.union(ips, view.get_floating_ips());
1137
          }
1138
        }, this);
1139
        return _.filter(
1140
          _.map(ips, function(ipid) { 
1141
          return synnefo.storage.floating_ips.get(parseInt(ipid))
1142
        }), function(ip) { return ip });
1143
      },
1144

    
1145
      get_selected_networks: function() {
1146
        var networks = [];
1147
        _.each(this._subviews, function(view) {
1148
          if (view.get_networks) {
1149
            networks = _.union(networks, view.get_networks());
1150
          }
1151
        }, this);
1152
        return _.filter(
1153
          _.map(networks, function(netid) { 
1154
          return synnefo.storage.networks.get(netid)
1155
        }), function(net) { return net });
1156
      }
1157
    });
1158
 
1159
})(this);