Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_create_view.js @ 397cd173

History | View | Annotate | Download (72.3 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 = snf.util || {};
46

    
47
    var views = snf.views = snf.views || {}
48

    
49
    // shortcuts
50
    var bb = root.Backbone;
51

    
52
    var min_vm_quota = {
53
      'cyclades.vm': 1, 
54
      'cyclades.ram': 1, 
55
      'cyclades.cpu': 1, 
56
      'cyclades.disk': 1
57
    };
58

    
59
    views.CreateVMSelectProjectItemView = views.ext.SelectModelView.extend({
60
      tpl: '#create-view-select-project-item-tpl',
61
      can_deselect: false,
62
      quotas_option_html: function() {
63
        var data = "(";
64
        _.each(min_vm_quota, function(val, key) {
65
          var q = this.model.quotas.get(key);
66
          if (!q) { return }
67
          var content = '{0}: {1}  ';
68
          data += content.format(q.get('resource').get('display_name'), 
69
                                 q.get_readable('available'));
70
        }, this);
71
        data = data.substring(0, data.length-2);
72
        data += ")";
73
        return data;
74
      }
75
    });
76

    
77
    views.CreateVMSelectProjectView = views.ext.CollectionSelectView.extend({
78
      tpl: '#create-view-projects-select-tpl',
79
      model_view_cls: views.CreateVMSelectProjectItemView,
80

    
81
      init: function() {
82
        this.handle_quota_changed = _.bind(this.handle_quota_changed, this);
83
        views.CreateVMSelectProjectView.__super__.init.apply(this, arguments);
84
      },
85

    
86
      handle_quota_changed: function() {
87
        _.each(this._model_views, function(view) {
88
          if (!view.model.quotas.can_fit(min_vm_quota)) {
89
            view.set_disabled();
90
          } else {
91
            view.set_enabled();
92
          }
93
        }, this);
94
      },
95

    
96
      set_handlers: function() {
97
        var self = this;
98
        synnefo.storage.quotas.bind("change", this.handle_quota_changed);
99
        this.el.bind("change", function() {
100
          var view = self._model_views[self.list_el.val()];
101
          self.deselect_all();
102
          view.delegate_checked = false;
103
          view.select();
104
          view.trigger('selected', view);
105
        });
106
        views.CreateVMSelectProjectView.__super__.set_handlers.apply(this, arguments);
107
      },
108

    
109
      remove_handlers: function() {
110
        synnefo.storage.quotas.unbind("change", this.handle_quota_changed);
111
        this.el.unbind("change");
112
        views.CreateVMSelectProjectView.__super__.remove_handlers.apply(this, arguments);
113
      },
114

    
115
      post_show: function() {
116
        views.CreateVMSelectProjectView.__super__.post_show.apply(this, arguments);
117
        this.handle_quota_changed();
118
      }
119
    });
120

    
121
    views.VMCreationPasswordView = views.Overlay.extend({
122
        view_id: "creation_password_view",
123
        content_selector: "#creation-password-overlay",
124
        css_class: 'overlay-password overlay-info',
125
        overlay_id: "creation-password-overlay",
126

    
127
        subtitle: "",
128
        title: "Machine password",
129

    
130
        initialize: function(options) {
131
            views.FeedbackView.__super__.initialize.apply(this, arguments);
132
            _.bindAll(this, 'show_password');
133

    
134
            this.password = this.$("#new-machine-password");
135
            this.copy = this.$(".clipboard");
136

    
137
            this.$(".show-machine").click(_.bind(function(){
138
                if (this.$(".show-machine").hasClass("in-progress")) {
139
                    return;
140
                }
141
                this.hide();
142
            }, this));
143

    
144
            _.bindAll(this, "handle_vm_added");
145
            storage.vms.bind("add", this.handle_vm_added);
146
            this.password.text("");
147
        },
148

    
149
        handle_vm_added: function() {
150
            this.$(".show-machine").removeClass("in-progress");
151
        },
152
        
153
        show_password: function() {
154
            this.$(".show-machine").addClass("in-progress");
155
            this.password.text(this.pass);
156
            if (storage.vms.get(this.vm_id)) {
157
                this.$(".show-machine").removeClass("in-progress");
158
            }
159
            
160
            this.clip = new snf.util.ClipHelper(this.copy, this.pass);
161
        },
162

    
163
        onClose: function() {
164
            this.password.text("");
165
            this.vm_id = undefined;
166
            try { delete this.clip; } catch (err) {};
167
        },
168
        
169
        beforeOpen: function() {
170
            this.copy.empty();
171
        },
172
        
173
        onOpen: function() {
174
            this.show_password();
175
        },
176

    
177
        show: function(pass, vm_id) {
178
            this.pass = pass;
179
            this.vm_id = vm_id;
180
            
181
            views.VMCreationPasswordView.__super__.show.apply(this, arguments);
182
        }
183
    })
184

    
185

    
186
    
187
    views.CreateVMStepView = views.View.extend({
188
        step: "1",
189
        title: "Image",
190
        submit: false,
191

    
192
        initialize: function(view) {
193
            this.parent = view;
194
            this.el = view.$("div.create-step-cont.step-" + this.step);
195
            this.header = this.$(".step-header .step-" + this.step);
196
            this.view_id = "create_step_" + this.step;
197

    
198
            views.CreateVMStepView.__super__.initialize.apply(this);
199
        },
200
      
201
        get_project: function() {
202
          return this.parent.project;
203
        },
204

    
205
        show: function() {
206
            // show current
207
            this.el.show();
208
            this.header.addClass("current");
209
            this.header.show();
210
            this.update_layout();
211
        },
212

    
213
        reset: function() {
214
        }
215
    })
216

    
217
    views.CreateImageSelectView = views.CreateVMStepView.extend({
218

    
219
        initialize: function() {
220
            views.CreateImageSelectView.__super__.initialize.apply(this, arguments);
221

    
222
            // elements
223
            this.images_list_cont = this.$(".images-list-cont");
224
            this.images_list = this.$(".images-list-cont ul");
225
            this.image_details = this.$(".images-info-cont");
226
            this.image_details_desc = this.$(".images-info-cont .description p");
227
            this.image_details_title = this.$(".images-info-cont h4");
228
            this.image_details_size = this.$(".images-info-cont .size p");
229
            this.image_details_os = this.$(".images-info-cont .os p");
230
            this.image_details_kernel = this.$(".images-info-cont .kernel p");
231
            this.image_details_gui = this.$(".images-info-cont .gui p");
232
            this.image_details_vm = this.$(".images-info-cont .vm-name p");
233

    
234
            this.categories_list = this.$(".category-filters");
235
            
236
            // params initialization
237
            this.type_selections = {"system": "System"};
238
            this.type_selections_order = ['system'];
239
            
240
            this.images_storage = snf.storage.images;
241

    
242
            // apply image service specific image types
243
            if (this.images_storage.type_selections) {
244
                this.type_selections = _.extend(
245
                    this.images_storage.type_selections,
246
                    this.type_selections)
247

    
248
                this.type_selections_order = this.images_storage.type_selections_order;
249
            }
250

    
251
            this.selected_type = undefined;
252
            this.selected_categories = [];
253

    
254
            this.images = [];
255
            this.images_ids = [];
256
            this.custom_images = [];
257

    
258
            // handlers initialization
259
            this.create_types_selection_options();
260
            this.init_handlers();
261
            this.init_position();
262
        },
263
        
264
        init_position: function() {
265
            //this.el.css({position: "absolute"});
266
            //this.el.css({top:"10px"})
267
        },
268
        
269
        init_handlers: function() {
270
            var self = this;
271
            this.types.live("click", function() {
272
                self.select_type($(this).attr("id").replace("type-select-",""));
273
            });
274
            
275
            this.image_details.find(".hide").click(_.bind(function(){
276
                this.hide_image_details();
277
            }, this));
278

    
279
            this.$(".register-custom-image").live("click", function(){
280
                var confirm_close = true;
281
                if (confirm_close) {
282
                    snf.ui.main.custom_images_view.show(self.parent);
283
                } else {
284
                }
285
            })
286

    
287
            $(".image-warning .confirm").bind('click', function(){
288
                $(".image-warning").hide();
289
                $(".create-controls").show();
290
                if (!self.parent.project) {
291
                  self.parent.set_no_project();
292
                }
293
            });
294
        },
295

    
296
        update_images: function(images) {
297
            this.images = images;
298
            this.images_ids = _.map(this.images, function(img){return img.id});
299
            return this.images;
300
        },
301

    
302
        create_types_selection_options: function() {
303
            var list = this.$("ul.type-filter");
304
            _.each(this.type_selections_order, _.bind(function(key) {
305
                list.append('<li id="type-select-{0}">{1}</li>'.format(key, this.type_selections[key]));
306
            }, this));
307
            this.types = this.$(".type-filter li");
308
        },
309

    
310
        update_layout: function() {
311
            if (!this.selected_type) {
312
                this.selected_type = _.keys(this.type_selections)[0];
313
            }
314
            this.select_type(this.selected_type);
315
        },
316
        
317
        get_categories: function(images) {
318
            return [];
319
            return ["Desktop", "Server", "Linux", "Windows"];
320
        },
321

    
322
        reset_categories: function() {
323
            var categories = this.get_categories(this.images);
324
            this.categories_list.find("li").remove();
325

    
326
            _.each(categories, _.bind(function(cat) {
327
                var el = $("<li />");
328
                el.text(cat);
329
                this.categories_list.append(el);
330
            }, this));
331

    
332
            if (!categories.length) { 
333
                this.categories_list.parent().find(".clear").hide();
334
                this.categories_list.parent().find(".empty").show();
335
            } else {
336
                this.categories_list.parent().find(".clear").show();
337
                this.categories_list.parent().find(".empty").hide();
338
            }
339
        },
340
        
341
        show_loading_view: function() {
342
            this.$(".images-list-cont .empty").hide();
343
            this.images_list.hide();
344
            this.$(".images-list-cont .loading").show();
345
            this.$(".images-list-cont .images-list").hide();
346
            this.reset_categories();
347
            this.update_images([]);
348
            this.reset_images();
349
            this.hide_list_loading();
350
        },
351

    
352
        hide_loading_view: function(images) {
353
            this.$(".images-list-cont .loading").hide();
354
            this.$(".images-list-cont .images-list").show();
355
            this.reset_categories();
356
            this.update_images(images);
357
            this.reset_images();
358
            this.select_image(this.selected_image);
359
            this.hide_list_loading();
360
            $(".custom-image-help").hide();
361
            if (this.selected_type == 'personal' && !images.length) {
362
                $(".custom-image-help").show();
363
            }
364

    
365
        },
366

    
367
        select_type: function(type) {
368
            this.selected_type = type;
369
            this.types.removeClass("selected");
370
            this.types.filter("#type-select-" + this.selected_type).addClass("selected");
371
            this.images_storage.update_images_for_type(
372
                this.selected_type, 
373
                _.bind(this.show_loading_view, this), 
374
                _.bind(this.hide_loading_view, this)
375
            );
376

    
377
            this.update_layout_for_type(type);
378
        },
379

    
380
        update_layout_for_type: function(type) {
381
            if (type != "system") {
382
                this.$(".custom-action").hide();
383
            } else {
384
                this.$(".custom-action").hide();
385
            }
386

    
387
        },
388

    
389
        show_list_loading: function() {
390
            this.$(".images-list-cont").addClass("loading");
391
        },
392

    
393
        hide_list_loading: function() {
394
            this.$(".images-list-cont").removeClass("loading");
395
        },
396
        
397
        display_warning_for_image: function(image) {
398
          if (image && !image.is_system_image() && !image.owned_by(synnefo.user)) {
399
            $(".create-vm .image-warning").show();
400
            $(".create-controls").hide();
401
          } else {
402
            $(".create-vm .image-warning").hide();
403
            $(".create-controls").show();
404
          }
405
        },
406

    
407
        select_image: function(image) {
408
            if (image && image.get('id') && !_.include(this.images_ids, image.get('id'))) {
409
                image = undefined;
410
            }
411
            if (!image && this.images_ids.length) {
412
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
413
                    image = this.selected_image;
414
                } else {
415
                    image = this.images_storage.get(this.images_ids[0]);
416
                }
417
            }
418
             
419
            // no images select null image so that next button gets hidden
420
            if (!this.images_ids.length) { image = undefined };
421
            
422
            if ((!this.selected_image && image) || (this.selected_image != image))
423
                this.trigger("change", image);
424
                this.display_warning_for_image(image);
425

    
426
            this.selected_image = image;
427
                
428
            if (image) {
429
                this.images_list.find(".image-details").removeClass("selected");
430
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
431
                this.update_image_details(image);
432

    
433
            } else {
434
            }
435

    
436
            this.image_details.hide();
437
            this.validate();
438
        },
439

    
440
        update_image_details: function(image) {
441
            this.image_details_desc.hide().parent().hide();
442
            if (image.get_description()) {
443
                this.image_details_desc.html(image.get_description(false)).show().parent().show();
444
            }
445
            var img = snf.ui.helpers.os_icon_tag(image.escape("OS"))
446
            if (image.get("name")) {
447
                this.image_details_title.html(img + image.escape("name")).show().parent().show();
448
            }
449
            
450
            var extra_details = this.image_details.find(".extra-details");
451
            // clean prevously added extra details
452
            extra_details.find(".image-detail").remove();
453
            
454
            var skip_keys = ['description', 'sortorder']
455
            var meta_keys = ['owner', 'OS', 'kernel', 'GUI'];
456
            var detail_tpl = ('<div class="clearfix image-detail {2}">' +
457
                             '<span class="title clearfix">{0}' +
458
                             '<span class="custom">custom</span></span>' +
459
                             '<p class="value">{1}</p>' + 
460
                             '</div>');
461
            meta_keys = _.union(meta_keys, this.images_storage.display_metadata || []);
462
            
463
            var append_metadata_row = function(key, is_extra) {
464
                var value;
465
                var method = 'get_' + key.toLowerCase();
466
                var display_method = 'display_' + key.toLowerCase();
467
                 
468
                if (image[display_method]) {
469
                    value = image[display_method]();
470
                } else if (image[method]) {
471
                    value = image[method]();
472
                } else {
473
                    value = image.get(key);
474

    
475
                    if (!value) {
476
                        value = image.get_meta(key);
477
                    }
478
                }
479
                    
480
                if (!value) { return; }
481
                 
482
                var label = this.images_storage.meta_labels[key];
483
                if (!label) {
484
                    var label = _(key.replace(/_/g," ")).capitalize();
485
                }
486
                var row_cls = key.toLowerCase();
487
                if (is_extra) { row_cls += " extra-meta" };
488
                extra_details.append(detail_tpl.format(_.escape(label), value, row_cls));
489
            }
490

    
491
            _.each(meta_keys, function(key) {
492
                append_metadata_row.apply(this, [key]);
493
            }, this);
494
            
495
            if (synnefo.storage.images.display_extra_metadata) {
496
                _.each(image.get('metadata'), function(value, key) {
497
                    if (!_.contains(meta_keys, key) && 
498
                        !_.contains(meta_keys, key.toLowerCase()) &&
499
                        !_.contains(meta_keys, key.toUpperCase()) &&
500
                        !_.contains(skip_keys, key)) {
501
                            append_metadata_row.apply(this, [key, true]);
502
                    }
503
                }, this);
504
            }
505
        },
506

    
507
        reset_images: function() {
508
            this.images_list.find("li").remove();
509
            _.each(this.images, _.bind(function(img){
510
                this.add_image(img);
511
            }, this))
512
            
513
            if (this.images.length) {
514
                this.images_list.parent().find(".empty").hide();
515
                this.images_list.show();
516
            } else {
517
                this.images_list.parent().find(".empty").show();
518
                this.images_list.hide();
519
            }
520

    
521
            var self = this;
522
            this.images_list.find(".image-details").click(function(){
523
                self.select_image($(this).data("image"));
524
            });
525
            
526
        },
527

    
528
        show: function() {
529
            this.image_details.hide();
530
            this.parent.$(".create-controls").show();
531

    
532
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
533
        },
534

    
535
        add_image: function(img) {
536
            var image = $(('<li id="create-vm-image-{1}"' +
537
                           'class="image-details clearfix">{2}{0}'+
538
                           '<span class="show-details">details</span>'+
539
                           '<span class="size"><span class="prepend">by </span>{5}</span>' + 
540
                           '<span class="owner">' +
541
                           '<span class="prepend"></span>' +
542
                           '{3}</span>' + 
543
                           '<p>{4}</p>' +
544
                           '</li>').format(img.escape("name"), 
545
                                                  img.id, 
546
                                                  snf.ui.helpers.os_icon_tag(img.escape("OS")),
547
                                                  _.escape(img.get_readable_size()),
548
                                                  util.truncate(img.get_description(false), 35),
549
                                                  _.escape(img.display_owner())));
550
            image.data("image", img);
551
            image.data("image_id", img.id);
552
            this.images_list.append(image);
553
            image.find(".show-details").click(_.bind(function(e){
554
                e.preventDefault();
555
                e.stopPropagation();
556
                this.show_image_details(img);
557
            }, this));
558
        },
559
            
560
        hide_image_details: function() {
561
            this.image_details.fadeOut(200);
562
            this.parent.$(".create-controls").show();
563
        },
564

    
565
        show_image_details: function(img) {
566
            this.parent.$(".create-controls").hide();
567
            this.update_image_details(img);
568
            this.image_details.fadeIn(100);
569
        },
570

    
571
        reset: function() {
572
            this.selected_image = false;
573
            this.select_type("system");
574
        },
575

    
576
        get: function() {
577
            return {'image': this.selected_image};
578
        },
579

    
580
        validate: function() {
581
            if (!this.selected_image) {
582
                this.parent.$(".form-action.next").hide();
583
            } else {
584
                this.parent.$(".form-action.next").show();
585
            }
586
        }
587
    });
