Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (66 kB)

1
// Copyright 2011 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

    
53
    views.VMCreationPasswordView = views.Overlay.extend({
54
        view_id: "creation_password_view",
55
        content_selector: "#creation-password-overlay",
56
        css_class: 'overlay-password overlay-info',
57
        overlay_id: "creation-password-overlay",
58

    
59
        subtitle: "",
60
        title: "Machine password",
61

    
62
        initialize: function(options) {
63
            views.FeedbackView.__super__.initialize.apply(this, arguments);
64
            _.bindAll(this, 'show_password');
65

    
66
            this.password = this.$("#new-machine-password");
67
            this.copy = this.$(".clipboard");
68

    
69
            this.$(".show-machine").click(_.bind(function(){
70
                if (this.$(".show-machine").hasClass("in-progress")) {
71
                    return;
72
                }
73
                this.hide();
74
            }, this));
75

    
76
            _.bindAll(this, "handle_vm_added");
77
            storage.vms.bind("add", this.handle_vm_added);
78
            this.password.text("");
79
        },
80

    
81
        handle_vm_added: function() {
82
            this.$(".show-machine").removeClass("in-progress");
83
        },
84
        
85
        show_password: function() {
86
            this.$(".show-machine").addClass("in-progress");
87
            this.password.text(this.pass);
88
            if (storage.vms.get(this.vm_id)) {
89
                this.$(".show-machine").removeClass("in-progress");
90
            }
91
            
92
            this.clip = new snf.util.ClipHelper(this.copy, this.pass);
93
        },
94

    
95
        onClose: function() {
96
            this.password.text("");
97
            this.vm_id = undefined;
98
            try { delete this.clip; } catch (err) {};
99
        },
100
        
101
        beforeOpen: function() {
102
            this.copy.empty();
103
        },
104
        
105
        onOpen: function() {
106
            this.show_password();
107
        },
108

    
109
        show: function(pass, vm_id) {
110
            this.pass = pass;
111
            this.vm_id = vm_id;
112
            
113
            views.VMCreationPasswordView.__super__.show.apply(this, arguments);
114
        }
115
    })
116

    
117

    
118
    
119
    views.CreateVMStepView = views.View.extend({
120
        step: "1",
121
        title: "Image",
122
        submit: false,
123

    
124
        initialize: function(view) {
125
            this.parent = view;
126
            this.el = view.$("div.create-step-cont.step-" + this.step);
127
            this.header = this.$(".step-header .step-" + this.step);
128
            this.view_id = "create_step_" + this.step;
129

    
130
            views.CreateVMStepView.__super__.initialize.apply(this);
131
        },
132

    
133
        show: function() {
134
            // show current
135
            this.el.show();
136
            this.header.addClass("current");
137
            this.header.show();
138
            this.update_layout();
139
        },
140

    
141
        reset: function() {
142
        }
143
    })
