Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (66.5 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(
613
              _.map(this.predefined_flavors, function(flv, key){
614
                var existing = storage.flavors.get_flavor(flv.cpu, 
615
                                                          flv.ram, 
616
                                                          flv.disk, 
617
                                                          flv.disk_template, 
618
                                                          self.flavors);
619
                // non existing
620
                if (!existing) {
621
                    return false;
622
                }
623
                
624
                // not available for image
625
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(
626
                    existing.get("disk")) > -1) {
627
                      return false
628
                }
629
                
630
                // quota check
631
                var quotas = synnefo.storage.quotas.get_available_for_vm();
632
                var unavailable_check = 
633
                  synnefo.storage.flavors.unavailable_values_for_quotas;
634
                var unavailable = unavailable_check(quotas, [existing]);
635
                if ((_.filter(unavailable, function(values, flvkey) {
636
                  return values.length > 0
637
                })).length > 0) {
638
                  return false;
639
                }
640
                
641
                return key;
642
            }), function(ret) { return ret });
643
            
644
            $("li.predefined-selection").addClass("disabled");
645
            _.each(this.valid_predefined, function(key) {
646
                $("#predefined-flavor-" + key).removeClass("disabled");
647
            })
648
        },
649

    
650
        update_selected_predefined: function() {
651
            var self = this;
652
            this.predefined.find("li").removeClass("selected");
653

    
654
            _.each(this.valid_predefined, function(key){
655
                var flv = self.predefined_flavors[key];
656
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
657

    
658
                if (exists && (exists.id == self.current_flavor.id)) {
659
                    $("#predefined-flavor-" + key).addClass("selected");
660
                }
661
            })
662
        },
663
        
664
        update_flavors_data: function() {
665
            this.flavors = storage.flavors.active();
666
            this.flavors_data = storage.flavors.get_data(this.flavors);
667
            
668
            var self = this;
669
            var set = false;
670
            
671
            // FIXME: validate current flavor
672
            
673
            if (!this.current_flavor) {
674
                _.each(this.valid_predefined, function(key) {
675
                    var flv = self.predefined_flavors[key];
676
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, flv.disk_template, self.flavors);
677
                    if (exists && !set) {
678
                        self.set_current(exists);
679
                        set = true;
680
                    }
681
                })
682
            }
683

    
684
            this.update_unavailable_values();
685
        },
686

    
687
        update_unavailable_values: function() {
688
            
689
            var unavailable = {disk:[], ram:[], cpu:[]}
690
            var user_excluded = {disk:[], ram:[], cpu:[]}
691
            var image_excluded = {disk:[], ram:[], cpu:[]}
692

    
693
            if (this.current_image) {
694
              image_excluded = storage.flavors.unavailable_values_for_image(this.current_image);
695
            }
696

    
697
            var quotas = synnefo.storage.quotas.get_available_for_vm({active: true});
698
            var user_excluded = storage.flavors.unavailable_values_for_quotas(quotas);
699

    
700
            unavailable.disk = user_excluded.disk.concat(image_excluded.disk);
701
            unavailable.ram = user_excluded.ram.concat(image_excluded.ram);
702
            unavailable.cpu = user_excluded.cpu.concat(image_excluded.cpu);
703
            
704
            this.unavailable_values = unavailable;
705
        },
706
        
707
        flavor_is_valid: function(flv) {
708
            if (!flv) { return false };
709

    
710
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), flv.get("disk_template"), this.flavors);
711
            if (!existing) { return false };
712
            
713
            if (this.unavailable_values && (this.unavailable_values.disk.indexOf(parseInt(flv.get("disk"))) > -1)) {
714
                return false;
715
            }
716
            if (this.unavailable_values && (this.unavailable_values.ram.indexOf(parseInt(flv.get("ram"))) > -1)) {
717
                return false;
718
            }
719
            if (this.unavailable_values && (this.unavailable_values.cpu.indexOf(parseInt(flv.get("cpu"))) > -1)) {
720
                return false;
721
            }
722
            return true;
723
        },
724
            
725
        set_valid_current_for: function(t, val) {
726
            var found = this.flavors[0];
727
            _.each(this.flavors, function(flv) {
728
                if (flv.get(t) == val) {
729
                    found = flv;
730
                }
731
            });
732

    
733
            this.set_current(found);
734
            this.validate_selected_flavor();
735
        },
736

    
737
        set_current: function(flv) {
738

    
739
            if (!flv) {
740
                // user clicked on invalid combination
741
                // force the first available choice for the
742
                // type of option he last clicked
743
                this.set_valid_current_for.apply(this, this.last_choice);
744
                return;
745
            }
746

    
747
            this.current_flavor = flv;
748
            this.trigger("change");
749
            if (this.current_flavor) {
750
                this.update_selected_flavor();
751
                this.update_selected_predefined();
752
            }
753
            
754
            this.validate();
755
        },
756
        
757
        select_default_flavor: function() {
758
               
759
        },
760

    
761
        update_selected_from_ui: function() {
762
            this.set_current(this.ui_selected());
763
        },
764
        
765
        update_disabled_flavors: function() {
766
            this.$(".flavor-options.disk li").removeClass("disabled");
767
            if (!this.unavailable_values) { return }
768
            
769
            this.$("#create-vm-flavor-options .flavor-options.disk li").each(_.bind(function(i, el){
770
                var el_value = $(el).data("value");
771
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
772
                    $(el).addClass("disabled");
773
                    $(el).removeClass("selected");
774
                };
775
            }, this));