588
    
589
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
590
        step: 2,
591
        initialize: function() {
592
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
593
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
594
            this.parent.bind("project:change", _.bind(this.handle_project_change, this));
595

    
596
            this.cpus = this.$(".flavors-cpu-list");
597
            this.disks = this.$(".flavors-disk-list");
598
            this.disk_templates = this.$(".flavors-disk-template-list");
599
            this.mems = this.$(".flavors-mem-list");
600

    
601
            this.predefined_flavors = SUGGESTED_FLAVORS;
602
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
603
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
604
                var flv = this.predefined_flavors[k];
605
                return (flv.ram * flv.cpu * flv.disk);
606
            }, this));
607

    
608
            this.predefined = this.$(".predefined-list");
609
            this.projects_list = this.$(".project-select");
610
            this.project_select_view = undefined;
611
        },
612
          
613
        init_subviews: function() {
614
          if (!this.project_select_view) {
615
            this.project_select_view = new views.CreateVMSelectProjectView({
616
              container: this.projects_list,
617
              collection: synnefo.storage.projects,
618
              parent_view: this
619
            });
620
            this.project_select_view.show(true);
621
            this.project_select_view.bind("change", 
622
                                          _.bind(this.handle_project_select, 
623
                                                 this))
624
          }
625
          this.project_select_view.set_current(this.parent.project);
626
          this.handle_project_select(this.parent.project);
627
        },