144

    
145
    views.CreateImageSelectView = views.CreateVMStepView.extend({
146

    
147
        initialize: function() {
148
            views.CreateImageSelectView.__super__.initialize.apply(this, arguments);
149

    
150
            // elements
151
            this.images_list_cont = this.$(".images-list-cont");
152
            this.images_list = this.$(".images-list-cont ul");
153
            this.image_details = this.$(".images-info-cont");
154
            this.image_details_desc = this.$(".images-info-cont .description p");
155
            this.image_details_title = this.$(".images-info-cont h4");
156
            this.image_details_size = this.$(".images-info-cont .size p");
157
            this.image_details_os = this.$(".images-info-cont .os p");
158
            this.image_details_kernel = this.$(".images-info-cont .kernel p");
159
            this.image_details_gui = this.$(".images-info-cont .gui p");
160
            this.image_details_vm = this.$(".images-info-cont .vm-name p");
161

    
162
            this.categories_list = this.$(".category-filters");
163
            
164
            // params initialization
165
            this.type_selections = {"system": "System"};
166
            this.type_selections_order = ['system'];
167
            
168
            this.images_storage = snf.storage.images;
169

    
170
            // apply image service specific image types
171
            if (this.images_storage.type_selections) {
172
                this.type_selections = _.extend(
173
                    this.images_storage.type_selections,
174
                    this.type_selections)
175

    
176
                this.type_selections_order = this.images_storage.type_selections_order;
177
            }
178

    
179
            this.selected_type = undefined;
180
            this.selected_categories = [];
181

    
182
            this.images = [];
183
            this.images_ids = [];
184
            this.custom_images = [];
185

    
186
            // handlers initialization
187
            this.create_types_selection_options();
188
            this.init_handlers();
189
            this.init_position();
190
        },
191
        
192
        init_position: function() {
193
            //this.el.css({position: "absolute"});
194
            //this.el.css({top:"10px"})
195
        },
196
        
197
        init_handlers: function() {
198
            var self = this;
199
            this.types.live("click", function() {
200
                self.select_type($(this).attr("id").replace("type-select-",""));
201
            });
202
            
203
            this.image_details.find(".hide").click(_.bind(function(){
204
                this.hide_image_details();
205
            }, this));
206

    
207
            this.$(".register-custom-image").live("click", function(){
208
                var confirm_close = true;
209
                if (confirm_close) {
210
                    snf.ui.main.custom_images_view.show(self.parent);
211
                } else {
212
                }
213
            })
214

    
215
            $(".image-warning .confirm").bind('click', function(){
216
                $(".image-warning").hide();
217
                $(".create-controls").show();
218
            })
219
        },
220

    
221
        update_images: function(images) {
222
            this.images = images;
223
            this.images_ids = _.map(this.images, function(img){return img.id});
224
            return this.images;
225
        },
226

    
227
        create_types_selection_options: function() {
228
            var list = this.$("ul.type-filter");
229
            _.each(this.type_selections_order, _.bind(function(key) {
230
                list.append('<li id="type-select-{0}">{1}</li>'.format(key, this.type_selections[key]));
231
            }, this));
232
            this.types = this.$(".type-filter li");
233
        },
234

    
235
        update_layout: function() {
236
            if (!this.selected_type) {
237
                this.selected_type = _.keys(this.type_selections)[0];
238
            }
239
            this.select_type(this.selected_type);
240
        },
241
        
242
        get_categories: function(images) {
243
            return [];
244
            return ["Desktop", "Server", "Linux", "Windows"];
245
        },
246

    
247
        reset_categories: function() {
248
            var categories = this.get_categories(this.images);
249
            this.categories_list.find("li").remove();
250

    
251
            _.each(categories, _.bind(function(cat) {
252
                var el = $("<li />");
253
                el.text(cat);
254
                this.categories_list.append(el);
255
            }, this));
256

    
257
            if (!categories.length) { 
258
                this.categories_list.parent().find(".clear").hide();
259
                this.categories_list.parent().find(".empty").show();
260
            } else {
261
                this.categories_list.parent().find(".clear").show();
262
                this.categories_list.parent().find(".empty").hide();
263
            }
264
        },
265
        
266
        show_loading_view: function() {
267
            this.$(".images-list-cont .empty").hide();
268
            this.images_list.hide();
269
            this.$(".images-list-cont .loading").show();
270
            this.$(".images-list-cont .images-list").hide();
271
            this.reset_categories();
272
            this.update_images([]);
273
            this.reset_images();
274
            this.hide_list_loading();
275
        },
276

    
277
        hide_loading_view: function(images) {
278
            this.$(".images-list-cont .loading").hide();
279
            this.$(".images-list-cont .images-list").show();
280
            this.reset_categories();
281
            this.update_images(images);
282
            this.reset_images();
283
            this.select_image(this.selected_image);
284
            this.hide_list_loading();
285
            $(".custom-image-help").hide();
286
            if (this.selected_type == 'personal' && !images.length) {
287
                $(".custom-image-help").show();
288
            }
289

    
290
        },
291

    
292
        select_type: function(type) {
293
            this.selected_type = type;
294
            this.types.removeClass("selected");
295
            this.types.filter("#type-select-" + this.selected_type).addClass("selected");
296
            this.images_storage.update_images_for_type(
297
                this.selected_type, 
298
                _.bind(this.show_loading_view, this), 
299
                _.bind(this.hide_loading_view, this)
300
            );
301

    
302
            this.update_layout_for_type(type);
303
        },
304

    
305
        update_layout_for_type: function(type) {
306
            if (type != "system") {
307
                this.$(".custom-action").hide();
308
            } else {
309
                this.$(".custom-action").hide();
310
            }
311

    
312
        },
313

    
314
        show_list_loading: function() {
315
            this.$(".images-list-cont").addClass("loading");
316
        },
317

    
318
        hide_list_loading: function() {
319
            this.$(".images-list-cont").removeClass("loading");
320
        },
321
        
322
        display_warning_for_image: function(image) {
323
          if (image && !image.is_system_image() && !image.owned_by(synnefo.user)) {
324
            $(".create-vm .image-warning").show();
325
            $(".create-controls").hide();
326
          } else {
327
            $(".create-vm .image-warning").hide();
328
            $(".create-controls").show();
329
          }
330
        },
331

    
332
        select_image: function(image) {
333
            if (image && image.get('id') && !_.include(this.images_ids, image.get('id'))) {
334
                image = undefined;
335
            }
336
            if (!image && this.images_ids.length) {
337
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
338
                    image = this.selected_image;
339
                } else {
340
                    image = this.images_storage.get(this.images_ids[0]);
341
                }
342
            }
343
             
344
            // no images select null image so that next button gets hidden
345
            if (!this.images_ids.length) { image = undefined };
346
            
347
            if ((!this.selected_image && image) || (this.selected_image != image))
348
                this.trigger("change", image);
349
                this.display_warning_for_image(image);
350

    
351
            this.selected_image = image;
352
                
353
            if (image) {
354
                this.images_list.find(".image-details").removeClass("selected");
355
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
356
                this.update_image_details(image);
357

    
358
            } else {
359
            }
360

    
361
            this.image_details.hide();
362
            this.validate();
363
        },
364

    
365
        update_image_details: function(image) {
366
            this.image_details_desc.hide().parent().hide();
367
            if (image.get_description()) {
368
                this.image_details_desc.html(image.get_description(false)).show().parent().show();
369
            }
370
            var img = snf.ui.helpers.os_icon_tag(image.escape("OS"))
371
            if (image.get("name")) {
372
                this.image_details_title.html(img + image.escape("name")).show().parent().show();
373
            }
374
            
375
            var extra_details = this.image_details.find(".extra-details");
376
            // clean prevously added extra details
377
            extra_details.find(".image-detail").remove();
378
            
379
            var skip_keys = ['description', 'sortorder']
380
            var meta_keys = ['owner', 'OS', 'kernel', 'GUI'];
381
            var detail_tpl = ('<div class="clearfix image-detail {2}">' +
382
                             '<span class="title clearfix">{0}' +
383
                             '<span class="custom">custom</span></span>' +
384
                             '<p class="value">{1}</p>' + 
385
                             '</div>');
386
            meta_keys = _.union(meta_keys, this.images_storage.display_metadata || []);
387
            
388
            var append_metadata_row = function(key, is_extra) {
389
                var value;
390
                var method = 'get_' + key.toLowerCase();
391
                var display_method = 'display_' + key.toLowerCase();
392
                 
393
                if (image[display_method]) {
394
                    value = image[display_method]();
395
                } else if (image[method]) {
396
                    value = image[method]();
397
                } else {
398
                    value = image.get(key);
399

    
400
                    if (!value) {
401
                        value = image.get_meta(key);
402
                    }
403
                }
404
                    
405
                if (!value) { return; }
406
                 
407
                var label = this.images_storage.meta_labels[key];
408
                if (!label) {
409
                    var label = _(key.replace(/_/g," ")).capitalize();
410
                }
411
                var row_cls = key.toLowerCase();
412
                if (is_extra) { row_cls += " extra-meta" };
413
                extra_details.append(detail_tpl.format(_.escape(label), value, row_cls));
414
            }
415

    
416
            _.each(meta_keys, function(key) {
417
                append_metadata_row.apply(this, [key]);
418
            }, this);
419
            
420
            if (synnefo.storage.images.display_extra_metadata) {
421
                _.each(image.get('metadata'), function(value, key) {
422
                    if (!_.contains(meta_keys, key) && 
423
                        !_.contains(meta_keys, key.toLowerCase()) &&
424
                        !_.contains(meta_keys, key.toUpperCase()) &&
425
                        !_.contains(skip_keys, key)) {
426
                            append_metadata_row.apply(this, [key, true]);
427
                    }
428
                }, this);
429
            }
430
        },
431

    
432
        reset_images: function() {
433
            this.images_list.find("li").remove();
434
            _.each(this.images, _.bind(function(img){
435
                this.add_image(img);
436
            }, this))
437
            
438
            if (this.images.length) {
439
                this.images_list.parent().find(".empty").hide();
440
                this.images_list.show();
441
            } else {
442
                this.images_list.parent().find(".empty").show();
443
                this.images_list.hide();
444
            }
445

    
446
            var self = this;
447
            this.images_list.find(".image-details").click(function(){
448
                self.select_image($(this).data("image"));
449
            });
450
            
451
        },
452

    
453
        show: function() {
454
            this.image_details.hide();
455
            this.parent.$(".create-controls").show();
456

    
457
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
458
        },
459

    
460
        add_image: function(img) {
461
            var image = $(('<li id="create-vm-image-{1}"' +
462
                           'class="image-details clearfix">{2}{0}'+
463
                           '<span class="show-details">details</span>'+
464
                           '<span class="size"><span class="prepend">by </span>{5}</span>' + 
465
                           '<span class="owner">' +
466
                           '<span class="prepend"></span>' +
467
                           '{3}</span>' + 
468
                           '<p>{4}</p>' +
469
                           '</li>').format(img.escape("name"), 
470
                                                  img.id, 
471
                                                  snf.ui.helpers.os_icon_tag(img.escape("OS")),
472
                                                  _.escape(img.get_readable_size()),
473
                                                  util.truncate(img.get_description(false), 35),
474
                                                  _.escape(img.display_owner())));
475
            image.data("image", img);
476
            image.data("image_id", img.id);
477
            this.images_list.append(image);
478
            image.find(".show-details").click(_.bind(function(e){
479
                e.preventDefault();
480
                e.stopPropagation();
481
                this.show_image_details(img);
482
            }, this))
483
        },
484
            
485
        hide_image_details: function() {
486
            this.image_details.fadeOut(200);
487
            this.parent.$(".create-controls").show();
488
        },
489

    
490
        show_image_details: function(img) {
491
            this.parent.$(".create-controls").hide();
492
            this.update_image_details(img);
493
            this.image_details.fadeIn(100);
494
        },
495

    
496
        reset: function() {
497
            this.selected_image = false;
498
            this.select_type("system");
499
        },
500

    
501
        get: function() {
502
            return {'image': this.selected_image};
503
        },
504

    
505
        validate: function() {
506
            if (!this.selected_image) {
507
                this.parent.$(".form-action.next").hide();
508
            } else {
509
                this.parent.$(".form-action.next").show();
510
            }
511
        }
512
    });