776

    
777
            this.$("#create-vm-flavor-options .flavor-options.mem li").each(_.bind(function(i, el){
778
                var el_value = $(el).data("value");
779
                if (this.unavailable_values.ram.indexOf(el_value) > -1) {
780
                    $(el).addClass("disabled");
781
                    $(el).removeClass("selected");
782
                };
783
            }, this));
784

    
785
            this.$("#create-vm-flavor-options .flavor-options.cpu li").each(_.bind(function(i, el){
786
                var el_value = $(el).data("value");
787
                if (this.unavailable_values.cpu.indexOf(el_value) > -1) {
788
                    $(el).addClass("disabled");
789
                    $(el).removeClass("selected");
790
                };
791
            }, this));
792
        },
793

    
794
        create_flavors: function() {
795
            var flavors = this.get_active_flavors();
796
            var valid_flavors = this.get_valid_flavors();
797
            this.__added_flavors = {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[] };
798

    
799
            _.each(flavors, _.bind(function(flv){
800
                this.add_flavor(flv);
801
            }, this));
802
            
803
            this.sort_flavors(this.disks);
804
            this.sort_flavors(this.cpus);
805
            this.sort_flavors(this.mems);
806
            this.sort_flavors(this.disk_templates);
807

    
808
            var self = this;
809
            this.$(".flavor-options li.option").click(function(){
810
                var el = $(this);
811

    
812
                if (el.hasClass("disabled")) { return }
813

    
814
                el.parent().find(".option").removeClass("selected");
815
                el.addClass("selected");
816

    
817
                if (el.hasClass("mem")) { self.last_choice = ["ram", $(this).data("value")] }
818
                if (el.hasClass("cpu")) { self.last_choice = ["cpu", $(this).data("value")] }
819
                if (el.hasClass("disk")) { self.last_choice = ["disk", $(this).data("value")] }
820
                if (el.hasClass("disk_template")) { self.last_choice = ["disk_template", $(this).data("value")] }
821

    
822
                self.update_selected_from_ui();
823
            });
824

    
825
            $(".flavor-opts-list").each(function(){
826
              var el = $(this);
827
              if (el.find(".option").length > 6) {
828
                el.addClass("compact");
829
              }
830
            });
831
        },
832

    
833
        sort_flavors: function(els) {
834
            var prev = undefined;
835
            els.find("li").each(function(i,el){
836
                el = $(el);
837
                if (!prev) { prev = el; return true };
838
                if (el.data("value") < prev.data("value")) {
839
                    prev.before(el);
840
                }
841
                prev = el;
842
            })
843
        },
844
        
845
        ui_selected: function() {
846
            var args = [this.$(".option.cpu.selected").data("value"), 
847
                this.$(".option.mem.selected").data("value"), 
848
                this.$(".option.disk.selected").data("value"),
849
                this.$(".option.disk_template.selected").data("value"),
850
            this.flavors];
851
            
852
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
853
            return flv;
854
        },
855

    
856
        update_selected_flavor: function() {
857
            var flv = this.current_flavor;
858
            if (!flv) { return }
859
            this.$(".option").removeClass("selected");
860

    
861
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
862
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
863
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
864
            this.$(".option.disk_template.value-" + flv.get("disk_template")).addClass("selected");
865
            
866
            var disk_el = this.$(".option.disk_template.value-" + flv.get("disk_template"));
867
            var basebgpos = 470;
868
                
869
            var append_to_bg_pos = 40 + (disk_el.index() * 91);
870
            var bg_pos = basebgpos - append_to_bg_pos;
871

    
872
            this.$(".disk-template-description").css({backgroundPosition:'-' + bg_pos + 'px top'})
873
            this.$(".disk-template-description p").html(flv.get_disk_template_info().description || "");
874
        },
875
        
876
        __added_flavors: {'cpu':[], 'ram':[], 'disk':[], 'disk_template':[]},
877
        add_flavor: function(flv) {
878
            var values = {'cpu': flv.get('cpu'), 
879
                          'mem': flv.get('ram'), 
880
                          'disk': flv.get('disk'), 
881
                          'disk_template': flv.get('disk_template')};
882

    
883
            disabled = "";
884
            
885
            if (this.__added_flavors.cpu.indexOf(values.cpu) == -1) {
886
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
887
                             '<span class="value">{0}</span>' + 
888
                             '<span class="metric">x</span></li>').format(
889
                            _.escape(values.cpu), disabled)).data('value', values.cpu);
890
                this.cpus.append(cpu);
891
                this.__added_flavors.cpu.push(values.cpu);
892
            }
893

    
894
            if (this.__added_flavors.ram.indexOf(values.mem) == -1) {
895
                var mem_value = parseInt(_.escape(values.mem))*1024*1024;
896
                var displayvalue = synnefo.util.readablizeBytes(mem_value, 
897
                                                               0).split(" ");
898
                var mem = $(('<li class="option mem value-{2}">' + 
899
                             '<span class="value">{0}</span>' + 
900
                             '<span class="metric">{1}</span></li>').format(
901
                          displayvalue[0], displayvalue[1], values.mem)).data(
902
                          'value', values.mem);
903
                this.mems.append(mem);
904
                this.__added_flavors.ram.push(values.mem);
905
            }