628

    
629
        hide_step: function() {
630
            this.project_select_view && this.project_select_view.hide(true);
631
        },
632

    
633
        handle_project_select: function(projects) {
634
          if (!projects.length ) { return }
635
          var project = projects[0];
636
          this.parent.set_project(project);
637
        },
638

    
639
        handle_project_change: function() {
640
            if (!this.parent.project) { return }
641
            this.update_valid_predefined();
642
            this.update_flavors_data();
643
            this.update_predefined_flavors();
644
            this.reset_flavors();
645
            this.update_layout();
646
        },
647

    
648
        show: function() {
649
          var args = _.toArray(arguments);
650
          this.init_subviews();
651
          this.project_select_view.show();
652
          views.CreateFlavorSelectView.__super__.show.call(this, args);
653
        },
654

    
655
        handle_image_change: function(data) {
656
            if (!this.parent.project) { return }
657
            this.current_image = data;
658
            this.update_valid_predefined();
659
            this.current_flavor = undefined;
660
            this.update_flavors_data();
661
            this.update_predefined_flavors();
662
            this.reset_flavors();
663
            this.update_layout();
664
        },
665

    
666
        validate_selected_flavor: function() {
667
            if (!this.flavor_is_valid(this.current_flavor)) {
668
                this.select_valid_flavor();
669
            }
670
        },
671

    
672
        reset_flavors: function() {
673
            this.$(".flavor-opts-list .option").remove();
674
            this.create_flavors();
675
        },
676

    
677
        update_predefined_flavors: function() {
678
            this.predefined.find("li").remove();
679
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
680
                var val = this.predefined_flavors[key];
681
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
682
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
683

    
684
                this.predefined.append(el);
685
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
686
                el.click(_.bind(function() {
687
                    this.handle_predefined_click(el);
688
                }, this))
689
            }, this));
690
            this.update_valid_predefined();
691
        },
692

    
693
        handle_predefined_click: function(el) {
694
            if (el.hasClass("disabled")) { return };
695
            this.set_current(el.data("flavor"));
696
        },
697

    
698
        select_valid_flavor: function() {
699
            var found = false;
700
            var self = this;
701

    
702
            _.each(["cpu", "mem", "disk"], function(t) {
703
              var el = $(".flavor-options."+t);
704
              var all = el.find(".flavor-opts-list li").length;
705
              var disabled = el.find(".flavor-opts-list li.disabled").length;
706
              if (disabled >= all) {
707
                el.find("h4").addClass("error");
708
              } else {
709
                el.find("h4").removeClass("error");
710
              }
711
            })
712

    
713
            _.each(this.flavors, function(flv) {
714
                if (self.flavor_is_valid(flv)) {
715
                    found = flv;
716
                    return false;
717
                }
718
            });
719
            
720
            if (found) {
721
                this.set_current(found);
722
            } else {
723
                this.current_flavor = undefined;
724
                this.validate();
725
                this.$("li.predefined-selection").addClass("disabled");
726
                this.$(".flavor-opts-list li").removeClass("selected");
727
            }
728
        },
729

    
730
        update_valid_predefined: function() {
731
            this.update_unavailable_values();
732
            var self = this;
733
            this.valid_predefined = _.select(
734
              _.map(this.predefined_flavors, function(flv, key){
735
                var existing = storage.flavors.get_flavor(flv.cpu, 
736
                                                          flv.ram, 
737
                                                          flv.disk, 
738
                                                          flv.disk_template, 
739
                                                          self.flavors);
740
                // non existing
741
                if (!existing) {
742
                    return false;
743
                }
744
                
745
                // not available for image
746
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(
747
                    existing.get("disk")) > -1) {
748
                      return false
749
                }
750
                
751
                // quota check
752
                var quotas = this.get_project().quotas.get_available_for_vm();
753
                var unavailable_check = 
754
                  synnefo.storage.flavors.unavailable_values_for_quotas;
755
                var unavailable = unavailable_check(quotas, [existing]);
756
                if ((_.filter(unavailable, function(values, flvkey) {
757
                  return values.length > 0
758
                })).length > 0) {
759
                  return false;
760
                }
761
                
762
                return key;
763
            }), function(ret) { return ret });
764
            
765
            $("li.predefined-selection").addClass("disabled");
766
            _.each(this.valid_predefined, function(key) {
767
                $("#predefined-flavor-" + key).removeClass("disabled");
768
            })
769
        },
770

    
771
        update_selected_predefined: function() {
772
            var self = this;
773
            this.predefined.find("li").removeClass("selected");
774

    
775
            _.each(this.valid_predefined, function(key){
776
                var flv = self.predefined_flavors[key];
777
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
778

    
779
                if (exists && (exists.id == self.current_flavor.id)) {
780
                    $("#predefined-flavor-" + key).addClass("selected");
781
                }
782
            })
783
        },
784
        