513

    
514
    views.CreateFlavorSelectView = views.CreateVMStepView.extend({
515
        step: 2,
516
        initialize: function() {
517
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
518
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
519

    
520
            this.cpus = this.$(".flavors-cpu-list");
521
            this.disks = this.$(".flavors-disk-list");
522
            this.disk_templates = this.$(".flavors-disk-template-list");
523
            this.mems = this.$(".flavors-mem-list");
524

    
525
            this.predefined_flavors = SUGGESTED_FLAVORS;
526
            this.predefined_flavors_keys = _.keys(SUGGESTED_FLAVORS);
527
            this.predefined_flavors_keys = _.sortBy(this.predefined_flavors_keys, _.bind(function(k){
528
                var flv = this.predefined_flavors[k];
529
                return (flv.ram * flv.cpu * flv.disk);
530
            }, this));
531

    
532
            this.predefined = this.$(".predefined-list");
533
        },
534

    
535
        handle_image_change: function(data) {
536
            this.current_image = data;
537
            this.update_valid_predefined();
538
            this.current_flavor = undefined;
539
            this.update_flavors_data();
540
            this.update_predefined_flavors();
541
            this.reset_flavors();
542
            this.update_layout();
543
        },
544

    
545
        validate_selected_flavor: function() {
546
            if (!this.flavor_is_valid(this.current_flavor)) {
547
                this.select_valid_flavor();
548
            }
549
        },
550

    
551
        reset_flavors: function() {
552
            this.$(".flavor-opts-list .option").remove();
553
            this.create_flavors();
554
        },
555

    
556
        update_predefined_flavors: function() {
557
            this.predefined.find("li").remove();
558
            _.each(this.predefined_flavors_keys, _.bind(function(key) {
559
                var val = this.predefined_flavors[key];
560
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
561
                           '{1}</li>').format(key, _.escape(_(key).capitalize())));
562

    
563
                this.predefined.append(el);
564
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, val.disk_template, this.flavors)});
565
                el.click(_.bind(function() {
566
                    this.handle_predefined_click(el);
567
                }, this))
568
            }, this));
569
            this.update_valid_predefined();
570
        },
571

    
572
        handle_predefined_click: function(el) {
573
            if (el.hasClass("disabled")) { return };
574
            this.set_current(el.data("flavor"));
575
        },
576

    
577
        select_valid_flavor: function() {
578
            var found = false;
579
            var self = this;
580

    
581
            _.each(["cpu", "mem", "disk"], function(t) {
582
              var el = $(".flavor-options."+t);
583
              var all = el.find(".flavor-opts-list li").length;
584
              var disabled = el.find(".flavor-opts-list li.disabled").length;
585
              if (disabled >= all) {
586
                el.find("h4").addClass("error");
587
              } else {
588
                el.find("h4").removeClass("error");
589
              }
590
            })
591

    
592
            _.each(this.flavors, function(flv) {
593
                if (self.flavor_is_valid(flv)) {
594
                    found = flv;
595
                    return false;
596
                }
597
            });
598
            
599
            if (found) {
600
                this.set_current(found);
601
            } else {
602
                this.current_flavor = undefined;
603
                this.validate();
604
                this.$("li.predefined-selection").addClass("disabled");
605
                this.$(".flavor-opts-list li").removeClass("selected");
606
            }
607
        },
608

    
609
        update_valid_predefined: function() {
610
            this.update_unavailable_values();
611
            var self = this;
612
            this.valid_predefined = _.select(_.map(this.predefined_flavors, function(flv, key){
613
                var existing = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
614
                // non existing
615
                if (!existing) {
616
                    return false;
617
                }
618
                
619
                // not available for image
620
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(
621
                    existing.get("disk")) > -1) {
622
                      return false
623
                }
624

    
625
                return key;
626
            }), function(ret) { return ret });
627
            
628
            $("li.predefined-selection").addClass("disabled");
629
            _.each(this.valid_predefined, function(key) {
630
                $("#predefined-flavor-" + key).removeClass("disabled");
631
            })
632
        },
633

    
634
        update_selected_predefined: function() {
635
            var self = this;
636
            this.predefined.find("li").removeClass("selected");
637

    
638
            _.each(this.valid_predefined, function(key){
639
                var flv = self.predefined_flavors[key];
640
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
641

    
642
                if (exists && (exists.id == self.current_flavor.id)) {
643
                    $("#predefined-flavor-" + key).addClass("selected");
644
                }
645
            })
646
        },
647
        
648
        update_flavors_data: function() {
649
            this.flavors = storage.flavors.active();
650
            this.flavors_data = storage.flavors.get_data(this.flavors);
651
            
652
            var self = this;
653
            var set = false;
654
            
655
            // FIXME: validate current flavor
656
            
657
            if (!this.current_flavor) {
658
                _.each(this.valid_predefined, function(key) {
659
                    var flv = self.predefined_flavors[key];
660
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
661
                    if (exists && !set) {
662
                        self.set_current(exists);
663
                        set = true;
664
                    }
665
                })
666
            }
667

    
668
            this.update_unavailable_values();
669
        },
670

    
671
        update_unavailable_values: function() {
672
            
673
            var unavailable = {disk:[], ram:[], cpu:[]}
674
            var user_excluded = {disk:[], ram:[], cpu:[]}
675
            var image_excluded = {disk:[], ram:[], cpu:[]}
676

    
677
            if (this.current_image) {
678
              image_excluded = storage.flavors.unavailable_values_for_image(this.current_image);
679
            }
680

    
681
            var quotas = synnefo.storage.quotas.get_available_for_vm({active: true});
682
            var user_excluded = storage.flavors.unavailable_values_for_quotas(quotas);
683

    
684
            unavailable.disk = user_excluded.disk.concat(image_excluded.disk);
685
            unavailable.ram = user_excluded.ram.concat(image_excluded.ram);
686
            unavailable.cpu = user_excluded.cpu.concat(image_excluded.cpu);
687
            
688
            this.unavailable_values = unavailable;
689
        },
690
        
691
        flavor_is_valid: function(flv) {
692
            if (!flv) { return false };
693

    
694
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
695
            if (!existing) { return false };
696
            
697
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk"))) > -1)) {
698
                return false;
699
            }
700
            if (this.unavailable_values && (this.unavailable_values.ram.indexOf(parseInt(flv.get("ram"))) > -1)) {
701
                return false;
702
            }
703
            if (this.unavailable_values && (this.unavailable_values.cpu.indexOf(parseInt(flv.get("cpu"))) > -1)) {
704
                return false;
705
            }
706
            return true;
707
        },
708
            