906

    
907
            if (this.__added_flavors.disk.indexOf(values.disk) == -1) {
908
                var disk = $(('<li class="option disk value-{0}">' + 
909
                              '<span class="value">{0}</span>' + 
910
                              '<span class="metric">GB</span></li>').format(
911
                            _.escape(values.disk))).data('value', values.disk);
912
                this.disks.append(disk);
913
                this.__added_flavors.disk.push(values.disk)
914
            }
915
            
916
            if (this.__added_flavors.disk_template.indexOf(values.disk_template) == -1) {
917
                var template_info = flv.get_disk_template_info();
918
                var disk_template = $(('<li title="{2}" class="option disk_template value-{0}">' + 
919
                                       '<span class="value name">{1}</span>' +
920
                                       '</li>').format(values.disk_template, 
921
                                            _.escape(template_info.name), 
922
                                            template_info.description)).data('value', 
923
                                                                values.disk_template);
924

    
925
                this.disk_templates.append(disk_template);
926
                //disk_template.tooltip({position:'top center', offset:[-5,0], delay:100, tipClass:'tooltip disktip'});
927
                this.__added_flavors.disk_template.push(values.disk_template)
928
            }
929
            
930
        },
931
        
932
        get_active_flavors: function() {
933
            return storage.flavors.active();
934
        },
935

    
936
        get_valid_flavors: function() {
937
            return this.flavors;
938
        },
939

    
940
        update_layout: function() {
941
            this.update_selected_flavor();
942
            this.update_disabled_flavors();
943
            this.validate();
944
            this.validate_selected_flavor();
945
            this.update_quota_display();
946
        },
947
        
948
        update_quota_display: function() {
949

    
950
          var quotas = synnefo.storage.quotas;
951
          _.each(["disk", "ram", "cpu"], function(type) {
952
            var active = true;
953
            var key = 'available';
954
            var available_dsp = quotas.get('cyclades.'+type).get_readable(key, active);
955
            var available = quotas.get('cyclades.'+type).get_available(key);
956
            var content = "({0} left)".format(available_dsp);
957
            if (available <= 0) { content = "(None left)" }
958
            
959
            if (type == "ram") { type = "mem" }
960
            $(".flavor-options."+type+" h4 .available").text(content);
961
            if (available <= 0) {
962
              $(".flavor-options."+type+" h4 .available").addClass("error");
963
            } else {
964
              $(".flavor-options."+type+" h4 .available").removeClass("error");
965
            }
966
          })
967
        },
968

    
969
        reset: function() {
970
            this.current_image = storage.images.at(0);
971
            this.flavors = [];
972
            this.flavors_data = {'cpu':[], 'mem':[], 'disk':[]};
973
            this.update_flavors_data();
974
        },
975

    
976
        validate: function() {
977
            if (!this.current_flavor) {
978
                this.parent.$(".form-action.next").hide();
979
            } else {
980
                this.parent.$(".form-action.next").show();
981
            }
982
        },
983

    
984
        get: function() {
985
            return {'flavor': this.current_flavor}
986
        }
987
    });
988
    
989

    
990
    views.CreateColumnSelectOptionView = bb.View.extend({
991
        tagName: 'li',
992
        el: undefined,
993
        model: undefined,
994
        id_prefix: 'model-',
995
        tpl: '<input type="checkbox" class="check"/><span class="title"></span>',
996
        className: 'list-item-option clearfix',
997
        events: {
998
          'click': 'handle_click'
999
        },
1000

    
1001
        initialize: function(options) {
1002
          _.bindAll(this);
1003
          this.model.bind("change", this.render);
1004
          this.model.bind("remove", this.remove);
1005
          this.selected = false;
1006
          if (options.get_model_title) {
1007
            this.get_model_title = _.bind(options.get_model_title, this);
1008
          }
1009
          this.model_title_attr = options.model_title_attr;
1010
          $(this.el).append($(this.tpl));
1011
        },
1012
        
1013
        id: function() {
1014
          return this.id_prefix + this.model && this.model.id || '';
1015
        },
1016
        
1017
        handle_click: function() {
1018
          this.selected = !this.selected;
1019
          this.render();
1020
        },
1021

    
1022
        remove: function() {
1023
          this.model.unbind("change", this.render);
1024
          this.model.unbind("remove", this.remove);
1025
        },
1026
        
1027
        get_model_title: function() {
1028
          return this.model.get(this.model_title_attr || 'id');
1029
        },
1030

    
1031
        render: function() {
1032
          $(this.el).find(".title").text(this.get_model_title());
1033
          $(this.el).toggleClass('selected', this.selected);
1034
          if (this.selected) {
1035
            $(this.el).find("input").attr("checked", true);
1036
          } else {
1037
            $(this.el).find("input").attr("checked", false);
1038
          }
1039
        }
1040
    });
1041
    
1042
    views.CreateColumnIPOptionView = views.CreateColumnSelectOptionView.extend({
1043
      get_model_title: function() {
1044
        return this.model.get('ip');
1045
      }
1046
    });
1047

    
1048
    views.CreateColumnPrivateNetworkOptionView = views.CreateColumnSelectOptionView.extend({
1049
      get_model_title: function() {
1050
        return this.model.get('name');
1051
      }
1052
    });