785
        update_flavors_data: function() {
786
            if (!this.parent.project) { return }
787
            this.flavors = storage.flavors.active();
788
            this.flavors_data = storage.flavors.get_data(this.flavors);
789
            
790
            var self = this;
791
            var set = false;
792
            
793
            // FIXME: validate current flavor
794
            
795
            if (!this.current_flavor) {
796
                _.each(this.valid_predefined, function(key) {
797
                    var flv = self.predefined_flavors[key];
798
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
799
                    if (exists && !set) {
800
                        self.set_current(exists);
801
                        set = true;
802
                    }
803
                })
804
            }
805

    
806
            this.update_unavailable_values();
807
        },
808

    
809
        update_unavailable_values: function() {
810
            
811
            var unavailable = {disk:[], ram:[], cpu:[]}
812
            var user_excluded = {disk:[], ram:[], cpu:[]}
813
            var image_excluded = {disk:[], ram:[], cpu:[]}
814

    
815
            if (this.current_image) {
816
              image_excluded = storage.flavors.unavailable_values_for_image(this.current_image);
817
            }
818
            
819
            var quotas = this.get_project().quotas.get_available_for_vm({active: true});
820
            var user_excluded = storage.flavors.unavailable_values_for_quotas(quotas);
821

    
822
            unavailable.disk = user_excluded.disk.concat(image_excluded.disk);
823
            unavailable.ram = user_excluded.ram.concat(image_excluded.ram);
824
            unavailable.cpu = user_excluded.cpu.concat(image_excluded.cpu);
825
            
826
            this.unavailable_values = unavailable;
827
        },
828
        
829
        flavor_is_valid: function(flv) {
830
            if (!flv) { return false };
831

    
832
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
833
            if (!existing) { return false };
834
            
835
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk"))) > -1)) {
836
                return false;
837
            }
838
            if (this.unavailable_values && (this.unavailable_values.ram.indexOf(parseInt(flv.get("ram"))) > -1)) {
839
                return false;
840
            }
841
            if (this.unavailable_values && (this.unavailable_values.cpu.indexOf(parseInt(flv.get("cpu"))) > -1)) {
842
                return false;
843
            }
844
            return true;
845
        },
846
            
847
        set_valid_current_for: function(t, val) {
848
            var found = this.flavors[0];
849
            _.each(this.flavors, function(flv) {
850
                if (flv.get(t) == val) {
851
                    found = flv;
852
                }
853
            });
854

    
855
            this.set_current(found);
856
            this.validate_selected_flavor();
857
        },
858

    
859
        set_current: function(flv) {
860

    
861
            if (!flv) {
862
                // user clicked on invalid combination
863
                // force the first available choice for the
864
                // type of option he last clicked
865
                this.set_valid_current_for.apply(this, this.last_choice);
866
                return;
867
            }
868

    
869
            this.current_flavor = flv;
870
            this.trigger("change");
871
            if (this.current_flavor) {
872
                this.update_selected_flavor();
873
                this.update_selected_predefined();
874
            }
875
            
876
            this.validate();
877
        },
878
        
879
        select_default_flavor: function() {
880
               
881
        },
882

    
883
        update_selected_from_ui: function() {
884
            this.set_current(this.ui_selected());
885
        },
886
        
887
        update_disabled_flavors: function() {
888
            this.$(".flavor-options.disk li").removeClass("disabled");
889
            if (!this.unavailable_values) { return }
890
            
891
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
892
                var el_value = $(el).data("value");
893
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
894
                    $(el).addClass("disabled");
895
                    $(el).removeClass("selected");
896
                };
897
            }, this));
898

    
899
            this.$("#create-vm-flavor-options .flavor-options.mem li").each(_.bind(function(i, el){
900
                var el_value = $(el).data("value");
901
                if (this.unavailable_values.ram.indexOf(el_value) > -1) {
902
                    $(el).addClass("disabled");
903
                    $(el).removeClass("selected");
904
                };
905
            }, this));
906

    
907
            this.$("#create-vm-flavor-options .flavor-options.cpu li").each(_.bind(function(i, el){
908
                var el_value = $(el).data("value");
909
                if (this.unavailable_values.cpu.indexOf(el_value) > -1) {
910
                    $(el).addClass("disabled");
911
                    $(el).removeClass("selected");
912
                };
913
            }, this));
914
        },
915

    
916
        create_flavors: function() {
917
            var flavors = this.get_active_flavors();
918
            var valid_flavors = this.get_valid_flavors();
919
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
920

    
921
            _.each(flavors, _.bind(function(flv){
922
                this.add_flavor(flv);
923
            }, this));
924
            
925
            this.sort_flavors(this.disks);
926
            this.sort_flavors(this.cpus);
927
            this.sort_flavors(this.mems);
928
            this.sort_flavors(this.disk_templates);
929

    
930
            var self = this;
931
            this.$(".flavor-options li.option").click(function(){
932
                var el = $(this);
933

    
934
                if (el.hasClass("disabled")) { return }
935

    
936
                el.parent().find(".option").removeClass("selected");
937
                el.addClass("selected");
938

    
939
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
940
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
941
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
942
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
943

    
944
                self.update_selected_from_ui();
945
            });
946

    
947
            $(".flavor-opts-list").each(function(){
948
              var el = $(this);
949
              if (el.find(".option").length > 6) {
950
                el.addClass("compact");
951
              }
952
            });
953
        },
954

    
955
        sort_flavors: function(els) {
956
            var prev = undefined;
957
            els.find("li").each(function(i,el){
958
                el = $(el);
959
                if (!prev) { prev = el; return true };
960
                if (el.data("value") < prev.data("value")) {
961
                    prev.before(el);
962
                }
963
                prev = el;
964
            })
965
        },
966
        
967
        ui_selected: function() {
968
            var args = [this.$(".option.cpu.selected").data("value"), 
969
                this.$(".option.mem.selected").data("value"), 
970
                this.$(".option.disk.selected").data("value"),
971
                this.$(".option.disk_template.selected").data("value"),
972
            this.flavors];
973
            
974
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
975
            return flv;
976
        },
977

    
978
        update_selected_flavor: function() {
979
            var flv = this.current_flavor;
980
            if (!flv) { return }
981
            this.$(".option").removeClass("selected");
982

    
983
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
984
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
985
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
986
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
987
            
988
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
989
            var basebgpos = 470;
990
                
991
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
992
            var bg_pos = basebgpos - append_to_bg_pos;
993

    
994
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
995
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
996
        },
997
        
998
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
999
        add_flavor: function(flv) {
1000
            var values = {'cpu': flv.get('cpu'), 
1001
                          'mem': flv.get('ram'), 
1002
                          'disk': flv.get('disk'), 
1003
                          'disk_template': flv.get('disk_template')};
1004

    
1005
            disabled = "";
1006
            
1007
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
1008
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
1009
                             '<span class="value">{0}</span>' + 
1010
                             '<span class="metric">x</span></li>').format(
1011
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
1012
                this.cpus.append(cpu);
1013
                this.__added_flavors.cpu.push(values.cpu);
1014
            }
1015

    
1016
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
1017
                var mem_value = parseInt(_.escape(values.mem))*1024*1024;
1018
                var displayvalue = synnefo.util.readablizeBytes(mem_value, 
1019
                                                               0).split(" ");
1020
                var mem = $(('<li class="option mem value-{2}">' + 
1021
                             '<span class="value">{0}</span>' + 
1022
                             '<span class="metric">{1}</span></li>').format(
1023
                          displayvalue[0], displayvalue[1], values.mem)).data(
1024
                          'value', values.mem);
1025
                this.mems.append(mem);
1026
                this.__added_flavors.ram.push(values.mem);
1027
            }
1028

    
1029
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
1030
                var disk = $(('<li class="option disk value-{0}">' + 
1031
                              '<span class="value">{0}</span>' + 
1032
                              '<span class="metric">GB</span></li>').format(
1033
                            _.escape(values.disk))).data('value', values.disk);
1034
                this.disks.append(disk);
1035
                this.__added_flavors.disk.push(values.disk)
1036
            }
1037
            
1038
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
1039
                var template_info = flv.get_disk_template_info();