709
        set_valid_current_for: function(t, val) {
710
            var found = this.flavors[0];
711
            _.each(this.flavors, function(flv) {
712
                if (flv.get(t) == val) {
713
                    found = flv;
714
                }
715
            });
716

    
717
            this.set_current(found);
718
            this.validate_selected_flavor();
719
        },
720

    
721
        set_current: function(flv) {
722

    
723
            if (!flv) {
724
                // user clicked on invalid combination
725
                // force the first available choice for the
726
                // type of option he last clicked
727
                this.set_valid_current_for.apply(this, this.last_choice);
728
                return;
729
            }
730

    
731
            this.current_flavor = flv;
732
            this.trigger("change");
733
            if (this.current_flavor) {
734
                this.update_selected_flavor();
735
                this.update_selected_predefined();
736
            }
737
            
738
            this.validate();
739
        },
740
        
741
        select_default_flavor: function() {
742
               
743
        },
744

    
745
        update_selected_from_ui: function() {
746
            this.set_current(this.ui_selected());
747
        },
748
        
749
        update_disabled_flavors: function() {
750
            this.$(".flavor-options.disk li").removeClass("disabled");
751
            if (!this.unavailable_values) { return }
752
            
753
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
754
                var el_value = $(el).data("value");
755
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
756
                    $(el).addClass("disabled");
757
                    $(el).removeClass("selected");
758
                };
759
            }, this));
760

    
761
            this.$("#create-vm-flavor-options .flavor-options.mem li").each(_.bind(function(i, el){
762
                var el_value = $(el).data("value");
763
                if (this.unavailable_values.ram.indexOf(el_value) > -1) {
764
                    $(el).addClass("disabled");
765
                    $(el).removeClass("selected");
766
                };
767
            }, this));
768

    
769
            this.$("#create-vm-flavor-options .flavor-options.cpu li").each(_.bind(function(i, el){
770
                var el_value = $(el).data("value");
771
                if (this.unavailable_values.cpu.indexOf(el_value) > -1) {
772
                    $(el).addClass("disabled");
773
                    $(el).removeClass("selected");
774
                };
775
            }, this));
776
        },
777

    
778
        create_flavors: function() {
779
            var flavors = this.get_active_flavors();
780
            var valid_flavors = this.get_valid_flavors();
781
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
782

    
783
            _.each(flavors, _.bind(function(flv){
784
                this.add_flavor(flv);
785
            }, this));
786
            
787
            this.sort_flavors(this.disks);
788
            this.sort_flavors(this.cpus);
789
            this.sort_flavors(this.mems);
790
            this.sort_flavors(this.disk_templates);
791

    
792
            var self = this;
793
            this.$(".flavor-options li.option").click(function(){
794
                var el = $(this);
795

    
796
                if (el.hasClass("disabled")) { return }
797

    
798
                el.parent().find(".option").removeClass("selected");
799
                el.addClass("selected");
800

    
801
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
802
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
803
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
804
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
805

    
806
                self.update_selected_from_ui();
807
            });
808

    
809
            $(".flavor-opts-list").each(function(){
810
              var el = $(this);
811
              if (el.find(".option").length > 6) {
812
                el.addClass("compact");
813
              }
814
            });
815
        },
816

    
817
        sort_flavors: function(els) {
818
            var prev = undefined;
819
            els.find("li").each(function(i,el){
820
                el = $(el);
821
                if (!prev) { prev = el; return true };
822
                if (el.data("value") < prev.data("value")) {
823
                    prev.before(el);
824
                }
825
                prev = el;
826
            })
827
        },
828
        
829
        ui_selected: function() {
830
            var args = [this.$(".option.cpu.selected").data("value"), 
831
                this.$(".option.mem.selected").data("value"), 
832
                this.$(".option.disk.selected").data("value"),
833
                this.$(".option.disk_template.selected").data("value"),
834
            this.flavors];
835
            
836
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
837
            return flv;
838
        },
839

    
840
        update_selected_flavor: function() {
841
            var flv = this.current_flavor;
842
            if (!flv) { return }
843
            this.$(".option").removeClass("selected");
844

    
845
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
846
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
847
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
848
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
849
            
850
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
851
            var basebgpos = 470;
852
                
853
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
854
            var bg_pos = basebgpos - append_to_bg_pos;
855

    
856
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
857
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
858
        },
859
        
860
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
861
        add_flavor: function(flv) {
862
            var values = {'cpu': flv.get('cpu'), 
863
                          'mem': flv.get('ram'), 
864
                          'disk': flv.get('disk'), 
865
                          'disk_template': flv.get('disk_template')};
866

    
867
            disabled = "";
868
            
869
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
870
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
871
                             '<span class="value">{0}</span>' + 
872
                             '<span class="metric">x</span></li>').format(
873
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
874
                this.cpus.append(cpu);
875
                this.__added_flavors.cpu.push(values.cpu);
876
            }
877

    
878
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
879
                var mem_value = parseInt(_.escape(values.mem))*1024*1024;
880
                var displayvalue = synnefo.util.readablizeBytes(mem_value, 
881
                                                               0).split(" ");
882
                var mem = $(('<li class="option mem value-{2}">' + 
883
                             '<span class="value">{0}</span>' + 
884
                             '<span class="metric">{1}</span></li>').format(
885
                          displayvalue[0], displayvalue[1], values.mem)).data(
886
                          'value', values.mem);
887
                this.mems.append(mem);
888
                this.__added_flavors.ram.push(values.mem);
889
            }
890

    
891
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
892
                var disk = $(('<li class="option disk value-{0}">' + 
893
                              '<span class="value">{0}</span>' + 
894
                              '<span class="metric">GB</span></li>').format(
895
                            _.escape(values.disk))).data('value', values.disk);
896
                this.disks.append(disk);
897
                this.__added_flavors.disk.push(values.disk)
898
            }
899
            
900
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
901
                var template_info = flv.get_disk_template_info();
902
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
903
                                       '<span class="value name">{1}</span>' +
904
                                       '</li>').format(values.disk_template, 
905
                                            _.escape(template_info.name), 
906
                                            template_info.description)).data('value', 
907
                                                                values.disk_template);
908

    
909
                this.disk_templates.append(disk_template);
910
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
911
                this.__added_flavors.disk_template.push(values.disk_template)
912
            }
913
            
914
        },
915
        
916
        get_active_flavors: function() {
917
            return storage.flavors.active();
918
        },
919

    
920
        get_valid_flavors: function() {
921
            return this.flavors;
922
        },
923

    
924
        update_layout: function() {
925
            this.update_selected_flavor();
926
            this.update_disabled_flavors();
927
            this.validate();
928
            this.validate_selected_flavor();
929
            this.update_quota_display();
930
        },
931
        
932
        update_quota_display: function() {
933

    
934
          var quotas = synnefo.storage.quotas;
935
          _.each(["disk", "ram", "cpu"], function(type) {
936
            var active = true;
937
            var key = 'available';
938
            var available_dsp = quotas.get('cyclades.'+type).get_readable(key, active);
939
            var available = quotas.get('cyclades.'+type).get_available(key);
940
            var content = "({0} left)".format(available_dsp);
941
            if (available <= 0) { content = "(None left)" }
942
            
943
            if (type == "ram") { type = "mem" }
944
            $(".flavor-options."+type+" h4 .available").text(content);
945
            if (available <= 0) {
946
              $(".flavor-options."+type+" h4 .available").addClass("error");
947
            } else {
948
              $(".flavor-options."+type+" h4 .available").removeClass("error");
949
            }
950
          })
951
        },