1053

    
1054
    views.CreateColumnSelectListView = bb.View.extend({
1055
        collection: undefined,
1056
        header: undefined,
1057
        tagName: 'div',
1058
        extra_class: '',
1059
        el: undefined,
1060
        title_tpl: undefined,
1061
        title: 'List view',
1062
        description: 'List view description.',
1063
        empty_msg: 'No entries.',
1064
        item_cls: views.CreateColumnSelectOptionView,
1065
        className: 'list-cont create-column-select personalize-cont',
1066

    
1067
        initialize: function(options) {
1068
          _.bindAll(this);
1069
          if (options.extra_class) {
1070
            $(this.el).addClass(options.extra_class);
1071
          }
1072
          this.update_collection = options.update_collection;
1073
          this.title = options.title || this.title;
1074
          this.titple_tpl = options.title_tpl || this.title_tpl;
1075
          this.description = options.description || this.description;
1076
          this.empty_msg = options.empty_msg || this.empty_msg;
1077
          this.item_cls = options.item_cls || this.item_cls;
1078
          this.select_first_as_default = options.select_first_as_default;
1079
          this.filter_items = options.filter_items;
1080
          this.post_render_entries = options.post_render_entries || function() {};
1081
          this.init_events = options.init_events || function() {};
1082

    
1083
          this.init_events = _.bind(this.init_events, this);
1084
          this.post_render_entries = _.bind(this.post_render_entries, this);
1085

    
1086
          this._ul = $('<ul class="confirm-params">');
1087
          this._title = $("<h4>");
1088
          this._description = $("<p class='desc'>");
1089
          this._empty = $("<p class='empty hidden desc'>");
1090
          this._empty.html(this.empty_msg);
1091
        
1092
          this.item_views = [];
1093

    
1094
          $(this.el).append(this._title);
1095
          $(this.el).append(this._description);
1096
          $(this.el).append(this._empty);
1097
          $(this.el).append(this._ul);
1098

    
1099
          this['$el'] = $(this.el);
1100

    
1101
          if (!this.title_tpl) { this.title_tpl = this.title };
1102

    
1103
          this.collection.bind("change", this.render_entries);
1104
          this.collection.bind("reset", this.render_entries);
1105
          this.collection.bind("add", this.render_entries);
1106
          this.collection.bind("remove", this.remove_entry);
1107
          
1108
          this.fetcher = undefined;
1109
          if (this.update_collection) {
1110
              this.fetcher_params = [snf.config.update_interval, 
1111
                    snf.config.update_interval_increase || 500,
1112
                    snf.config.fast_interval || snf.config.update_interval/2, 
1113
                    snf.config.update_interval_increase_after_calls || 4,
1114
                    snf.config.update_interval_max || 20000,
1115
                    true, 
1116
                    {is_recurrent: true, update: true}];
1117
              this.fetcher = this.collection.get_fetcher.apply(this.collection, 
1118
                                                _.clone(this.fetcher_params));
1119
              this.fetcher.start();
1120
          }
1121
          this.render();
1122
          this.init_events();
1123
        },
1124
        
1125
        render: function() {
1126
          this._title.html(this.title_tpl);
1127
          this._description.html(this.description);
1128
          this.render_entries();
1129
        },
1130
        
1131
        remove_entry: function(model) {
1132
          if (!this.item_views[model.id]) { return }
1133
          this.item_views[model.id].remove();
1134
          delete this.item_views[model.pk]
1135
        },
1136
        
1137
        get_selected: function() {
1138
          return _.map(_.filter(this.item_views, function(v) { 
1139
            return v.selected
1140
          }), function(v) {
1141
            return v.model
1142
          });
1143
        },
1144
        
1145
        check_empty: function() {
1146
          if (this.item_views.length == 0) {
1147
            this._empty.show();
1148
          } else {
1149
            this._empty.hide();
1150
          }
1151
        },
1152

    
1153
        render_entries: function() {
1154
          var entries;
1155
          if (this.filter_items) {
1156
            entries = this.collection.filter(this.filter_items);
1157
          } else {
1158
            entries = this.collection.models;
1159
          }
1160
          
1161
          var selected = this.get_selected();
1162
          
1163
          _.each(entries, _.bind(function(model) {
1164
            if (this.item_views[model.id]) {
1165
              this.item_views[model.id].render();
1166
            } else {
1167
              var view = new this.item_cls({model:model});
1168
              if (!selected.length && this.select_first_as_default) { 
1169
                view.selected = true; selected = [1] 
1170
              }
1171
              view.render();
1172
              this.item_views[model.id] = view;
1173
              this._ul.append($(view.el));
1174
            }
1175
          }, this));
1176
          this.check_empty();
1177
          this.post_render_entries();
1178
        },
1179

    
1180
        remove: function() {
1181
          _.each(this.item_views, function(v){
1182
            v.remove();
1183
          });
1184
          if (this.update_collection) { this.fetcher.stop() }
1185
          this.unbind();
1186
          this.collection.unbind("change", this.render_entries);
1187
          this.collection.unbind("reset", this.render_entries);
1188
          this.collection.unbind("add", this.render_entries);
1189
          this.collection.unbind("remove", this.remove_entry);
1190
          views.CreateColumnSelectListView.__super__.remove.apply(this, arguments);
1191
        }
1192
    });