1040
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
1041
                                       '<span class="value name">{1}</span>' +
1042
                                       '</li>').format(values.disk_template, 
1043
                                            _.escape(template_info.name), 
1044
                                            template_info.description)).data('value', 
1045
                                                                values.disk_template);
1046

    
1047
                this.disk_templates.append(disk_template);
1048
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
1049
                this.__added_flavors.disk_template.push(values.disk_template)
1050
            }
1051
            
1052
        },
1053
        
1054
        get_active_flavors: function() {
1055
            return storage.flavors.active();
1056
        },
1057

    
1058
        get_valid_flavors: function() {
1059
            return this.flavors;
1060
        },
1061

    
1062
        update_layout: function() {
1063
            this.update_selected_flavor();
1064
            this.update_disabled_flavors();
1065
            this.validate();
1066
            this.validate_selected_flavor();
1067
            this.update_quota_display();
1068
        },
1069
        
1070
        update_quota_display: function() {
1071

    
1072
          var quotas = this.get_project().quotas;
1073
          _.each(["disk", "ram", "cpu"], function(type) {
1074
            var active = true;
1075
            var key = 'available';
1076
            var available_dsp = quotas.get('cyclades.'+type).get_readable(key, active);
1077
            var available = quotas.get('cyclades.'+type).get_available(key);
1078
            var content = "({0} left)".format(available_dsp);
1079
            if (available <= 0) { content = "(None left)" }
1080
            
1081
            if (type == "ram") { type = "mem" }
1082
            $(".flavor-options."+type+" h4 .available").text(content);
1083
            if (available <= 0) {
1084
              $(".flavor-options."+type+" h4 .available").addClass("error");
1085
            } else {
1086
              $(".flavor-options."+type+" h4 .available").removeClass("error");
1087
            }
1088
          })
1089
        },
1090

    
1091
        reset: function() {
1092
            this.current_image = storage.images.at(0);
1093
            this.flavors = [];
1094
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
1095
            this.update_flavors_data();
1096
        },
1097

    
1098
        validate: function() {
1099
            if (!this.current_flavor) {
1100
                this.parent.$(".form-action.next").hide();
1101
            } else {
1102
                this.parent.$(".form-action.next").show();
1103
            }
1104
        },
1105

    
1106
        get: function() {
1107
            return {'flavor': this.current_flavor}
1108
        }
1109
    });
1110
    
1111

    
1112
    views.CreateColumnSelectOptionView = bb.View.extend({
1113
        tagName: 'li',
1114
        el: undefined,
1115
        model: undefined,
1116
        id_prefix: 'model-',
1117
        tpl: '<input type="checkbox" class="check"/><span class="title"></span>',
1118
        className: 'list-item-option clearfix',
1119
        events: {
1120
          'click': 'handle_click'
1121
        },
1122

    
1123
        initialize: function(options) {
1124
          _.bindAll(this);
1125
          this.model.bind("change", this.render);
1126
          this.model.bind("remove", this.remove);
1127
          this.selected = false;
1128
          if (options.get_model_title) {
1129
            this.get_model_title = _.bind(options.get_model_title, this);
1130
          }
1131
          this.model_title_attr = options.model_title_attr;
1132
          $(this.el).append($(this.tpl));
1133
        },
1134
        
1135
        id: function() {
1136
          return this.id_prefix + this.model && this.model.id || '';
1137
        },
1138
        
1139
        handle_click: function() {
1140
          this.selected = !this.selected;
1141
          this.render();
1142
        },
1143

    
1144
        remove: function() {
1145
          this.model.unbind("change", this.render);
1146
          this.model.unbind("remove", this.remove);
1147
        },
1148
        
1149
        get_model_title: function() {
1150
          return this.model.get(this.model_title_attr || 'id');
1151
        },
1152

    
1153
        render: function() {
1154
          $(this.el).find(".title").text(this.get_model_title());
1155
          $(this.el).toggleClass('selected', this.selected);
1156
          if (this.selected) {
1157
            $(this.el).find("input").attr("checked", true);
1158
          } else {
1159
            $(this.el).find("input").attr("checked", false);
1160
          }
1161
        }
1162
    });
1163
    
1164
    views.CreateColumnIPOptionView = views.CreateColumnSelectOptionView.extend({
1165
      get_model_title: function() {
1166
        return this.model.get('ip');
1167
      }
1168
    });
1169

    
1170
    views.CreateColumnPrivateNetworkOptionView = views.CreateColumnSelectOptionView.extend({
1171
      get_model_title: function() {
1172
        return this.model.get('name');
1173
      }
1174
    });
1175

    
1176
    views.CreateColumnSelectListView = bb.View.extend({
1177
        collection: undefined,
1178
        header: undefined,
1179
        tagName: 'div',
1180
        extra_class: '',
1181
        el: undefined,
1182
        title_tpl: undefined,
1183
        title: 'List view',
1184
        description: 'List view description.',
1185
        empty_msg: 'No entries.',
1186
        item_cls: views.CreateColumnSelectOptionView,
1187
        className: 'list-cont create-column-select personalize-cont',
1188

    
1189
        initialize: function(options) {
1190
          _.bindAll(this);
1191
          if (options.extra_class) {
1192
            $(this.el).addClass(options.extra_class);
1193
          }
1194
          this.update_collection = options.update_collection;
1195
          this.title = options.title || this.title;
1196
          this.titple_tpl = options.title_tpl || this.title_tpl;
1197
          this.description = options.description || this.description;
1198
          this.empty_msg = options.empty_msg || this.empty_msg;
1199
          this.item_cls = options.item_cls || this.item_cls;
1200
          this.select_first_as_default = options.select_first_as_default;
1201
          this.filter_items = options.filter_items;
1202
          this.post_render_entries = options.post_render_entries || function() {};
1203
          this.init_events = options.init_events || function() {};
1204

    
1205
          this.init_events = _.bind(this.init_events, this);
1206
          this.post_render_entries = _.bind(this.post_render_entries, this);
1207

    
1208
          this._ul = $('<ul class="confirm-params">');
1209
          this._title = $("<h4>");
1210
          this._description = $("<p class='desc'>");
1211
          this._empty = $("<p class='empty hidden desc'>");
1212
          this._empty.html(this.empty_msg);
1213
        
1214
          this.item_views = [];
1215

    
1216
          $(this.el).append(this._title);
1217
          $(this.el).append(this._description);
1218
          $(this.el).append(this._empty);
1219
          $(this.el).append(this._ul);
1220

    
1221
          this['$el'] = $(this.el);
1222

    
1223
          if (!this.title_tpl) { this.title_tpl = this.title };
1224

    
1225
          this.collection.bind("change", this.render_entries);
1226
          this.collection.bind("reset", this.render_entries);
1227
          this.collection.bind("add", this.render_entries);
1228
          this.collection.bind("remove", this.remove_entry);
1229
          
1230
          this.fetcher = undefined;
1231
          if (this.update_collection) {
1232
              this.fetcher_params = [snf.config.update_interval, 
1233
                    snf.config.update_interval_increase || 500,
1234
                    snf.config.fast_interval || snf.config.update_interval/2, 
1235
                    snf.config.update_interval_increase_after_calls || 4,
1236
                    snf.config.update_interval_max || 20000,
1237
                    true, 
1238
                    {is_recurrent: true, update: true}];
1239
              this.fetcher = this.collection.get_fetcher.apply(this.collection, 
1240
                                                _.clone(this.fetcher_params));
1241
              this.fetcher.start();
1242
          }
1243
          this.render();
1244
          this.init_events();
1245
        },
1246
        
1247
        render: function() {
1248
          this._title.html(this.title_tpl);
1249
          this._description.html(this.description);
1250
          this.render_entries();
1251
        },
1252
        
1253
        remove_entry: function(model) {
1254
          if (!this.item_views[model.id]) { return }
1255
          this.item_views[model.id].remove();
1256
          delete this.item_views[model.pk]
1257
        },
1258
        
1259
        get_selected: function() {
1260
          return _.map(_.filter(this.item_views, function(v) { 
1261
            return v.selected
1262
          }), function(v) {
1263
            return v.model
1264
          });
1265
        },
1266
        
1267
        check_empty: function() {
1268
          if (this.item_views.length == 0) {
1269
            this._empty.show();
1270
          } else {
1271
            this._empty.hide();
1272
          }
1273
        },
1274

    
1275
        render_entries: function() {
1276
          var entries;
1277
          if (this.filter_items) {
1278
            entries = this.collection.filter(this.filter_items);
1279
          } else {
1280
            entries = this.collection.models;
1281
          }
1282
          
1283
          var selected = this.get_selected();
1284
          
1285
          _.each(entries, _.bind(function(model) {
1286
            if (this.item_views[model.id]) {
1287
              this.item_views[model.id].render();
1288
            } else {
1289
              var view = new this.item_cls({model:model});
1290
              if (!selected.length && this.select_first_as_default) { 
1291
                view.selected = true; selected = [1] 
1292
              }
1293
              view.render();
1294
              this.item_views[model.id] = view;
1295
              this._ul.append($(view.el));
1296
            }
1297
          }, this));
1298
          this.check_empty();
1299
          this.post_render_entries();
1300
        },
1301

    
1302
        remove: function() {
1303
          _.each(this.item_views, function(v){
1304
            v.remove();
1305
          });
1306
          if (this.update_collection) { this.fetcher.stop() }
1307
          this.unbind();
1308
          this.collection.unbind("change", this.render_entries);
1309
          this.collection.unbind("reset", this.render_entries);
1310
          this.collection.unbind("add", this.render_entries);
1311
          this.collection.unbind("remove", this.remove_entry);
1312
          views.CreateColumnSelectListView.__super__.remove.apply(this, arguments);
1313
        }
1314
    });