952

    
953
        reset: function() {
954
            this.current_image = storage.images.at(0);
955
            this.flavors = [];
956
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
957
            this.update_flavors_data();
958
        },
959

    
960
        validate: function() {
961
            if (!this.current_flavor) {
962
                this.parent.$(".form-action.next").hide();
963
            } else {
964
                this.parent.$(".form-action.next").show();
965
            }
966
        },
967

    
968
        get: function() {
969
            return {'flavor': this.current_flavor}
970
        }
971
    });
972
    
973

    
974
    views.CreateColumnSelectOptionView = bb.View.extend({
975
        tagName: 'li',
976
        el: undefined,
977
        model: undefined,
978
        id_prefix: 'model-',
979
        tpl: '<input type="checkbox" class="check"/><span class="title"></span>',
980
        className: 'list-item-option clearfix',
981
        events: {
982
          'click': 'handle_click'
983
        },
984

    
985
        initialize: function(options) {
986
          _.bindAll(this);
987
          this.model.bind("change", this.render);
988
          this.model.bind("remove", this.remove);
989
          this.selected = false;
990
          if (options.get_model_title) {
991
            this.get_model_title = _.bind(options.get_model_title, this);
992
          }
993
          this.model_title_attr = options.model_title_attr;
994
          $(this.el).append($(this.tpl));
995
        },
996
        
997
        id: function() {
998
          return this.id_prefix + this.model && this.model.id || '';
999
        },
1000
        
1001
        handle_click: function() {
1002
          this.selected = !this.selected;
1003
          this.render();
1004
        },
1005

    
1006
        remove: function() {
1007
          this.model.unbind("change", this.render);
1008
          this.model.unbind("remove", this.remove);
1009
        },
1010
        
1011
        get_model_title: function() {
1012
          return this.model.get(this.model_title_attr || 'id');
1013
        },
1014

    
1015
        render: function() {
1016
          $(this.el).find(".title").text(this.get_model_title());
1017
          $(this.el).toggleClass('selected', this.selected);
1018
          if (this.selected) {
1019
            $(this.el).find("input").attr("checked", true);
1020
          } else {
1021
            $(this.el).find("input").attr("checked", false);
1022
          }
1023
        }
1024
    });
1025
    
1026
    views.CreateColumnIPOptionView = views.CreateColumnSelectOptionView.extend({
1027
      get_model_title: function() {
1028
        return this.model.get('ip');
1029
      }
1030
    });
1031

    
1032
    views.CreateColumnPrivateNetworkOptionView = views.CreateColumnSelectOptionView.extend({
1033
      get_model_title: function() {
1034
        return this.model.get('name');
1035
      }
1036
    });
1037

    
1038
    views.CreateColumnSelectListView = bb.View.extend({
1039
        collection: undefined,
1040
        header: undefined,
1041
        tagName: 'div',
1042
        extra_class: '',
1043
        el: undefined,
1044
        title_tpl: undefined,
1045
        title: 'List view',
1046
        description: 'List view description.',
1047
        empty_msg: 'No entries.',
1048
        item_cls: views.CreateColumnSelectOptionView,
1049
        className: 'list-cont create-column-select personalize-cont',
1050

    
1051
        initialize: function(options) {
1052
          _.bindAll(this);
1053
          if (options.extra_class) {
1054
            $(this.el).addClass(options.extra_class);
1055
          }
1056
          this.update_collection = options.update_collection;
1057
          this.title = options.title || this.title;
1058
          this.titple_tpl = options.title_tpl || this.title_tpl;
1059
          this.description = options.description || this.description;
1060
          this.empty_msg = options.empty_msg || this.empty_msg;
1061
          this.item_cls = options.item_cls || this.item_cls;
1062
          this.select_first_as_default = options.select_first_as_default;
1063
          this.filter_items = options.filter_items;
1064
          this.post_render_entries = options.post_render_entries || function() {};
1065
          this.init_events = options.init_events || function() {};
1066

    
1067
          this.init_events = _.bind(this.init_events, this);
1068
          this.post_render_entries = _.bind(this.post_render_entries, this);
1069

    
1070
          this._ul = $('<ul class="confirm-params">');
1071
          this._title = $("<h4>");
1072
          this._description = $("<p class='desc'>");
1073
          this._empty = $("<p class='empty hidden desc'>");
1074
          this._empty.html(this.empty_msg);
1075
        
1076
          this.item_views = [];
1077

    
1078
          $(this.el).append(this._title);
1079
          $(this.el).append(this._description);
1080
          $(this.el).append(this._empty);
1081
          $(this.el).append(this._ul);
1082

    
1083
          this['$el'] = $(this.el);
1084

    
1085
          if (!this.title_tpl) { this.title_tpl = this.title };
1086

    
1087
          this.collection.bind("change", this.render_entries);
1088
          this.collection.bind("reset", this.render_entries);
1089
          this.collection.bind("add", this.render_entries);
1090
          this.collection.bind("remove", this.remove_entry);
1091
          
1092
          this.fetcher = undefined;
1093
          if (this.update_collection) {
1094
              this.fetcher_params = [snf.config.update_interval, 
1095
                    snf.config.update_interval_increase || 500,
1096
                    snf.config.fast_interval || snf.config.update_interval/2, 
1097
                    snf.config.update_interval_increase_after_calls || 4,
1098
                    snf.config.update_interval_max || 20000,
1099
                    true, 
1100
                    {is_recurrent: true, update: true}];
1101
              this.fetcher = this.collection.get_fetcher.apply(this.collection, 
1102
                                                _.clone(this.fetcher_params));
1103
              this.fetcher.start();
1104
          }
1105
          this.render();
1106
          this.init_events();
1107
        },
1108
        
1109
        render: function() {
1110
          this._title.html(this.title_tpl);
1111
          this._description.html(this.description);
1112
          this.render_entries();
1113
        },
1114
        
1115
        remove_entry: function(model) {
1116
          if (!this.item_views[model.id]) { return }
1117
          this.item_views[model.id].remove();
1118
          delete this.item_views[model.pk]
1119
        },
1120
        
1121
        get_selected: function() {
1122
          return _.map(_.filter(this.item_views, function(v) { 
1123
            return v.selected
1124
          }), function(v) {
1125
            return v.model
1126
          });
1127
        },
1128
        
1129
        check_empty: function() {
1130
          if (this.item_views.length == 0) {
1131
            this._empty.show();
1132
          } else {
1133
            this._empty.hide();
1134
          }
1135
        },
1136

    
1137
        render_entries: function() {
1138
          var entries;
1139
          if (this.filter_items) {
1140
            entries = this.collection.filter(this.filter_items);
1141
          } else {
1142
            entries = this.collection.models;
1143
          }
1144
          
1145
          var selected = this.get_selected();
1146
          
1147
          _.each(entries, _.bind(function(model) {
1148
            if (this.item_views[model.id]) {
1149
              this.item_views[model.id].render();
1150
            } else {
1151
              var view = new this.item_cls({model:model});
1152
              if (!selected.length && this.select_first_as_default) { 
1153
                view.selected = true; selected = [1] 
1154
              }
1155
              view.render();
1156
              this.item_views[model.id] = view;
1157
              this._ul.append($(view.el));
1158
            }
1159
          }, this));
1160
          this.check_empty();
1161
          this.post_render_entries();
1162
        },
1163

    
1164
        remove: function() {
1165
          _.each(this.item_views, function(v){
1166
            v.remove();
1167
          });
1168
          if (this.update_collection) { this.fetcher.stop() }
1169
          this.unbind();
1170
          this.collection.unbind("change", this.render_entries);
1171
          this.collection.unbind("reset", this.render_entries);
1172
          this.collection.unbind("add", this.render_entries);
1173
          this.collection.unbind("remove", this.remove_entry);
1174
          views.CreateColumnSelectListView.__super__.remove.apply(this, arguments);
1175
        }
1176
    });