1193

    
1194
    views.CreateNetworkingView = views.CreateVMStepView.extend({
1195
        step: 3,
1196
        initialize: function() {
1197
            views.CreateNetworkingView.__super__.initialize.apply(this, arguments);
1198
            this.init_handlers();
1199
            this.selected_keys = [];
1200
            this.cont = this.$(".step-cont");
1201
        },
1202
        
1203
        init_subviews: function() {
1204
            var create_view = this.parent;
1205
            if (!this.networks_view) {
1206
              this.networks_view = new views.NetworkSelectView({
1207
                container: this.cont
1208
              });
1209
              this.networks_view.hide(true);
1210
            }
1211
        },
1212

    
1213
        init_handlers: function() {
1214
        },
1215

    
1216
        show: function() {
1217
            views.CreateNetworkingView.__super__.show.apply(this, arguments);
1218
            this.init_subviews();
1219
            this.update_layout();
1220
            this.networks_view.show(true);
1221
        },
1222
        
1223
        hide_step: function() {
1224
            this.networks_view && this.networks_view.hide(true);
1225
        },
1226

    
1227
        update_layout: function() {
1228
        },
1229

    
1230
        reset: function() {
1231
            this.selected_keys = [];
1232
            this.update_layout();
1233
        },
1234
        
1235
        get_selected_networks: function() {
1236
            if (!this.networks_view) { return [] }
1237
            return this.networks_view.get_selected_networks();
1238
        },
1239
        
1240
        get_selected_addresses: function() {
1241
            if (!this.networks_view) { return [] }
1242
            return this.networks_view.get_selected_floating_ips();
1243
        },
1244

    
1245
        get: function() {
1246
            return {
1247
              'addresses': this.get_selected_addresses(),
1248
              'networks': this.get_selected_networks()
1249
            }
1250
        },
1251

    
1252
        remove: function() {
1253
          if (this.networks_view) {
1254
            this.networks_view.remove();
1255
            delete this.networks_view;
1256
          }
1257
        }
1258
    });
1259

    
1260
    views.CreatePersonalizeView = views.CreateVMStepView.extend({
1261
        step: 4,
1262
        initialize: function() {
1263
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1264
            this.roles = this.$("li.predefined-meta.role .values");
1265
            this.name = this.$("input.rename-field");
1266
            this.name_changed = false;
1267
            this.init_suggested_roles();
1268
            this.init_handlers();
1269
            this.ssh_list = this.$(".ssh ul");
1270
            this.selected_keys = [];
1271

    
1272
            var self = this;
1273
        },
1274

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

    
1297
        select_key: function(key) {
1298
            var exists = this.selected_keys.indexOf(key.id);
1299
            if (exists > -1) {
1300
                this.selected_keys.splice(exists, 1);
1301
            } else {
1302
                this.selected_keys.push(key.id);
1303
            }
1304
            this.update_ui_keys_selections(this.selected_keys);
1305
        },
1306

    
1307
        update_ui_keys_selections: function(keys) {
1308
            var self = this;
1309
            self.$(".ssh-key-option").removeClass("selected");
1310
            self.$(".ssh-key-option .check").attr("checked", false);
1311
            _.each(keys, function(kid) {
1312
                $("#ssh-key-option-" + kid).addClass("selected");
1313
                $("#ssh-key-option-" + kid).find(".check").attr("checked", true);
1314
            });
1315
        },
1316

    
1317
        update_ssh_keys: function() {
1318
            this.ssh_list.empty();
1319
            var keys = snf.storage.keys.models;
1320
            if (keys.length == 0) { 
1321
                this.$(".ssh .empty").show();
1322
            } else {
1323
                this.$(".ssh .empty").hide();
1324
            }
1325
            _.each(keys, _.bind(function(key){
1326
                var name = _.escape(util.truncate(key.get("name"), 45));
1327
                var el = $('<li id="ssh-key-option-{1}" class="ssh-key-option">{0}</li>'.format(name, key.id));
1328
                var check = $('<input class="check" type="checkbox"></input>')
1329
                el.append(check);
1330
                el.data("model", key);
1331
                this.ssh_list.append(el);
1332
            }, this));
1333
        },
1334

    
1335
        init_handlers: function() {
1336
            this.name.bind("keypress", _.bind(function(e) {
1337
                this.name_changed = true;
1338
                if (e.keyCode == 13) { this.parent.set_step(5); this.parent.update_layout() };    
1339
            }, this));
1340

    
1341
            this.name.bind("click", _.bind(function() {
1342
                if (!this.name_changed) {
1343
                    this.name.val("");
1344
                }
1345
            }, this))
1346
        },
1347

    
1348
        show: function() {
1349
            views.CreatePersonalizeView.__super__.show.apply(this, arguments);
1350
            this.update_layout();
1351
        },
1352
        
1353
        update_layout: function() {
1354
            var params = this.parent.get_params();
1355

    
1356
            if (!params.image || !params.flavor) { return }
1357

    
1358
            if (!params.image) { return }
1359
            var vm_name_tpl = snf.config.vm_name_template || "My {0} server";
1360
            //if (params.image.is_snapshot()) { vm_name_tpl = "{0}" };
1361
            var vm_name = vm_name_tpl.format(params.image.get("name"));
1362
            var orig_name = vm_name;
1363
            
1364
            var existing = true;
1365
            var j = 0;
1366

    
1367
            while (existing && !this.name_changed) {
1368
                var existing = storage.vms.select(function(vm){
1369
                  return vm.get("name") == vm_name
1370
                }).length;
1371
                if (existing) {
1372
                    j++;
1373
                    vm_name = orig_name + " " + j;
1374
                }
1375
            }
1376

    
1377
            if (!_(this.name.val()).trim() || !this.name_changed) {
1378
                this.name.val(vm_name);
1379
            }
1380

    
1381
            if (!this.name_changed && this.parent.visible()) {
1382
                if (!$.browser.msie && !$.browser.opera) {
1383
                    this.$("#create-vm-name").select();
1384
                } else {
1385
                    window.setTimeout(_.bind(function(){
1386
                        this.$("#create-vm-name").select();
1387
                    }, this), 400)
1388
                }
1389
            }
1390
            
1391
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
1392
            this.name.css({backgroundImage:"url({0})".format(img)})
1393
            
1394
            if (!params.image.supports('ssh')) {
1395
                this.disable_ssh_keys();
1396
            } else {
1397
                this.enable_ssh_keys();
1398
                this.update_ssh_keys();
1399
            }
1400

    
1401
            this.update_ui_keys_selections(this.selected_keys);
1402
        },
1403

    
1404
        disable_ssh_keys: function() {
1405
            this.$(".disabled.desc").show();
1406
            this.$(".empty.desc").hide();
1407
            this.$(".ssh .confirm-params").hide();
1408
            this.selected_keys = [];
1409
        },
1410

    
1411
        enable_ssh_keys: function() {
1412
            this.$(".ssh .confirm-params").show();
1413
            this.$(".disabled.desc").hide();
1414
        },
1415

    
1416
        reset: function() {
1417
            this.roles.find(".val").removeClass("selected");
1418
            this.name_changed = false;
1419
            this.selected_keys = [];
1420
            this.update_layout();
1421
        },
1422

    
1423
        get_meta: function() {
1424
            if (this.roles.find(".selected").length == 0) {
1425
                return false;
1426
            }
1427

    
1428
            var role = $(this.roles.find(".selected").get(0)).data("value");
1429
            return {'Role': role }
1430
        },
1431

    
1432
        get: function() {
1433
            var val = {'name': this.name.val() };
1434
            if (this.get_meta()) {
1435
                val.metadata = this.get_meta();
1436
            }
1437

    
1438
            val.keys = _.map(this.selected_keys, function(k){ return snf.storage.keys.get(k)});
1439
            
1440
            return val;
1441
        }
1442
    });