1315

    
1316
    views.CreateNetworkingView = views.CreateVMStepView.extend({
1317
        step: 3,
1318
        initialize: function() {
1319
            views.CreateNetworkingView.__super__.initialize.apply(this, arguments);
1320
            this.init_handlers();
1321
            this.selected_keys = [];
1322
            this.cont = this.$(".step-cont");
1323
        },
1324
        
1325
        init_subviews: function() {
1326
            var create_view = this.parent;
1327
            if (!this.networks_view) {
1328
              this.networks_view = new views.NetworkSelectView({
1329
                container: this.cont,
1330
                project: this.get_project()
1331
              });
1332
              this.networks_view.hide(true);
1333
            }
1334
        },
1335

    
1336
        init_handlers: function() {
1337
        },
1338

    
1339
        show: function() {
1340
            views.CreateNetworkingView.__super__.show.apply(this, arguments);
1341
            this.init_subviews();
1342
            this.update_layout();
1343
            this.networks_view.show(true);
1344
        },
1345
        
1346
        hide_step: function() {
1347
            this.networks_view && this.networks_view.hide(true);
1348
        },
1349

    
1350
        update_layout: function() {
1351
        },
1352

    
1353
        reset: function() {
1354
            this.selected_keys = [];
1355
            this.update_layout();
1356
        },
1357
        
1358
        get_selected_networks: function() {
1359
            if (!this.networks_view) { return [] }
1360
            return this.networks_view.get_selected_networks();
1361
        },
1362
        
1363
        get_selected_addresses: function() {
1364
            if (!this.networks_view) { return [] }
1365
            return this.networks_view.get_selected_floating_ips();
1366
        },
1367

    
1368
        get: function() {
1369
            return {
1370
              'addresses': this.get_selected_addresses(),
1371
              'networks': this.get_selected_networks()
1372
            }
1373
        },
1374

    
1375
        remove: function() {
1376
          if (this.networks_view) {
1377
            this.networks_view.remove();
1378
            delete this.networks_view;
1379
          }
1380
        }
1381
    });
1382

    
1383
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
1384
        step: 4,
1385
        initialize: function() {
1386
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1387
            this.roles = this.$("li.predefined-meta.role .values");
1388
            this.name = this.$("input.rename-field");
1389
            this.name_changed = false;
1390
            this.init_suggested_roles();
1391
            this.init_handlers();
1392
            this.ssh_list = this.$(".ssh ul");
1393
            this.selected_keys = [];
1394

    
1395
            var self = this;
1396
        },
1397

    
1398
        init_suggested_roles: function() {
1399
            var cont = this.roles;
1400
            cont.empty();
1401
            
1402
            // TODO: get suggested from snf.api.conf
1403
            _.each(window.SUGGESTED_ROLES, function(r){
1404
                var el = $('<span class="val">{0}</span>'.format(_.escape(r)));
1405
                el.data("value", r);
1406
                cont.append(el);
1407
                el.click(function() {
1408
                    $(this).parent().find(".val").removeClass("selected");
1409
                    $(this).toggleClass("selected");
1410
                })
1411
            });
1412
            
1413
            var self = this;
1414
            $(".ssh li.ssh-key-option").live("click", function(e) {
1415
                var key = $(this).data("model");
1416
                self.select_key(key);
1417
            });
1418
        },
1419

    
1420
        select_key: function(key) {
1421
            var exists = this.selected_keys.indexOf(key.id);
1422
            if (exists > -1) {
1423
                this.selected_keys.splice(exists, 1);
1424
            } else {
1425
                this.selected_keys.push(key.id);
1426
            }
1427
            this.update_ui_keys_selections(this.selected_keys);
1428
        },
1429

    
1430
        update_ui_keys_selections: function(keys) {
1431
            var self = this;
1432
            self.$(".ssh-key-option").removeClass("selected");
1433
            self.$(".ssh-key-option .check").attr("checked", false);
1434
            _.each(keys, function(kid) {
1435
                $("#ssh-key-option-" + kid).addClass("selected");
1436
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
1437
            });
1438
        },
1439

    
1440
        update_ssh_keys: function() {
1441
            this.ssh_list.empty();
1442
            var keys = snf.storage.keys.models;
1443
            if (keys.length == 0) { 
1444
                this.$(".ssh .empty").show();
1445
            } else {
1446
                this.$(".ssh .empty").hide();
1447
            }
1448
            _.each(keys, _.bind(function(key){
1449
                var name = _.escape(util.truncate(key.get("name"), 45));
1450
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(name, key.id));
1451
                var check = $('<input class="check" type="checkbox"></input>')
1452
                el.append(check);
1453
                el.data("model", key);
1454
                this.ssh_list.append(el);
1455
            }, this));
1456
        },
1457

    
1458
        init_handlers: function() {
1459
            this.name.bind("keypress", _.bind(function(e) {
1460
                this.name_changed = true;
1461
                if (e.keyCode == 13) { this.parent.set_step(5); this.parent.update_layout() };    
1462
            }, this));
1463

    
1464
            this.name.bind("click", _.bind(function() {
1465
                if (!this.name_changed) {
1466
                    this.name.val("");
1467
                }
1468
            }, this))
1469
        },
1470

    
1471
        show: function() {
1472
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
1473
            this.update_layout();
1474
        },
1475
        
1476
        update_layout: function() {
1477
            var params = this.parent.get_params();
1478

    
1479
            if (!params.image || !params.flavor) { return }
1480

    
1481
            if (!params.image) { return }
1482
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
1483
            //if (params.image.is_snapshot()) { vm_name_tpl = "{0}" };
1484
            var vm_name = vm_name_tpl.format(params.image.get("name"));
1485
            var orig_name = vm_name;
1486
            
1487
            var existing = true;
1488
            var j = 0;
1489

    
1490
            while (existing && !this.name_changed) {
1491
                var existing = storage.vms.select(function(vm){
1492
                  return vm.get("name") == vm_name
1493
                }).length;
1494
                if (existing) {
1495
                    j++;
1496
                    vm_name = orig_name + " " + j;
1497
                }
1498
            }
1499

    
1500
            if (!_(this.name.val()).trim() || !this.name_changed) {
1501
                this.name.val(vm_name);
1502
            }
1503

    
1504
            if (!this.name_changed && this.parent.visible()) {
1505
                if (!$.browser.msie && !$.browser.opera) {
1506
                    this.$("#create-vm-name").select();
1507
                } else {
1508
                    window.setTimeout(_.bind(function(){
1509
                        this.$("#create-vm-name").select();
1510
                    }, this), 400)
1511
                }
1512
            }
1513
            
1514
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1515
            this.name.css({backgroundImage:"url({0})".format(img)})
1516
            
1517
            if (!params.image.supports('ssh')) {
1518
                this.disable_ssh_keys();
1519
            } else {
1520
                this.enable_ssh_keys();
1521
                this.update_ssh_keys();
1522
            }
1523

    
1524
            this.update_ui_keys_selections(this.selected_keys);
1525
        },