1177

    
1178
    views.CreateNetworkingView = views.CreateVMStepView.extend({
1179
        step: 3,
1180
        initialize: function() {
1181
            views.CreateNetworkingView.__super__.initialize.apply(this, arguments);
1182
            this.init_handlers();
1183
            this.selected_keys = [];
1184
            this.cont = this.$(".step-cont");
1185
        },
1186
        
1187
        init_subviews: function() {
1188
            var create_view = this.parent;
1189
            if (!this.networks_view) {
1190
              this.networks_view = new views.NetworkSelectView({
1191
                container: this.cont
1192
              });
1193
              this.networks_view.hide(true);
1194
            }
1195
        },
1196

    
1197
        init_handlers: function() {
1198
        },
1199

    
1200
        show: function() {
1201
            views.CreateNetworkingView.__super__.show.apply(this, arguments);
1202
            this.init_subviews();
1203
            this.update_layout();
1204
            this.networks_view.show(true);
1205
        },
1206
        
1207
        hide_step: function() {
1208
            this.networks_view && this.networks_view.hide(true);
1209
        },
1210

    
1211
        update_layout: function() {
1212
        },
1213

    
1214
        reset: function() {
1215
            this.selected_keys = [];
1216
            this.update_layout();
1217
        },
1218
        
1219
        get_selected_networks: function() {
1220
            if (!this.networks_view) { return [] }
1221
            return this.networks_view.get_selected_networks();
1222
        },
1223
        
1224
        get_selected_addresses: function() {
1225
            if (!this.networks_view) { return [] }
1226
            return this.networks_view.get_selected_floating_ips();
1227
        },
1228

    
1229
        get: function() {
1230
            return {
1231
              'addresses': this.get_selected_addresses(),
1232
              'networks': this.get_selected_networks()
1233
            }
1234
        },
1235

    
1236
        remove: function() {
1237
          if (this.networks_view) {
1238
            this.networks_view.remove();
1239
            delete this.networks_view;
1240
          }
1241
        }
1242
    });
1243

    
1244
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
1245
        step: 4,
1246
        initialize: function() {
1247
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1248
            this.roles = this.$("li.predefined-meta.role .values");
1249
            this.name = this.$("input.rename-field");
1250
            this.name_changed = false;
1251
            this.init_suggested_roles();
1252
            this.init_handlers();
1253
            this.ssh_list = this.$(".ssh ul");
1254
            this.selected_keys = [];
1255

    
1256
            var self = this;
1257
            this.$(".create-ssh-key").click(function() {
1258
                var confirm_close = true;
1259
                if (confirm_close) {
1260
                    snf.ui.main.public_keys_view.show(self.parent);
1261
                } else {
1262
                }
1263
            });
1264
        },
1265

    
1266
        init_suggested_roles: function() {
1267
            var cont = this.roles;
1268
            cont.empty();
1269
            
1270
            // TODO: get suggested from snf.api.conf
1271
            _.each(window.SUGGESTED_ROLES, function(r){
1272
                var el = $('<span class="val">{0}</span>'.format(_.escape(r)));
1273
                el.data("value", r);
1274
                cont.append(el);
1275
                el.click(function() {
1276
                    $(this).parent().find(".val").removeClass("selected");
1277
                    $(this).toggleClass("selected");
1278
                })
1279
            });
1280
            
1281
            var self = this;
1282
            $(".ssh li.ssh-key-option").live("click", function(e) {
1283
                var key = $(this).data("model");
1284
                self.select_key(key);
1285
            });
1286
        },
1287

    
1288
        select_key: function(key) {
1289
            var exists = this.selected_keys.indexOf(key.id);
1290
            if (exists > -1) {
1291
                this.selected_keys.splice(exists, 1);
1292
            } else {
1293
                this.selected_keys.push(key.id);
1294
            }
1295
            this.update_ui_keys_selections(this.selected_keys);
1296
        },
1297

    
1298
        update_ui_keys_selections: function(keys) {
1299
            var self = this;
1300
            self.$(".ssh-key-option").removeClass("selected");
1301
            self.$(".ssh-key-option .check").attr("checked", false);
1302
            _.each(keys, function(kid) {
1303
                $("#ssh-key-option-" + kid).addClass("selected");
1304
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
1305
            });
1306
        },
1307

    
1308
        update_ssh_keys: function() {
1309
            this.ssh_list.empty();
1310
            var keys = snf.storage.keys.models;
1311
            if (keys.length == 0) { 
1312
                this.$(".ssh .empty").show();
1313
            } else {
1314
                this.$(".ssh .empty").hide();
1315
            }
1316
            _.each(keys, _.bind(function(key){
1317
                var name = _.escape(util.truncate(key.get("name"), 45));
1318
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(name, key.id));
1319
                var check = $('<input class="check" type="checkbox"></input>')
1320
                el.append(check);
1321
                el.data("model", key);
1322
                this.ssh_list.append(el);
1323
            }, this));
1324
        },
1325

    
1326
        init_handlers: function() {
1327
            this.name.bind("keypress", _.bind(function(e) {
1328
                this.name_changed = true;
1329
                if (e.keyCode == 13) { this.parent.set_step(5); this.parent.update_layout() };    
1330
            }, this));
1331

    
1332
            this.name.bind("click", _.bind(function() {
1333
                if (!this.name_changed) {
1334
                    this.name.val("");
1335
                }
1336
            }, this))
1337
        },
1338

    
1339
        show: function() {
1340
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
1341
            this.update_layout();
1342
        },
1343
        
1344
        update_layout: function() {
1345
            var params = this.parent.get_params();
1346

    
1347
            if (!params.image || !params.flavor) { return }
1348

    
1349
            if (!params.image) { return }
1350
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
1351
            //if (params.image.is_snapshot()) { vm_name_tpl = "{0}" };
1352
            var vm_name = vm_name_tpl.format(params.image.get("name"));
1353
            var orig_name = vm_name;
1354
            
1355
            var existing = true;
1356
            var j = 0;
1357

    
1358
            while (existing && !this.name_changed) {
1359
                var existing = storage.vms.select(function(vm){
1360
                  return vm.get("name") == vm_name
1361
                }).length;
1362
                if (existing) {
1363
                    j++;
1364
                    vm_name = orig_name + " " + j;
1365
                }
1366
            }
1367

    
1368
            if (!_(this.name.val()).trim() || !this.name_changed) {
1369
                this.name.val(vm_name);
1370
            }
1371

    
1372
            if (!this.name_changed && this.parent.visible()) {
1373
                if (!$.browser.msie && !$.browser.opera) {
1374
                    this.$("#create-vm-name").select();
1375
                } else {
1376
                    window.setTimeout(_.bind(function(){
1377
                        this.$("#create-vm-name").select();
1378
                    }, this), 400)
1379
                }
1380
            }
1381
            
1382
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1383
            this.name.css({backgroundImage:"url({0})".format(img)})
1384
            
1385
            if (!params.image.supports('ssh')) {
1386
                this.disable_ssh_keys();
1387
            } else {
1388
                this.enable_ssh_keys();
1389
                this.update_ssh_keys();
1390
            }
1391

    
1392
            this.update_ui_keys_selections(this.selected_keys);
1393
        },