1443

    
1444
    views.CreateSubmitView = views.CreateVMStepView.extend({
1445
        step: 5,
1446
        initialize: function() {
1447
            views.CreateSubmitView.__super__.initialize.apply(this, arguments);
1448
            this.roles = this.$("li.predefined-meta.role .values");
1449
            this.confirm = this.$(".confirm-params ul");
1450
            this.name = this.$("h3.vm-name");
1451
            this.keys = this.$(".confirm-params.ssh");
1452
            this.meta = this.$(".confirm-params.meta");
1453
            this.ip_addresses = this.$(".confirm-params.ip-addresses");
1454
            this.private_networks = this.$(".confirm-params.private-networks");
1455
            this.init_handlers();
1456
        },
1457

    
1458
        init_handlers: function() {
1459
        },
1460

    
1461
        show: function() {
1462
            views.CreateSubmitView.__super__.show.apply(this, arguments);
1463
            this.update_layout();
1464
        },
1465
        
1466
        update_network_details: function() {
1467
            var data = this.parent.get_params();
1468
            var ips = data.addresses;
1469
            var networks = data.networks;
1470

    
1471
            this.ip_addresses.empty();
1472
            if (!ips|| ips.length == 0) {
1473
                this.ip_addresses.append(this.make("li", {'class':'empty'}, 
1474
                                           'No ip addresses selected'))
1475
            }
1476
            _.each(ips, _.bind(function(ip) {
1477
                var el = this.make("li", {'class':'selected-ip-address'}, 
1478
                                  ip.get('floating_ip_address'));
1479
                this.ip_addresses.append(el);
1480
            }, this))
1481

    
1482
            this.private_networks.empty();
1483
            if (!networks || networks.length == 0) {
1484
                this.private_networks.append(this.make("li", {'class':'empty'}, 
1485
                                             'No private networks selected'))
1486
            }
1487
            _.each(networks, _.bind(function(network) {
1488
                var el = this.make("li", {'class':'selected-private-network'}, 
1489
                                  network.get('name'));
1490
                this.private_networks.append(el);
1491
            }, this))
1492

    
1493
        },
1494

    
1495
        update_flavor_details: function() {
1496
            var flavor = this.parent.get_params().flavor;
1497

    
1498
            function set_detail(sel, key) {
1499
                var val = key;
1500
                if (key == undefined) { val = flavor.get(sel) };
1501
                this.$(".confirm-cont.flavor .flavor-" + sel + " .value").text(val)
1502
            }
1503
            
1504
            set_detail("cpu", flavor.get("cpu") + "x");
1505
            set_detail("ram", flavor.get("ram") + " MB");
1506
            set_detail("disk", util.readablizeBytes(flavor.get("disk") * 1024 * 1024 * 1024));
1507
            set_detail("disktype", flavor.get_disk_template_info().name);
1508
        },
1509

    
1510
        update_image_details: function() {
1511
            var image = this.parent.get_params().image;
1512

    
1513
            function set_detail(sel, key) {
1514
                var val = key;
1515
                if (key == undefined) { val = image.get(sel) };
1516
                this.$(".confirm-cont.image .image-" + sel + " .value").text(val)
1517
            }
1518
            
1519
            set_detail("description", image.get_description());
1520
            set_detail("name", util.truncate(image.get("name"), 30));
1521
            set_detail("os", _(image.get_os()).capitalize());
1522
            set_detail("gui", image.get_gui());
1523
            set_detail("size", _.escape(image.get_readable_size()));
1524
            set_detail("kernel");
1525
        },
1526

    
1527
        update_selected_keys: function(keys) {
1528
            this.keys.empty();
1529
            if (!keys || keys.length == 0) {
1530
                this.keys.append(this.make("li", {'class':'empty'}, 'No keys selected'))
1531
            }
1532
            _.each(keys, _.bind(function(key) {
1533
                var name = _.escape(util.truncate(key.get("name"), 20))
1534
                var el = this.make("li", {'class':'selected-ssh-key'}, name);
1535
                this.keys.append(el);
1536
            }, this))
1537
        },
1538

    
1539
        update_selected_meta: function(meta) {
1540
            this.meta.empty();
1541
            if (!meta || meta.length == 0) {
1542
                this.meta.append(this.make("li", {'class':'empty'}, 'No tags selected'))
1543
            }
1544
            _.each(meta, _.bind(function(value, key) {
1545
                var el = this.make("li", {'class':'confirm-value'});
1546
                var name = this.make("span", {'class':'ckey'}, key);
1547
                var value = this.make("span", {'class':'cval'}, value);
1548

    
1549
                $(el).append(name)
1550
                $(el).append(value);
1551
                this.meta.append(el);
1552
            }, this));
1553
        },
1554

    
1555
        update_layout: function() {
1556
            var params = this.parent.get_params();
1557
            if (!params.image || !params.flavor) { return }
1558

    
1559
            if (!params.image) { return }
1560

    
1561
            this.name.text(util.truncate(params.name, 50));
1562

    
1563
            this.confirm.find("li.image .value").text(params.flavor.get("image"));
1564
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu") + "x");
1565
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
1566
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
1567

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

    
1571
            this.update_image_details();
1572
            this.update_flavor_details();
1573
            this.update_network_details();
1574

    
1575
            if (!params.image.supports('ssh')) {
1576
                this.keys.hide();
1577
                this.keys.prev().hide();
1578
            } else {
1579
                this.keys.show();
1580
                this.keys.prev().show();
1581
                this.update_selected_keys(params.keys);
1582
            }
1583
            
1584
            this.update_selected_meta(params.metadata);
1585
        },
1586

    
1587
        reset: function() {
1588
            this.update_layout();
1589
        },
1590

    
1591
        get_meta: function() {
1592
        },
1593

    
1594
        get: function() {
1595
            return {};
1596
        }
1597
    });