1526

    
1527
        disable_ssh_keys: function() {
1528
            this.$(".disabled.desc").show();
1529
            this.$(".empty.desc").hide();
1530
            this.$(".ssh .confirm-params").hide();
1531
            this.selected_keys = [];
1532
        },
1533

    
1534
        enable_ssh_keys: function() {
1535
            this.$(".ssh .confirm-params").show();
1536
            this.$(".disabled.desc").hide();
1537
        },
1538

    
1539
        reset: function() {
1540
            this.roles.find(".val").removeClass("selected");
1541
            this.name_changed = false;
1542
            this.selected_keys = [];
1543
            this.update_layout();
1544
        },
1545

    
1546
        get_meta: function() {
1547
            if (this.roles.find(".selected").length == 0) {
1548
                return false;
1549
            }
1550

    
1551
            var role = $(this.roles.find(".selected").get(0)).data("value");
1552
            return {'Role': role }
1553
        },
1554

    
1555
        get: function() {
1556
            var val = {'name': this.name.val() };
1557
            if (this.get_meta()) {
1558
                val.metadata = this.get_meta();
1559
            }
1560

    
1561
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1562
            
1563
            return val;
1564
        }
1565
    });
1566

    
1567
    views.CreateSubmitView = views.CreateVMStepView.extend({
1568
        step: 5,
1569
        initialize: function() {
1570
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1571
            this.roles = this.$("li.predefined-meta.role .values");
1572
            this.confirm = this.$(".confirm-params ul");
1573
            this.name = this.$("h3.vm-name");
1574
            this.keys = this.$(".confirm-params.ssh");
1575
            this.meta = this.$(".confirm-params.meta");
1576
            this.project = this.$(".confirm-cont.image .project-title");
1577
            this.ip_addresses = this.$(".confirm-params.ip-addresses");
1578
            this.private_networks = this.$(".confirm-params.private-networks");
1579
            this.init_handlers();
1580
        },
1581

    
1582
        init_handlers: function() {
1583
        },
1584

    
1585
        show: function() {
1586
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1587
            this.update_layout();
1588
        },
1589
        
1590
        update_network_details: function() {
1591
            var data = this.parent.get_params();
1592
            var ips = data.addresses;
1593
            var networks = data.networks;
1594

    
1595
            this.ip_addresses.empty();
1596
            if (!ips|| ips.length == 0) {
1597
                this.ip_addresses.append(this.make("li", {'class':'empty'}, 
1598
                                           'No ip addresses selected'))
1599
            }
1600
            _.each(ips, _.bind(function(ip) {
1601
                var el = this.make("li", {'class':'selected-ip-address'}, 
1602
                                  ip.get('floating_ip_address'));
1603
                this.ip_addresses.append(el);
1604
            }, this))
1605

    
1606
            this.private_networks.empty();
1607
            if (!networks || networks.length == 0) {
1608
                this.private_networks.append(this.make("li", {'class':'empty'}, 
1609
                                             'No private networks selected'))
1610
            }
1611
            _.each(networks, _.bind(function(network) {
1612
                var el = this.make("li", {'class':'selected-private-network'}, 
1613
                                  network.get('name'));
1614
                this.private_networks.append(el);
1615
            }, this))
1616

    
1617
        },
1618

    
1619
        update_flavor_details: function() {
1620
            var flavor = this.parent.get_params().flavor;
1621

    
1622
            function set_detail(sel, key) {
1623
                var val = key;
1624
                if (key == undefined) { val = flavor.get(sel) };
1625
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1626
            }
1627
            
1628
            set_detail("cpu", flavor.get("cpu") + "x");
1629
            set_detail("ram", flavor.get("ram") + " MB");
1630
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1631
            set_detail("disktype", flavor.get_disk_template_info().name);
1632
        },
1633

    
1634
        update_image_details: function() {
1635
            var image = this.parent.get_params().image;
1636

    
1637
            function set_detail(sel, key) {
1638
                var val = key;
1639
                if (key == undefined) { val = image.get(sel) };
1640
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1641
            }
1642
            
1643
            set_detail("description", image.get_description());
1644
            set_detail("name", util.truncate(image.get("name"), 30));
1645
            set_detail("os", _(image.get_os()).capitalize());
1646
            set_detail("gui", image.get_gui());
1647
            set_detail("size", _.escape(image.get_readable_size()));
1648
            set_detail("kernel");
1649
        },
1650

    
1651
        update_selected_keys: function(keys) {
1652
            this.keys.empty();
1653
            if (!keys || keys.length == 0) {
1654
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1655
            }
1656
            _.each(keys, _.bind(function(key) {
1657
                var name = _.escape(util.truncate(key.get("name"), 20))
1658
                var el = this.make("li", {'class':'selected-ssh-key'}, name);
1659
                this.keys.append(el);
1660
            }, this))
1661
        },
1662

    
1663
        update_selected_meta: function(meta) {
1664
            this.meta.empty();
1665
            if (!meta || meta.length == 0) {
1666
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1667
            }
1668
            _.each(meta, _.bind(function(value, key) {
1669
                var el = this.make("li", {'class':'confirm-value'});
1670
                var name = this.make("span", {'class':'ckey'}, key);
1671
                var value = this.make("span", {'class':'cval'}, value);
1672

    
1673
                $(el).append(name)
1674
                $(el).append(value);
1675
                this.meta.append(el);
1676
            }, this));
1677
        },
1678

    
1679
        update_layout: function() {
1680
            var params = this.parent.get_params();
1681
            if (!params.image || !params.flavor) { return }
1682

    
1683
            if (!params.image) { return }
1684

    
1685
            this.name.text(util.truncate(params.name, 50));
1686

    
1687
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1688
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1689
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1690
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1691

    
1692
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1693
            this.name.css({backgroundImage:"url({0})".format(img)})
1694

    
1695
            this.update_image_details();
1696
            this.update_flavor_details();
1697
            this.update_network_details();
1698
            
1699
            var project_name = this.get_project().get('name');
1700
            project_name = util.truncate(project_name, 25);
1701
            this.project.text(project_name);
1702

    
1703
            if (!params.image.supports('ssh')) {
1704
                this.keys.hide();
1705
                this.keys.prev().hide();
1706
            } else {
1707
                this.keys.show();
1708
                this.keys.prev().show();
1709
                this.update_selected_keys(params.keys);
1710
            }
1711
            
1712
            this.update_selected_meta(params.metadata);
1713
        },
1714

    
1715
        reset: function() {
1716
            this.update_layout();
1717
        },
1718

    
1719
        get_meta: function() {
1720
        },
1721

    
1722
        get: function() {
1723
            return {};
1724
        }
1725
    });