1394

    
1395
        disable_ssh_keys: function() {
1396
            this.$(".disabled.desc").show();
1397
            this.$(".empty.desc").hide();
1398
            this.$(".ssh .confirm-params").hide();
1399
            this.selected_keys = [];
1400
        },
1401

    
1402
        enable_ssh_keys: function() {
1403
            this.$(".ssh .confirm-params").show();
1404
            this.$(".disabled.desc").hide();
1405
        },
1406

    
1407
        reset: function() {
1408
            this.roles.find(".val").removeClass("selected");
1409
            this.name_changed = false;
1410
            this.selected_keys = [];
1411
            this.update_layout();
1412
        },
1413

    
1414
        get_meta: function() {
1415
            if (this.roles.find(".selected").length == 0) {
1416
                return false;
1417
            }
1418

    
1419
            var role = $(this.roles.find(".selected").get(0)).data("value");
1420
            return {'Role': role }
1421
        },
1422

    
1423
        get: function() {
1424
            var val = {'name': this.name.val() };
1425
            if (this.get_meta()) {
1426
                val.metadata = this.get_meta();
1427
            }
1428

    
1429
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1430
            
1431
            return val;
1432
        }
1433
    });
1434

    
1435
    views.CreateSubmitView = views.CreateVMStepView.extend({
1436
        step: 5,
1437
        initialize: function() {
1438
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1439
            this.roles = this.$("li.predefined-meta.role .values");
1440
            this.confirm = this.$(".confirm-params ul");
1441
            this.name = this.$("h3.vm-name");
1442
            this.keys = this.$(".confirm-params.ssh");
1443
            this.meta = this.$(".confirm-params.meta");
1444
            this.ip_addresses = this.$(".confirm-params.ip-addresses");
1445
            this.private_networks = this.$(".confirm-params.private-networks");
1446
            this.init_handlers();
1447
        },
1448

    
1449
        init_handlers: function() {
1450
        },
1451

    
1452
        show: function() {
1453
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1454
            this.update_layout();
1455
        },
1456
        
1457
        update_network_details: function() {
1458
            var data = this.parent.get_params();
1459
            var ips = data.addresses;
1460
            var networks = data.networks;
1461

    
1462
            this.ip_addresses.empty();
1463
            if (!ips|| ips.length == 0) {
1464
                this.ip_addresses.append(this.make("li", {'class':'empty'}, 
1465
                                           'No ip addresses selected'))
1466
            }
1467
            _.each(ips, _.bind(function(ip) {
1468
                var el = this.make("li", {'class':'selected-ip-address'}, 
1469
                                  ip.get('floating_ip_address'));
1470
                this.ip_addresses.append(el);
1471
            }, this))
1472

    
1473
            this.private_networks.empty();
1474
            if (!networks || networks.length == 0) {
1475
                this.private_networks.append(this.make("li", {'class':'empty'}, 
1476
                                             'No private networks selected'))
1477
            }
1478
            _.each(networks, _.bind(function(network) {
1479
                var el = this.make("li", {'class':'selected-private-network'}, 
1480
                                  network.get('name'));
1481
                this.private_networks.append(el);
1482
            }, this))
1483

    
1484
        },
1485

    
1486
        update_flavor_details: function() {
1487
            var flavor = this.parent.get_params().flavor;
1488

    
1489
            function set_detail(sel, key) {
1490
                var val = key;
1491
                if (key == undefined) { val = flavor.get(sel) };
1492
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1493
            }
1494
            
1495
            set_detail("cpu", flavor.get("cpu") + "x");
1496
            set_detail("ram", flavor.get("ram") + " MB");
1497
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1498
            set_detail("disktype", flavor.get_disk_template_info().name);
1499
        },
1500

    
1501
        update_image_details: function() {
1502
            var image = this.parent.get_params().image;
1503

    
1504
            function set_detail(sel, key) {
1505
                var val = key;
1506
                if (key == undefined) { val = image.get(sel) };
1507
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1508
            }
1509
            
1510
            set_detail("description", image.get_description());
1511
            set_detail("name", util.truncate(image.get("name"), 30));
1512
            set_detail("os", _(image.get_os()).capitalize());
1513
            set_detail("gui", image.get_gui());
1514
            set_detail("size", _.escape(image.get_readable_size()));
1515
            set_detail("kernel");
1516
        },
1517

    
1518
        update_selected_keys: function(keys) {
1519
            this.keys.empty();
1520
            if (!keys || keys.length == 0) {
1521
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1522
            }
1523
            _.each(keys, _.bind(function(key) {
1524
                var name = _.escape(util.truncate(key.get("name"), 20))
1525
                var el = this.make("li", {'class':'selected-ssh-key'}, name);
1526
                this.keys.append(el);
1527
            }, this))
1528
        },
1529

    
1530
        update_selected_meta: function(meta) {
1531
            this.meta.empty();
1532
            if (!meta || meta.length == 0) {
1533
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1534
            }
1535
            _.each(meta, _.bind(function(value, key) {
1536
                var el = this.make("li", {'class':'confirm-value'});
1537
                var name = this.make("span", {'class':'ckey'}, key);
1538
                var value = this.make("span", {'class':'cval'}, value);
1539

    
1540
                $(el).append(name)
1541
                $(el).append(value);
1542
                this.meta.append(el);
1543
            }, this));
1544
        },
1545

    
1546
        update_layout: function() {
1547
            var params = this.parent.get_params();
1548
            if (!params.image || !params.flavor) { return }
1549

    
1550
            if (!params.image) { return }
1551

    
1552
            this.name.text(util.truncate(params.name, 50));
1553

    
1554
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1555
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1556
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1557
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1558

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

    
1562
            this.update_image_details();
1563
            this.update_flavor_details();
1564
            this.update_network_details();
1565

    
1566
            if (!params.image.supports('ssh')) {
1567
                this.keys.hide();
1568
                this.keys.prev().hide();
1569
            } else {
1570
                this.keys.show();
1571
                this.keys.prev().show();
1572
                this.update_selected_keys(params.keys);
1573
            }
1574
            
1575
            this.update_selected_meta(params.metadata);
1576
        },
1577

    
1578
        reset: function() {
1579
            this.update_layout();
1580
        },
1581

    
1582
        get_meta: function() {
1583
        },
1584

    
1585
        get: function() {
1586
            return {};
1587
        }
1588
    });