1598

    
1599
    views.CreateVMView = views.Overlay.extend({
1600
        
1601
        view_id: "create_vm_view",
1602
        content_selector: "#createvm-overlay-content",
1603
        css_class: 'overlay-createvm overlay-info',
1604
        overlay_id: "metadata-overlay",
1605

    
1606
        subtitle: false,
1607
        title: "Create new machine",
1608

    
1609
        initialize: function(options) {
1610
            views.CreateVMView.__super__.initialize.apply(this);
1611
            this.current_step = 1;
1612

    
1613
            this.password_view = new views.VMCreationPasswordView();
1614

    
1615
            this.steps = [];
1616
            this.steps[1] = new views.CreateImageSelectView(this);
1617
            this.steps[1].bind("change", _.bind(function(data) {this.trigger("image:change", data)}, this));
1618

    
1619
            this.steps[2] = new views.CreateFlavorSelectView(this);
1620
            this.steps[3] = new views.CreateNetworkingView(this);
1621
            this.steps[4] = new views.CreatePersonalizeView(this);
1622
            this.steps[5] = new views.CreateSubmitView(this);
1623

    
1624
            this.cancel_btn = this.$(".create-controls .cancel");
1625
            this.next_btn = this.$(".create-controls .next");
1626
            this.prev_btn = this.$(".create-controls .prev");
1627
            this.submit_btn = this.$(".create-controls .submit");
1628

    
1629
            this.history = this.$(".steps-history");
1630
            this.history_steps = this.$(".steps-history .steps-history-step");
1631
            
1632
            this.init_handlers();
1633
        },
1634

    
1635
        init_handlers: function() {
1636
            var self = this;
1637
            this.next_btn.click(_.bind(function(){
1638
                this.set_step(this.current_step + 1);
1639
                this.update_layout();
1640
            }, this))
1641
            this.prev_btn.click(_.bind(function(){
1642
                this.set_step(this.current_step - 1);
1643
                this.update_layout();
1644
            }, this))
1645
            this.cancel_btn.click(_.bind(function(){
1646
                this.close_all();
1647
            }, this))
1648
            this.submit_btn.click(_.bind(function(){
1649
                this.submit();
1650
            }, this))
1651
            
1652
            this.history.find(".completed").live("click", function() {
1653
                var step = parseInt($(this).attr("id").replace("vm-create-step-history-", ""));
1654
                self.set_step(step);
1655
                self.update_layout();
1656
            })
1657
        },
1658

    
1659
        set_step: function(st) {
1660
        },
1661
        
1662
        validate: function(data) {
1663
            if (_(data.name).trim() == "") {
1664
                this.$(".form-field").addClass("error");
1665
                return false;
1666
            } else {
1667
                return true;
1668
            }
1669
        },
1670

    
1671
        submit: function() {
1672
            if (this.submiting) { return };
1673
            var data = this.get_params();
1674
            var meta = {};
1675
            var extra = {};
1676
            var personality = [];
1677

    
1678
            if (this.validate(data)) {
1679
                this.submit_btn.addClass("in-progress");
1680
                this.submiting = true;
1681
                if (data.metadata) { meta = data.metadata; }
1682
                if (data.keys && data.keys.length > 0) {
1683
                    personality.push(
1684
                      data.image.personality_data_for_keys(data.keys))
1685
                }
1686

    
1687
                if (personality.length) {
1688
                    extra['personality'] = _.flatten(personality);
1689
                }
1690
                
1691
                extra['networks'] = [];
1692
                _.each(data.networks, function(n) {
1693
                  extra.networks.push({'uuid': n.get('id')})
1694
                });
1695
                _.each(data.addresses, function(ip) {
1696
                  extra.networks.push({
1697
                    'uuid': ip.get('network').get('id'),
1698
                    'fixed_ip': ip.get('floating_ip_address')
1699
                  });
1700
                });
1701

    
1702
                _.map(data.networks, function(n) { return n.get('id') });
1703
                storage.vms.create(data.name, data.image, data.flavor, 
1704
                                   meta, extra, _.bind(function(data){
1705
                    _.each(data.addresses, function(ip) {
1706
                      ip.set({'status': 'connecting'});
1707
                    });
1708
                    this.close_all();
1709
                    this.password_view.show(data.server.adminPass, 
1710
                                            data.server.id);
1711
                    var self = this;
1712
                    window.setTimeout(function() {
1713
                      self.submiting = false;
1714
                    }, 1000);
1715
                }, this));
1716
            }
1717
        },
1718

    
1719
        close_all: function() {
1720
          this.hide();
1721
        },
1722

    
1723
        onClose: function() {
1724
          this.steps[3].remove();
1725
        },
1726

    
1727
        reset: function() {
1728
          this.current_step = 1;
1729

    
1730
          this.steps[1].reset();
1731
          this.steps[2].reset();
1732
          this.steps[3].reset();
1733
          this.steps[4].reset();
1734

    
1735
          //this.steps[1].show();
1736
          //this.steps[2].show();
1737
          //this.steps[3].show();
1738
          //this.steps[4].show();
1739

    
1740
          this.submit_btn.removeClass("in-progress");
1741
        },
1742

    
1743
        onShow: function() {
1744
        },
1745

    
1746
        update_layout: function() {
1747
            this.show_step(this.current_step);
1748
            this.current_view.update_layout();
1749
        },
1750

    
1751
        beforeOpen: function() {
1752
            if (!this.skip_reset_on_next_open) {
1753
                this.submiting = false;
1754
                this.reset();
1755
                this.current_step = 1;
1756
                this.$(".steps-container").css({"margin-left":0 + "px"});
1757
                this.show_step(1);
1758
            }
1759
            
1760
            this.skip_reset_on_next_open = false;
1761
            this.update_layout();
1762
        },
1763
        
1764
        set_step: function(step) {
1765
            if (step <= 1) {
1766
                step = 1
1767
            }
1768
            if (step > this.steps.length - 1) {
1769
                step = this.steps.length - 1;
1770
            }
1771
            this.current_step = step;
1772
        },
1773

    
1774
        show_step: function(step) {
1775
            // FIXME: this shouldn't be here
1776
            // but since we are not calling step.hide this should work
1777
            this.steps[1].image_details.hide();
1778
            
1779
            this.current_view && this.current_view.hide_step && this.current_view.hide_step();
1780
            this.current_view = this.steps[step];
1781
            this.update_controls();
1782

    
1783
            this.steps[step].show();
1784
            var width = this.el.find('.container').width();
1785
            var left = (step -1) * width * -1;
1786
            this.$(".steps-container").animate({"margin-left": left + "px"}, 300);
1787

    
1788
            this.update_steps_history();
1789
        },
1790

    
1791
        update_steps_history: function() {
1792
            var self = this;
1793
            function get_step(s) {
1794
                return self.history.find(".step" + s + "h");
1795
            }
1796
            
1797
            var current_step = parseInt(this.current_view.step);
1798
            _.each(this.steps, function(stepv) {
1799
                var step = parseInt(stepv.step);
1800
                get_step(step).removeClass("completed").removeClass("current");
1801
                if (step == current_step) {
1802
                    get_step(step).removeClass("completed").addClass("current");
1803
                }
1804
                if (step < current_step) {
1805
                    get_step(step).removeClass("current").addClass("completed");
1806
                }
1807
            });
1808
        },
1809

    
1810
        update_controls: function() {
1811
            var step = this.current_step;
1812
            if (step == 1) {
1813
                this.prev_btn.hide();
1814
                this.cancel_btn.show();
1815
            } else {
1816
                this.prev_btn.show();
1817
                this.cancel_btn.hide();
1818
            }
1819
            
1820
            if (step == this.steps.length - 1) {
1821
                this.next_btn.hide();
1822
                this.submit_btn.show();
1823
            } else {
1824
                this.next_btn.show();
1825
                this.submit_btn.hide();
1826
            }
1827
        },
1828

    
1829
        get_params: function() {
1830
            return _.extend({}, this.steps[1].get(), this.steps[2].get(), this.steps[3].get(), this.steps[4].get());
1831
        }
1832
    });
1833
    
1834
})(this);
1835