1726

    
1727
    views.CreateVMView = views.Overlay.extend({
1728
        
1729
        view_id: "create_vm_view",
1730
        content_selector: "#createvm-overlay-content",
1731
        css_class: 'overlay-createvm overlay-info',
1732
        overlay_id: "metadata-overlay",
1733

    
1734
        subtitle: false,
1735
        title: "Create new machine",
1736

    
1737
        initialize: function(options) {
1738
            views.CreateVMView.__super__.initialize.apply(this);
1739
            this.current_step = 1;
1740

    
1741
            this.password_view = new views.VMCreationPasswordView();
1742

    
1743

    
1744
            this.steps = [];
1745
            this.steps[1] = new views.CreateImageSelectView(this);
1746
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1747

    
1748
            this.steps[2] = new views.CreateFlavorSelectView(this);
1749
            this.steps[3] = new views.CreateNetworkingView(this);
1750
            this.steps[4] = new views.CreatePersonalizeView(this);
1751
            this.steps[5] = new views.CreateSubmitView(this);
1752

    
1753
            this.cancel_btn = this.$(".create-controls .cancel");
1754
            this.next_btn = this.$(".create-controls .next");
1755
            this.prev_btn = this.$(".create-controls .prev");
1756
            this.no_project_notice = this.$(".no-project-notice");
1757
            this.submit_btn = this.$(".create-controls .submit");
1758

    
1759
            this.history = this.$(".steps-history");
1760
            this.history_steps = this.$(".steps-history .steps-history-step");
1761
            
1762
            this.init_handlers();
1763
        },
1764

    
1765
        get_available_project: function() {
1766
          var project = undefined;
1767
          var user_project = synnefo.storage.projects.get_user_project();
1768
          if (user_project && user_project.quotas.can_fit(min_vm_quota)) {
1769
            project = user_project;
1770
          }
1771
          if (!project) {
1772
            synnefo.storage.projects.each(function(p) {
1773
              if (p.quotas.can_fit(min_vm_quota)) {
1774
                project = p;
1775
              }
1776
            }, this);
1777
          }
1778
          return project;
1779
        },
1780

    
1781
        set_project: function(project) {
1782
          var trigger = false;
1783
          if (project != this.project) {
1784
            trigger = true;
1785
          }
1786
          this.project = project;
1787
          if (trigger) { this.trigger("project:change", project)}
1788
        },
1789

    
1790
        init_handlers: function() {
1791
            var self = this;
1792
            this.next_btn.click(_.bind(function(){
1793
                this.set_step(this.current_step + 1);
1794
                this.update_layout();
1795
            }, this))
1796
            this.prev_btn.click(_.bind(function(){
1797
                this.set_step(this.current_step - 1);
1798
                this.update_layout();
1799
            }, this))
1800
            this.cancel_btn.click(_.bind(function(){
1801
                this.close_all();
1802
            }, this))
1803
            this.submit_btn.click(_.bind(function(){
1804
                this.submit();
1805
            }, this))
1806
            
1807
            this.history.find(".completed").live("click", function() {
1808
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1809
                self.set_step(step);
1810
                self.update_layout();
1811
            })
1812
        },
1813

    
1814
        set_step: function(st) {
1815
        },
1816
        
1817
        validate: function(data) {
1818
            if (_(data.name).trim() == "") {
1819
                this.$(".form-field").addClass("error");
1820
                return false;
1821
            } else {
1822
                return true;
1823
            }
1824
        },
1825

    
1826
        submit: function() {
1827
            if (this.submiting) { return };
1828
            var data = this.get_params();
1829
            var meta = {};
1830
            var extra = {};
1831
            var personality = [];
1832

    
1833
            if (this.validate(data)) {
1834
                this.submit_btn.addClass("in-progress");
1835
                this.submiting = true;
1836
                if (data.metadata) { meta = data.metadata; }
1837
                if (data.keys && data.keys.length > 0) {
1838
                    personality.push(
1839
                      data.image.personality_data_for_keys(data.keys))
1840
                }
1841

    
1842
                if (personality.length) {
1843
                    extra['personality'] = _.flatten(personality);
1844
                }
1845
                
1846
                extra['networks'] = [];
1847
                _.each(data.networks, function(n) {
1848
                  extra.networks.push({'uuid': n.get('id')})
1849
                });
1850
                _.each(data.addresses, function(ip) {
1851
                  extra.networks.push({
1852
                    'uuid': ip.get('network').get('id'),
1853
                    'fixed_ip': ip.get('floating_ip_address')
1854
                  });
1855
                });
1856
                
1857
                _.map(data.networks, function(n) { return n.get('id') });
1858
                storage.vms.create(data.name, data.image, data.flavor, 
1859
                                   meta, this.project, extra, 
1860
                                   _.bind(function(data) {
1861
                    _.each(data.addresses, function(ip) {
1862
                      ip.set({'status': 'connecting'});
1863
                    });
1864
                    this.close_all();
1865
                    this.password_view.show(data.server.adminPass, 
1866
                                            data.server.id);
1867
                    var self = this;
1868
                    window.setTimeout(function() {
1869
                      self.submiting = false;
1870
                    }, 1000);
1871
                }, this));
1872
            }
1873
        },
1874

    
1875
        close_all: function() {
1876
          this.hide();
1877
        },
1878

    
1879
        onClose: function() {
1880
          if (this.steps && this.steps[3]) {
1881
            this.steps[3].remove();
1882
          }
1883
        },
1884

    
1885
        reset: function() {
1886
          this.current_step = 1;
1887

    
1888
          this.steps[1].reset();
1889
          this.steps[2].reset();
1890
          this.steps[3].reset();
1891
          this.steps[4].reset();
1892

    
1893
          this.submit_btn.removeClass("in-progress");
1894
        },
1895

    
1896
        onShow: function() {
1897
        },
1898

    
1899
        update_layout: function() {
1900
            if (!this.project) { 
1901
              this.set_no_project();
1902
            } else {
1903
              this.unset_no_project();
1904
            }
1905
            this.show_step(this.current_step);
1906
            this.current_view.update_layout();
1907
        },
1908
        
1909
        set_no_project: function() {
1910
          this.next_btn.hide();
1911
          this.no_project_notice.show();
1912
        },
1913

    
1914
        unset_no_project: function() {
1915
          this.next_btn.show();
1916
          this.no_project_notice.hide();
1917
        },
1918

    
1919
        beforeOpen: function() {
1920
            this.set_project(this.get_available_project());
1921
            if (!this.skip_reset_on_next_open) {
1922
                this.submiting = false;
1923
                this.reset();
1924
                this.current_step = 1;
1925
                this.$(".steps-container").css({"margin-left":0 + "px"});
1926
                this.show_step(1);
1927
            }
1928
            
1929
            this.skip_reset_on_next_open = false;
1930
            this.update_layout();
1931
        },
1932
        
1933
        set_step: function(step) {
1934
            if (step <= 1) {
1935
                step = 1
1936
            }
1937
            if (step > this.steps.length - 1) {
1938
                step = this.steps.length - 1;
1939
            }
1940
            this.current_step = step;
1941
        },
1942

    
1943
        show_step: function(step) {
1944
            // FIXME: this shouldn't be here
1945
            // but since we are not calling step.hide this should work
1946
            this.steps[1].image_details.hide();
1947
            
1948
            this.current_view && this.current_view.hide_step && this.current_view.hide_step();
1949
            this.current_view = this.steps[step];
1950
            this.update_controls();
1951

    
1952
            this.steps[step].show();
1953
            var width = this.el.find('.container').width();
1954
            var left = (step -1) * width * -1;
1955
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1956

    
1957
            this.update_steps_history();
1958
        },
1959

    
1960
        update_steps_history: function() {
1961
            var self = this;
1962
            function get_step(s) {
1963
                return self.history.find(".step" + s + "h");
1964
            }
1965
            
1966
            var current_step = parseInt(this.current_view.step);
1967
            _.each(this.steps, function(stepv) {
1968
                var step = parseInt(stepv.step);
1969
                get_step(step).removeClass("completed").removeClass("current");
1970
                if (step == current_step) {
1971
                    get_step(step).removeClass("completed").addClass("current");
1972
                }
1973
                if (step < current_step) {
1974
                    get_step(step).removeClass("current").addClass("completed");
1975
                }
1976
            });
1977
        },
1978

    
1979
        update_controls: function() {
1980
            var step = this.current_step;
1981
            if (step == 1) {
1982
                this.prev_btn.hide();
1983
                this.cancel_btn.show();
1984
            } else {
1985
                this.prev_btn.show();
1986
                this.cancel_btn.hide();
1987
            }
1988
            
1989
            if (step == this.steps.length - 1) {
1990
                this.next_btn.hide();
1991
                this.submit_btn.show();
1992
            } else {
1993
                this.next_btn.show();
1994
                this.submit_btn.hide();
1995
            }
1996
        },
1997

    
1998
        get_params: function() {
1999
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get(), this.steps[4].get());
2000
        }
2001
    });
2002
    
2003
})(this);
2004