1589

    
1590
    views.CreateVMView = views.Overlay.extend({
1591
        
1592
        view_id: "create_vm_view",
1593
        content_selector: "#createvm-overlay-content",
1594
        css_class: 'overlay-createvm overlay-info',
1595
        overlay_id: "metadata-overlay",
1596

    
1597
        subtitle: false,
1598
        title: "Create new machine",
1599

    
1600
        initialize: function(options) {
1601
            views.CreateVMView.__super__.initialize.apply(this);
1602
            this.current_step = 1;
1603

    
1604
            this.password_view = new views.VMCreationPasswordView();
1605

    
1606
            this.steps = [];
1607
            this.steps[1] = new views.CreateImageSelectView(this);
1608
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1609

    
1610
            this.steps[2] = new views.CreateFlavorSelectView(this);
1611
            this.steps[3] = new views.CreateNetworkingView(this);
1612
            this.steps[4] = new views.CreatePersonalizeView(this);
1613
            this.steps[5] = new views.CreateSubmitView(this);
1614

    
1615
            this.cancel_btn = this.$(".create-controls .cancel");
1616
            this.next_btn = this.$(".create-controls .next");
1617
            this.prev_btn = this.$(".create-controls .prev");
1618
            this.submit_btn = this.$(".create-controls .submit");
1619

    
1620
            this.history = this.$(".steps-history");
1621
            this.history_steps = this.$(".steps-history .steps-history-step");
1622
            
1623
            this.init_handlers();
1624
        },
1625

    
1626
        init_handlers: function() {
1627
            var self = this;
1628
            this.next_btn.click(_.bind(function(){
1629
                this.set_step(this.current_step + 1);
1630
                this.update_layout();
1631
            }, this))
1632
            this.prev_btn.click(_.bind(function(){
1633
                this.set_step(this.current_step - 1);
1634
                this.update_layout();
1635
            }, this))
1636
            this.cancel_btn.click(_.bind(function(){
1637
                this.close_all();
1638
            }, this))
1639
            this.submit_btn.click(_.bind(function(){
1640
                this.submit();
1641
            }, this))
1642
            
1643
            this.history.find(".completed").live("click", function() {
1644
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1645
                self.set_step(step);
1646
                self.update_layout();
1647
            })
1648
        },
1649

    
1650
        set_step: function(st) {
1651
        },
1652
        
1653
        validate: function(data) {
1654
            if (_(data.name).trim() == "") {
1655
                this.$(".form-field").addClass("error");
1656
                return false;
1657
            } else {
1658
                return true;
1659
            }
1660
        },
1661

    
1662
        submit: function() {
1663
            if (this.submiting) { return };
1664
            var data = this.get_params();
1665
            var meta = {};
1666
            var extra = {};
1667
            var personality = [];
1668

    
1669
            if (this.validate(data)) {
1670
                this.submit_btn.addClass("in-progress");
1671
                this.submiting = true;
1672
                if (data.metadata) { meta = data.metadata; }
1673
                if (data.keys && data.keys.length > 0) {
1674
                    personality.push(
1675
                      data.image.personality_data_for_keys(data.keys))
1676
                }
1677

    
1678
                if (personality.length) {
1679
                    extra['personality'] = _.flatten(personality);
1680
                }
1681
                
1682
                extra['networks'] = [];
1683
                _.each(data.networks, function(n) {
1684
                  extra.networks.push({'uuid': n.get('id')})
1685
                });
1686
                _.each(data.addresses, function(ip) {
1687
                  extra.networks.push({
1688
                    'uuid': ip.get('network').get('id'),
1689
                    'fixed_ip': ip.get('floating_ip_address')
1690
                  });
1691
                });
1692

    
1693
                _.map(data.networks, function(n) { return n.get('id') });
1694
                storage.vms.create(data.name, data.image, data.flavor, 
1695
                                   meta, extra, _.bind(function(data){
1696
                    _.each(data.addresses, function(ip) {
1697
                      ip.set({'status': 'connecting'});
1698
                    });
1699
                    this.close_all();
1700
                    this.password_view.show(data.server.adminPass, 
1701
                                            data.server.id);
1702
                    var self = this;
1703
                    window.setTimeout(function() {
1704
                      self.submiting = false;
1705
                    }, 1000);
1706
                }, this));
1707
            }
1708
        },
1709

    
1710
        close_all: function() {
1711
          this.hide();
1712
        },
1713

    
1714
        onClose: function() {
1715
          this.steps[3].remove();
1716
        },
1717

    
1718
        reset: function() {
1719
          this.current_step = 1;
1720

    
1721
          this.steps[1].reset();
1722
          this.steps[2].reset();
1723
          this.steps[3].reset();
1724
          this.steps[4].reset();
1725

    
1726
          //this.steps[1].show();
1727
          //this.steps[2].show();
1728
          //this.steps[3].show();
1729
          //this.steps[4].show();
1730

    
1731
          this.submit_btn.removeClass("in-progress");
1732
        },
1733

    
1734
        onShow: function() {
1735
        },
1736

    
1737
        update_layout: function() {
1738
            this.show_step(this.current_step);
1739
            this.current_view.update_layout();
1740
        },
1741

    
1742
        beforeOpen: function() {
1743
            if (!this.skip_reset_on_next_open) {
1744
                this.submiting = false;
1745
                this.reset();
1746
                this.current_step = 1;
1747
                this.$(".steps-container").css({"margin-left":0 + "px"});
1748
                this.show_step(1);
1749
            }
1750
            
1751
            this.skip_reset_on_next_open = false;
1752
            this.update_layout();
1753
        },
1754
        
1755
        set_step: function(step) {
1756
            if (step <= 1) {
1757
                step = 1
1758
            }
1759
            if (step > this.steps.length - 1) {
1760
                step = this.steps.length - 1;
1761
            }
1762
            this.current_step = step;
1763
        },
1764

    
1765
        show_step: function(step) {
1766
            // FIXME: this shouldn't be here
1767
            // but since we are not calling step.hide this should work
1768
            this.steps[1].image_details.hide();
1769
            
1770
            this.current_view && this.current_view.hide_step && this.current_view.hide_step();
1771
            this.current_view = this.steps[step];
1772
            this.update_controls();
1773

    
1774
            this.steps[step].show();
1775
            var width = this.el.find('.container').width();
1776
            var left = (step -1) * width * -1;
1777
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1778

    
1779
            this.update_steps_history();
1780
        },
1781

    
1782
        update_steps_history: function() {
1783
            var self = this;
1784
            function get_step(s) {
1785
                return self.history.find(".step" + s + "h");
1786
            }
1787
            
1788
            var current_step = parseInt(this.current_view.step);
1789
            _.each(this.steps, function(stepv) {
1790
                var step = parseInt(stepv.step);
1791
                get_step(step).removeClass("completed").removeClass("current");
1792
                if (step == current_step) {
1793
                    get_step(step).removeClass("completed").addClass("current");
1794
                }
1795
                if (step < current_step) {
1796
                    get_step(step).removeClass("current").addClass("completed");
1797
                }
1798
            });
1799
        },
1800

    
1801
        update_controls: function() {
1802
            var step = this.current_step;
1803
            if (step == 1) {
1804
                this.prev_btn.hide();
1805
                this.cancel_btn.show();
1806
            } else {
1807
                this.prev_btn.show();
1808
                this.cancel_btn.hide();
1809
            }
1810
            
1811
            if (step == this.steps.length - 1) {
1812
                this.next_btn.hide();
1813
                this.submit_btn.show();
1814
            } else {
1815
                this.next_btn.show();
1816
                this.submit_btn.hide();
1817
            }
1818
        },
1819

    
1820
        get_params: function() {
1821
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get(), this.steps[4].get());
1822
        }
1823
    });
1824
    
1825
})(this);
1826